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