Convert consecutive FSF copyright years to ranges.
[bpt/emacs.git] / lisp / eshell / em-ls.el
CommitLineData
60370d40 1;;; em-ls.el --- implementation of ls in Lisp
affbf647 2
73b0cd50 3;; Copyright (C) 1999-2011 Free Software Foundation, Inc.
affbf647 4
7de5b421
GM
5;; Author: John Wiegley <johnw@gnu.org>
6
affbf647
GM
7;; This file is part of GNU Emacs.
8
4ee57b2a 9;; GNU Emacs is free software: you can redistribute it and/or modify
affbf647 10;; it under the terms of the GNU General Public License as published by
4ee57b2a
GM
11;; the Free Software Foundation, either version 3 of the License, or
12;; (at your option) any later version.
affbf647
GM
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
4ee57b2a 20;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
affbf647 21
dbba8a04
GM
22;;; Commentary:
23
24;; Most of the command switches recognized by GNU's ls utility are
25;; supported ([(fileutils)ls invocation]).
affbf647 26
dbba8a04
GM
27;;; Code:
28
fc17acd1
GM
29(eval-when-compile
30 (require 'cl)
31 (require 'eshell))
dbba8a04
GM
32(require 'esh-util)
33(require 'esh-opt)
affbf647 34
3146b070
GM
35;;;###autoload
36(eshell-defgroup eshell-ls nil
affbf647
GM
37 "This module implements the \"ls\" utility fully in Lisp. If it is
38passed any unrecognized command switches, it will revert to the
39operating system's version. This version of \"ls\" uses text
40properties to colorize its output based on the setting of
41`eshell-ls-use-colors'."
42 :tag "Implementation of `ls' in Lisp"
43 :group 'eshell-module)
44
affbf647
GM
45;;; User Variables:
46
47(defvar eshell-ls-orig-insert-directory
48 (symbol-function 'insert-directory)
49 "Preserve the original definition of `insert-directory'.")
50
51(defcustom eshell-ls-unload-hook
52 (list
53 (function
54 (lambda ()
55 (fset 'insert-directory eshell-ls-orig-insert-directory))))
ec60da52 56 "When unloading `eshell-ls', restore the definition of `insert-directory'."
affbf647
GM
57 :type 'hook
58 :group 'eshell-ls)
59
dace60cf 60(defcustom eshell-ls-initial-args nil
ec60da52 61 "If non-nil, this list of args is included before any call to `ls'.
dace60cf
JW
62This is useful for enabling human-readable format (-h), for example."
63 :type '(repeat :tag "Arguments" string)
64 :group 'eshell-ls)
65
ef94bd99 66(defcustom eshell-ls-dired-initial-args nil
ec60da52 67 "If non-nil, args is included before any call to `ls' in Dired.
ef94bd99
JW
68This is useful for enabling human-readable format (-h), for example."
69 :type '(repeat :tag "Arguments" string)
70 :group 'eshell-ls)
71
affbf647 72(defcustom eshell-ls-use-in-dired nil
ec60da52 73 "If non-nil, use `eshell-ls' to read directories in Dired."
affbf647
GM
74 :set (lambda (symbol value)
75 (if value
76 (unless (and (boundp 'eshell-ls-use-in-dired)
77 eshell-ls-use-in-dired)
78 (fset 'insert-directory 'eshell-ls-insert-directory))
79 (when (and (boundp 'eshell-ls-insert-directory)
80 eshell-ls-use-in-dired)
81 (fset 'insert-directory eshell-ls-orig-insert-directory)))
82 (setq eshell-ls-use-in-dired value))
83 :type 'boolean
84 :require 'em-ls
85 :group 'eshell-ls)
86
87(defcustom eshell-ls-default-blocksize 1024
ec60da52 88 "The default blocksize to use when display file sizes with -s."
affbf647
GM
89 :type 'integer
90 :group 'eshell-ls)
91
dace60cf 92(defcustom eshell-ls-exclude-regexp nil
ec60da52 93 "Unless -a is specified, files matching this regexp will not be shown."
762fe76e 94 :type '(choice regexp (const nil))
affbf647
GM
95 :group 'eshell-ls)
96
dace60cf 97(defcustom eshell-ls-exclude-hidden t
ec60da52 98 "Unless -a is specified, files beginning with . will not be shown.
dace60cf
JW
99Using this boolean, instead of `eshell-ls-exclude-regexp', is both
100faster and conserves more memory."
101 :type 'boolean
102 :group 'eshell-ls)
103
affbf647 104(defcustom eshell-ls-use-colors t
ec60da52 105 "If non-nil, use colors in file listings."
affbf647
GM
106 :type 'boolean
107 :group 'eshell-ls)
108
958e6876 109(defface eshell-ls-directory
1fd714a4
RS
110 '((((class color) (background light)) (:foreground "Blue" :weight bold))
111 (((class color) (background dark)) (:foreground "SkyBlue" :weight bold))
112 (t (:weight bold)))
ec60da52 113 "The face used for highlight directories."
affbf647 114 :group 'eshell-ls)
2fb1ec93
GM
115(define-obsolete-face-alias 'eshell-ls-directory-face
116 'eshell-ls-directory "22.1")
affbf647 117
958e6876 118(defface eshell-ls-symlink
1fd714a4
RS
119 '((((class color) (background light)) (:foreground "Dark Cyan" :weight bold))
120 (((class color) (background dark)) (:foreground "Cyan" :weight bold)))
ec60da52 121 "The face used for highlight symbolic links."
affbf647 122 :group 'eshell-ls)
2fb1ec93 123(define-obsolete-face-alias 'eshell-ls-symlink-face 'eshell-ls-symlink "22.1")
affbf647 124
958e6876 125(defface eshell-ls-executable
1fd714a4
RS
126 '((((class color) (background light)) (:foreground "ForestGreen" :weight bold))
127 (((class color) (background dark)) (:foreground "Green" :weight bold)))
ec60da52 128 "The face used for highlighting executables (not directories, though)."
affbf647 129 :group 'eshell-ls)
2fb1ec93
GM
130(define-obsolete-face-alias 'eshell-ls-executable-face
131 'eshell-ls-executable "22.1")
affbf647 132
958e6876 133(defface eshell-ls-readonly
affbf647
GM
134 '((((class color) (background light)) (:foreground "Brown"))
135 (((class color) (background dark)) (:foreground "Pink")))
ec60da52 136 "The face used for highlighting read-only files."
affbf647 137 :group 'eshell-ls)
2fb1ec93 138(define-obsolete-face-alias 'eshell-ls-readonly-face 'eshell-ls-readonly "22.1")
affbf647 139
958e6876 140(defface eshell-ls-unreadable
affbf647
GM
141 '((((class color) (background light)) (:foreground "Grey30"))
142 (((class color) (background dark)) (:foreground "DarkGrey")))
ec60da52 143 "The face used for highlighting unreadable files."
affbf647 144 :group 'eshell-ls)
2fb1ec93
GM
145(define-obsolete-face-alias 'eshell-ls-unreadable-face
146 'eshell-ls-unreadable "22.1")
affbf647 147
958e6876 148(defface eshell-ls-special
1fd714a4
RS
149 '((((class color) (background light)) (:foreground "Magenta" :weight bold))
150 (((class color) (background dark)) (:foreground "Magenta" :weight bold)))
ec60da52 151 "The face used for highlighting non-regular files."
affbf647 152 :group 'eshell-ls)
2fb1ec93 153(define-obsolete-face-alias 'eshell-ls-special-face 'eshell-ls-special "22.1")
affbf647 154
958e6876 155(defface eshell-ls-missing
1fd714a4
RS
156 '((((class color) (background light)) (:foreground "Red" :weight bold))
157 (((class color) (background dark)) (:foreground "Red" :weight bold)))
ec60da52 158 "The face used for highlighting non-existent file names."
affbf647 159 :group 'eshell-ls)
2fb1ec93 160(define-obsolete-face-alias 'eshell-ls-missing-face 'eshell-ls-missing "22.1")
affbf647
GM
161
162(defcustom eshell-ls-archive-regexp
163 (concat "\\.\\(t\\(a[rz]\\|gz\\)\\|arj\\|lzh\\|"
4c964351 164 "zip\\|[zZ]\\|gz\\|bz2\\|xz\\|deb\\|rpm\\)\\'")
ec60da52 165 "A regular expression that matches names of file archives.
affbf647
GM
166This typically includes both traditional archives and compressed
167files."
4c964351 168 :version "24.1" ; added xz
affbf647
GM
169 :type 'regexp
170 :group 'eshell-ls)
171
958e6876 172(defface eshell-ls-archive
1fd714a4
RS
173 '((((class color) (background light)) (:foreground "Orchid" :weight bold))
174 (((class color) (background dark)) (:foreground "Orchid" :weight bold)))
ec60da52 175 "The face used for highlighting archived and compressed file names."
affbf647 176 :group 'eshell-ls)
2fb1ec93 177(define-obsolete-face-alias 'eshell-ls-archive-face 'eshell-ls-archive "22.1")
affbf647
GM
178
179(defcustom eshell-ls-backup-regexp
180 "\\(\\`\\.?#\\|\\(\\.bak\\|~\\)\\'\\)"
ec60da52 181 "A regular expression that matches names of backup files."
affbf647
GM
182 :type 'regexp
183 :group 'eshell-ls)
184
958e6876 185(defface eshell-ls-backup
affbf647
GM
186 '((((class color) (background light)) (:foreground "OrangeRed"))
187 (((class color) (background dark)) (:foreground "LightSalmon")))
ec60da52 188 "The face used for highlighting backup file names."
affbf647 189 :group 'eshell-ls)
2fb1ec93 190(define-obsolete-face-alias 'eshell-ls-backup-face 'eshell-ls-backup "22.1")
affbf647
GM
191
192(defcustom eshell-ls-product-regexp
087f110d 193 "\\.\\(elc\\|o\\(bj\\)?\\|a\\|lib\\|res\\)\\'"
ec60da52 194 "A regular expression that matches names of product files.
affbf647
GM
195Products are files that get generated from a source file, and hence
196ought to be recreatable if they are deleted."
197 :type 'regexp
198 :group 'eshell-ls)
199
958e6876 200(defface eshell-ls-product
affbf647
GM
201 '((((class color) (background light)) (:foreground "OrangeRed"))
202 (((class color) (background dark)) (:foreground "LightSalmon")))
ec60da52 203 "The face used for highlighting files that are build products."
affbf647 204 :group 'eshell-ls)
2fb1ec93 205(define-obsolete-face-alias 'eshell-ls-product-face 'eshell-ls-product "22.1")
affbf647
GM
206
207(defcustom eshell-ls-clutter-regexp
208 "\\(^texput\\.log\\|^core\\)\\'"
ec60da52 209 "A regular expression that matches names of junk files.
affbf647
GM
210These are mainly files that get created for various reasons, but don't
211really need to stick around for very long."
212 :type 'regexp
213 :group 'eshell-ls)
214
958e6876 215(defface eshell-ls-clutter
1fd714a4
RS
216 '((((class color) (background light)) (:foreground "OrangeRed" :weight bold))
217 (((class color) (background dark)) (:foreground "OrangeRed" :weight bold)))
ec60da52 218 "The face used for highlighting junk file names."
affbf647 219 :group 'eshell-ls)
2fb1ec93 220(define-obsolete-face-alias 'eshell-ls-clutter-face 'eshell-ls-clutter "22.1")
affbf647
GM
221
222(defsubst eshell-ls-filetype-p (attrs type)
223 "Test whether ATTRS specifies a directory."
224 (if (nth 8 attrs)
225 (eq (aref (nth 8 attrs) 0) type)))
226
227(defmacro eshell-ls-applicable (attrs index func file)
3a66e78f
CY
228 "Test whether, for ATTRS, the user can do what corresponds to INDEX.
229ATTRS is a string of file modes. See `file-attributes'.
230If we cannot determine the answer using ATTRS (e.g., if we need
231to know what group the user is in), compute the return value by
232calling FUNC with FILE as an argument."
233 `(let ((owner (nth 2 ,attrs))
234 (modes (nth 8 ,attrs)))
235 (cond ((cond ((numberp owner)
236 (= owner (user-uid)))
237 ((stringp owner)
238 (or (string-equal owner (user-login-name))
239 (member owner (eshell-current-ange-uids)))))
240 ;; The user owns this file.
241 (not (eq (aref modes ,index) ?-)))
242 ((eq (aref modes (+ ,index 3))
243 (aref modes (+ ,index 6)))
244 ;; If the "group" and "other" fields give identical
245 ;; results, use that.
246 (not (eq (aref modes (+ ,index 3)) ?-)))
247 (t
248 ;; Otherwise call FUNC.
249 (,(eval func) ,file)))))
affbf647
GM
250
251(defcustom eshell-ls-highlight-alist nil
ec60da52 252 "This alist correlates test functions to color.
affbf647
GM
253The format of the members of this alist is
254
255 (TEST-SEXP . FACE)
256
257If TEST-SEXP evals to non-nil, that face will be used to highlight the
258name of the file. The first match wins. `file' and `attrs' are in
259scope during the evaluation of TEST-SEXP."
260 :type '(repeat (cons function face))
261 :group 'eshell-ls)
262
263;;; Functions:
264
265(defun eshell-ls-insert-directory
266 (file switches &optional wildcard full-directory-p)
267 "Insert directory listing for FILE, formatted according to SWITCHES.
268Leaves point after the inserted text.
269SWITCHES may be a string of options, or a list of strings.
270Optional third arg WILDCARD means treat FILE as shell wildcard.
271Optional fourth arg FULL-DIRECTORY-P means file is a directory and
272switches do not contain `d', so that a full listing is expected.
273
274This version of the function uses `eshell/ls'. If any of the switches
275passed are not recognized, the operating system's version will be used
276instead."
277 (let ((handler (find-file-name-handler file 'insert-directory)))
278 (if handler
279 (funcall handler 'insert-directory file switches
280 wildcard full-directory-p)
281 (if (stringp switches)
282 (setq switches (split-string switches)))
283 (let (eshell-current-handles
73f99a66
JW
284 eshell-current-subjob-p
285 font-lock-mode)
affbf647
GM
286 ;; use the fancy highlighting in `eshell-ls' rather than font-lock
287 (when (and eshell-ls-use-colors
288 (featurep 'font-lock))
289 (font-lock-mode -1)
6c9e58c4 290 (setq font-lock-defaults nil)
affbf647
GM
291 (if (boundp 'font-lock-buffers)
292 (set 'font-lock-buffers
293 (delq (current-buffer)
294 (symbol-value 'font-lock-buffers)))))
295 (let ((insert-func 'insert)
296 (error-func 'insert)
dace60cf 297 (flush-func 'ignore)
ef94bd99 298 eshell-ls-dired-initial-args)
affbf647
GM
299 (eshell-do-ls (append switches (list file))))))))
300
301(defsubst eshell/ls (&rest args)
302 "An alias version of `eshell-do-ls'."
303 (let ((insert-func 'eshell-buffered-print)
304 (error-func 'eshell-error)
305 (flush-func 'eshell-flush))
306 (eshell-do-ls args)))
307
127fd3c2
JW
308(put 'eshell/ls 'eshell-no-numeric-conversions t)
309
1a32899d
GM
310(defvar block-size)
311(defvar dereference-links)
312(defvar dir-literal)
313(defvar error-func)
314(defvar flush-func)
315(defvar human-readable)
316(defvar ignore-pattern)
317(defvar insert-func)
318(defvar listing-style)
319(defvar numeric-uid-gid)
320(defvar reverse-list)
321(defvar show-all)
322(defvar show-recursive)
323(defvar show-size)
324(defvar sort-method)
325(defvar ange-cache)
326(defvar dired-flag)
affbf647
GM
327
328(defun eshell-do-ls (&rest args)
329 "Implementation of \"ls\" in Lisp, passing ARGS."
330 (funcall flush-func -1)
331 ;; process the command arguments, and begin listing files
332 (eshell-eval-using-options
dace60cf
JW
333 "ls" (if eshell-ls-initial-args
334 (list eshell-ls-initial-args args)
335 args)
7f09df7a
JW
336 `((?a "all" nil show-all
337 "show all files in directory")
affbf647 338 (?c nil by-ctime sort-method
73f99a66 339 "sort by last status change time")
affbf647
GM
340 (?d "directory" nil dir-literal
341 "list directory entries instead of contents")
342 (?k "kilobytes" 1024 block-size
7f09df7a 343 "using 1024 as the block size")
affbf647
GM
344 (?h "human-readable" 1024 human-readable
345 "print sizes in human readable format")
7f09df7a
JW
346 (?H "si" 1000 human-readable
347 "likewise, but use powers of 1000 not 1024")
affbf647
GM
348 (?I "ignore" t ignore-pattern
349 "do not list implied entries matching pattern")
350 (?l nil long-listing listing-style
351 "use a long listing format")
352 (?n "numeric-uid-gid" nil numeric-uid-gid
353 "list numeric UIDs and GIDs instead of names")
354 (?r "reverse" nil reverse-list
355 "reverse order while sorting")
356 (?s "size" nil show-size
357 "print size of each file, in blocks")
358 (?t nil by-mtime sort-method
359 "sort by modification time")
360 (?u nil by-atime sort-method
361 "sort by last access time")
70a06174
JW
362 (?x nil by-lines listing-style
363 "list entries by lines instead of by columns")
7f09df7a
JW
364 (?C nil by-columns listing-style
365 "list entries by columns")
ce343c43 366 (?L "dereference" nil dereference-links
7f09df7a
JW
367 "list entries pointed to by symbolic links")
368 (?R "recursive" nil show-recursive
369 "list subdirectories recursively")
370 (?S nil by-size sort-method
371 "sort by file size")
372 (?U nil unsorted sort-method
373 "do not sort; list entries in directory order")
affbf647
GM
374 (?X nil by-extension sort-method
375 "sort alphabetically by entry extension")
376 (?1 nil single-column listing-style
377 "list one file per line")
73f99a66
JW
378 (nil "dired" nil dired-flag
379 "Here for compatibility with GNU ls.")
affbf647 380 (nil "help" nil nil
7f09df7a 381 "show this usage display")
affbf647
GM
382 :external "ls"
383 :usage "[OPTION]... [FILE]...
384List information about the FILEs (the current directory by default).
7f09df7a 385Sort entries alphabetically across.")
affbf647
GM
386 ;; setup some defaults, based on what the user selected
387 (unless block-size
388 (setq block-size eshell-ls-default-blocksize))
389 (unless listing-style
390 (setq listing-style 'by-columns))
391 (unless args
392 (setq args (list ".")))
8c6b1d83 393 (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp) ange-cache)
7f09df7a 394 (when ignore-pattern
affbf647
GM
395 (unless (eshell-using-module 'eshell-glob)
396 (error (concat "-I option requires that `eshell-glob'"
397 " be a member of `eshell-modules-list'")))
398 (set-text-properties 0 (length ignore-pattern) nil ignore-pattern)
dace60cf
JW
399 (setq eshell-ls-exclude-regexp
400 (if eshell-ls-exclude-regexp
affbf647 401 (concat "\\(" eshell-ls-exclude-regexp "\\|"
dace60cf
JW
402 (eshell-glob-regexp ignore-pattern) "\\)")
403 (eshell-glob-regexp ignore-pattern))))
affbf647
GM
404 ;; list the files!
405 (eshell-ls-entries
a4cc44cf
CY
406 (mapcar (lambda (arg)
407 (cons (if (and (eshell-under-windows-p)
408 (file-name-absolute-p arg))
409 (expand-file-name arg)
410 arg)
411 (eshell-file-attributes
412 arg (if numeric-uid-gid 'integer 'string))))
dace60cf 413 args)
affbf647
GM
414 t (expand-file-name default-directory)))
415 (funcall flush-func)))
416
417(defsubst eshell-ls-printable-size (filesize &optional by-blocksize)
418 "Return a printable FILESIZE."
419 (eshell-printable-size filesize human-readable
420 (and by-blocksize block-size)
421 eshell-ls-use-colors))
422
423(defsubst eshell-ls-size-string (attrs size-width)
424 "Return the size string for ATTRS length, using SIZE-WIDTH."
425 (let* ((str (eshell-ls-printable-size (nth 7 attrs) t))
426 (len (length str)))
427 (if (< len size-width)
428 (concat (make-string (- size-width len) ? ) str)
429 str)))
430
431(defun eshell-ls-annotate (fileinfo)
432 "Given a FILEINFO object, return a resolved, decorated FILEINFO.
433This means resolving any symbolic links, determining what face the
434name should be displayed as, etc. Think of it as cooking a FILEINFO."
435 (if (not (and (stringp (cadr fileinfo))
436 (or dereference-links
437 (eq listing-style 'long-listing))))
438 (setcar fileinfo (eshell-ls-decorated-name fileinfo))
439 (let (dir attr)
440 (unless (file-name-absolute-p (cadr fileinfo))
441 (setq dir (file-truename
442 (file-name-directory
443 (expand-file-name (car fileinfo))))))
444 (setq attr
8c6b1d83 445 (eshell-file-attributes
affbf647
GM
446 (let ((target (if dir
447 (expand-file-name (cadr fileinfo) dir)
448 (cadr fileinfo))))
449 (if dereference-links
450 (file-truename target)
451 target))))
452 (if (or dereference-links
453 (string-match "^\\.\\.?$" (car fileinfo)))
454 (progn
455 (setcdr fileinfo attr)
456 (setcar fileinfo (eshell-ls-decorated-name fileinfo)))
457 (assert (eq listing-style 'long-listing))
458 (setcar fileinfo
459 (concat (eshell-ls-decorated-name fileinfo) " -> "
460 (eshell-ls-decorated-name
461 (cons (cadr fileinfo) attr)))))))
462 fileinfo)
463
464(defun eshell-ls-file (fileinfo &optional size-width copy-fileinfo)
465 "Output FILE in long format.
466FILE may be a string, or a cons cell whose car is the filename and
467whose cdr is the list of file attributes."
468 (if (not (cdr fileinfo))
469 (funcall error-func (format "%s: No such file or directory\n"
470 (car fileinfo)))
471 (setq fileinfo
472 (eshell-ls-annotate (if copy-fileinfo
473 (cons (car fileinfo)
474 (cdr fileinfo))
475 fileinfo)))
476 (let ((file (car fileinfo))
477 (attrs (cdr fileinfo)))
478 (if (not (eq listing-style 'long-listing))
479 (if show-size
480 (funcall insert-func (eshell-ls-size-string attrs size-width)
481 " " file "\n")
482 (funcall insert-func file "\n"))
483 (let ((line
484 (concat
485 (if show-size
486 (concat (eshell-ls-size-string attrs size-width) " "))
487 (format
ce343c43
EZ
488 (if numeric-uid-gid
489 "%s%4d %-8s %-8s "
490 "%s%4d %-14s %-8s ")
affbf647
GM
491 (or (nth 8 attrs) "??????????")
492 (or (nth 1 attrs) 0)
8c6b1d83 493 (or (let ((user (nth 2 attrs)))
ce343c43
EZ
494 (and (stringp user)
495 (eshell-substring user 14)))
affbf647
GM
496 (nth 2 attrs)
497 "")
8c6b1d83 498 (or (let ((group (nth 3 attrs)))
ce343c43
EZ
499 (and (stringp group)
500 (eshell-substring group 8)))
affbf647
GM
501 (nth 3 attrs)
502 ""))
503 (let* ((str (eshell-ls-printable-size (nth 7 attrs)))
504 (len (length str)))
7ed88398
EZ
505 ;; Let file sizes shorter than 9 align neatly.
506 (if (< len (or size-width 8))
507 (concat (make-string (- (or size-width 8) len) ? ) str)
affbf647
GM
508 str))
509 " " (format-time-string
7f09df7a
JW
510 (concat
511 "%b %e "
512 (if (= (nth 5 (decode-time (current-time)))
513 (nth 5 (decode-time
514 (nth (cond
515 ((eq sort-method 'by-atime) 4)
516 ((eq sort-method 'by-ctime) 6)
517 (t 5)) attrs))))
518 "%H:%M"
519 " %Y")) (nth (cond
520 ((eq sort-method 'by-atime) 4)
521 ((eq sort-method 'by-ctime) 6)
522 (t 5)) attrs)) " ")))
affbf647
GM
523 (funcall insert-func line file "\n"))))))
524
525(defun eshell-ls-dir (dirinfo &optional insert-name root-dir size-width)
526 "Output the entries in DIRINFO.
527If INSERT-NAME is non-nil, the name of DIRINFO will be output. If
528ROOT-DIR is also non-nil, and a directory name, DIRINFO will be output
529relative to that directory."
530 (let ((dir (car dirinfo)))
531 (if (not (cdr dirinfo))
532 (funcall error-func (format "%s: No such file or directory\n" dir))
533 (if dir-literal
534 (eshell-ls-file dirinfo size-width)
535 (if insert-name
536 (funcall insert-func
537 (eshell-ls-decorated-name
538 (cons (concat
539 (if root-dir
540 (file-relative-name dir root-dir)
541 (expand-file-name dir)))
542 (cdr dirinfo))) ":\n"))
dace60cf 543 (let ((entries (eshell-directory-files-and-attributes
7f09df7a
JW
544 dir nil (and (not show-all)
545 eshell-ls-exclude-hidden
ce343c43
EZ
546 "\\`[^.]") t
547 ;; Asking for UID and GID as
548 ;; strings saves another syscall
549 ;; later when we are going to
550 ;; display user and group names.
551 (if numeric-uid-gid 'integer 'string))))
dace60cf
JW
552 (when (and (not show-all) eshell-ls-exclude-regexp)
553 (while (and entries (string-match eshell-ls-exclude-regexp
554 (caar entries)))
affbf647
GM
555 (setq entries (cdr entries)))
556 (let ((e entries))
557 (while (cdr e)
558 (if (string-match eshell-ls-exclude-regexp (car (cadr e)))
559 (setcdr e (cddr e))
560 (setq e (cdr e))))))
561 (when (or (eq listing-style 'long-listing) show-size)
562 (let ((total 0.0))
563 (setq size-width 0)
564 (eshell-for e entries
565 (if (nth 7 (cdr e))
566 (setq total (+ total (nth 7 (cdr e)))
567 size-width
568 (max size-width
569 (length (eshell-ls-printable-size
7ed88398
EZ
570 (nth 7 (cdr e))
571 (not
572 ;; If we are under -l, count length
573 ;; of sizes in bytes, not in blocks.
574 (eq listing-style 'long-listing))))))))
affbf647
GM
575 (funcall insert-func "total "
576 (eshell-ls-printable-size total t) "\n")))
577 (let ((default-directory (expand-file-name dir)))
578 (if show-recursive
579 (eshell-ls-entries
580 (let ((e entries) (good-entries (list t)))
581 (while e
582 (unless (let ((len (length (caar e))))
583 (and (eq (aref (caar e) 0) ?.)
584 (or (= len 1)
585 (and (= len 2)
586 (eq (aref (caar e) 1) ?.)))))
587 (nconc good-entries (list (car e))))
588 (setq e (cdr e)))
589 (cdr good-entries))
590 nil root-dir)
591 (eshell-ls-files (eshell-ls-sort-entries entries)
592 size-width))))))))
593
594(defsubst eshell-ls-compare-entries (l r inx func)
595 "Compare the time of two files, L and R, the attribute indexed by INX."
596 (let ((lt (nth inx (cdr l)))
597 (rt (nth inx (cdr r))))
598 (if (equal lt rt)
599 (string-lessp (directory-file-name (car l))
600 (directory-file-name (car r)))
601 (funcall func rt lt))))
602
603(defun eshell-ls-sort-entries (entries)
604 "Sort the given ENTRIES, which may be files, directories or both.
605In Eshell's implementation of ls, ENTRIES is always reversed."
606 (if (eq sort-method 'unsorted)
607 (nreverse entries)
608 (sort entries
609 (function
610 (lambda (l r)
611 (let ((result
612 (cond
613 ((eq sort-method 'by-atime)
73171bd4 614 (eshell-ls-compare-entries l r 4 'time-less-p))
affbf647 615 ((eq sort-method 'by-mtime)
73171bd4 616 (eshell-ls-compare-entries l r 5 'time-less-p))
affbf647 617 ((eq sort-method 'by-ctime)
73171bd4 618 (eshell-ls-compare-entries l r 6 'time-less-p))
7f09df7a
JW
619 ((eq sort-method 'by-size)
620 (eshell-ls-compare-entries l r 7 '<))
affbf647
GM
621 ((eq sort-method 'by-extension)
622 (let ((lx (file-name-extension
623 (directory-file-name (car l))))
624 (rx (file-name-extension
625 (directory-file-name (car r)))))
626 (cond
627 ((or (and (not lx) (not rx))
628 (equal lx rx))
629 (string-lessp (directory-file-name (car l))
630 (directory-file-name (car r))))
631 ((not lx) t)
632 ((not rx) nil)
633 (t
634 (string-lessp lx rx)))))
70a06174 635 (t
7f09df7a
JW
636 (string-lessp (directory-file-name (car l))
637 (directory-file-name (car r)))))))
affbf647
GM
638 (if reverse-list
639 (not result)
640 result)))))))
641
642(defun eshell-ls-files (files &optional size-width copy-fileinfo)
643 "Output a list of FILES.
644Each member of FILES is either a string or a cons cell of the form
645\(FILE . ATTRS)."
b7e9b5b0
GM
646 ;; Mimic behavior of coreutils ls, which lists a single file per
647 ;; line when output is not a tty. Exceptions: if -x was supplied,
648 ;; or if we are the _last_ command in a pipeline.
649 ;; FIXME Not really the same since not testing output destination.
650 (if (or (and eshell-in-pipeline-p
651 (not (eq eshell-in-pipeline-p 'last))
652 (not (eq listing-style 'by-lines)))
653 (memq listing-style '(long-listing single-column)))
affbf647
GM
654 (eshell-for file files
655 (if file
656 (eshell-ls-file file size-width copy-fileinfo)))
657 (let ((f files)
658 last-f
659 display-files
660 ignore)
661 (while f
662 (if (cdar f)
663 (setq last-f f
664 f (cdr f))
665 (unless ignore
666 (funcall error-func
667 (format "%s: No such file or directory\n" (caar f))))
668 (if (eq f files)
669 (setq files (cdr files)
670 f files)
671 (if (not (cdr f))
672 (progn
673 (setcdr last-f nil)
674 (setq f nil))
675 (setcar f (cadr f))
676 (setcdr f (cddr f))))))
677 (if (not show-size)
678 (setq display-files (mapcar 'eshell-ls-annotate files))
679 (eshell-for file files
680 (let* ((str (eshell-ls-printable-size (nth 7 (cdr file)) t))
681 (len (length str)))
682 (if (< len size-width)
683 (setq str (concat (make-string (- size-width len) ? ) str)))
684 (setq file (eshell-ls-annotate file)
685 display-files (cons (cons (concat str " " (car file))
686 (cdr file))
687 display-files))))
688 (setq display-files (nreverse display-files)))
689 (let* ((col-vals
690 (if (eq listing-style 'by-columns)
691 (eshell-ls-find-column-lengths display-files)
692 (assert (eq listing-style 'by-lines))
693 (eshell-ls-find-column-widths display-files)))
694 (col-widths (car col-vals))
695 (display-files (cdr col-vals))
696 (columns (length col-widths))
697 (col-index 1)
698 need-return)
699 (eshell-for file display-files
700 (let ((name
701 (if (car file)
702 (if show-size
703 (concat (substring (car file) 0 size-width)
704 (eshell-ls-decorated-name
705 (cons (substring (car file) size-width)
706 (cdr file))))
707 (eshell-ls-decorated-name file))
708 "")))
709 (if (< col-index columns)
710 (setq need-return
711 (concat need-return name
712 (make-string
713 (max 0 (- (aref col-widths
714 (1- col-index))
715 (length name))) ? ))
716 col-index (1+ col-index))
717 (funcall insert-func need-return name "\n")
718 (setq col-index 1 need-return nil))))
719 (if need-return
720 (funcall insert-func need-return "\n"))))))
721
722(defun eshell-ls-entries (entries &optional separate root-dir)
a4cc44cf 723 "Output PATH's directory ENTRIES.
affbf647
GM
724Each member of ENTRIES may either be a string or a cons cell, the car
725of which is the file name, and the cdr of which is the list of
726attributes.
727If SEPARATE is non-nil, directories name will be entirely separated
728from the filenames. This is the normal behavior, except when doing a
729recursive listing.
730ROOT-DIR, if non-nil, specifies the root directory of the listing, to
731which non-absolute directory names will be made relative if ever they
732need to be printed."
733 (let (dirs files show-names need-return (size-width 0))
734 (eshell-for entry entries
735 (if (and (not dir-literal)
736 (or (eshell-ls-filetype-p (cdr entry) ?d)
737 (and (eshell-ls-filetype-p (cdr entry) ?l)
738 (file-directory-p (car entry)))))
739 (progn
740 (unless separate
741 (setq files (cons entry files)
742 size-width
743 (if show-size
744 (max size-width
745 (length (eshell-ls-printable-size
746 (nth 7 (cdr entry)) t))))))
747 (setq dirs (cons entry dirs)))
748 (setq files (cons entry files)
749 size-width
750 (if show-size
751 (max size-width
752 (length (eshell-ls-printable-size
753 (nth 7 (cdr entry)) t)))))))
754 (when files
755 (eshell-ls-files (eshell-ls-sort-entries files)
756 size-width show-recursive)
757 (setq need-return t))
758 (setq show-names (or show-recursive
759 (> (+ (length files) (length dirs)) 1)))
760 (eshell-for dir (eshell-ls-sort-entries dirs)
761 (if (and need-return (not dir-literal))
762 (funcall insert-func "\n"))
763 (eshell-ls-dir dir show-names
dace60cf
JW
764 (unless (file-name-absolute-p (car dir)) root-dir)
765 size-width)
affbf647
GM
766 (setq need-return t))))
767
768(defun eshell-ls-find-column-widths (files)
769 "Find the best fitting column widths for FILES.
770It will be returned as a vector, whose length is the number of columns
771to use, and each member of which is the width of that column
772\(including spacing)."
773 (let* ((numcols 0)
774 (width 0)
775 (widths
776 (mapcar
777 (function
778 (lambda (file)
779 (+ 2 (length (car file)))))
780 files))
781 ;; must account for the added space...
782 (max-width (+ (window-width) 2))
783 (best-width 0)
784 col-widths)
785
786 ;; determine the largest number of columns in the first row
787 (let ((w widths))
788 (while (and w (< width max-width))
789 (setq width (+ width (car w))
790 numcols (1+ numcols)
791 w (cdr w))))
792
793 ;; refine it based on the following rows
794 (while (> numcols 0)
795 (let ((i 0)
796 (colw (make-vector numcols 0))
797 (w widths))
798 (while w
799 (if (= i numcols)
800 (setq i 0))
801 (aset colw i (max (aref colw i) (car w)))
802 (setq w (cdr w) i (1+ i)))
803 (setq i 0 width 0)
804 (while (< i numcols)
805 (setq width (+ width (aref colw i))
806 i (1+ i)))
807 (if (and (< width max-width)
808 (> width best-width))
809 (setq col-widths colw
810 best-width width)))
811 (setq numcols (1- numcols)))
812
813 (cons (or col-widths (vector max-width)) files)))
814
815(defun eshell-ls-find-column-lengths (files)
816 "Find the best fitting column lengths for FILES.
817It will be returned as a vector, whose length is the number of columns
818to use, and each member of which is the width of that column
819\(including spacing)."
820 (let* ((numcols 1)
821 (width 0)
822 (widths
823 (mapcar
824 (function
825 (lambda (file)
826 (+ 2 (length (car file)))))
827 files))
828 (max-width (+ (window-width) 2))
829 col-widths
830 colw)
831
832 ;; refine it based on the following rows
833 (while numcols
834 (let* ((rows (ceiling (/ (length widths)
835 (float numcols))))
836 (w widths)
837 (len (* rows numcols))
838 (index 0)
839 (i 0))
840 (setq width 0)
841 (unless (or (= rows 0)
842 (<= (/ (length widths) (float rows))
843 (float (1- numcols))))
844 (setq colw (make-vector numcols 0))
845 (while (> len 0)
846 (if (= i numcols)
847 (setq i 0 index (1+ index)))
848 (aset colw i
849 (max (aref colw i)
850 (or (nth (+ (* i rows) index) w) 0)))
851 (setq len (1- len) i (1+ i)))
852 (setq i 0)
853 (while (< i numcols)
854 (setq width (+ width (aref colw i))
855 i (1+ i))))
856 (if (>= width max-width)
857 (setq numcols nil)
858 (if colw
859 (setq col-widths colw))
860 (if (>= numcols (length widths))
861 (setq numcols nil)
862 (setq numcols (1+ numcols))))))
863
864 (if (not col-widths)
865 (cons (vector max-width) files)
866 (setq numcols (length col-widths))
867 (let* ((rows (ceiling (/ (length widths)
868 (float numcols))))
869 (len (* rows numcols))
870 (newfiles (make-list len nil))
871 (index 0)
872 (i 0)
873 (j 0))
874 (while (< j len)
875 (if (= i numcols)
876 (setq i 0 index (1+ index)))
877 (setcar (nthcdr j newfiles)
878 (nth (+ (* i rows) index) files))
879 (setq j (1+ j) i (1+ i)))
880 (cons col-widths newfiles)))))
881
882(defun eshell-ls-decorated-name (file)
3eed132b 883 "Return FILE, possibly decorated."
7f09df7a
JW
884 (if eshell-ls-use-colors
885 (let ((face
886 (cond
887 ((not (cdr file))
958e6876 888 'eshell-ls-missing)
7f09df7a
JW
889
890 ((stringp (cadr file))
958e6876 891 'eshell-ls-symlink)
7f09df7a
JW
892
893 ((eq (cadr file) t)
958e6876 894 'eshell-ls-directory)
7f09df7a
JW
895
896 ((not (eshell-ls-filetype-p (cdr file) ?-))
958e6876 897 'eshell-ls-special)
7f09df7a
JW
898
899 ((and (/= (user-uid) 0) ; root can execute anything
900 (eshell-ls-applicable (cdr file) 3
901 'file-executable-p (car file)))
958e6876 902 'eshell-ls-executable)
7f09df7a
JW
903
904 ((not (eshell-ls-applicable (cdr file) 1
905 'file-readable-p (car file)))
958e6876 906 'eshell-ls-unreadable)
7f09df7a
JW
907
908 ((string-match eshell-ls-archive-regexp (car file))
958e6876 909 'eshell-ls-archive)
7f09df7a
JW
910
911 ((string-match eshell-ls-backup-regexp (car file))
958e6876 912 'eshell-ls-backup)
7f09df7a
JW
913
914 ((string-match eshell-ls-product-regexp (car file))
958e6876 915 'eshell-ls-product)
7f09df7a
JW
916
917 ((string-match eshell-ls-clutter-regexp (car file))
958e6876 918 'eshell-ls-clutter)
7f09df7a
JW
919
920 ((not (eshell-ls-applicable (cdr file) 2
921 'file-writable-p (car file)))
958e6876 922 'eshell-ls-readonly)
7f09df7a
JW
923 (eshell-ls-highlight-alist
924 (let ((tests eshell-ls-highlight-alist)
925 value)
926 (while tests
927 (if (funcall (caar tests) (car file) (cdr file))
928 (setq value (cdar tests) tests nil)
929 (setq tests (cdr tests))))
930 value)))))
931 (if face
932 (add-text-properties 0 (length (car file))
933 (list 'face face)
934 (car file)))))
935 (car file))
affbf647 936
dbba8a04 937(provide 'em-ls)
affbf647 938
3146b070
GM
939;; Local Variables:
940;; generated-autoload-file: "esh-groups.el"
941;; End:
942
affbf647 943;;; em-ls.el ends here