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