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