diff lisp/eshell/em-ls.el @ 29876:edfec1c0d511

*** empty log message ***
author Gerd Moellmann <gerd@gnu.org>
date Fri, 23 Jun 2000 05:24:10 +0000
parents
children 34b1ab9d583d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/eshell/em-ls.el	Fri Jun 23 05:24:10 2000 +0000
@@ -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