summaryrefslogtreecommitdiff
path: root/lisp/eshell/em-ls.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/eshell/em-ls.el')
-rw-r--r--lisp/eshell/em-ls.el863
1 files changed, 863 insertions, 0 deletions
diff --git a/lisp/eshell/em-ls.el b/lisp/eshell/em-ls.el
new file mode 100644
index 00000000000..1cea10314ba
--- /dev/null
+++ b/lisp/eshell/em-ls.el
@@ -0,0 +1,863 @@
+;;; em-ls --- implementation of ls in Lisp
+
+;; Copyright (C) 1999, 2000 Free Sofware Foundation
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+(provide 'em-ls)
+
+(eval-when-compile (require 'esh-maint))
+
+(defgroup eshell-ls nil
+ "This module implements the \"ls\" utility fully in Lisp. If it is
+passed any unrecognized command switches, it will revert to the
+operating system's version. This version of \"ls\" uses text
+properties to colorize its output based on the setting of
+`eshell-ls-use-colors'."
+ :tag "Implementation of `ls' in Lisp"
+ :group 'eshell-module)
+
+;;; Commentary:
+
+;; Most of the command switches recognized by GNU's ls utility are
+;; supported ([(fileutils)ls invocation]).
+
+(require 'esh-util)
+(require 'esh-opt)
+
+;;; User Variables:
+
+(defvar eshell-ls-orig-insert-directory
+ (symbol-function 'insert-directory)
+ "Preserve the original definition of `insert-directory'.")
+
+(defcustom eshell-ls-unload-hook
+ (list
+ (function
+ (lambda ()
+ (fset 'insert-directory eshell-ls-orig-insert-directory))))
+ "*When unloading `eshell-ls', restore the definition of `insert-directory'."
+ :type 'hook
+ :group 'eshell-ls)
+
+(defcustom eshell-ls-use-in-dired nil
+ "*If non-nil, use `eshell-ls' to read directories in dired."
+ :set (lambda (symbol value)
+ (if value
+ (unless (and (boundp 'eshell-ls-use-in-dired)
+ eshell-ls-use-in-dired)
+ (fset 'insert-directory 'eshell-ls-insert-directory))
+ (when (and (boundp 'eshell-ls-insert-directory)
+ eshell-ls-use-in-dired)
+ (fset 'insert-directory eshell-ls-orig-insert-directory)))
+ (setq eshell-ls-use-in-dired value))
+ :type 'boolean
+ :require 'em-ls
+ :group 'eshell-ls)
+
+(defcustom eshell-ls-default-blocksize 1024
+ "*The default blocksize to use when display file sizes with -s."
+ :type 'integer
+ :group 'eshell-ls)
+
+(defcustom eshell-ls-exclude-regexp "\\`\\."
+ "*Unless -a is specified, files matching this regexp will not be shown."
+ :type 'regexp
+ :group 'eshell-ls)
+
+(defcustom eshell-ls-use-colors t
+ "*If non-nil, use colors in file listings."
+ :type 'boolean
+ :group 'eshell-ls)
+
+(defface eshell-ls-directory-face
+ '((((class color) (background light)) (:foreground "Blue" :bold t))
+ (((class color) (background dark)) (:foreground "SkyBlue" :bold t))
+ (t (:bold t)))
+ "*The face used for highlight directories."
+ :group 'eshell-ls)
+
+(defface eshell-ls-symlink-face
+ '((((class color) (background light)) (:foreground "Dark Cyan" :bold t))
+ (((class color) (background dark)) (:foreground "Cyan" :bold t)))
+ "*The face used for highlight symbolic links."
+ :group 'eshell-ls)
+
+(defface eshell-ls-executable-face
+ '((((class color) (background light)) (:foreground "ForestGreen" :bold t))
+ (((class color) (background dark)) (:foreground "Green" :bold t)))
+ "*The face used for highlighting executables (not directories, though)."
+ :group 'eshell-ls)
+
+(defface eshell-ls-readonly-face
+ '((((class color) (background light)) (:foreground "Brown"))
+ (((class color) (background dark)) (:foreground "Pink")))
+ "*The face used for highlighting read-only files."
+ :group 'eshell-ls)
+
+(defface eshell-ls-unreadable-face
+ '((((class color) (background light)) (:foreground "Grey30"))
+ (((class color) (background dark)) (:foreground "DarkGrey")))
+ "*The face used for highlighting unreadable files."
+ :group 'eshell-ls)
+
+(defface eshell-ls-special-face
+ '((((class color) (background light)) (:foreground "Magenta" :bold t))
+ (((class color) (background dark)) (:foreground "Magenta" :bold t)))
+ "*The face used for highlighting non-regular files."
+ :group 'eshell-ls)
+
+(defface eshell-ls-missing-face
+ '((((class color) (background light)) (:foreground "Red" :bold t))
+ (((class color) (background dark)) (:foreground "Red" :bold t)))
+ "*The face used for highlighting non-existant file names."
+ :group 'eshell-ls)
+
+(defcustom eshell-ls-archive-regexp
+ (concat "\\.\\(t\\(a[rz]\\|gz\\)\\|arj\\|lzh\\|"
+ "zip\\|[zZ]\\|gz\\|bz2\\|deb\\|rpm\\)\\'")
+ "*A regular expression that matches names of file archives.
+This typically includes both traditional archives and compressed
+files."
+ :type 'regexp
+ :group 'eshell-ls)
+
+(defface eshell-ls-archive-face
+ '((((class color) (background light)) (:foreground "Orchid" :bold t))
+ (((class color) (background dark)) (:foreground "Orchid" :bold t)))
+ "*The face used for highlighting archived and compressed file names."
+ :group 'eshell-ls)
+
+(defcustom eshell-ls-backup-regexp
+ "\\(\\`\\.?#\\|\\(\\.bak\\|~\\)\\'\\)"
+ "*A regular expression that matches names of backup files."
+ :type 'regexp
+ :group 'eshell-ls)
+
+(defface eshell-ls-backup-face
+ '((((class color) (background light)) (:foreground "OrangeRed"))
+ (((class color) (background dark)) (:foreground "LightSalmon")))
+ "*The face used for highlighting backup file names."
+ :group 'eshell-ls)
+
+(defcustom eshell-ls-product-regexp
+ "\\.\\(elc\\|o\\(bj\\)?\\|a\\||lib\\|res\\)\\'"
+ "*A regular expression that matches names of product files.
+Products are files that get generated from a source file, and hence
+ought to be recreatable if they are deleted."
+ :type 'regexp
+ :group 'eshell-ls)
+
+(defface eshell-ls-product-face
+ '((((class color) (background light)) (:foreground "OrangeRed"))
+ (((class color) (background dark)) (:foreground "LightSalmon")))
+ "*The face used for highlighting files that are build products."
+ :group 'eshell-ls)
+
+(defcustom eshell-ls-clutter-regexp
+ "\\(^texput\\.log\\|^core\\)\\'"
+ "*A regular expression that matches names of junk files.
+These are mainly files that get created for various reasons, but don't
+really need to stick around for very long."
+ :type 'regexp
+ :group 'eshell-ls)
+
+(defface eshell-ls-clutter-face
+ '((((class color) (background light)) (:foreground "OrangeRed" :bold t))
+ (((class color) (background dark)) (:foreground "OrangeRed" :bold t)))
+ "*The face used for highlighting junk file names."
+ :group 'eshell-ls)
+
+(defsubst eshell-ls-filetype-p (attrs type)
+ "Test whether ATTRS specifies a directory."
+ (if (nth 8 attrs)
+ (eq (aref (nth 8 attrs) 0) type)))
+
+(defmacro eshell-ls-applicable (attrs index func file)
+ "Test whether, for ATTRS, the user UID can do what corresponds to INDEX.
+This is really just for efficiency, to avoid having to stat the file
+yet again."
+ `(if (= (user-uid) (nth 2 ,attrs))
+ (not (eq (aref (nth 8 ,attrs) ,index) ?-))
+ (,(eval func) ,file)))
+
+(defcustom eshell-ls-highlight-alist nil
+ "*This alist correlates test functions to color.
+The format of the members of this alist is
+
+ (TEST-SEXP . FACE)
+
+If TEST-SEXP evals to non-nil, that face will be used to highlight the
+name of the file. The first match wins. `file' and `attrs' are in
+scope during the evaluation of TEST-SEXP."
+ :type '(repeat (cons function face))
+ :group 'eshell-ls)
+
+;;; Functions:
+
+(defun eshell-ls-insert-directory
+ (file switches &optional wildcard full-directory-p)
+ "Insert directory listing for FILE, formatted according to SWITCHES.
+Leaves point after the inserted text.
+SWITCHES may be a string of options, or a list of strings.
+Optional third arg WILDCARD means treat FILE as shell wildcard.
+Optional fourth arg FULL-DIRECTORY-P means file is a directory and
+switches do not contain `d', so that a full listing is expected.
+
+This version of the function uses `eshell/ls'. If any of the switches
+passed are not recognized, the operating system's version will be used
+instead."
+ (let ((handler (find-file-name-handler file 'insert-directory)))
+ (if handler
+ (funcall handler 'insert-directory file switches
+ wildcard full-directory-p)
+ (if (stringp switches)
+ (setq switches (split-string switches)))
+ (let (eshell-current-handles
+ eshell-current-subjob-p)
+ ;; use the fancy highlighting in `eshell-ls' rather than font-lock
+ (when (and eshell-ls-use-colors
+ (featurep 'font-lock))
+ (font-lock-mode -1)
+ (if (boundp 'font-lock-buffers)
+ (set 'font-lock-buffers
+ (delq (current-buffer)
+ (symbol-value 'font-lock-buffers)))))
+ (let ((insert-func 'insert)
+ (error-func 'insert)
+ (flush-func 'ignore))
+ (eshell-do-ls (append switches (list file))))))))
+
+(defsubst eshell/ls (&rest args)
+ "An alias version of `eshell-do-ls'."
+ (let ((insert-func 'eshell-buffered-print)
+ (error-func 'eshell-error)
+ (flush-func 'eshell-flush))
+ (eshell-do-ls args)))
+
+(eval-when-compile
+ (defvar block-size)
+ (defvar dereference-links)
+ (defvar dir-literal)
+ (defvar error-func)
+ (defvar flush-func)
+ (defvar human-readable)
+ (defvar ignore-pattern)
+ (defvar insert-func)
+ (defvar listing-style)
+ (defvar numeric-uid-gid)
+ (defvar reverse-list)
+ (defvar show-all)
+ (defvar show-recursive)
+ (defvar show-size)
+ (defvar sort-method))
+
+(defun eshell-do-ls (&rest args)
+ "Implementation of \"ls\" in Lisp, passing ARGS."
+ (funcall flush-func -1)
+ ;; process the command arguments, and begin listing files
+ (eshell-eval-using-options
+ "ls" args
+ `((?a "all" nil show-all
+ "show all files in directory")
+ (?c nil by-ctime sort-method
+ "sort by modification time")
+ (?d "directory" nil dir-literal
+ "list directory entries instead of contents")
+ (?k "kilobytes" 1024 block-size
+ "using 1024 as the block size")
+ (?h "human-readable" 1024 human-readable
+ "print sizes in human readable format")
+ (?H "si" 1000 human-readable
+ "likewise, but use powers of 1000 not 1024")
+ (?I "ignore" t ignore-pattern
+ "do not list implied entries matching pattern")
+ (?l nil long-listing listing-style
+ "use a long listing format")
+ (?n "numeric-uid-gid" nil numeric-uid-gid
+ "list numeric UIDs and GIDs instead of names")
+ (?r "reverse" nil reverse-list
+ "reverse order while sorting")
+ (?s "size" nil show-size
+ "print size of each file, in blocks")
+ (?t nil by-mtime sort-method
+ "sort by modification time")
+ (?u nil by-atime sort-method
+ "sort by last access time")
+ (?x nil by-lines listing-style
+ "list entries by lines instead of by columns")
+ (?C nil by-columns listing-style
+ "list entries by columns")
+ (?L "deference" nil dereference-links
+ "list entries pointed to by symbolic links")
+ (?R "recursive" nil show-recursive
+ "list subdirectories recursively")
+ (?S nil by-size sort-method
+ "sort by file size")
+ (?U nil unsorted sort-method
+ "do not sort; list entries in directory order")
+ (?X nil by-extension sort-method
+ "sort alphabetically by entry extension")
+ (?1 nil single-column listing-style
+ "list one file per line")
+ (nil "help" nil nil
+ "show this usage display")
+ :external "ls"
+ :usage "[OPTION]... [FILE]...
+List information about the FILEs (the current directory by default).
+Sort entries alphabetically across.")
+ ;; setup some defaults, based on what the user selected
+ (unless block-size
+ (setq block-size eshell-ls-default-blocksize))
+ (unless listing-style
+ (setq listing-style 'by-columns))
+ (unless args
+ (setq args (list ".")))
+ (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp))
+ (when ignore-pattern
+ (unless (eshell-using-module 'eshell-glob)
+ (error (concat "-I option requires that `eshell-glob'"
+ " be a member of `eshell-modules-list'")))
+ (set-text-properties 0 (length ignore-pattern) nil ignore-pattern)
+ (if eshell-ls-exclude-regexp
+ (setq eshell-ls-exclude-regexp
+ (concat "\\(" eshell-ls-exclude-regexp "\\|"
+ (eshell-glob-regexp ignore-pattern) "\\)"))
+ (setq eshell-ls-exclude-regexp (eshell-glob-regexp ignore-pattern))))
+ ;; list the files!
+ (eshell-ls-entries
+ (mapcar (function
+ (lambda (arg)
+ (cons (if (and (eshell-under-windows-p)
+ (file-name-absolute-p arg))
+ (expand-file-name arg)
+ arg)
+ (file-attributes arg)))) args)
+ t (expand-file-name default-directory)))
+ (funcall flush-func)))
+
+(defsubst eshell-ls-printable-size (filesize &optional by-blocksize)
+ "Return a printable FILESIZE."
+ (eshell-printable-size filesize human-readable
+ (and by-blocksize block-size)
+ eshell-ls-use-colors))
+
+(defsubst eshell-ls-size-string (attrs size-width)
+ "Return the size string for ATTRS length, using SIZE-WIDTH."
+ (let* ((str (eshell-ls-printable-size (nth 7 attrs) t))
+ (len (length str)))
+ (if (< len size-width)
+ (concat (make-string (- size-width len) ? ) str)
+ str)))
+
+(defun eshell-ls-annotate (fileinfo)
+ "Given a FILEINFO object, return a resolved, decorated FILEINFO.
+This means resolving any symbolic links, determining what face the
+name should be displayed as, etc. Think of it as cooking a FILEINFO."
+ (if (not (and (stringp (cadr fileinfo))
+ (or dereference-links
+ (eq listing-style 'long-listing))))
+ (setcar fileinfo (eshell-ls-decorated-name fileinfo))
+ (let (dir attr)
+ (unless (file-name-absolute-p (cadr fileinfo))
+ (setq dir (file-truename
+ (file-name-directory
+ (expand-file-name (car fileinfo))))))
+ (setq attr
+ (file-attributes
+ (let ((target (if dir
+ (expand-file-name (cadr fileinfo) dir)
+ (cadr fileinfo))))
+ (if dereference-links
+ (file-truename target)
+ target))))
+ (if (or dereference-links
+ (string-match "^\\.\\.?$" (car fileinfo)))
+ (progn
+ (setcdr fileinfo attr)
+ (setcar fileinfo (eshell-ls-decorated-name fileinfo)))
+ (assert (eq listing-style 'long-listing))
+ (setcar fileinfo
+ (concat (eshell-ls-decorated-name fileinfo) " -> "
+ (eshell-ls-decorated-name
+ (cons (cadr fileinfo) attr)))))))
+ fileinfo)
+
+(defun eshell-ls-file (fileinfo &optional size-width copy-fileinfo)
+ "Output FILE in long format.
+FILE may be a string, or a cons cell whose car is the filename and
+whose cdr is the list of file attributes."
+ (if (not (cdr fileinfo))
+ (funcall error-func (format "%s: No such file or directory\n"
+ (car fileinfo)))
+ (setq fileinfo
+ (eshell-ls-annotate (if copy-fileinfo
+ (cons (car fileinfo)
+ (cdr fileinfo))
+ fileinfo)))
+ (let ((file (car fileinfo))
+ (attrs (cdr fileinfo)))
+ (if (not (eq listing-style 'long-listing))
+ (if show-size
+ (funcall insert-func (eshell-ls-size-string attrs size-width)
+ " " file "\n")
+ (funcall insert-func file "\n"))
+ (let ((line
+ (concat
+ (if show-size
+ (concat (eshell-ls-size-string attrs size-width) " "))
+ (format
+ "%s%4d %-8s %-8s "
+ (or (nth 8 attrs) "??????????")
+ (or (nth 1 attrs) 0)
+ (or (and (not numeric-uid-gid)
+ (nth 2 attrs)
+ (eshell-substring
+ (user-login-name (nth 2 attrs)) 8))
+ (nth 2 attrs)
+ "")
+ (or (and (not numeric-uid-gid)
+ (nth 3 attrs)
+ (eshell-substring
+ (eshell-group-name (nth 3 attrs)) 8))
+ (nth 3 attrs)
+ ""))
+ (let* ((str (eshell-ls-printable-size (nth 7 attrs)))
+ (len (length str)))
+ (if (< len 8)
+ (concat (make-string (- 8 len) ? ) str)
+ str))
+ " " (format-time-string
+ (concat
+ "%b %e "
+ (if (= (nth 5 (decode-time (current-time)))
+ (nth 5 (decode-time
+ (nth (cond
+ ((eq sort-method 'by-atime) 4)
+ ((eq sort-method 'by-ctime) 6)
+ (t 5)) attrs))))
+ "%H:%M"
+ " %Y")) (nth (cond
+ ((eq sort-method 'by-atime) 4)
+ ((eq sort-method 'by-ctime) 6)
+ (t 5)) attrs)) " ")))
+ (funcall insert-func line file "\n"))))))
+
+(defun eshell-ls-dir (dirinfo &optional insert-name root-dir size-width)
+ "Output the entries in DIRINFO.
+If INSERT-NAME is non-nil, the name of DIRINFO will be output. If
+ROOT-DIR is also non-nil, and a directory name, DIRINFO will be output
+relative to that directory."
+ (let ((dir (car dirinfo)))
+ (if (not (cdr dirinfo))
+ (funcall error-func (format "%s: No such file or directory\n" dir))
+ (if dir-literal
+ (eshell-ls-file dirinfo size-width)
+ (if insert-name
+ (funcall insert-func
+ (eshell-ls-decorated-name
+ (cons (concat
+ (if root-dir
+ (file-relative-name dir root-dir)
+ (expand-file-name dir)))
+ (cdr dirinfo))) ":\n"))
+ (let ((entries
+ (eshell-directory-files-and-attributes dir nil nil t)))
+ (unless show-all
+ (while (and entries
+ (string-match eshell-ls-exclude-regexp
+ (caar entries)))
+ (setq entries (cdr entries)))
+ (let ((e entries))
+ (while (cdr e)
+ (if (string-match eshell-ls-exclude-regexp (car (cadr e)))
+ (setcdr e (cddr e))
+ (setq e (cdr e))))))
+ (when (or (eq listing-style 'long-listing) show-size)
+ (let ((total 0.0))
+ (setq size-width 0)
+ (eshell-for e entries
+ (if (nth 7 (cdr e))
+ (setq total (+ total (nth 7 (cdr e)))
+ size-width
+ (max size-width
+ (length (eshell-ls-printable-size
+ (nth 7 (cdr e)) t))))))
+ (funcall insert-func "total "
+ (eshell-ls-printable-size total t) "\n")))
+ (let ((default-directory (expand-file-name dir)))
+ (if show-recursive
+ (eshell-ls-entries
+ (let ((e entries) (good-entries (list t)))
+ (while e
+ (unless (let ((len (length (caar e))))
+ (and (eq (aref (caar e) 0) ?.)
+ (or (= len 1)
+ (and (= len 2)
+ (eq (aref (caar e) 1) ?.)))))
+ (nconc good-entries (list (car e))))
+ (setq e (cdr e)))
+ (cdr good-entries))
+ nil root-dir)
+ (eshell-ls-files (eshell-ls-sort-entries entries)
+ size-width))))))))
+
+(defsubst eshell-ls-compare-entries (l r inx func)
+ "Compare the time of two files, L and R, the attribute indexed by INX."
+ (let ((lt (nth inx (cdr l)))
+ (rt (nth inx (cdr r))))
+ (if (equal lt rt)
+ (string-lessp (directory-file-name (car l))
+ (directory-file-name (car r)))
+ (funcall func rt lt))))
+
+(defun eshell-ls-sort-entries (entries)
+ "Sort the given ENTRIES, which may be files, directories or both.
+In Eshell's implementation of ls, ENTRIES is always reversed."
+ (if (eq sort-method 'unsorted)
+ (nreverse entries)
+ (sort entries
+ (function
+ (lambda (l r)
+ (let ((result
+ (cond
+ ((eq sort-method 'by-atime)
+ (eshell-ls-compare-entries
+ l r 4 'eshell-time-less-p))
+ ((eq sort-method 'by-mtime)
+ (eshell-ls-compare-entries
+ l r 5 'eshell-time-less-p))
+ ((eq sort-method 'by-ctime)
+ (eshell-ls-compare-entries
+ l r 6 'eshell-time-less-p))
+ ((eq sort-method 'by-size)
+ (eshell-ls-compare-entries
+ l r 7 '<))
+ ((eq sort-method 'by-extension)
+ (let ((lx (file-name-extension
+ (directory-file-name (car l))))
+ (rx (file-name-extension
+ (directory-file-name (car r)))))
+ (cond
+ ((or (and (not lx) (not rx))
+ (equal lx rx))
+ (string-lessp (directory-file-name (car l))
+ (directory-file-name (car r))))
+ ((not lx) t)
+ ((not rx) nil)
+ (t
+ (string-lessp lx rx)))))
+ (t
+ (string-lessp (directory-file-name (car l))
+ (directory-file-name (car r)))))))
+ (if reverse-list
+ (not result)
+ result)))))))
+
+(defun eshell-ls-files (files &optional size-width copy-fileinfo)
+ "Output a list of FILES.
+Each member of FILES is either a string or a cons cell of the form
+\(FILE . ATTRS)."
+ (if (memq listing-style '(long-listing single-column))
+ (eshell-for file files
+ (if file
+ (eshell-ls-file file size-width copy-fileinfo)))
+ (let ((f files)
+ last-f
+ display-files
+ ignore)
+ (while f
+ (if (cdar f)
+ (setq last-f f
+ f (cdr f))
+ (unless ignore
+ (funcall error-func
+ (format "%s: No such file or directory\n" (caar f))))
+ (if (eq f files)
+ (setq files (cdr files)
+ f files)
+ (if (not (cdr f))
+ (progn
+ (setcdr last-f nil)
+ (setq f nil))
+ (setcar f (cadr f))
+ (setcdr f (cddr f))))))
+ (if (not show-size)
+ (setq display-files (mapcar 'eshell-ls-annotate files))
+ (eshell-for file files
+ (let* ((str (eshell-ls-printable-size (nth 7 (cdr file)) t))
+ (len (length str)))
+ (if (< len size-width)
+ (setq str (concat (make-string (- size-width len) ? ) str)))
+ (setq file (eshell-ls-annotate file)
+ display-files (cons (cons (concat str " " (car file))
+ (cdr file))
+ display-files))))
+ (setq display-files (nreverse display-files)))
+ (let* ((col-vals
+ (if (eq listing-style 'by-columns)
+ (eshell-ls-find-column-lengths display-files)
+ (assert (eq listing-style 'by-lines))
+ (eshell-ls-find-column-widths display-files)))
+ (col-widths (car col-vals))
+ (display-files (cdr col-vals))
+ (columns (length col-widths))
+ (col-index 1)
+ need-return)
+ (eshell-for file display-files
+ (let ((name
+ (if (car file)
+ (if show-size
+ (concat (substring (car file) 0 size-width)
+ (eshell-ls-decorated-name
+ (cons (substring (car file) size-width)
+ (cdr file))))
+ (eshell-ls-decorated-name file))
+ "")))
+ (if (< col-index columns)
+ (setq need-return
+ (concat need-return name
+ (make-string
+ (max 0 (- (aref col-widths
+ (1- col-index))
+ (length name))) ? ))
+ col-index (1+ col-index))
+ (funcall insert-func need-return name "\n")
+ (setq col-index 1 need-return nil))))
+ (if need-return
+ (funcall insert-func need-return "\n"))))))
+
+(defun eshell-ls-entries (entries &optional separate root-dir)
+ "Output PATH's directory ENTRIES, formatted according to OPTIONS.
+Each member of ENTRIES may either be a string or a cons cell, the car
+of which is the file name, and the cdr of which is the list of
+attributes.
+If SEPARATE is non-nil, directories name will be entirely separated
+from the filenames. This is the normal behavior, except when doing a
+recursive listing.
+ROOT-DIR, if non-nil, specifies the root directory of the listing, to
+which non-absolute directory names will be made relative if ever they
+need to be printed."
+ (let (dirs files show-names need-return (size-width 0))
+ (eshell-for entry entries
+ (if (and (not dir-literal)
+ (or (eshell-ls-filetype-p (cdr entry) ?d)
+ (and (eshell-ls-filetype-p (cdr entry) ?l)
+ (file-directory-p (car entry)))))
+ (progn
+ (unless separate
+ (setq files (cons entry files)
+ size-width
+ (if show-size
+ (max size-width
+ (length (eshell-ls-printable-size
+ (nth 7 (cdr entry)) t))))))
+ (setq dirs (cons entry dirs)))
+ (setq files (cons entry files)
+ size-width
+ (if show-size
+ (max size-width
+ (length (eshell-ls-printable-size
+ (nth 7 (cdr entry)) t)))))))
+ (when files
+ (eshell-ls-files (eshell-ls-sort-entries files)
+ size-width show-recursive)
+ (setq need-return t))
+ (setq show-names (or show-recursive
+ (> (+ (length files) (length dirs)) 1)))
+ (eshell-for dir (eshell-ls-sort-entries dirs)
+ (if (and need-return (not dir-literal))
+ (funcall insert-func "\n"))
+ (eshell-ls-dir dir show-names
+ (unless (file-name-absolute-p (car dir))
+ root-dir) size-width)
+ (setq need-return t))))
+
+(defun eshell-ls-find-column-widths (files)
+ "Find the best fitting column widths for FILES.
+It will be returned as a vector, whose length is the number of columns
+to use, and each member of which is the width of that column
+\(including spacing)."
+ (let* ((numcols 0)
+ (width 0)
+ (widths
+ (mapcar
+ (function
+ (lambda (file)
+ (+ 2 (length (car file)))))
+ files))
+ ;; must account for the added space...
+ (max-width (+ (window-width) 2))
+ (best-width 0)
+ col-widths)
+
+ ;; determine the largest number of columns in the first row
+ (let ((w widths))
+ (while (and w (< width max-width))
+ (setq width (+ width (car w))
+ numcols (1+ numcols)
+ w (cdr w))))
+
+ ;; refine it based on the following rows
+ (while (> numcols 0)
+ (let ((i 0)
+ (colw (make-vector numcols 0))
+ (w widths))
+ (while w
+ (if (= i numcols)
+ (setq i 0))
+ (aset colw i (max (aref colw i) (car w)))
+ (setq w (cdr w) i (1+ i)))
+ (setq i 0 width 0)
+ (while (< i numcols)
+ (setq width (+ width (aref colw i))
+ i (1+ i)))
+ (if (and (< width max-width)
+ (> width best-width))
+ (setq col-widths colw
+ best-width width)))
+ (setq numcols (1- numcols)))
+
+ (cons (or col-widths (vector max-width)) files)))
+
+(defun eshell-ls-find-column-lengths (files)
+ "Find the best fitting column lengths for FILES.
+It will be returned as a vector, whose length is the number of columns
+to use, and each member of which is the width of that column
+\(including spacing)."
+ (let* ((numcols 1)
+ (width 0)
+ (widths
+ (mapcar
+ (function
+ (lambda (file)
+ (+ 2 (length (car file)))))
+ files))
+ (max-width (+ (window-width) 2))
+ col-widths
+ colw)
+
+ ;; refine it based on the following rows
+ (while numcols
+ (let* ((rows (ceiling (/ (length widths)
+ (float numcols))))
+ (w widths)
+ (len (* rows numcols))
+ (index 0)
+ (i 0))
+ (setq width 0)
+ (unless (or (= rows 0)
+ (<= (/ (length widths) (float rows))
+ (float (1- numcols))))
+ (setq colw (make-vector numcols 0))
+ (while (> len 0)
+ (if (= i numcols)
+ (setq i 0 index (1+ index)))
+ (aset colw i
+ (max (aref colw i)
+ (or (nth (+ (* i rows) index) w) 0)))
+ (setq len (1- len) i (1+ i)))
+ (setq i 0)
+ (while (< i numcols)
+ (setq width (+ width (aref colw i))
+ i (1+ i))))
+ (if (>= width max-width)
+ (setq numcols nil)
+ (if colw
+ (setq col-widths colw))
+ (if (>= numcols (length widths))
+ (setq numcols nil)
+ (setq numcols (1+ numcols))))))
+
+ (if (not col-widths)
+ (cons (vector max-width) files)
+ (setq numcols (length col-widths))
+ (let* ((rows (ceiling (/ (length widths)
+ (float numcols))))
+ (len (* rows numcols))
+ (newfiles (make-list len nil))
+ (index 0)
+ (i 0)
+ (j 0))
+ (while (< j len)
+ (if (= i numcols)
+ (setq i 0 index (1+ index)))
+ (setcar (nthcdr j newfiles)
+ (nth (+ (* i rows) index) files))
+ (setq j (1+ j) i (1+ i)))
+ (cons col-widths newfiles)))))
+
+(defun eshell-ls-decorated-name (file)
+ "Return FILE, possibly decorated.
+Use TRUENAME for predicate tests, if passed."
+ (if eshell-ls-use-colors
+ (let ((face
+ (cond
+ ((not (cdr file))
+ 'eshell-ls-missing-face)
+
+ ((stringp (cadr file))
+ 'eshell-ls-symlink-face)
+
+ ((eq (cadr file) t)
+ 'eshell-ls-directory-face)
+
+ ((not (eshell-ls-filetype-p (cdr file) ?-))
+ 'eshell-ls-special-face)
+
+ ((and (not (= (user-uid) 0)) ; root can execute anything
+ (eshell-ls-applicable (cdr file) 3
+ 'file-executable-p (car file)))
+ 'eshell-ls-executable-face)
+
+ ((not (eshell-ls-applicable (cdr file) 1
+ 'file-readable-p (car file)))
+ 'eshell-ls-unreadable-face)
+
+ ((string-match eshell-ls-archive-regexp (car file))
+ 'eshell-ls-archive-face)
+
+ ((string-match eshell-ls-backup-regexp (car file))
+ 'eshell-ls-backup-face)
+
+ ((string-match eshell-ls-product-regexp (car file))
+ 'eshell-ls-product-face)
+
+ ((string-match eshell-ls-clutter-regexp (car file))
+ 'eshell-ls-clutter-face)
+
+ ((not (eshell-ls-applicable (cdr file) 2
+ 'file-writable-p (car file)))
+ 'eshell-ls-readonly-face)
+ (eshell-ls-highlight-alist
+ (let ((tests eshell-ls-highlight-alist)
+ value)
+ (while tests
+ (if (funcall (caar tests) (car file) (cdr file))
+ (setq value (cdar tests) tests nil)
+ (setq tests (cdr tests))))
+ value)))))
+ (if face
+ (add-text-properties 0 (length (car file))
+ (list 'face face)
+ (car file)))))
+ (car file))
+
+;;; Code:
+
+;;; em-ls.el ends here