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