1 ;;; dired-sort.el --- Sort by by size, date, field, name and type
3 ;; This file is not part of Emacs
7 ;; Maintainer: Jari Aalto
9 ;; Keywords: extensions
11 ;; This program is free software; you can redistribute it and/or modify it
12 ;; under the terms of the GNU General Public License as published by the Free
13 ;; Software Foundation; either version 2 of the License, or (at your option)
16 ;; This program is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with program; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
26 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
33 ;; Put this file on your Emacs-Lisp load path, add following into your
34 ;; ~/.emacs startup file.
36 ;; (add-hook 'dired-mode-hook '(lambda () (require 'dired-sort)))
38 ;; NOTE: ls-lisp.el, which dired.el uses, by default inserts month names
39 ;; in national format. If the setting is anything other than English,
40 ;; this module *cannot* parse the dired lines. Please add this setting
41 ;; to your Emacs (21.4+) to make dired listing use ISO 8601 date stamps:
43 ;; (setq ls-lisp-format-time-list
55 ;; Not much to say. I have had this package lying in my lisp directory
56 ;; since Emacs 18.xx days. When I noticed that this package doesn't
57 ;; exist in the OHIO archive, neither did the ftpsearch locate it,
58 ;; I decided to clen it up and put publically available.
60 ;; The original author is unknown And the only thing that was in the
61 ;; original documentation was this line:
63 ;; $Header: /tmp_mnt/am/p7/utility/gmacs/f2/RCS/dired-resort.el,v
64 ;; 1.1 88/11/03 13:22:08 fad Exp $
66 ;; New bindings in dired
68 ;; When you load this file, function `dired-sort-default-keys' is called.
69 ;; The following bindings to dired mode are defined.
71 ;; S" " dired-sort-resort (that's an "s" + SPACE)
72 ;; Ss dired-sort-by-size
73 ;; Sd dired-sort-by-date
74 ;; Sf dired-sort-by-field
75 ;; Sn dired-sort-by-name
76 ;; St dired-sort-by-type
87 ;; Silence Byte compiler
88 (defvar revert-buffer-function)
89 (defvar current-prefix-arg) ;; Elint.el
90 ;; Not exported from sort.el
91 (autoload 'sort-subr "sort")
92 (autoload 'sort-fields-1 "sort")
93 (autoload 'dired-revert "dired")
94 (autoload 'dired-get-filename "dired")
95 (autoload 'dired-move-to-filename "dired")
96 (autoload 'sort-skip-fields "sort"))
98 ;;; ....................................................... &variables ...
100 (defvar dired-sort-load-hook '(dired-sort-default-keys)
101 "Hook run when file is loaded.")
103 ;; File property caching mechanism for dired
105 (defvar dired-sort-line-property-table nil
106 "Buffer local obarray:
107 Each symbol is a file name whose plist caches file properties,
108 accessed by #'dired-line-property")
109 (make-variable-buffer-local 'dired-sort-line-property-table)
111 (defvar dired-sort-resort-last-kind '(date)
112 "What the last sort did to the buffer.")
113 (make-variable-buffer-local 'dired-sort-resort-last-kind)
115 (defvar dired-sort-resort-alist
116 '(("name" dired-sort-by-name nil "ascending order")
117 (nil dired-sort-by-name t "descending order")
118 ("date" dired-sort-by-date nil "most recent first")
119 (nil dired-sort-by-date t "oldest first")
120 ("size" dired-sort-by-size nil "biggest first")
121 (nil dired-sort-by-size t "smallest first")
122 ("type" dired-sort-by-type t "alphabetically")
123 ("modes" dired-sort-by-field 1 "file modes")
124 ("links" dired-sort-by-field 2 "number of links")
125 ("owner" dired-sort-by-field 3 "file owner")
126 ("field" dired-sort-by-field (1) "textual field")))
128 ;;; ............................................................ &code ...
130 (defvar dired-sort-last-sort nil
131 "Last sort indication.")
133 (defun dired-sort-revert-and-decache (&optional arg noconfirm)
134 "Revert buffer using `dired-revert' ARG and NOCONFIRM."
135 (if dired-sort-line-property-table
136 (mapatoms (function (lambda (file) (setplist file nil)))
137 dired-sort-line-property-table))
138 (dired-revert arg noconfirm))
140 (defun dired-sort-line-property (func)
141 "Call FUNC with one argument: The (absolute) file name of this dired line.
142 Cache the result, and return it the next time without calling FUNC.
143 The caches are cleared when the buffer is reverted.
144 See dired-sort-line-property-table."
145 (or dired-sort-line-property-table
147 (if (eq revert-buffer-function 'dired-revert)
148 (setq revert-buffer-function 'dired-sort-revert-and-decache))
149 (setq dired-sort-line-property-table (make-vector 31 0))))
150 (let ((file (intern (dired-get-filename t) dired-sort-line-property-table)))
153 (funcall func (symbol-name file))))))
155 (defun dired-sort-move-word-backward ()
156 "move one space dlimited word backward. Must already be on word."
157 (skip-chars-backward "^ \t" (line-beginning-position))
158 (skip-chars-backward " \t" (line-beginning-position))
159 (skip-chars-backward "^ \t" (line-beginning-position)))
161 ;; FIXME: It is unreliable to read words from dired buffer,
162 ;; because the Month name can be in national format.
163 ;; => There is no eas way, doing file stat() would be too
164 ;; expensive to find out the month name?
165 ;; => It is bets to configure Emacs to always use
168 (defun dired-sort-move-to-date (&optional and-extract)
169 "Details depend on the `dired-extract-size' AND-EXTRACT."
170 ;; Go two words backward
171 ;; 4694 Month 16 19:44 file
172 ;; -rw-rw---- 1 foo foo 2082 2004-10-14 17:23 .
175 (when (dired-move-to-filename)
177 (dired-sort-move-word-backward)
178 (dired-sort-move-word-backward)
179 ;; Now, should we still take on leap due to Month name?
180 (unless (looking-at "[0-9][0-9][0-9][0-9]-")
181 (dired-sort-move-word-backward))
183 (parse-date (buffer-substring (point) end) t)
186 (defun dired-sort-extract-date ()
187 "Call `dired-sort-move-to-date'."
188 (dired-sort-move-to-date t))
190 (defun dired-sort-extract-size ()
191 "Read size with regular expression."
193 (when (dired-sort-move-to-date)
194 (skip-chars-backward " " (line-beginning-position))
195 (skip-chars-backward "0-9" (line-beginning-position))
196 (if (looking-at "[0-9]+ ")
197 (setq ret (read (current-buffer)))))
200 (defun dired-sort-header-line-p ()
201 "Check `dired-sort-extract-size'."
203 (minusp (dired-sort-extract-size))))
205 (defun dired-sort-first-file ()
208 (goto-char (point-min))
209 (while (and (dired-sort-header-line-p)
212 (dired-move-to-filename))
214 (defun dired-sort-extract-date-key (&optional ignore)
215 "Extract key with IGNORE."
216 (let ((date (dired-sort-extract-date)))
218 (date-compare-key date 'integer))))
220 (defun dired-sort-by-size-key (&optional ignore)
221 "Sort by zise or IGNORE."
222 (dired-sort-by-size-key-1 nil))
224 (defun dired-sort-by-size-increasing-key (&optional ignore)
225 "Sort by zise or IGNORE."
226 (dired-sort-by-size-key-1 t))
228 (defun dired-sort-resort-menu-options ()
229 "See `dired-sort-resort-alist'."
231 (cons "Sort Dired listing by:"
233 (function(lambda (elt)
236 (capitalize (or (nth 0 elt) " '' "))
239 dired-sort-resort-alist))))
241 (defun dired-sort-by-size-key-1 (incr-p)
242 "Sort possibly with INCR-P."
243 (let ((size (dired-sort-extract-size))
244 (char (save-excursion
246 (skip-chars-forward " ")
248 (setq char (downcase char))
251 ((= char ?-) (setq char ?~))
252 ((>= char ?a) (setq char (- (+ ?a ?z) char))))
253 (format "%c%09d" char size)))
255 (defun dired-sort-read-resort-args (&optional res)
256 "Produce a 1- or 2- list.
257 Suitable for non-interactive calling of dired-sort-resort.
258 Optional RES is a line from dired-sort-resort-alist."
262 (format "Sort by: [%s] " (car dired-sort-resort-last-kind))
263 dired-sort-resort-alist
265 (if (zerop (length res))
266 dired-sort-resort-last-kind
268 (setq res (or (assoc res dired-sort-resort-alist)
269 (error "reading resort"))))
270 (let ((type (nth 0 res))
274 (let ((ptr dired-sort-resort-alist) elt)
275 (while (and ptr (null type))
276 (setq elt (car ptr) ptr (cdr ptr))
277 (if (eq func (nth 1 elt))
278 (setq type (nth 0 elt)))))
279 (setq type (intern type))
284 (if (integerp (car arg))
285 (prefix-numeric-value current-prefix-arg)
286 (and current-prefix-arg t))))
287 ((integerp (car arg))
288 (setq arg (read-string (format "What %s? " what))))
289 (t (setq arg (y-or-n-p (format "%s? " what)))))
294 ;;; ..................................................... &interactive ...
296 (defun dired-sort-last-file ()
299 (goto-char (point-max))
300 (while (and (dired-sort-header-line-p)
303 (dired-move-to-filename))
305 (defun dired-sort-narrow-to-files ()
306 "Narrow to visible files."
310 (dired-sort-first-file)
314 (dired-sort-last-file)
319 (defun dired-sort-by-date (&optional arg)
320 "In dired, sort the lines by date, newest first.
321 With ARG, sorts oldest first."
324 (dired-sort-narrow-to-files)
325 (let (buffer-read-only)
326 (goto-char (point-min))
328 (not arg) 'forward-line 'end-of-line
331 (or (dired-sort-line-property 'dired-sort-extract-date-key)))))))
333 (setq dired-sort-last-sort (if arg
336 (message "Dired-sort: Now sorted by date, %s first."
341 (defun dired-sort-by-name (&optional arg skip-to sort-by)
342 "In dired, sort the lines by file name.
343 With ARG, sorts in reverse order. SKIP-TO SORT-BY."
345 (or sort-by (setq sort-by 'name))
347 (dired-sort-narrow-to-files)
348 (let ((buffer-read-only nil)
349 (reverse-sort-p arg))
350 (goto-char (point-min))
352 reverse-sort-p 'forward-line 'end-of-line
354 (dired-move-to-filename)
358 (let ((here (point)))
361 skip-to here 'move)))
364 (save-excursion (end-of-line) (point))
366 (goto-char (match-beginning 0))))
368 (setq dired-sort-last-sort sort-by)
369 (message "Dired-sort: Now sorted by %s%s." sort-by
375 (defun dired-sort-by-type (&optional arg)
376 "Sort by type, ARG means reverse."
385 (defun dired-sort-by-field (field)
386 "In dired, sort the lines by FIELD (defaults to the mode field)."
389 (dired-sort-narrow-to-files)
390 (let ((buffer-read-only nil))
391 (goto-char (point-min))
393 field (point-min) (point-max)
395 (sort-skip-fields (1- field))
396 (skip-chars-backward " ")
399 (setq dired-sort-last-sort 'fields)
400 (message "Dired-sort: Now sorted by %s."
401 (cond ((= field 1) "file mode")
402 ((= field 2) "number of links")
403 ((= field 3) "file owner")
404 (t (format "field #%d" field)))))
407 (defun dired-sort-by-size (&optional arg)
408 "In dired, sort the lines by file size, largest first.
409 With ARG, sorts in the reverse order (smallest first).
410 All directories are grouped together at the head of the buffer,
411 and other file types are also grouped."
413 (let ((buffer-read-only nil)
416 (dired-sort-narrow-to-files)
417 (goto-char (point-min))
419 (not incr-p) 'forward-line 'end-of-line
421 (function (lambda () (dired-sort-line-property
422 'dired-sort-by-size-increasing-key)))
423 (function (lambda () (dired-sort-line-property
424 'dired-sort-by-size-key))))))
425 (setq dired-sort-last-sort (if incr-p
428 (message "Dired-sort: Now sorted by type and size, %s first."
434 (defun dired-sort-resort (kind &optional args)
435 "In dired, change the sorting of lines. Prompt for the KIND of sorting.
436 Non-interactively, takes a sort-kind, and an optional argument for
437 the associated function. To get a list of such arguments interactively,
438 call dired-sort-read-resort-args. ARGS are passed to sort."
439 (interactive (list (dired-sort-read-resort-args)))
441 (setq kind dired-sort-resort-last-kind))
443 (setq args (cdr kind) kind (car kind)))
444 (if (symbolp kind) (setq kind (symbol-name kind)))
446 (or (nth 1 (assoc kind dired-sort-resort-alist))
447 (error "No such sorting method: %s" kind))
449 (setq dired-sort-resort-last-kind (cons kind args)))
452 (defun dired-sort-default-keys-dired-mode-map ()
453 "Define default bindings to dired map."
455 (let* ((map (symbol-value 'dired-mode-map)))
457 (error "dired-sort.el: [ERROR] dired is not yet loaded."))
458 (define-key map "\C-cs" nil)
459 (define-key map "\C-cs " 'dired-sort-resort)
460 (define-key map "\C-css" 'dired-sort-by-size)
461 (define-key map "\C-csd" 'dired-sort-by-date)
462 (define-key map "\C-csf" 'dired-sort-by-field)
463 (define-key map "\C-csn" 'dired-sort-by-name)
464 (define-key map "\C-cst" 'dired-sort-by-type)))
467 (defun dired-sort-default-keys ()
468 "Define default bindings to dired map."
469 (eval-after-load "dired"
470 '(progn (dired-sort-default-keys-dired-mode-map))))
473 (add-hook 'dired-mode-hook 'dired-sort-default-keys 'end)
475 (run-hooks 'dired-sort-load-hook)
476 (provide 'dired-sort)
478 ;;; dired-sort.el ends here