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