(custom-load-symbol): Moved to custom.el.
[bpt/emacs.git] / lisp / recentf.el
CommitLineData
e8af40ee 1;;; recentf.el --- setup a menu of recently opened files
bc66a9a9 2
7f7d5b9e 3;; Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
bc66a9a9
DL
4
5;; Author: David Ponce <david@dponce.com>
6;; Created: July 19 1999
7;; Keywords: customization
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
25
26;;; Commentary:
27
28;; This package maintains a menu for visiting files that were operated
29;; on recently. When enabled a new "Open Recent" submenu is displayed
30;; in the "Files" menu. The recent files list is automatically saved
31;; across Emacs sessions. You can customize the number of recent
32;; files displayed, the location of the menu and others options (see
33;; the source code for details). To install and use, put the file on
34;; your Emacs-Lisp load path and add the following into your ~/.emacs
35;; startup file:
36;;
37;; (require 'recentf)
fdd63a1c 38;; (recentf-mode 1)
bc66a9a9
DL
39
40;;; Code:
41
42(require 'easymenu)
43(require 'wid-edit)
44
45(defconst recentf-save-file-header
46 ";;; Automatically generated by `recentf' on %s.\n"
47 "Header to be written into the `recentf-save-file'.")
48
49(defvar recentf-list nil
50 "List of recently opened files.")
51
52(defvar recentf-update-menu-p t
53 "Non-nil if the recentf menu must be updated.")
54
55(defvar recentf-initialized-p nil
56 "Non-nil if recentf already initialized.")
57
58;; IMPORTANT: This function must be defined before the following defcustoms
59;; because it is used in their :set clause. To avoid byte-compiler warnings
60;; the `symbol-value' function is used to access the `recentf-menu-path'
61;; and `recentf-menu-title' values.
62(defun recentf-menu-customization-changed (sym val)
63 "Function called when menu customization has changed.
64It removes the recentf menu and forces its complete redrawing."
65 (when recentf-initialized-p
fdd63a1c 66 (easy-menu-remove-item nil
bc66a9a9
DL
67 (symbol-value 'recentf-menu-path)
68 (symbol-value 'recentf-menu-title))
69 (setq recentf-update-menu-p t))
70 (custom-set-default sym val))
71
72(defgroup recentf nil
73 "Maintain a menu of recently opened files."
74 :version "21.1"
75 :group 'files)
76
77(defgroup recentf-filters nil
78 "Group to customize recentf menu filters.
79You should define the options of your own filters in this group."
80 :group 'recentf)
81
82(defcustom recentf-max-saved-items 20
83 "*Maximum number of items saved to `recentf-save-file'."
84 :group 'recentf
85 :type 'integer)
86
87(defcustom recentf-save-file (expand-file-name "~/.recentf")
88 "*File to save `recentf-list' into."
89 :group 'recentf
90 :type 'file)
91
92(defcustom recentf-exclude nil
93 "*List of regexps for filenames excluded from `recentf-list'."
94 :group 'recentf
95 :type '(repeat regexp))
96
97(defcustom recentf-menu-title "Open Recent"
98 "*Name of the recentf menu."
99 :group 'recentf
100 :type 'string
101 :set 'recentf-menu-customization-changed)
102
103(defcustom recentf-menu-path '("files")
104 "*Path where to add the recentf menu.
0a1280b2 105If nil add it at top level (see also `easy-menu-change')."
bc66a9a9
DL
106 :group 'recentf
107 :type '(choice (const :tag "Top Level" nil)
108 (sexp :tag "Menu Path"))
109 :set 'recentf-menu-customization-changed)
110
111(defcustom recentf-menu-before "open-file"
112 "*Name of the menu before which the recentf menu will be added.
113If nil add it at end of menu (see also `easy-menu-change')."
114 :group 'recentf
115 :type '(choice (string :tag "Name")
116 (const :tag "Last" nil))
117 :set 'recentf-menu-customization-changed)
118
119(defcustom recentf-menu-action 'recentf-find-file
120 "*Function to invoke with a filename item of the recentf menu.
121The default action `recentf-find-file' calls `find-file' to edit an
122existing file. If the file does not exist or is not readable, it is
fdd63a1c 123not edited and its name is removed from `recentf-list'. You can use
bc66a9a9
DL
124`find-file' instead to open non-existing files and keep them in the
125list of recently opened files."
126 :group 'recentf
127 :type 'function
128 :set 'recentf-menu-customization-changed)
129
130(defcustom recentf-max-menu-items 10
131 "*Maximum number of items in the recentf menu."
132 :group 'recentf
133 :type 'integer
134 :set 'recentf-menu-customization-changed)
135
136(defcustom recentf-menu-filter nil
137 "*Function used to filter files displayed in the recentf menu.
0ff9b955 138nil means no filter. The following functions are predefined:
bc66a9a9 139
b2639d51
DL
140- `recentf-sort-ascending' to sort menu items in ascending order.
141- `recentf-sort-descending' to sort menu items in descending order.
142- `recentf-sort-basenames-ascending' to sort file names in descending order.
143- `recentf-sort-basenames-descending' to sort file names in descending order.
144- `recentf-sort-directories-ascending' to sort directories in ascending order.
145- `recentf-sort-directories-descending' to sort directories in descending order.
146- `recentf-show-basenames' to show file names (no directories) in menu items.
147- `recentf-show-basenames-ascending' to show file names in ascending order.
148- `recentf-show-basenames-descending' to show file names in descending order.
149- `recentf-relative-filter' to show file names relative to `default-directory'.
150- `recentf-arrange-by-rule' to show sub-menus following user defined rules.
151- `recentf-arrange-by-mode' to show a sub-menu for each major mode.
152- `recentf-arrange-by-dir' to show a sub-menu for each directory.
153- `recentf-filter-changer' to manage a ring of filters.
bc66a9a9
DL
154
155The filter function is called with one argument, the list of menu elements
156used to build the menu and must return a new list of menu elements (see
157`recentf-make-menu-element' for menu element form)."
158 :group 'recentf
b2639d51
DL
159 :type '(radio (const nil)
160 (function-item recentf-sort-ascending)
161 (function-item recentf-sort-descending)
162 (function-item recentf-sort-basenames-ascending)
163 (function-item recentf-sort-basenames-descending)
164 (function-item recentf-sort-directories-ascending)
165 (function-item recentf-sort-directories-descending)
166 (function-item recentf-show-basenames)
167 (function-item recentf-show-basenames-ascending)
168 (function-item recentf-show-basenames-descending)
169 (function-item recentf-relative-filter)
170 (function-item recentf-arrange-by-rule)
171 (function-item recentf-arrange-by-mode)
172 (function-item recentf-arrange-by-dir)
173 (function-item recentf-filter-changer)
174 function)
bc66a9a9
DL
175 :set 'recentf-menu-customization-changed)
176
177(defcustom recentf-menu-append-commands-p t
178 "*If not-nil command items are appended to the menu."
179 :group 'recentf
180 :type 'boolean
181 :set 'recentf-menu-customization-changed)
182
183(defcustom recentf-keep-non-readable-files-p nil
184 "*If nil (default), non-readable files are not kept in `recentf-list'."
185 :group 'recentf
186 :type 'boolean
187 :require 'recentf
188 :initialize 'custom-initialize-default
189 :set (lambda (sym val)
190 (if val
191 (remove-hook 'kill-buffer-hook 'recentf-remove-file-hook)
192 (add-hook 'kill-buffer-hook 'recentf-remove-file-hook))
193 (custom-set-default sym val)))
194
bc66a9a9
DL
195(defcustom recentf-load-hook nil
196 "*Normal hook run at end of loading the `recentf' package."
197 :group 'recentf
198 :type 'hook)
199
0a1280b2
DL
200;;;;
201;;;; Common functions
202;;;;
bc66a9a9
DL
203(defconst recentf-case-fold-search
204 (memq system-type '(vax-vms windows-nt))
205 "Non-nil if recentf searches and matches should ignore case.")
206
207(defun recentf-include-p (filename)
0a1280b2 208 "Return t if FILENAME match none of the `recentf-exclude' regexps."
bc66a9a9
DL
209 (let ((case-fold-search recentf-case-fold-search)
210 (rl recentf-exclude))
211 (while (and rl (not (string-match (car rl) filename)))
212 (setq rl (cdr rl)))
213 (null rl)))
214
215(defun recentf-add-file (filename)
216 "Add or move FILENAME at the beginning of `recentf-list'.
217Does nothing if FILENAME matches one of the `recentf-exclude' regexps."
218 (let ((filename (expand-file-name filename)))
219 (when (recentf-include-p filename)
220 (setq recentf-list (cons filename (delete filename recentf-list)))
221 (setq recentf-update-menu-p t))))
222
223(defun recentf-remove-if-non-readable (filename)
224 "Remove FILENAME from `recentf-list' if not readable."
225 (unless (file-readable-p filename)
226 (setq recentf-list (delete filename recentf-list))
227 (setq recentf-update-menu-p t)))
228
229(defun recentf-find-file (filename)
230 "Edit file FILENAME using `find-file'.
231If FILENAME is not readable it is removed from `recentf-list'."
232 (if (file-readable-p filename)
233 (find-file filename)
234 (progn
235 (message "File `%s' not found." filename)
236 (setq recentf-list (delete filename recentf-list))
237 (setq recentf-update-menu-p t))))
238
239(defun recentf-trunc-list (l n)
240 "Return a list of the first N elements of L."
241 (let ((lh nil))
242 (while (and l (> n 0))
243 (setq lh (cons (car l) lh))
244 (setq n (1- n))
245 (setq l (cdr l)))
246 (nreverse lh)))
247
248(defun recentf-elements (n)
249 "Return a list of the first N elements of `recentf-list'."
250 (recentf-trunc-list recentf-list n))
251
252(defun recentf-make-menu-element (menu-item menu-value)
253 "Create a new menu-element.
254
255A menu element is a pair (MENU-ITEM . MENU-VALUE) where:
256
257- - MENU-ITEM is the menu item string displayed.
258- - MENU-VALUE is the path used to open the file when the
fdd63a1c 259 corresponding MENU-ITEM is selected. Or it is
bc66a9a9
DL
260 a pair (SUB-MENU-TITLE . MENU-ELEMENTS) where
261 SUB-MENU-TITLE is a sub-menu title and
262 MENU-ELEMENTS is the list of menu elements in
263 the sub-menu."
264 (cons menu-item menu-value))
265
266(defun recentf-menu-element-item (e)
267 "Return the item part of the menu-element E."
268 (car e))
269
270(defun recentf-menu-element-value (e)
271 "Return the value part of the menu-element E."
272 (cdr e))
273
274(defun recentf-set-menu-element-item (e item)
275 "Change the item part of menu-element E to ITEM."
276 (setcar e item))
277
278(defun recentf-set-menu-element-value (e value)
279 "Change the value part of menu-element E to VALUE."
280 (setcdr e value))
281
282(defun recentf-sub-menu-element-p (e)
283 "Return non-nil if menu-element E defines a sub-menu."
284 (consp (recentf-menu-element-value e)))
285
286(defun recentf-make-default-menu-element (file-path)
fdd63a1c
DL
287 "Make a new default menu element (MENU-ITEM . MENU-VALUE).
288Do so for the given recent file path FILE-PATH. MENU-ITEM and
289MENU-VALUE are set to FILE-PATH. See also
290`recentf-make-menu-element'."
bc66a9a9
DL
291 (recentf-make-menu-element file-path file-path))
292
293(defun recentf-menu-elements (n)
fdd63a1c
DL
294 "Return a list of the first N default menu elements from `recentf-list'.
295See also `recentf-make-default-menu-element'."
bc66a9a9
DL
296 (mapcar 'recentf-make-default-menu-element
297 (recentf-elements n)))
298
299(defun recentf-apply-menu-filter (filter l)
fdd63a1c
DL
300 "Apply function FILTER to the list of menu-elements L.
301It takes care of sub-menu elements in L and recursively apply FILTER
0a1280b2 302to them. It is guaranteed that FILTER receives only a list of single
fdd63a1c 303menu-elements (no sub-menu)."
bc66a9a9
DL
304 (if (and (functionp filter) l)
305 (let ((case-fold-search recentf-case-fold-search)
306 menu-element sub-menu-elements single-elements)
307 ;; split L in two sub-listes:
308 ;; one of sub-menus elements and
309 ;; one of single menu elements
310 (while l
311 (setq menu-element (car l))
312 (if (recentf-sub-menu-element-p menu-element)
313 (setq sub-menu-elements
314 (cons menu-element sub-menu-elements))
315 (setq single-elements
316 (cons menu-element single-elements)))
317 (setq l (cdr l)))
318 ;; apply FILTER to the list of single menu elements
319 (if single-elements
320 (setq single-elements (funcall filter
321 (nreverse single-elements))))
322 ;; apply FILTER to sub-menu menu element list
323 (setq l sub-menu-elements)
324 (setq sub-menu-elements nil)
325 (while l
326 (setq menu-element (car l))
327 (recentf-set-menu-element-value
328 menu-element
329 (recentf-apply-menu-filter
330 filter
331 (recentf-menu-element-value menu-element)))
332 (setq sub-menu-elements (cons menu-element sub-menu-elements))
333 (setq l (cdr l)))
334 ;; build and return the new filtered menu element list
335 (nconc sub-menu-elements single-elements))
336 l))
337
338(defvar recentf-menu-items-for-commands
3493008d
GM
339 (list ["Cleanup list"
340 recentf-cleanup
341 :help "Remove all non-readable and excluded files from the recent list"
342 :active t]
343 ["Edit list..."
344 recentf-edit-list
345 :help "Edit the files that are kept in the recent list"
346 :active t]
347 ["Save list now"
348 recentf-save-list
349 :help "Save the list of recently opened files now"
350 :active t]
351 ["Options..."
352 (customize-group "recentf")
353 :help "Customize recently opened files menu and options"
354 :active t]
355 )
bc66a9a9
DL
356 "List of menu items for recentf commands.")
357
358(defvar recentf-menu-filter-commands nil
359 "This variable can be used by menu filters to setup their own command menu.
360
361If non-nil it must contain a list of valid menu-items to be appended
362to the recent file list part of the menu. Before calling a menu
363filter function this variable is reset to nil.")
364
365(defun recentf-make-menu-items ()
366 "Make menu items from `recentf-list'."
367 (setq recentf-menu-filter-commands nil)
368 (let ((file-items
369 (mapcar 'recentf-make-menu-item
370 (recentf-apply-menu-filter
371 recentf-menu-filter
372 (recentf-menu-elements recentf-max-menu-items)))))
3493008d
GM
373 (append (or file-items (list ["No files" t
374 :help "No recent file to open"
375 :active nil]))
bc66a9a9 376 (and (< recentf-max-menu-items (length recentf-list))
3493008d
GM
377 (list ["More..." recentf-open-more-files
378 :help "Open files that are not in the menu"
379 :active t]))
bc66a9a9
DL
380 (and recentf-menu-filter-commands
381 (cons "---"
382 recentf-menu-filter-commands))
383 (and recentf-menu-append-commands-p
384 (cons "---"
385 recentf-menu-items-for-commands)))))
386
387(defun recentf-make-menu-item (menu-element)
0a1280b2 388 "Make a menu item from MENU-ELEMENT (see `recentf-make-menu-element')."
bc66a9a9
DL
389 (let ((menu-item (recentf-menu-element-item menu-element))
390 (menu-value (recentf-menu-element-value menu-element)))
391 (if (recentf-sub-menu-element-p menu-element)
392 (cons menu-item (mapcar 'recentf-make-menu-item menu-value))
393 (vector menu-item
394 (list recentf-menu-action menu-value)
3493008d
GM
395 :help (concat "Open " menu-value)
396 :active t))))
bc66a9a9 397
0a1280b2
DL
398;;;;
399;;;; Predefined menu filter functions
400;;;;
bc66a9a9
DL
401
402(defun recentf-sort-ascending (l)
403 "Sort the list of menu elements L in ascending order.
404The MENU-ITEM part of each menu element is compared."
405 (sort (copy-sequence l)
406 (function
407 (lambda (e1 e2)
408 (string-lessp (recentf-menu-element-item e1)
409 (recentf-menu-element-item e2))))))
410
411(defun recentf-sort-descending (l)
412 "Sort the list of menu elements L in descending order.
413The MENU-ITEM part of each menu element is compared."
414 (sort (copy-sequence l)
415 (function
416 (lambda (e1 e2)
417 (string-lessp (recentf-menu-element-item e2)
418 (recentf-menu-element-item e1))))))
419
420(defun recentf-sort-basenames-ascending (l)
421 "Sort the list of menu elements L in ascending order.
422Only file names (without directories) are compared."
423 (sort (copy-sequence l)
424 (function
425 (lambda (e1 e2)
426 (string-lessp
427 (file-name-nondirectory (recentf-menu-element-value e1))
428 (file-name-nondirectory (recentf-menu-element-value e2)))))))
429
430(defun recentf-sort-basenames-descending (l)
431 "Sort the list of menu elements L in descending order.
432Only file names (without directories) are compared."
433 (sort (copy-sequence l)
434 (function
435 (lambda (e1 e2)
436 (string-lessp
437 (file-name-nondirectory (recentf-menu-element-value e2))
438 (file-name-nondirectory (recentf-menu-element-value e1)))))))
439
440(defun recentf-directory-compare (p1 p2)
fdd63a1c
DL
441 "Compare directories then filenames in paths P1 and P2.
442Return non-nil if P1 is less than P2."
bc66a9a9
DL
443 (let ((d1 (file-name-directory p1))
444 (f1 (file-name-nondirectory p1))
445 (d2 (file-name-directory p2))
446 (f2 (file-name-nondirectory p2)))
447 (if (string= d1 d2)
448 (string-lessp f1 f2)
449 (string-lessp d1 d2))))
450
451(defun recentf-sort-directories-ascending (l)
452 "Sort the list of menu elements L in ascending order.
453Compares directories then filenames to order the list."
454 (sort (copy-sequence l)
455 (function
456 (lambda (e1 e2)
457 (recentf-directory-compare (recentf-menu-element-value e1)
458 (recentf-menu-element-value e2))))))
459
460(defun recentf-sort-directories-descending (l)
461 "Sort the list of menu elements L in descending order.
462Compares directories then filenames to order the list."
463 (sort (copy-sequence l)
464 (function
465 (lambda (e1 e2)
466 (recentf-directory-compare (recentf-menu-element-value e2)
467 (recentf-menu-element-value e1))))))
468
469(defun recentf-show-basenames (l)
470 "Filter the list of menu elements L to show only file names (no directories)
fdd63a1c 471in the menu. When file names are duplicated their directory component is added."
bc66a9a9
DL
472 (let ((names (mapcar (function
473 (lambda (item)
474 (file-name-nondirectory
475 (recentf-menu-element-value item))))
476 l))
477 (dirs (mapcar (function
478 (lambda (item)
479 (file-name-directory
480 (recentf-menu-element-value item))))
481 l))
482 (pathes (mapcar 'recentf-menu-element-value l))
483 (pos -1)
484 item filtered-items filtered-list)
485 (while names
486 (setq item (car names))
487 (setq names (cdr names))
488 (setq pos (1+ pos))
489 (setq filtered-list
490 (cons (recentf-make-menu-element
491 (if (or (member item names) (member item filtered-items))
492 (concat item " (" (nth pos dirs) ")")
493 item)
494 (nth pos pathes))
495 filtered-list))
496 (setq filtered-items (cons item filtered-items)))
497 (nreverse filtered-list)))
498
499(defun recentf-show-basenames-ascending (l)
fdd63a1c
DL
500 "Filter the list of menu elements L.
501Show only file names in the menu, sorted in ascending order. This
502filter combines the `recentf-sort-basenames-ascending' and
503`recentf-show-basenames' filters."
bc66a9a9
DL
504 (recentf-show-basenames (recentf-sort-basenames-ascending l)))
505
506(defun recentf-show-basenames-descending (l)
fdd63a1c
DL
507 "Filter the list of menu elements L.
508Show only file names in the menu, sorted in descending order. This
509filter combines the `recentf-sort-basenames-descending' and
510`recentf-show-basenames' filters."
bc66a9a9
DL
511 (recentf-show-basenames (recentf-sort-basenames-descending l)))
512
513(defun recentf-relative-filter (l)
fdd63a1c
DL
514 "Filter the list of `recentf-menu-elements' L.
515Show filenames relative to `default-directory'."
bc66a9a9
DL
516 (setq recentf-update-menu-p t) ; force menu update
517 (mapcar (function
518 (lambda (menu-element)
519 (let* ((ful-path (recentf-menu-element-value menu-element))
520 (rel-path (file-relative-name ful-path)))
521 (if (string-match "^\\.\\." rel-path)
522 menu-element
523 (recentf-make-menu-element rel-path ful-path)))))
524 l))
525
526(defcustom recentf-arrange-rules
527 '(
528 ("Elisp files (%d)" ".\\.el$")
529 ("Java files (%d)" ".\\.java$")
530 ("C/C++ files (%d)" "c\\(pp\\)?$")
531 )
532 "*List of rules used by `recentf-arrange-by-rule' to build sub-menus.
fdd63a1c 533A rule is a pair (SUB-MENU-TITLE . MATCHER). SUB-MENU-TITLE is the
bc66a9a9 534displayed title of the sub-menu where a '%d' `format' pattern is
fdd63a1c
DL
535replaced by the number of items in the sub-menu. MATCHER is a regexp
536or a list of regexps. Items matching one of the regular expressions in
bc66a9a9
DL
537MATCHER are added to the corresponding sub-menu."
538 :group 'recentf-filters
539 :type '(repeat (cons string (repeat regexp)))
540 :set 'recentf-menu-customization-changed)
541
542(defcustom recentf-arrange-by-rule-others "Other files (%d)"
fdd63a1c
DL
543 "*Title of the `recentf-arrange-by-rule' sub-menu.
544This is for the menu where items that don't match any
545`recentf-arrange-rules' are displayed. If nil these items are
546displayed in the main recent files menu. A '%d' `format' pattern in
547the title is replaced by the number of items in the sub-menu."
bc66a9a9
DL
548 :group 'recentf-filters
549 :type '(choice (const :tag "Main menu" nil)
550 (string :tag "Title"))
551 :set 'recentf-menu-customization-changed)
552
553(defcustom recentf-arrange-by-rules-min-items 0
554 "*Minimum number of items in a `recentf-arrange-by-rule' sub-menu.
555If the number of items in a sub-menu is less than this value the
556corresponding sub-menu items are displayed in the main recent files
557menu or in the `recentf-arrange-by-rule-others' sub-menu if
558defined."
559 :group 'recentf-filters
560 :type 'number
561 :set 'recentf-menu-customization-changed)
562
563(defcustom recentf-arrange-by-rule-subfilter nil
564 "*Function used by `recentf-arrange-by-rule' to filter sub-menu elements.
0ff9b955 565nil means no filter. See also `recentf-menu-filter'. You can't use
bc66a9a9
DL
566`recentf-arrange-by-rule' itself here!"
567 :group 'recentf-filters
b2639d51 568 :type '(choice (const nil) function)
bc66a9a9
DL
569 :set (lambda (sym val)
570 (if (eq val 'recentf-arrange-by-rule)
571 (error "Can't use `recentf-arrange-by-rule' itself here!")
572 (recentf-menu-customization-changed sym val))))
573
574(defun recentf-match-rule-p (matcher file-path)
575 "Return non-nil if FILE-PATH match the rule specified by MATCHER.
576See `recentf-arrange-rules' for details on MATCHER."
577 (if (stringp matcher)
578 (string-match matcher file-path)
579 (while (and (consp matcher)
580 (not (string-match (car matcher) file-path)))
581 (setq matcher (cdr matcher)))
582 matcher))
583
584(defun recentf-arrange-by-rule (l)
fdd63a1c
DL
585 "Filter the list of menu-elements L.
586Arrange them in sub-menus following rules in `recentf-arrange-rules'."
bc66a9a9
DL
587 (let ((sub-menus-number (length recentf-arrange-rules)))
588 (if (> sub-menus-number 0)
589 (let ((sub-menus (apply 'vector
590 (mapcar (function
591 (lambda (pair)
592 (list (car pair))))
593 recentf-arrange-rules)))
594 other-menu-elements index min-size)
595 (while l
596 (let* ((menu-element (car l))
597 (file-path (recentf-menu-element-value menu-element))
598 (rules recentf-arrange-rules)
599 (found nil))
600 (setq index 0)
601 (while (and (not found) rules)
602 (if (recentf-match-rule-p (cdar rules) file-path)
603 (let ((sub-menu (aref sub-menus index)))
604 (setq found t)
605 (recentf-set-menu-element-value
606 sub-menu
607 (cons menu-element (recentf-menu-element-value sub-menu)))
608 ))
609 (setq index (1+ index))
610 (setq rules (cdr rules)))
611 (or found
612 (setq other-menu-elements
613 (cons menu-element other-menu-elements)))
614 (setq l (cdr l))))
615 (setq index 0)
616 (setq l nil)
617 (setq min-size (if (integerp recentf-arrange-by-rules-min-items)
618 (max 0 recentf-arrange-by-rules-min-items)
619 0))
620 (while (< index sub-menus-number)
621 (let* ((sub-menu (aref sub-menus index))
622 (sub-menu-title (recentf-menu-element-item sub-menu))
623 (sub-menu-elements (recentf-menu-element-value sub-menu))
624 (sub-menu-length (length sub-menu-elements)))
625 (if (> sub-menu-length 0)
626 (cond
627 ((< sub-menu-length min-size)
628 (setq other-menu-elements
629 (nconc sub-menu-elements other-menu-elements)))
630 ((>= sub-menu-length min-size)
631 (recentf-set-menu-element-item
632 sub-menu
633 (format sub-menu-title sub-menu-length))
634 (recentf-set-menu-element-value
635 sub-menu
636 (recentf-apply-menu-filter
637 recentf-arrange-by-rule-subfilter
638 (nreverse sub-menu-elements)))
639 (setq l (cons sub-menu l)))))
640 (setq index (1+ index))))
641 (if (and (stringp recentf-arrange-by-rule-others)
642 other-menu-elements)
643 (setq l
644 (nreverse
645 (cons (recentf-make-menu-element
646 (format recentf-arrange-by-rule-others
647 (length other-menu-elements))
648 (recentf-apply-menu-filter
649 recentf-arrange-by-rule-subfilter
650 (nreverse other-menu-elements)))
651 l)))
652 (setq l (nconc (nreverse l)
653 (recentf-apply-menu-filter
654 recentf-arrange-by-rule-subfilter
655 (nreverse other-menu-elements)))))))
656 l))
657
658(defun recentf-build-mode-rules ()
659 "Convert `auto-mode-alist' to `recentf-arrange-rules' format."
660 (let ((case-fold-search recentf-case-fold-search)
661 (modes auto-mode-alist)
662 regexp mode rule-name rule rules)
663 (while modes
664 (setq regexp (caar modes))
665 (setq mode (cdar modes))
666 (when (symbolp mode)
667 (setq rule-name (symbol-name mode))
668 (if (string-match "\\(.*\\)-mode$" rule-name)
669 (setq rule-name (match-string 1 rule-name)))
670 (setq rule-name (concat rule-name " (%d)"))
671 (setq rule (assoc rule-name rules))
672 (if rule
673 (setcdr rule (cons regexp (cdr rule)))
674 (setq rules (cons (list rule-name regexp) rules))))
675 (setq modes (cdr modes)))
676 ;; It is important to preserve auto-mode-alist order
677 ;; to ensure the right file <-> mode association
678 (nreverse rules)))
679
680(defun recentf-arrange-by-mode (l)
fdd63a1c 681 "Filter the list of menu-elements L to build sub-menus for each major mode."
bc66a9a9
DL
682 (let ((recentf-arrange-rules (recentf-build-mode-rules))
683 (recentf-arrange-by-rule-others "others (%d)"))
684 (recentf-arrange-by-rule l)))
685
686(defun recentf-build-dir-rules (l)
fdd63a1c 687 "Convert directories in menu-elements L to rules in `recentf-arrange-rules' format."
bc66a9a9 688 (let (dirs)
3493008d
GM
689 (mapc (function
690 (lambda (e)
691 (let ((dir (file-name-directory
692 (recentf-menu-element-value e))))
693 (or (member dir dirs)
694 (setq dirs (cons dir dirs))))))
695 l)
bc66a9a9
DL
696 (mapcar (function
697 (lambda (d)
698 (cons (concat d " (%d)")
699 (concat "\\`" d))))
700 (nreverse (sort dirs 'string-lessp)))))
701
702(defun recentf-file-name-nondir (l)
fdd63a1c
DL
703 "Filter the list of menu-elements L to show only filenames.
704This simplified version of `recentf-show-basenames' does not handle
705duplicates. It is used by `recentf-arrange-by-dir' as its
bc66a9a9
DL
706`recentf-arrange-by-rule-subfilter'."
707 (mapcar (function
708 (lambda (e)
709 (recentf-make-menu-element
710 (file-name-nondirectory (recentf-menu-element-value e))
711 (recentf-menu-element-value e))))
712 l))
713
714(defun recentf-arrange-by-dir (l)
fdd63a1c 715 "Filter the list of menu-elements L to build sub-menus for each directory."
bc66a9a9
DL
716 (let ((recentf-arrange-rules (recentf-build-dir-rules l))
717 (recentf-arrange-by-rule-subfilter 'recentf-file-name-nondir)
718 recentf-arrange-by-rule-others)
719 (nreverse (recentf-arrange-by-rule l))))
720
721(defvar recentf-filter-changer-state nil
722 "Used by `recentf-filter-changer' to hold its state.")
723
724(defcustom recentf-filter-changer-alist
725 '(
726 (recentf-arrange-by-mode . "*Files by Mode*")
727 (recentf-arrange-by-dir . "*Files by Directory*")
728 (recentf-arrange-by-rule . "*Files by User Rule*")
729 )
730 "*List of filters managed by `recentf-filter-changer'.
731Each filter is defined by a pair (FILTER-FUN . FILTER-LBL) where:
732
733- - FILTER-FUN is the function that filters menu-elements
734- - FILTER-LBL is the menu item used to activate the filter"
735 :group 'recentf-filters
736 :type '(repeat (cons function string))
737 :set (lambda (sym val)
738 (setq recentf-filter-changer-state nil)
739 (recentf-menu-customization-changed sym val)))
740
741(defun recentf-filter-changer-goto-next ()
742 "Go to the next filter available (see `recentf-filter-changer')."
743 (and (consp recentf-filter-changer-state)
744 (setq recentf-filter-changer-state
745 (cdr recentf-filter-changer-state)))
746 (setq recentf-update-menu-p t))
747
748(defun recentf-filter-changer-get-current ()
749 "Get the current filter available (see `recentf-filter-changer')."
750 (if (null recentf-filter-changer-state)
751 (setq recentf-filter-changer-state recentf-filter-changer-alist))
752 (and (consp recentf-filter-changer-state)
753 (car recentf-filter-changer-state)))
754
755(defun recentf-filter-changer-get-next ()
756 "Get the next filter available (see `recentf-filter-changer')."
757 (let ((filters recentf-filter-changer-state))
758 (cond ((consp filters)
759 (setq filters (cdr filters))
760 (if (null filters)
761 (setq filters recentf-filter-changer-alist)))
762 (t
763 (setq filters recentf-filter-changer-alist)
764 (if (consp filters)
765 (setq filters (cdr filters)))))
766 (if (consp filters)
767 (car filters))))
768
769(defun recentf-filter-changer (l)
fdd63a1c
DL
770 "Manage a ring of filters.
771`recentf-filter-changer-alist' defines the filters in the ring.
772Actual filtering of L is delegated to the current filter in the
773ring. A filter menu item is displayed allowing to dynamically activate
774the next filter in the ring. If the filter ring is empty L is left
775unchanged."
bc66a9a9
DL
776 (let ((current-filter-item (recentf-filter-changer-get-current))
777 (next-filter-item (recentf-filter-changer-get-next)))
778 (when current-filter-item
779 (setq l (recentf-apply-menu-filter (car current-filter-item) l))
780 (if next-filter-item
781 (setq recentf-menu-filter-commands
782 (list (vector (cdr next-filter-item)
783 '(recentf-filter-changer-goto-next)
3493008d 784 :active t)))))
bc66a9a9
DL
785 l))
786
0a1280b2
DL
787;;;;
788;;;; Dialogs stuff
789;;;;
bc66a9a9
DL
790
791(defun recentf-cancel-dialog (&rest ignore)
fdd63a1c
DL
792 "Cancel the current dialog.
793Used by `recentf-edit-list' and `recentf-open-files' dialogs."
bc66a9a9
DL
794 (interactive)
795 (kill-buffer (current-buffer))
796 (message "Dialog canceled."))
797
798(defvar recentf-dialog-mode-map nil
799 "`recentf-dialog-mode' keymap.")
800
801(if recentf-dialog-mode-map
802 ()
803 (setq recentf-dialog-mode-map (make-sparse-keymap))
804 (define-key recentf-dialog-mode-map "q" 'recentf-cancel-dialog)
0c8f8759 805 (define-key recentf-dialog-mode-map [down-mouse-1] 'widget-button-click)
bc66a9a9
DL
806 (set-keymap-parent recentf-dialog-mode-map widget-keymap))
807
808(defun recentf-dialog-mode ()
809 "Major mode used in recentf dialogs.
810
0a1280b2 811These are the special commands of `recentf-dialog-mode' mode:
bc66a9a9
DL
812 q -- cancel this dialog."
813 (interactive)
814 (setq major-mode 'recentf-dialog-mode)
815 (setq mode-name "recentf-dialog")
816 (use-local-map recentf-dialog-mode-map))
817
0a1280b2
DL
818;;;;
819;;;; Hooks and Commands
820;;;;
bc66a9a9
DL
821
822(defun recentf-add-file-hook ()
823 "Insert the name of the file just opened or written into `recentf-list'."
824 (and buffer-file-name (recentf-add-file buffer-file-name))
825 nil)
826
827(defun recentf-remove-file-hook ()
828 "When a buffer is killed remove a non readable file from `recentf-list'."
829 (and buffer-file-name (recentf-remove-if-non-readable buffer-file-name))
830 nil)
831
832(defun recentf-update-menu-hook ()
833 "Update the recentf menu from the current `recentf-list'."
834 (when recentf-update-menu-p
835 (condition-case nil
836 (progn
837 (setq recentf-update-menu-p nil)
838 (easy-menu-change recentf-menu-path
839 recentf-menu-title
840 (recentf-make-menu-items)
841 recentf-menu-before))
842 (error nil))))
843
844(defun recentf-dump-variable (variable &optional limit)
fdd63a1c
DL
845 "Insert a \"(setq VARIABLE value)\" in the current buffer.
846Optional argument LIMIT specifies a maximum length when VARIABLE value
847is a list (default to the full list)."
bc66a9a9 848 (let ((value (symbol-value variable)))
7f7d5b9e
GM
849 (if (listp value)
850 (progn
851 (when (and (integerp limit) (> limit 0))
852 (setq value (recentf-trunc-list value limit)))
853 (insert (format "(setq %S '(" variable))
854 (mapc (lambda (e) (insert (format "\n%S" e))) value)
855 (insert "))\n"))
856 (insert (format "(setq %S %S)\n" variable value)))))
bc66a9a9
DL
857
858;;;###autoload
859(defun recentf-save-list ()
860 "Save the current `recentf-list' to the file `recentf-save-file'."
861 (interactive)
862 (with-temp-buffer
863 (erase-buffer)
864 (insert (format recentf-save-file-header (current-time-string)))
865 (recentf-dump-variable 'recentf-list recentf-max-saved-items)
866 (recentf-dump-variable 'recentf-filter-changer-state)
867 (if (file-writable-p recentf-save-file)
868 (write-region (point-min) (point-max) recentf-save-file))
869 (kill-buffer (current-buffer)))
870 nil)
871
872(defvar recentf-edit-selected-items nil
fdd63a1c
DL
873 "Used by `recentf-edit-list'.
874Holds list of files to be deleted from `recentf-list'.")
bc66a9a9
DL
875
876(defun recentf-edit-list-action (widget &rest ignore)
0a1280b2 877 "Checkbox WIDGET action used by `recentf-edit-list' to select/unselect a file."
bc66a9a9
DL
878 (let ((value (widget-get widget ':tag)))
879 ;; if value is already in the selected items
880 (if (memq value recentf-edit-selected-items)
881 ;; then remove it
882 (progn
883 (setq recentf-edit-selected-items
884 (delq value recentf-edit-selected-items))
885 (message "%s removed from selection." value))
886 ;; else add it
887 (progn
888 (setq recentf-edit-selected-items
889 (nconc (list value) recentf-edit-selected-items))
890 (message "%s added to selection." value)))))
891
892;;;###autoload
893(defun recentf-edit-list ()
894 "Allow the user to edit the files that are kept in the recent list."
895 (interactive)
896 (with-current-buffer (get-buffer-create (concat "*" recentf-menu-title " - Edit list*"))
897 (switch-to-buffer (current-buffer))
898 (kill-all-local-variables)
899 (let ((inhibit-read-only t))
900 (erase-buffer))
901 (let ((all (overlay-lists)))
902 ;; Delete all the overlays.
3493008d
GM
903 (mapc 'delete-overlay (car all))
904 (mapc 'delete-overlay (cdr all)))
bc66a9a9
DL
905 (setq recentf-edit-selected-items nil)
906 ;; Insert the dialog header
907 (widget-insert "Select the files to be deleted from the 'recentf-list'.\n\n")
908 (widget-insert "Click on Ok to update the list. ")
909 (widget-insert "Click on Cancel or type \"q\" to quit.\n")
910 ;; Insert the list of files as checkboxes
3493008d
GM
911 (mapc (function
912 (lambda (item)
913 (widget-create 'checkbox
914 :value nil ; unselected checkbox
915 :format "\n %[%v%] %t"
916 :tag item
917 :notify 'recentf-edit-list-action)))
918 recentf-list)
bc66a9a9
DL
919 (widget-insert "\n\n")
920 ;; Insert the Ok button
921 (widget-create 'push-button
922 :notify (lambda (&rest ignore)
923 (if recentf-edit-selected-items
924 (progn (kill-buffer (current-buffer))
3493008d
GM
925 (mapc (function
926 (lambda (item)
927 (setq recentf-list
928 (delq item recentf-list))))
929 recentf-edit-selected-items)
bc66a9a9
DL
930 (message "%S file(s) removed from the list"
931 (length recentf-edit-selected-items))
932 (setq recentf-update-menu-p t))
933 (message "No file selected.")))
934 "Ok")
935 (widget-insert " ")
936 ;; Insert the Cancel button
937 (widget-create 'push-button
938 :notify 'recentf-cancel-dialog
939 "Cancel")
940 (recentf-dialog-mode)
941 (widget-setup)
942 (goto-char (point-min))))
943
944;;;###autoload
945(defun recentf-cleanup ()
946 "Remove all non-readable and excluded files from `recentf-list'."
947 (interactive)
948 (let ((count (length recentf-list)))
949 (setq recentf-list
950 (delq nil
951 (mapcar (function
952 (lambda (filename)
953 (and (file-readable-p filename)
954 (recentf-include-p filename)
955 filename)))
956 recentf-list)))
957 (setq count (- count (length recentf-list)))
958 (message "%s removed from the list"
959 (cond ((= count 0) "No file")
960 ((= count 1) "One file")
961 (t (format "%d files" count)))))
962 (setq recentf-update-menu-p t))
963
964(defun recentf-open-files-action (widget &rest ignore)
0a1280b2 965 "Button WIDGET action used by `recentf-open-files' to open a file."
bc66a9a9
DL
966 (kill-buffer (current-buffer))
967 (funcall recentf-menu-action (widget-value widget)))
968
969(defvar recentf-open-files-item-shift ""
fdd63a1c 970 "String used by `recentf-open-files' to shift right sub-menu items.")
bc66a9a9
DL
971
972(defun recentf-open-files-item (menu-element)
0a1280b2 973 "Insert MENU-ELEMENT item in the current interaction buffer."
bc66a9a9
DL
974 (let ((menu-item (car menu-element))
975 (file-path (cdr menu-element)))
976 (if (consp file-path) ; This is a sub-menu
977 (let* ((shift recentf-open-files-item-shift)
978 (recentf-open-files-item-shift (concat shift " ")))
979 (widget-create 'item
980 :tag menu-item
981 :sample-face 'bold
982 :format (concat shift "%{%t%}:\n"))
3493008d
GM
983 (mapc 'recentf-open-files-item
984 file-path)
bc66a9a9
DL
985 (widget-insert "\n"))
986 (widget-create 'push-button
987 :button-face 'default
988 :tag menu-item
989 :help-echo (concat "Open " file-path)
990 :format (concat recentf-open-files-item-shift "%[%t%]")
991 :notify 'recentf-open-files-action
992 file-path)
993 (widget-insert "\n"))))
994
995;;;###autoload
996(defun recentf-open-files (&optional files buffer-name)
fdd63a1c
DL
997 "Display buffer allowing user to choose a file from recently-opened list.
998The optional argument FILES may be used to specify the list, otherwise
0a1280b2 999`recentf-list' is used. The optional argument BUFFER-NAME specifies
fdd63a1c 1000which buffer to use for the interaction."
bc66a9a9
DL
1001 (interactive)
1002 (if (null files)
1003 (setq files recentf-list))
1004 (if (null buffer-name)
1005 (setq buffer-name (concat "*" recentf-menu-title "*")))
1006 (with-current-buffer (get-buffer-create buffer-name)
1007 (switch-to-buffer (current-buffer))
1008 (kill-all-local-variables)
1009 (let ((inhibit-read-only t))
1010 (erase-buffer))
1011 (let ((all (overlay-lists)))
1012 ;; Delete all the overlays.
3493008d
GM
1013 (mapc 'delete-overlay (car all))
1014 (mapc 'delete-overlay (cdr all)))
bc66a9a9
DL
1015 ;; Insert the dialog header
1016 (widget-insert "Click on a file to open it. ")
1017 (widget-insert "Click on Cancel or type \"q\" to quit.\n\n" )
1018 ;; Insert the list of files as buttons
1019 (let ((recentf-open-files-item-shift ""))
3493008d
GM
1020 (mapc 'recentf-open-files-item
1021 (recentf-apply-menu-filter
1022 recentf-menu-filter
1023 (mapcar 'recentf-make-default-menu-element files))))
bc66a9a9
DL
1024 (widget-insert "\n")
1025 ;; Insert the Cancel button
1026 (widget-create 'push-button
1027 :notify 'recentf-cancel-dialog
1028 "Cancel")
1029 (recentf-dialog-mode)
1030 (widget-setup)
1031 (goto-char (point-min))))
1032
1033;;;###autoload
1034(defun recentf-open-more-files ()
1035 "Allow the user to open files that are not in the menu."
1036 (interactive)
1037 (recentf-open-files (nthcdr recentf-max-menu-items recentf-list)
1038 (concat "*" recentf-menu-title " - More*")))
1039
a30ccae6
MB
1040
1041;;; Note this definition must be at the end of the file, because
1042;;; `define-minor-mode' actually calls the mode-function if the
1043;;; associated variable is non-nil, which requires that all needed
1044;;; functions be already defined. [This is arguably a bug in d-m-m]
bc66a9a9 1045;;;###autoload
a30ccae6 1046(define-minor-mode recentf-mode
bc66a9a9 1047 "Toggle recentf mode.
a30ccae6
MB
1048With prefix argument ARG, turn on if positive, otherwise off.
1049Returns non-nil if the new state is enabled.
bc66a9a9
DL
1050
1051When recentf mode is enabled, it maintains a menu for visiting files that
1052were operated on recently."
a30ccae6
MB
1053 :global t
1054 :group 'recentf
1055 (if recentf-mode
1056 (unless recentf-initialized-p
1057 (setq recentf-initialized-p t)
1058 (if (file-readable-p recentf-save-file)
1059 (load-file recentf-save-file))
1060 (setq recentf-update-menu-p t)
1061 (add-hook 'find-file-hooks 'recentf-add-file-hook)
1062 (add-hook 'write-file-hooks 'recentf-add-file-hook)
1063 (add-hook 'menu-bar-update-hook 'recentf-update-menu-hook)
1064 (add-hook 'kill-emacs-hook 'recentf-save-list))
1065 (when recentf-initialized-p
1066 (setq recentf-initialized-p nil)
1067 (recentf-save-list)
1068 (easy-menu-remove-item nil recentf-menu-path recentf-menu-title)
1069 (remove-hook 'find-file-hooks 'recentf-add-file-hook)
1070 (remove-hook 'write-file-hooks 'recentf-add-file-hook)
1071 (remove-hook 'menu-bar-update-hook 'recentf-update-menu-hook)
1072 (remove-hook 'kill-emacs-hook 'recentf-save-list))))
1073
bc66a9a9
DL
1074
1075(provide 'recentf)
1076
1077(run-hooks 'recentf-load-hook)
1078
0a1280b2 1079;;; recentf.el ends here