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