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