* bitmaps/README:
[bpt/emacs.git] / lisp / recentf.el
CommitLineData
e8af40ee 1;;; recentf.el --- setup a menu of recently opened files
bc66a9a9 2
0d30b337 3;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004,
409cc4a3 4;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
bc66a9a9
DL
5
6;; Author: David Ponce <david@dponce.com>
7;; Created: July 19 1999
be9e7056
JB
8;; Keywords: files
9
bc66a9a9
DL
10;; This file is part of GNU Emacs.
11
eb3fa2cf
GM
12;; GNU Emacs is free software: you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
bc66a9a9
DL
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
eb3fa2cf 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
bc66a9a9
DL
24
25;;; Commentary:
26
27;; This package maintains a menu for visiting files that were operated
7b2ab969
DP
28;; on recently. When enabled a new "Open Recent" sub menu is
29;; displayed in the "Files" menu. The recent files list is
30;; automatically saved across Emacs sessions. You can customize the
31;; number of recent files displayed, the location of the menu and
32;; others options (see the source code for details).
be9e7056
JB
33
34;;; History:
bc66a9a9 35;;
bc66a9a9
DL
36
37;;; Code:
bc66a9a9 38(require 'easymenu)
7b2ab969 39(require 'tree-widget)
be9e7056 40(require 'timer)
bc66a9a9 41
be9e7056
JB
42;;; Internal data
43;;
bc66a9a9
DL
44(defvar recentf-list nil
45 "List of recently opened files.")
46
52d2876f
DP
47(defsubst recentf-enabled-p ()
48 "Return non-nil if recentf mode is currently enabled."
49 (memq 'recentf-save-list kill-emacs-hook))
be9e7056
JB
50\f
51;;; Customization
52;;
bc66a9a9
DL
53(defgroup recentf nil
54 "Maintain a menu of recently opened files."
55 :version "21.1"
56 :group 'files)
57
58(defgroup recentf-filters nil
59 "Group to customize recentf menu filters.
60You should define the options of your own filters in this group."
61 :group 'recentf)
62
63(defcustom recentf-max-saved-items 20
2aa7c4d5 64 "Maximum number of items of the recent list that will be saved.
8154a06e 65A nil value means to save the whole list.
be9e7056 66See the command `recentf-save-list'."
bc66a9a9
DL
67 :group 'recentf
68 :type 'integer)
69
be9e7056 70(defcustom recentf-save-file "~/.recentf"
2aa7c4d5 71 "File to save the recent list into."
bc66a9a9 72 :group 'recentf
a0314231
GM
73 :type 'file
74 :initialize 'custom-initialize-default
75 :set (lambda (symbol value)
76 (let ((oldvalue (eval symbol)))
77 (custom-set-default symbol value)
78 (and (not (equal value oldvalue))
79 recentf-mode
80 (recentf-load-list)))))
bc66a9a9 81
9be6a039
DP
82(defcustom recentf-save-file-modes 384 ;; 0600
83 "Mode bits of recentf save file, as an integer, or nil.
84If non-nil, after writing `recentf-save-file', set its mode bits to
85this value. By default give R/W access only to the user who owns that
86file. See also the function `set-file-modes'."
87 :group 'recentf
88 :type '(choice (const :tag "Don't change" nil)
89 integer))
60f164bd 90
bc66a9a9 91(defcustom recentf-exclude nil
2aa7c4d5 92 "List of regexps and predicates for filenames excluded from the recent list.
f6a9235a
GM
93When a filename matches any of the regexps or satisfies any of the
94predicates it is excluded from the recent list.
95A predicate is a function that is passed a filename to check and that
96must return non-nil to exclude it."
bc66a9a9 97 :group 'recentf
f6a9235a 98 :type '(repeat (choice regexp function)))
bc66a9a9 99
60f164bd
MA
100(defun recentf-keep-default-predicate (file)
101 "Return non-nil if FILE should be kept in the recent list.
102It handles the case of remote files as well."
103 (cond
4e44b31f 104 ((file-remote-p file nil t) (file-readable-p file))
60f164bd
MA
105 ((file-remote-p file))
106 ((file-readable-p file))))
107
eafc2b27 108(defcustom recentf-keep
60f164bd 109 '(recentf-keep-default-predicate)
2aa7c4d5 110 "List of regexps and predicates for filenames kept in the recent list.
eafc2b27
DP
111Regexps and predicates are tried in the specified order.
112When nil all filenames are kept in the recent list.
113When a filename matches any of the regexps or satisfies any of the
114predicates it is kept in the recent list.
60f164bd
MA
115The default is to keep readable files. Remote files are checked
116for readability only in case a connection is established to that
117remote system, otherwise they are kept in the recent list without
118checking their readability.
eafc2b27 119A predicate is a function that is passed a filename to check and that
60f164bd 120must return non-nil to keep it."
eafc2b27
DP
121 :group 'recentf
122 :type '(repeat (choice regexp function)))
123
be9e7056
JB
124(defun recentf-menu-customization-changed (variable value)
125 "Function called when the recentf menu customization has changed.
126Set VARIABLE with VALUE, and force a rebuild of the recentf menu."
52d2876f
DP
127 (if (and (featurep 'recentf) (recentf-enabled-p))
128 (progn
129 ;; Unavailable until recentf has been loaded.
130 (recentf-hide-menu)
131 (set-default variable value)
132 (recentf-show-menu))
133 (set-default variable value)))
be9e7056 134
bc66a9a9 135(defcustom recentf-menu-title "Open Recent"
2aa7c4d5 136 "Name of the recentf menu."
bc66a9a9
DL
137 :group 'recentf
138 :type 'string
139 :set 'recentf-menu-customization-changed)
140
50ed4c96 141(defcustom recentf-menu-path '("File")
2aa7c4d5 142 "Path where to add the recentf menu.
d5d78bd5 143If nil add it at top level (see also `easy-menu-add-item')."
bc66a9a9
DL
144 :group 'recentf
145 :type '(choice (const :tag "Top Level" nil)
146 (sexp :tag "Menu Path"))
147 :set 'recentf-menu-customization-changed)
148
b03a2115 149(defcustom recentf-menu-before "Open File..."
2aa7c4d5 150 "Name of the menu before which the recentf menu will be added.
d5d78bd5 151If nil add it at end of menu (see also `easy-menu-add-item')."
bc66a9a9
DL
152 :group 'recentf
153 :type '(choice (string :tag "Name")
154 (const :tag "Last" nil))
155 :set 'recentf-menu-customization-changed)
156
eafc2b27 157(defcustom recentf-menu-action 'find-file
2aa7c4d5 158 "Function to invoke with a filename item of the recentf menu.
eafc2b27 159The default is to call `find-file' to edit the selected file."
bc66a9a9 160 :group 'recentf
52d2876f 161 :type 'function)
bc66a9a9
DL
162
163(defcustom recentf-max-menu-items 10
2aa7c4d5 164 "Maximum number of items in the recentf menu."
bc66a9a9 165 :group 'recentf
52d2876f 166 :type 'integer)
bc66a9a9
DL
167
168(defcustom recentf-menu-filter nil
2aa7c4d5 169 "Function used to filter files displayed in the recentf menu.
8154a06e 170A nil value means no filter. The following functions are predefined:
bc66a9a9 171
be9e7056
JB
172- `recentf-sort-ascending'
173 Sort menu items in ascending order.
174- `recentf-sort-descending'
175 Sort menu items in descending order.
176- `recentf-sort-basenames-ascending'
177 Sort menu items by filenames sans directory in ascending order.
178- `recentf-sort-basenames-descending'
179 Sort menu items by filenames sans directory in descending order.
180- `recentf-sort-directories-ascending'
181 Sort menu items by directories in ascending order.
182- `recentf-sort-directories-descending'
183 Sort menu items by directories in descending order.
184- `recentf-show-basenames'
185 Show filenames sans directory in menu items.
186- `recentf-show-basenames-ascending'
187 Show filenames sans directory in ascending order.
188- `recentf-show-basenames-descending'
189 Show filenames sans directory in descending order.
190- `recentf-relative-filter'
191 Show filenames relative to `default-directory'.
192- `recentf-arrange-by-rule'
193 Show sub-menus following user defined rules.
194- `recentf-arrange-by-mode'
195 Show a sub-menu for each major mode.
196- `recentf-arrange-by-dir'
197 Show a sub-menu for each directory.
198- `recentf-filter-changer'
52d2876f 199 Manage a menu of filters.
be9e7056
JB
200
201The filter function is called with one argument, the list of menu
202elements used to build the menu and must return a new list of menu
203elements (see `recentf-make-menu-element' for menu element form)."
bc66a9a9 204 :group 'recentf
c60ee5e7 205 :type '(radio (const nil)
be9e7056
JB
206 (function-item recentf-sort-ascending)
207 (function-item recentf-sort-descending)
208 (function-item recentf-sort-basenames-ascending)
209 (function-item recentf-sort-basenames-descending)
210 (function-item recentf-sort-directories-ascending)
211 (function-item recentf-sort-directories-descending)
212 (function-item recentf-show-basenames)
213 (function-item recentf-show-basenames-ascending)
214 (function-item recentf-show-basenames-descending)
215 (function-item recentf-relative-filter)
216 (function-item recentf-arrange-by-rule)
217 (function-item recentf-arrange-by-mode)
218 (function-item recentf-arrange-by-dir)
219 (function-item recentf-filter-changer)
52d2876f 220 function))
bc66a9a9 221
4e8cb311 222(defcustom recentf-menu-open-all-flag nil
2aa7c4d5 223 "Non-nil means to show an \"All...\" item in the menu.
4e8cb311
DP
224This item will replace the \"More...\" item."
225 :group 'recentf
52d2876f 226 :type 'boolean)
4e8cb311 227
cd6ef82d
GM
228(define-obsolete-variable-alias 'recentf-menu-append-commands-p
229 'recentf-menu-append-commands-flag
230 "22.1")
231
be9e7056 232(defcustom recentf-menu-append-commands-flag t
2aa7c4d5 233 "Non-nil means to append command items to the menu."
bc66a9a9 234 :group 'recentf
52d2876f 235 :type 'boolean)
bc66a9a9 236
be9e7056 237(defcustom recentf-auto-cleanup 'mode
2aa7c4d5 238 "Define when to automatically cleanup the recent list.
be9e7056
JB
239The following values can be set:
240
241- `mode'
242 Cleanup when turning the mode on (default).
243- `never'
244 Never cleanup the list automatically.
245- A number
246 Cleanup each time Emacs has been idle that number of seconds.
247- A time string
248 Cleanup at specified time string, for example at \"11:00pm\".
249
250Setting this variable directly does not take effect;
251use \\[customize].
252
253See also the command `recentf-cleanup', that can be used to manually
254cleanup the list."
255 :group 'recentf
256 :type '(radio (const :tag "When mode enabled"
257 :value mode)
258 (const :tag "Never"
259 :value never)
260 (number :tag "When idle that seconds"
261 :value 300)
262 (string :tag "At time"
263 :value "11:00pm"))
264 :set (lambda (variable value)
265 (set-default variable value)
266 (when (featurep 'recentf)
267 ;; Unavailable until recentf has been loaded.
268 (recentf-auto-cleanup))))
bc66a9a9 269
51c8b53f 270(defcustom recentf-initialize-file-name-history t
2aa7c4d5 271 "Non-nil means to initialize `file-name-history' with the recent list.
51c8b53f
EZ
272If `file-name-history' is not empty, do nothing."
273 :group 'recentf
274 :type 'boolean)
275
bc66a9a9 276(defcustom recentf-load-hook nil
2aa7c4d5 277 "Normal hook run at end of loading the `recentf' package."
bc66a9a9
DL
278 :group 'recentf
279 :type 'hook)
280
ad8b6d89
DP
281(defcustom recentf-filename-handlers nil
282 "Functions to post process recent file names.
283They are successively passed a file name to transform it."
be9e7056 284 :group 'recentf
ad8b6d89
DP
285 :type '(choice
286 (const :tag "None" nil)
287 (repeat :tag "Functions"
288 (choice
289 (const file-truename)
290 (const abbreviate-file-name)
291 (function :tag "Other function")))))
e58af6f1
DP
292
293(defcustom recentf-show-file-shortcuts-flag t
294 "Whether to show ``[N]'' for the Nth item up to 10.
295If non-nil, `recentf-open-files' will show labels for keys that can be
296used as shortcuts to open the Nth file."
297 :group 'recentf
298 :type 'boolean)
be9e7056
JB
299\f
300;;; Utilities
301;;
bc66a9a9 302(defconst recentf-case-fold-search
7c2fb837 303 (memq system-type '(windows-nt cygwin))
bc66a9a9
DL
304 "Non-nil if recentf searches and matches should ignore case.")
305
be9e7056
JB
306(defsubst recentf-string-equal (s1 s2)
307 "Return non-nil if strings S1 and S2 have identical contents.
308Ignore case if `recentf-case-fold-search' is non-nil."
309 (if recentf-case-fold-search
310 (string-equal (downcase s1) (downcase s2))
311 (string-equal s1 s2)))
312
313(defsubst recentf-string-lessp (s1 s2)
314 "Return non-nil if string S1 is less than S2 in lexicographic order.
315Ignore case if `recentf-case-fold-search' is non-nil."
316 (if recentf-case-fold-search
317 (string-lessp (downcase s1) (downcase s2))
318 (string-lessp s1 s2)))
319
320(defun recentf-string-member (elt list)
321 "Return non-nil if ELT is an element of LIST.
322The value is actually the tail of LIST whose car is ELT.
323ELT must be a string and LIST a list of strings.
324Ignore case if `recentf-case-fold-search' is non-nil."
325 (while (and list (not (recentf-string-equal elt (car list))))
326 (setq list (cdr list)))
327 list)
328
329(defsubst recentf-trunc-list (l n)
330 "Return from L the list of its first N elements."
331 (let (nl)
332 (while (and l (> n 0))
333 (setq nl (cons (car l) nl)
334 n (1- n)
335 l (cdr l)))
336 (nreverse nl)))
337
338(defun recentf-dump-variable (variable &optional limit)
339 "Insert a \"(setq VARIABLE value)\" in the current buffer.
340When the value of VARIABLE is a list, optional argument LIMIT
341specifies a maximum number of elements to insert. By default insert
342the full list."
343 (let ((value (symbol-value variable)))
344 (if (atom value)
52d2876f 345 (insert (format "\n(setq %S '%S)\n" variable value))
be9e7056
JB
346 (when (and (integerp limit) (> limit 0))
347 (setq value (recentf-trunc-list value limit)))
348 (insert (format "\n(setq %S\n '(" variable))
349 (dolist (e value)
350 (insert (format "\n %S" e)))
351 (insert "\n ))\n"))))
352
353(defvar recentf-auto-cleanup-timer nil
354 "Timer used to automatically cleanup the recent list.
355See also the option `recentf-auto-cleanup'.")
356
357(defun recentf-auto-cleanup ()
358 "Automatic cleanup of the recent list."
359 (when (timerp recentf-auto-cleanup-timer)
360 (cancel-timer recentf-auto-cleanup-timer))
361 (when recentf-mode
362 (setq recentf-auto-cleanup-timer
363 (cond
364 ((eq 'mode recentf-auto-cleanup)
365 (recentf-cleanup)
366 nil)
367 ((numberp recentf-auto-cleanup)
368 (run-with-idle-timer
369 recentf-auto-cleanup t 'recentf-cleanup))
370 ((stringp recentf-auto-cleanup)
371 (run-at-time
372 recentf-auto-cleanup nil 'recentf-cleanup))))))
373\f
374;;; File functions
375;;
376(defsubst recentf-push (filename)
377 "Push FILENAME into the recent list, if it isn't there yet.
378If it is there yet, move it at the beginning of the list.
379If `recentf-case-fold-search' is non-nil, ignore case when comparing
380filenames."
381 (let ((m (recentf-string-member filename recentf-list)))
382 (and m (setq recentf-list (delq (car m) recentf-list)))
383 (push filename recentf-list)))
384
ad8b6d89
DP
385(defun recentf-apply-filename-handlers (name)
386 "Apply `recentf-filename-handlers' to file NAME.
387Return the transformed file name, or NAME if any handler failed, or
388returned nil."
389 (or (condition-case nil
390 (let ((handlers recentf-filename-handlers)
391 (filename name))
392 (while (and filename handlers)
393 (setq filename (funcall (car handlers) filename)
394 handlers (cdr handlers)))
395 filename)
396 (error nil))
397 name))
398
be9e7056 399(defsubst recentf-expand-file-name (name)
ad8b6d89
DP
400 "Convert file NAME to absolute, and canonicalize it.
401NAME is first passed to the function `expand-file-name', then to
402`recentf-filename-handlers' to post process it."
403 (recentf-apply-filename-handlers (expand-file-name name)))
be9e7056 404
bc66a9a9 405(defun recentf-include-p (filename)
f6a9235a
GM
406 "Return non-nil if FILENAME should be included in the recent list.
407That is, if it doesn't match any of the `recentf-exclude' checks."
bc66a9a9 408 (let ((case-fold-search recentf-case-fold-search)
f6a9235a 409 (checks recentf-exclude)
eafc2b27 410 (keepit t))
f6a9235a 411 (while (and checks keepit)
eafc2b27
DP
412 (setq keepit (condition-case nil
413 (not (if (stringp (car checks))
414 ;; A regexp
415 (string-match (car checks) filename)
416 ;; A predicate
417 (funcall (car checks) filename)))
418 (error nil))
419 checks (cdr checks)))
420 keepit))
421
422(defun recentf-keep-p (filename)
423 "Return non-nil if FILENAME should be kept in the recent list.
424That is, if it matches any of the `recentf-keep' checks."
425 (let* ((case-fold-search recentf-case-fold-search)
426 (checks recentf-keep)
427 (keepit (null checks)))
428 (while (and checks (not keepit))
429 (setq keepit (condition-case nil
430 (if (stringp (car checks))
431 ;; A regexp
432 (string-match (car checks) filename)
433 ;; A predicate
434 (funcall (car checks) filename))
435 (error nil))
436 checks (cdr checks)))
f6a9235a 437 keepit))
bc66a9a9 438
be9e7056
JB
439(defsubst recentf-add-file (filename)
440 "Add or move FILENAME at the beginning of the recent list.
eafc2b27
DP
441Does nothing if the name satisfies any of the `recentf-exclude'
442regexps or predicates."
be9e7056
JB
443 (setq filename (recentf-expand-file-name filename))
444 (when (recentf-include-p filename)
445 (recentf-push filename)))
bc66a9a9 446
eafc2b27
DP
447(defsubst recentf-remove-if-non-kept (filename)
448 "Remove FILENAME from the recent list, if file is not kept.
be9e7056 449Return non-nil if FILENAME has been removed."
eafc2b27 450 (unless (recentf-keep-p filename)
be9e7056
JB
451 (let ((m (recentf-string-member
452 (recentf-expand-file-name filename) recentf-list)))
453 (and m (setq recentf-list (delq (car m) recentf-list))))))
bc66a9a9 454
be9e7056
JB
455(defsubst recentf-directory-compare (f1 f2)
456 "Compare absolute filenames F1 and F2.
457First compare directories, then filenames sans directory.
458Return non-nil if F1 is less than F2."
459 (let ((d1 (file-name-directory f1))
460 (d2 (file-name-directory f2)))
461 (if (recentf-string-equal d1 d2)
462 (recentf-string-lessp (file-name-nondirectory f1)
463 (file-name-nondirectory f2))
464 (recentf-string-lessp d1 d2))))
465\f
466;;; Menu building
467;;
4e8cb311
DP
468(defsubst recentf-digit-shortcut-command-name (n)
469 "Return a command name to open the Nth most recent file.
470See also the command `recentf-open-most-recent-file'."
471 (intern (format "recentf-open-most-recent-file-%d" n)))
472
473(defvar recentf--shortcuts-keymap
474 (let ((km (make-sparse-keymap)))
475 (dolist (k '(0 9 8 7 6 5 4 3 2 1))
476 (let ((cmd (recentf-digit-shortcut-command-name k)))
477 ;; Define a shortcut command.
478 (defalias cmd
479 `(lambda ()
480 (interactive)
481 (recentf-open-most-recent-file ,k)))
482 ;; Bind it to a digit key.
483 (define-key km (vector (+ k ?0)) cmd)))
484 km)
485 "Digit shortcuts keymap.")
486
be9e7056 487(defvar recentf-menu-items-for-commands
ad8b6d89
DP
488 (list
489 ["Cleanup list"
490 recentf-cleanup
491 :help "Remove duplicates, and obsoletes files from the recent list"
492 :active t]
493 ["Edit list..."
494 recentf-edit-list
495 :help "Manually remove files from the recent list"
496 :active t]
497 ["Save list now"
498 recentf-save-list
499 :help "Save the list of recently opened files now"
500 :active t]
501 ["Options..."
502 (customize-group "recentf")
503 :help "Customize recently opened files menu and options"
504 :active t]
505 )
be9e7056 506 "List of menu items for recentf commands.")
bc66a9a9 507
be9e7056
JB
508(defvar recentf-menu-filter-commands nil
509 "This variable can be used by menu filters to setup their own command menu.
510If non-nil it must contain a list of valid menu-items to be appended
511to the recent file list part of the menu. Before calling a menu
512filter function this variable is reset to nil.")
513
514(defsubst recentf-elements (n)
515 "Return a list of the first N elements of the recent list."
bc66a9a9
DL
516 (recentf-trunc-list recentf-list n))
517
be9e7056 518(defsubst recentf-make-menu-element (menu-item menu-value)
bc66a9a9 519 "Create a new menu-element.
be9e7056
JB
520A menu element is a pair (MENU-ITEM . MENU-VALUE), where MENU-ITEM is
521the menu item string displayed. MENU-VALUE is the file to be open
522when the corresponding MENU-ITEM is selected. Or it is a
523pair (SUB-MENU-TITLE . MENU-ELEMENTS) where SUB-MENU-TITLE is a
524sub-menu title and MENU-ELEMENTS is the list of menu elements in the
525sub-menu."
bc66a9a9
DL
526 (cons menu-item menu-value))
527
be9e7056 528(defsubst recentf-menu-element-item (e)
bc66a9a9
DL
529 "Return the item part of the menu-element E."
530 (car e))
531
be9e7056 532(defsubst recentf-menu-element-value (e)
bc66a9a9
DL
533 "Return the value part of the menu-element E."
534 (cdr e))
535
be9e7056 536(defsubst recentf-set-menu-element-item (e item)
bc66a9a9
DL
537 "Change the item part of menu-element E to ITEM."
538 (setcar e item))
539
be9e7056 540(defsubst recentf-set-menu-element-value (e value)
bc66a9a9
DL
541 "Change the value part of menu-element E to VALUE."
542 (setcdr e value))
543
be9e7056 544(defsubst recentf-sub-menu-element-p (e)
bc66a9a9
DL
545 "Return non-nil if menu-element E defines a sub-menu."
546 (consp (recentf-menu-element-value e)))
547
be9e7056
JB
548(defsubst recentf-make-default-menu-element (file)
549 "Make a new default menu element with FILE.
550This a menu element (FILE . FILE)."
551 (recentf-make-menu-element file file))
bc66a9a9 552
be9e7056
JB
553(defsubst recentf-menu-elements (n)
554 "Return a list of the first N default menu elements from the recent list.
fdd63a1c 555See also `recentf-make-default-menu-element'."
bc66a9a9
DL
556 (mapcar 'recentf-make-default-menu-element
557 (recentf-elements n)))
558
559(defun recentf-apply-menu-filter (filter l)
fdd63a1c
DL
560 "Apply function FILTER to the list of menu-elements L.
561It takes care of sub-menu elements in L and recursively apply FILTER
0a1280b2 562to them. It is guaranteed that FILTER receives only a list of single
fdd63a1c 563menu-elements (no sub-menu)."
be9e7056 564 (if (and l (functionp filter))
bc66a9a9 565 (let ((case-fold-search recentf-case-fold-search)
be9e7056
JB
566 elts others)
567 ;; split L into two sub-listes, one of sub-menus elements and
568 ;; another of single menu elements.
569 (dolist (elt l)
570 (if (recentf-sub-menu-element-p elt)
571 (push elt elts)
572 (push elt others)))
573 ;; Apply FILTER to single elements.
574 (when others
575 (setq others (funcall filter (nreverse others))))
576 ;; Apply FILTER to sub-menu elements.
577 (setq l nil)
578 (dolist (elt elts)
bc66a9a9 579 (recentf-set-menu-element-value
be9e7056
JB
580 elt (recentf-apply-menu-filter
581 filter (recentf-menu-element-value elt)))
582 (push elt l))
583 ;; Return the new filtered menu element list.
584 (nconc l others))
bc66a9a9
DL
585 l))
586
4e8cb311
DP
587;; Count the number of assigned menu shortcuts.
588(defvar recentf-menu-shortcuts)
589
52d2876f
DP
590(defun recentf-make-menu-items (&optional menu)
591 "Make menu items from the recent list.
592This is a menu filter function which ignores the MENU argument."
bc66a9a9 593 (setq recentf-menu-filter-commands nil)
4e8cb311
DP
594 (let* ((recentf-menu-shortcuts 0)
595 (file-items
52d2876f
DP
596 (condition-case err
597 (mapcar 'recentf-make-menu-item
598 (recentf-apply-menu-filter
599 recentf-menu-filter
600 (recentf-menu-elements recentf-max-menu-items)))
601 (error
602 (message "recentf update menu failed: %s"
603 (error-message-string err))))))
604 (append
605 (or file-items
606 '(["No files" t
607 :help "No recent file to open"
608 :active nil]))
609 (if recentf-menu-open-all-flag
610 '(["All..." recentf-open-files
611 :help "Open recent files through a dialog"
612 :active t])
613 (and (< recentf-max-menu-items (length recentf-list))
614 '(["More..." recentf-open-more-files
615 :help "Open files not in the menu through a dialog"
616 :active t])))
617 (and recentf-menu-filter-commands '("---"))
618 recentf-menu-filter-commands
619 (and recentf-menu-items-for-commands '("---"))
620 recentf-menu-items-for-commands)))
bc66a9a9 621
4e8cb311 622(defun recentf-menu-value-shortcut (name)
52d2876f 623 "Return a shortcut digit for file NAME.
4e8cb311
DP
624Return nil if file NAME is not one of the ten more recent."
625 (let ((i 0) k)
626 (while (and (not k) (< i 10))
627 (if (string-equal name (nth i recentf-list))
628 (progn
629 (setq recentf-menu-shortcuts (1+ recentf-menu-shortcuts))
630 (setq k (% (1+ i) 10)))
631 (setq i (1+ i))))
632 k))
633
634(defun recentf-make-menu-item (elt)
be9e7056
JB
635 "Make a menu item from menu element ELT."
636 (let ((item (recentf-menu-element-item elt))
637 (value (recentf-menu-element-value elt)))
638 (if (recentf-sub-menu-element-p elt)
639 (cons item (mapcar 'recentf-make-menu-item value))
4e8cb311
DP
640 (let ((k (and (< recentf-menu-shortcuts 10)
641 (recentf-menu-value-shortcut value))))
642 (vector item
643 ;; If the file name is one of the ten more recent, use
644 ;; a digit shortcut command to open it, else use an
645 ;; anonymous command.
646 (if k
647 (recentf-digit-shortcut-command-name k)
648 `(lambda ()
649 (interactive)
650 (,recentf-menu-action ,value)))
651 :help (concat "Open " value)
652 :active t)))))
bc66a9a9 653
d5d78bd5
EZ
654(defsubst recentf-menu-bar ()
655 "Return the keymap of the global menu bar."
656 (lookup-key global-map [menu-bar]))
657
52d2876f
DP
658(defun recentf-show-menu ()
659 "Show the menu of recently opened files."
660 (easy-menu-add-item
661 (recentf-menu-bar) recentf-menu-path
662 (list recentf-menu-title :filter 'recentf-make-menu-items)
663 recentf-menu-before))
664
665(defun recentf-hide-menu ()
666 "Hide the menu of recently opened files."
667 (easy-menu-remove-item (recentf-menu-bar) recentf-menu-path
668 recentf-menu-title))
be9e7056
JB
669\f
670;;; Predefined menu filters
671;;
672(defsubst recentf-sort-ascending (l)
bc66a9a9
DL
673 "Sort the list of menu elements L in ascending order.
674The MENU-ITEM part of each menu element is compared."
675 (sort (copy-sequence l)
be9e7056
JB
676 #'(lambda (e1 e2)
677 (recentf-string-lessp
678 (recentf-menu-element-item e1)
679 (recentf-menu-element-item e2)))))
bc66a9a9 680
be9e7056 681(defsubst recentf-sort-descending (l)
bc66a9a9
DL
682 "Sort the list of menu elements L in descending order.
683The MENU-ITEM part of each menu element is compared."
684 (sort (copy-sequence l)
be9e7056
JB
685 #'(lambda (e1 e2)
686 (recentf-string-lessp
687 (recentf-menu-element-item e2)
688 (recentf-menu-element-item e1)))))
bc66a9a9 689
be9e7056 690(defsubst recentf-sort-basenames-ascending (l)
bc66a9a9 691 "Sort the list of menu elements L in ascending order.
be9e7056 692Only filenames sans directory are compared."
bc66a9a9 693 (sort (copy-sequence l)
be9e7056
JB
694 #'(lambda (e1 e2)
695 (recentf-string-lessp
696 (file-name-nondirectory (recentf-menu-element-value e1))
697 (file-name-nondirectory (recentf-menu-element-value e2))))))
bc66a9a9 698
be9e7056 699(defsubst recentf-sort-basenames-descending (l)
bc66a9a9 700 "Sort the list of menu elements L in descending order.
be9e7056 701Only filenames sans directory are compared."
bc66a9a9 702 (sort (copy-sequence l)
be9e7056
JB
703 #'(lambda (e1 e2)
704 (recentf-string-lessp
705 (file-name-nondirectory (recentf-menu-element-value e2))
706 (file-name-nondirectory (recentf-menu-element-value e1))))))
707
708(defsubst recentf-sort-directories-ascending (l)
bc66a9a9
DL
709 "Sort the list of menu elements L in ascending order.
710Compares directories then filenames to order the list."
711 (sort (copy-sequence l)
be9e7056
JB
712 #'(lambda (e1 e2)
713 (recentf-directory-compare
714 (recentf-menu-element-value e1)
715 (recentf-menu-element-value e2)))))
bc66a9a9 716
be9e7056 717(defsubst recentf-sort-directories-descending (l)
bc66a9a9
DL
718 "Sort the list of menu elements L in descending order.
719Compares directories then filenames to order the list."
720 (sort (copy-sequence l)
be9e7056
JB
721 #'(lambda (e1 e2)
722 (recentf-directory-compare
723 (recentf-menu-element-value e2)
724 (recentf-menu-element-value e1)))))
725
726(defun recentf-show-basenames (l &optional no-dir)
727 "Filter the list of menu elements L to show filenames sans directory.
728When a filename is duplicated, it is appended a sequence number if
729optional argument NO-DIR is non-nil, or its directory otherwise."
730 (let (filtered-names filtered-list full name counters sufx)
731 (dolist (elt l (nreverse filtered-list))
732 (setq full (recentf-menu-element-value elt)
733 name (file-name-nondirectory full))
734 (if (not (member name filtered-names))
735 (push name filtered-names)
736 (if no-dir
737 (if (setq sufx (assoc name counters))
738 (setcdr sufx (1+ (cdr sufx)))
739 (setq sufx 1)
740 (push (cons name sufx) counters))
741 (setq sufx (file-name-directory full)))
742 (setq name (format "%s(%s)" name sufx)))
743 (push (recentf-make-menu-element name full) filtered-list))))
744
745(defsubst recentf-show-basenames-ascending (l)
746 "Filter the list of menu elements L to show filenames sans directory.
747Filenames are sorted in ascending order.
748This filter combines the `recentf-sort-basenames-ascending' and
fdd63a1c 749`recentf-show-basenames' filters."
bc66a9a9
DL
750 (recentf-show-basenames (recentf-sort-basenames-ascending l)))
751
be9e7056
JB
752(defsubst recentf-show-basenames-descending (l)
753 "Filter the list of menu elements L to show filenames sans directory.
754Filenames are sorted in descending order.
755This filter combines the `recentf-sort-basenames-descending' and
fdd63a1c 756`recentf-show-basenames' filters."
bc66a9a9
DL
757 (recentf-show-basenames (recentf-sort-basenames-descending l)))
758
759(defun recentf-relative-filter (l)
be9e7056
JB
760 "Filter the list of menu-elements L to show relative filenames.
761Filenames are relative to the `default-directory'."
762 (mapcar #'(lambda (menu-element)
763 (let* ((ful (recentf-menu-element-value menu-element))
764 (rel (file-relative-name ful default-directory)))
765 (if (string-match "^\\.\\." rel)
766 menu-element
767 (recentf-make-menu-element rel ful))))
bc66a9a9 768 l))
be9e7056
JB
769\f
770;;; Rule based menu filters
771;;
bc66a9a9
DL
772(defcustom recentf-arrange-rules
773 '(
52d2876f
DP
774 ("Elisp files (%d)" ".\\.el\\'")
775 ("Java files (%d)" ".\\.java\\'")
776 ("C/C++ files (%d)" "c\\(pp\\)?\\'")
bc66a9a9 777 )
2aa7c4d5 778 "List of rules used by `recentf-arrange-by-rule' to build sub-menus.
fdd63a1c 779A rule is a pair (SUB-MENU-TITLE . MATCHER). SUB-MENU-TITLE is the
bc66a9a9 780displayed title of the sub-menu where a '%d' `format' pattern is
fdd63a1c
DL
781replaced by the number of items in the sub-menu. MATCHER is a regexp
782or a list of regexps. Items matching one of the regular expressions in
52d2876f
DP
783MATCHER are added to the corresponding sub-menu.
784SUB-MENU-TITLE can be a function. It is passed every items that
785matched the corresponding MATCHER, and it must return a
786pair (SUB-MENU-TITLE . ITEM). SUB-MENU-TITLE is a computed sub-menu
787title that can be another function. ITEM is the received item which
788may have been modified to match another rule."
bc66a9a9 789 :group 'recentf-filters
52d2876f
DP
790 :type '(repeat (cons (choice string function)
791 (repeat regexp))))
bc66a9a9
DL
792
793(defcustom recentf-arrange-by-rule-others "Other files (%d)"
2aa7c4d5 794 "Title of the `recentf-arrange-by-rule' sub-menu.
fdd63a1c
DL
795This is for the menu where items that don't match any
796`recentf-arrange-rules' are displayed. If nil these items are
797displayed in the main recent files menu. A '%d' `format' pattern in
798the title is replaced by the number of items in the sub-menu."
bc66a9a9
DL
799 :group 'recentf-filters
800 :type '(choice (const :tag "Main menu" nil)
52d2876f 801 (string :tag "Title")))
bc66a9a9
DL
802
803(defcustom recentf-arrange-by-rules-min-items 0
2aa7c4d5 804 "Minimum number of items in a `recentf-arrange-by-rule' sub-menu.
bc66a9a9
DL
805If the number of items in a sub-menu is less than this value the
806corresponding sub-menu items are displayed in the main recent files
807menu or in the `recentf-arrange-by-rule-others' sub-menu if
808defined."
809 :group 'recentf-filters
52d2876f 810 :type 'number)
bc66a9a9
DL
811
812(defcustom recentf-arrange-by-rule-subfilter nil
2aa7c4d5 813 "Function called by a rule based filter to filter sub-menu elements.
8154a06e 814A nil value means no filter. See also `recentf-menu-filter'.
be9e7056 815You can't use another rule based filter here."
bc66a9a9 816 :group 'recentf-filters
b2639d51 817 :type '(choice (const nil) function)
be9e7056
JB
818 :set (lambda (variable value)
819 (when (memq value '(recentf-arrange-by-rule
820 recentf-arrange-by-mode
821 recentf-arrange-by-dir))
822 (error "Recursive use of a rule based filter"))
52d2876f
DP
823 (set-default variable value)))
824
825(defun recentf-match-rule (file)
826 "Return the rule that match FILE."
827 (let ((rules recentf-arrange-rules)
828 match found)
829 (while (and (not found) rules)
830 (setq match (cdar rules))
831 (when (stringp match)
832 (setq match (list match)))
833 (while (and match (not (string-match (car match) file)))
834 (setq match (cdr match)))
835 (if match
836 (setq found (cons (caar rules) file))
837 (setq rules (cdr rules))))
838 found))
bc66a9a9
DL
839
840(defun recentf-arrange-by-rule (l)
fdd63a1c
DL
841 "Filter the list of menu-elements L.
842Arrange them in sub-menus following rules in `recentf-arrange-rules'."
52d2876f
DP
843 (when recentf-arrange-rules
844 (let (menus others menu file min count)
d973cf9c 845 ;; Put menu items into sub-menus as defined by rules.
be9e7056 846 (dolist (elt l)
52d2876f
DP
847 (setq file (recentf-menu-element-value elt)
848 menu (recentf-match-rule file))
849 (while (functionp (car menu))
850 (setq menu (funcall (car menu) (cdr menu))))
851 (if (not (stringp (car menu)))
852 (push elt others)
853 (setq menu (or (assoc (car menu) menus)
854 (car (push (list (car menu)) menus))))
855 (recentf-set-menu-element-value
856 menu (cons elt (recentf-menu-element-value menu)))))
857 ;; Finalize each sub-menu:
d973cf9c
DP
858 ;; - truncate it depending on the value of
859 ;; `recentf-arrange-by-rules-min-items',
860 ;; - replace %d by the number of menu items,
861 ;; - apply `recentf-arrange-by-rule-subfilter' to menu items.
862 (setq min (if (natnump recentf-arrange-by-rules-min-items)
863 recentf-arrange-by-rules-min-items 0)
52d2876f
DP
864 l nil)
865 (dolist (elt menus)
866 (setq menu (recentf-menu-element-value elt)
867 count (length menu))
868 (if (< count min)
869 (setq others (nconc menu others))
870 (recentf-set-menu-element-item
871 elt (format (recentf-menu-element-item elt) count))
872 (recentf-set-menu-element-value
873 elt (recentf-apply-menu-filter
874 recentf-arrange-by-rule-subfilter (nreverse menu)))
875 (push elt l)))
d973cf9c 876 ;; Add the menu items remaining in the `others' bin.
52d2876f
DP
877 (when (setq others (nreverse others))
878 (setq l (nconc
879 l
880 ;; Put items in an sub menu.
881 (if (stringp recentf-arrange-by-rule-others)
882 (list
883 (recentf-make-menu-element
884 (format recentf-arrange-by-rule-others
885 (length others))
886 (recentf-apply-menu-filter
887 recentf-arrange-by-rule-subfilter others)))
888 ;; Append items to the main menu.
889 (recentf-apply-menu-filter
890 recentf-arrange-by-rule-subfilter others)))))))
891 l)
be9e7056
JB
892\f
893;;; Predefined rule based menu filters
894;;
52d2876f
DP
895(defun recentf-indirect-mode-rule (file)
896 "Apply a second level `auto-mode-alist' regexp to FILE."
897 (recentf-match-rule (substring file 0 (match-beginning 0))))
898
bc66a9a9 899(defun recentf-build-mode-rules ()
be9e7056
JB
900 "Convert `auto-mode-alist' to menu filter rules.
901Rules obey `recentf-arrange-rules' format."
bc66a9a9 902 (let ((case-fold-search recentf-case-fold-search)
be9e7056
JB
903 regexp rule-name rule rules)
904 (dolist (mode auto-mode-alist)
905 (setq regexp (car mode)
906 mode (cdr mode))
d973cf9c
DP
907 (when mode
908 (cond
909 ;; Build a special "strip suffix" rule from entries of the
910 ;; form (REGEXP FUNCTION NON-NIL). Notice that FUNCTION is
911 ;; ignored by the menu filter. So in some corner cases a
912 ;; wrong mode could be guessed.
913 ((and (consp mode) (cadr mode))
52d2876f 914 (setq rule-name 'recentf-indirect-mode-rule))
d973cf9c
DP
915 ((and mode (symbolp mode))
916 (setq rule-name (symbol-name mode))
917 (if (string-match "\\(.*\\)-mode$" rule-name)
918 (setq rule-name (match-string 1 rule-name)))
919 (setq rule-name (concat rule-name " (%d)"))))
920 (setq rule (assoc rule-name rules))
bc66a9a9
DL
921 (if rule
922 (setcdr rule (cons regexp (cdr rule)))
be9e7056 923 (push (list rule-name regexp) rules))))
bc66a9a9
DL
924 ;; It is important to preserve auto-mode-alist order
925 ;; to ensure the right file <-> mode association
926 (nreverse rules)))
c60ee5e7 927
bc66a9a9 928(defun recentf-arrange-by-mode (l)
be9e7056 929 "Split the list of menu-elements L into sub-menus by major mode."
bc66a9a9
DL
930 (let ((recentf-arrange-rules (recentf-build-mode-rules))
931 (recentf-arrange-by-rule-others "others (%d)"))
932 (recentf-arrange-by-rule l)))
933
bc66a9a9 934(defun recentf-file-name-nondir (l)
be9e7056 935 "Filter the list of menu-elements L to show filenames sans directory.
fdd63a1c
DL
936This simplified version of `recentf-show-basenames' does not handle
937duplicates. It is used by `recentf-arrange-by-dir' as its
bc66a9a9 938`recentf-arrange-by-rule-subfilter'."
be9e7056
JB
939 (mapcar #'(lambda (e)
940 (recentf-make-menu-element
941 (file-name-nondirectory (recentf-menu-element-value e))
942 (recentf-menu-element-value e)))
bc66a9a9
DL
943 l))
944
52d2876f
DP
945(defun recentf-dir-rule (file)
946 "Return as a sub-menu, the directory FILE belongs to."
947 (cons (file-name-directory file) file))
948
bc66a9a9 949(defun recentf-arrange-by-dir (l)
be9e7056 950 "Split the list of menu-elements L into sub-menus by directory."
52d2876f 951 (let ((recentf-arrange-rules '((recentf-dir-rule . ".*")))
bc66a9a9
DL
952 (recentf-arrange-by-rule-subfilter 'recentf-file-name-nondir)
953 recentf-arrange-by-rule-others)
52d2876f 954 (recentf-arrange-by-rule l)))
be9e7056 955\f
52d2876f 956;;; Menu of menu filters
be9e7056 957;;
52d2876f
DP
958(defvar recentf-filter-changer-current nil
959 "Current filter used by `recentf-filter-changer'.")
bc66a9a9
DL
960
961(defcustom recentf-filter-changer-alist
962 '(
52d2876f
DP
963 (recentf-arrange-by-mode . "Grouped by Mode")
964 (recentf-arrange-by-dir . "Grouped by Directory")
965 (recentf-arrange-by-rule . "Grouped by Custom Rules")
bc66a9a9 966 )
2aa7c4d5 967 "List of filters managed by `recentf-filter-changer'.
be9e7056
JB
968Each filter is defined by a pair (FUNCTION . LABEL), where FUNCTION is
969the filter function, and LABEL is the menu item displayed to select
970that filter."
bc66a9a9
DL
971 :group 'recentf-filters
972 :type '(repeat (cons function string))
be9e7056 973 :set (lambda (variable value)
52d2876f
DP
974 (setq recentf-filter-changer-current nil)
975 (set-default variable value)))
be9e7056 976
52d2876f
DP
977(defun recentf-filter-changer-select (filter)
978 "Select FILTER as the current menu filter.
be9e7056 979See `recentf-filter-changer'."
52d2876f 980 (setq recentf-filter-changer-current filter))
c60ee5e7 981
bc66a9a9 982(defun recentf-filter-changer (l)
52d2876f
DP
983 "Manage a sub-menu of menu filters.
984`recentf-filter-changer-alist' defines the filters in the menu.
985Filtering of L is delegated to the selected filter in the menu."
986 (unless recentf-filter-changer-current
987 (setq recentf-filter-changer-current
988 (caar recentf-filter-changer-alist)))
989 (if (not recentf-filter-changer-current)
990 l
991 (setq recentf-menu-filter-commands
992 (list
993 `("Show files"
994 ,@(mapcar
995 #'(lambda (f)
996 `[,(cdr f)
997 (setq recentf-filter-changer-current ',(car f))
998 ;;:active t
999 :style radio ;;radio Don't work with GTK :-(
1000 :selected (eq recentf-filter-changer-current
1001 ',(car f))
1002 ;;:help ,(cdr f)
1003 ])
1004 recentf-filter-changer-alist))))
1005 (recentf-apply-menu-filter recentf-filter-changer-current l)))
be9e7056 1006\f
b6b5618c
DP
1007;;; Hooks
1008;;
1009(defun recentf-track-opened-file ()
1010 "Insert the name of the file just opened or written into the recent list."
1011 (and buffer-file-name
1012 (recentf-add-file buffer-file-name))
1013 ;; Must return nil because it is run from `write-file-functions'.
1014 nil)
1015
1016(defun recentf-track-closed-file ()
1017 "Update the recent list when a buffer is killed.
1018That is, remove a non kept file from the recent list."
1019 (and buffer-file-name
1020 (recentf-remove-if-non-kept buffer-file-name)))
1021
b6b5618c
DP
1022(defconst recentf-used-hooks
1023 '(
1024 (find-file-hook recentf-track-opened-file)
1025 (write-file-functions recentf-track-opened-file)
1026 (kill-buffer-hook recentf-track-closed-file)
b6b5618c
DP
1027 (kill-emacs-hook recentf-save-list)
1028 )
1029 "Hooks used by recentf.")
b6b5618c
DP
1030\f
1031;;; Commands
1032;;
1033
be9e7056
JB
1034;;; Common dialog stuff
1035;;
bc66a9a9 1036(defun recentf-cancel-dialog (&rest ignore)
fdd63a1c 1037 "Cancel the current dialog.
be9e7056 1038IGNORE arguments."
bc66a9a9
DL
1039 (interactive)
1040 (kill-buffer (current-buffer))
b1d1e938 1041 (message "Dialog canceled"))
bc66a9a9 1042
7b2ab969
DP
1043(defun recentf-dialog-goto-first (widget-type)
1044 "Move the cursor to the first WIDGET-TYPE in current dialog.
1045Go to the beginning of buffer if not found."
1046 (goto-char (point-min))
1047 (condition-case nil
1048 (let (done)
1049 (widget-move 1)
1050 (while (not done)
1051 (if (eq widget-type (widget-type (widget-at (point))))
1052 (setq done t)
1053 (widget-move 1))))
a07efa9f
DP
1054 (error
1055 (goto-char (point-min)))))
7b2ab969 1056
be9e7056 1057(defvar recentf-dialog-mode-map
4e8cb311 1058 (let ((km (copy-keymap recentf--shortcuts-keymap)))
7b2ab969 1059 (set-keymap-parent km widget-keymap)
be9e7056 1060 (define-key km "q" 'recentf-cancel-dialog)
b6b5618c 1061 (define-key km [follow-link] "\C-m")
be9e7056
JB
1062 km)
1063 "Keymap used in recentf dialogs.")
bc66a9a9 1064
7b2ab969 1065(define-derived-mode recentf-dialog-mode nil "recentf-dialog"
be9e7056 1066 "Major mode of recentf dialogs.
bc66a9a9 1067
be9e7056 1068\\{recentf-dialog-mode-map}"
7b2ab969
DP
1069 :syntax-table nil
1070 :abbrev-table nil
1071 (setq truncate-lines t))
1072
1073(defmacro recentf-dialog (name &rest forms)
1074 "Show a dialog buffer with NAME, setup with FORMS."
1075 (declare (indent 1) (debug t))
1076 `(with-current-buffer (get-buffer-create ,name)
1077 ;; Cleanup buffer
1078 (let ((inhibit-read-only t)
1079 (ol (overlay-lists)))
1080 (mapc 'delete-overlay (car ol))
1081 (mapc 'delete-overlay (cdr ol))
1082 (erase-buffer))
1083 (recentf-dialog-mode)
1084 ,@forms
1085 (widget-setup)
1086 (switch-to-buffer (current-buffer))))
be9e7056 1087\f
7b2ab969
DP
1088;;; Edit list dialog
1089;;
1090(defvar recentf-edit-list nil)
1091
1092(defun recentf-edit-list-select (widget &rest ignore)
1093 "Toggle a file selection based on the checkbox WIDGET state.
be9e7056 1094IGNORE other arguments."
7b2ab969
DP
1095 (let ((value (widget-get widget :tag))
1096 (check (widget-value widget)))
1097 (if check
1098 (add-to-list 'recentf-edit-list value)
1099 (setq recentf-edit-list (delq value recentf-edit-list)))
1100 (message "%s %sselected" value (if check "" "un"))))
1101
1102(defun recentf-edit-list-validate (&rest ignore)
1103 "Process the recent list when the edit list dialog is committed.
1104IGNORE arguments."
1105 (if recentf-edit-list
1106 (let ((i 0))
1107 (dolist (e recentf-edit-list)
1108 (setq recentf-list (delq e recentf-list)
1109 i (1+ i)))
1110 (kill-buffer (current-buffer))
52d2876f 1111 (message "%S file(s) removed from the list" i))
7b2ab969 1112 (message "No file selected")))
c60ee5e7 1113
bc66a9a9 1114(defun recentf-edit-list ()
7b2ab969 1115 "Show a dialog to delete selected files from the recent list."
bc66a9a9 1116 (interactive)
a07efa9f
DP
1117 (unless recentf-list
1118 (error "The list of recent files is empty"))
7b2ab969
DP
1119 (recentf-dialog (format "*%s - Edit list*" recentf-menu-title)
1120 (set (make-local-variable 'recentf-edit-list) nil)
be9e7056 1121 (widget-insert
7b2ab969
DP
1122 "Click on OK to delete selected files from the recent list.
1123Click on Cancel or type `q' to cancel.\n")
bc66a9a9 1124 ;; Insert the list of files as checkboxes
be9e7056 1125 (dolist (item recentf-list)
7b2ab969
DP
1126 (widget-create 'checkbox
1127 :value nil ; unselected checkbox
1128 :format "\n %[%v%] %t"
1129 :tag item
1130 :notify 'recentf-edit-list-select))
bc66a9a9 1131 (widget-insert "\n\n")
be9e7056
JB
1132 (widget-create
1133 'push-button
7b2ab969
DP
1134 :notify 'recentf-edit-list-validate
1135 :help-echo "Delete selected files from the recent list"
4e8cb311 1136 "Ok")
bc66a9a9 1137 (widget-insert " ")
be9e7056
JB
1138 (widget-create
1139 'push-button
1140 :notify 'recentf-cancel-dialog
1141 "Cancel")
7b2ab969 1142 (recentf-dialog-goto-first 'checkbox)))
b6b5618c 1143\f
7b2ab969
DP
1144;;; Open file dialog
1145;;
bc66a9a9 1146(defun recentf-open-files-action (widget &rest ignore)
7b2ab969 1147 "Open the file stored in WIDGET's value when notified.
be9e7056 1148IGNORE other arguments."
bc66a9a9
DL
1149 (kill-buffer (current-buffer))
1150 (funcall recentf-menu-action (widget-value widget)))
1151
e58af6f1
DP
1152;; List of files associated to a digit shortcut key.
1153(defvar recentf--files-with-key nil)
1154
1155(defun recentf-show-digit-shortcut-filter (l)
1156 "Filter the list of menu-elements L to show digit shortcuts."
1157 (let ((i 0))
1158 (dolist (e l)
1159 (setq i (1+ i))
1160 (recentf-set-menu-element-item
1161 e (format "[%d] %s" (% i 10) (recentf-menu-element-item e))))
1162 l))
1163
bc66a9a9 1164(defun recentf-open-files-item (menu-element)
7b2ab969
DP
1165 "Return a widget to display MENU-ELEMENT in a dialog buffer."
1166 (if (consp (cdr menu-element))
1167 ;; Represent a sub-menu with a tree widget
1168 `(tree-widget
1169 :open t
1170 :match ignore
1171 :node (item :tag ,(car menu-element)
1172 :sample-face bold
1173 :format "%{%t%}:\n")
1174 ,@(mapcar 'recentf-open-files-item
1175 (cdr menu-element)))
1176 ;; Represent a single file with a link widget
1177 `(link :tag ,(car menu-element)
1178 :button-prefix ""
1179 :button-suffix ""
1180 :button-face default
5d24c60e 1181 :format "%[%t\n%]"
7b2ab969
DP
1182 :help-echo ,(concat "Open " (cdr menu-element))
1183 :action recentf-open-files-action
1184 ,(cdr menu-element))))
bc66a9a9 1185
e58af6f1
DP
1186(defun recentf-open-files-items (files)
1187 "Return a list of widgets to display FILES in a dialog buffer."
1188 (set (make-local-variable 'recentf--files-with-key)
1189 (recentf-trunc-list files 10))
1190 (mapcar 'recentf-open-files-item
1191 (append
1192 ;; When requested group the files with shortcuts together
1193 ;; at the top of the list.
1194 (when recentf-show-file-shortcuts-flag
1195 (setq files (nthcdr 10 files))
1196 (recentf-apply-menu-filter
1197 'recentf-show-digit-shortcut-filter
1198 (mapcar 'recentf-make-default-menu-element
1199 recentf--files-with-key)))
1200 ;; Then the other files.
1201 (recentf-apply-menu-filter
1202 recentf-menu-filter
1203 (mapcar 'recentf-make-default-menu-element
1204 files)))))
1205
bc66a9a9 1206(defun recentf-open-files (&optional files buffer-name)
7b2ab969
DP
1207 "Show a dialog to open a recent file.
1208If optional argument FILES is non-nil, it is a list of recently-opened
1209files to choose from. It defaults to the whole recent list.
1210If optional argument BUFFER-NAME is non-nil, it is a buffer name to
1211use for the dialog. It defaults to \"*`recentf-menu-title'*\"."
bc66a9a9 1212 (interactive)
a07efa9f
DP
1213 (unless (or files recentf-list)
1214 (error "There is no recent file to open"))
7b2ab969 1215 (recentf-dialog (or buffer-name (format "*%s*" recentf-menu-title))
e58af6f1
DP
1216 (widget-insert "Click on a file"
1217 (if recentf-show-file-shortcuts-flag
1218 ", or type the corresponding digit key,"
1219 "")
1220 " to open it.\n"
1221 "Click on Cancel or type `q' to cancel.\n")
7b2ab969
DP
1222 ;; Use a L&F that looks like the recentf menu.
1223 (tree-widget-set-theme "folder")
1224 (apply 'widget-create
1225 `(group
1226 :indent 2
1227 :format "\n%v\n"
e58af6f1 1228 ,@(recentf-open-files-items (or files recentf-list))))
be9e7056
JB
1229 (widget-create
1230 'push-button
1231 :notify 'recentf-cancel-dialog
1232 "Cancel")
7b2ab969 1233 (recentf-dialog-goto-first 'link)))
bc66a9a9 1234
bc66a9a9 1235(defun recentf-open-more-files ()
7b2ab969 1236 "Show a dialog to open a recent file that is not in the menu."
bc66a9a9
DL
1237 (interactive)
1238 (recentf-open-files (nthcdr recentf-max-menu-items recentf-list)
be9e7056 1239 (format "*%s - More*" recentf-menu-title)))
bc66a9a9 1240
4e8cb311
DP
1241(defun recentf-open-most-recent-file (&optional n)
1242 "Open the Nth most recent file.
1243Optional argument N must be a valid digit number. It defaults to 1.
12441 opens the most recent file, 2 the second most recent one, etc..
12450 opens the tenth most recent file."
1246 (interactive "p")
1247 (cond
1248 ((zerop n) (setq n 10))
1249 ((and (> n 0) (< n 10)))
1250 ((error "Recent file number out of range [0-9], %d" n)))
1251 (let ((file (nth (1- n) (or recentf--files-with-key recentf-list))))
1252 (unless file (error "Not that many recent files"))
1253 ;; Close the open files dialog.
1254 (when recentf--files-with-key
1255 (kill-buffer (current-buffer)))
1256 (funcall recentf-menu-action file)))
b6b5618c 1257\f
7b2ab969
DP
1258;;; Save/load/cleanup the recent list
1259;;
be9e7056
JB
1260(defconst recentf-save-file-header
1261 ";;; Automatically generated by `recentf' on %s.\n"
1262 "Header to be written into the `recentf-save-file'.")
1263
8dde0e95
KH
1264(defconst recentf-save-file-coding-system
1265 (if (coding-system-p 'utf-8-emacs)
1266 'utf-8-emacs
1267 'emacs-mule)
1268 "Coding system of the file `recentf-save-file'.")
1269
be9e7056
JB
1270(defun recentf-save-list ()
1271 "Save the recent list.
1272Write data into the file specified by `recentf-save-file'."
1273 (interactive)
a0df7a32
RS
1274 (condition-case error
1275 (with-temp-buffer
7b2ab969
DP
1276 (erase-buffer)
1277 (set-buffer-file-coding-system recentf-save-file-coding-system)
1278 (insert (format recentf-save-file-header (current-time-string)))
1279 (recentf-dump-variable 'recentf-list recentf-max-saved-items)
52d2876f 1280 (recentf-dump-variable 'recentf-filter-changer-current)
2aa7c4d5
SM
1281 (insert "\n\f\n;; Local Variables:\n"
1282 (format ";; coding: %s\n" recentf-save-file-coding-system)
1283 ";; End:\n")
7b2ab969 1284 (write-file (expand-file-name recentf-save-file))
9be6a039
DP
1285 (when recentf-save-file-modes
1286 (set-file-modes recentf-save-file recentf-save-file-modes))
7b2ab969 1287 nil)
a0df7a32
RS
1288 (error
1289 (warn "recentf mode: %s" (error-message-string error)))))
be9e7056
JB
1290
1291(defun recentf-load-list ()
1292 "Load a previously saved recent list.
51c8b53f
EZ
1293Read data from the file specified by `recentf-save-file'.
1294When `recentf-initialize-file-name-history' is non-nil, initialize an
1295empty `file-name-history' with the recent list."
be9e7056
JB
1296 (interactive)
1297 (let ((file (expand-file-name recentf-save-file)))
1298 (when (file-readable-p file)
51c8b53f
EZ
1299 (load-file file)
1300 (and recentf-initialize-file-name-history
1301 (not file-name-history)
1302 (setq file-name-history (mapcar 'abbreviate-file-name
1303 recentf-list))))))
be9e7056
JB
1304
1305(defun recentf-cleanup ()
ad8b6d89
DP
1306 "Cleanup the recent list.
1307That is, remove duplicates, non-kept, and excluded files."
be9e7056
JB
1308 (interactive)
1309 (message "Cleaning up the recentf list...")
eafc2b27 1310 (let ((n 0) newlist)
be9e7056 1311 (dolist (f recentf-list)
ad8b6d89 1312 (setq f (recentf-expand-file-name f))
068f123a 1313 (if (and (recentf-include-p f)
ad8b6d89
DP
1314 (recentf-keep-p f)
1315 (not (recentf-string-member f newlist)))
be9e7056 1316 (push f newlist)
eafc2b27 1317 (setq n (1+ n))
be9e7056 1318 (message "File %s removed from the recentf list" f)))
eafc2b27
DP
1319 (message "Cleaning up the recentf list...done (%d removed)" n)
1320 (setq recentf-list (nreverse newlist))))
b6b5618c
DP
1321\f
1322;;; The minor mode
1323;;
4e8cb311
DP
1324(defvar recentf-mode-map (make-sparse-keymap)
1325 "Keymap to use in recentf mode.")
1326
bc66a9a9 1327;;;###autoload
a30ccae6 1328(define-minor-mode recentf-mode
bc66a9a9 1329 "Toggle recentf mode.
a30ccae6
MB
1330With prefix argument ARG, turn on if positive, otherwise off.
1331Returns non-nil if the new state is enabled.
bc66a9a9 1332
be9e7056 1333When recentf mode is enabled, it maintains a menu for visiting files
851370b0 1334that were operated on recently."
a30ccae6
MB
1335 :global t
1336 :group 'recentf
4e8cb311 1337 :keymap recentf-mode-map
be9e7056
JB
1338 (unless (and recentf-mode (recentf-enabled-p))
1339 (if recentf-mode
52d2876f
DP
1340 (progn
1341 (recentf-load-list)
1342 (recentf-show-menu))
1343 (recentf-hide-menu)
be9e7056
JB
1344 (recentf-save-list))
1345 (recentf-auto-cleanup)
be9e7056
JB
1346 (let ((hook-setup (if recentf-mode 'add-hook 'remove-hook)))
1347 (dolist (hook recentf-used-hooks)
1348 (apply hook-setup hook)))
1349 (run-hooks 'recentf-mode-hook)
1350 (when (interactive-p)
1351 (message "Recentf mode %sabled" (if recentf-mode "en" "dis"))))
1352 recentf-mode)
bc66a9a9
DL
1353
1354(provide 'recentf)
1355
1356(run-hooks 'recentf-load-hook)
8dde0e95 1357\f
7b2ab969 1358;; arch-tag: 78f1eec9-0d16-4d19-a4eb-2e4529edb62a
0a1280b2 1359;;; recentf.el ends here