1 ;; recentf.el --- setup a menu of recently opened files
3 ;; Copyright (C) 1999 Free Software Foundation, Inc.
5 ;; Author: David Ponce <david.ponce@wanadoo.fr>
6 ;; Created: July 19 1999
7 ;; Keywords: customization
9 ;; This file is part of GNU Emacs.
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)
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.
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.
35 (defconst recentf-save-file-header
36 ";;; Automatically generated by `recentf' on %s.\n"
37 "Header to be written into the `recentf-save-file'.")
39 (defvar recentf-list nil
40 "List of recently opened files.")
42 (defvar recentf-update-menu-p t
43 "Non-nil if the recentf menu must be updated.")
45 (defvar recentf-initialized-p nil
46 "Non-nil if recentf already initialized.")
48 ;; IMPORTANT: This function must be defined before the following defcustoms
49 ;; because it is used in their :set clause. To avoid byte-compiler warnings
50 ;; the `symbol-value' function is used to access the `recentf-menu-path'
51 ;; and `recentf-menu-title' values.
52 (defun recentf-menu-customization-changed (sym val
)
53 "Function called when menu customization has changed.
54 It removes the recentf menu and forces its complete redrawing."
55 (when recentf-initialized-p
56 (easy-menu-remove-item nil
57 (symbol-value 'recentf-menu-path
)
58 (symbol-value 'recentf-menu-title
))
59 (setq recentf-update-menu-p t
))
60 (custom-set-default sym val
))
63 "Maintain a menu of recently opened files."
67 (defcustom recentf-max-saved-items
20
68 "*Maximum number of items saved to `recentf-save-file'."
72 (defcustom recentf-save-file
(expand-file-name "~/.recentf")
73 "*File to save `recentf-list' into."
77 (defcustom recentf-exclude nil
78 "*List of regexps for filenames excluded from `recentf-list'."
80 :type
'(repeat regexp
))
82 (defcustom recentf-menu-title
"Open Recent"
83 "*Name of the recentf menu."
86 :set
'recentf-menu-customization-changed
)
88 (defcustom recentf-menu-path
'("files")
89 "*Path where to add the recentf menu.
90 If nil add it at top-level (see also `easy-menu-change')."
92 :type
'(choice (const :tag
"Top Level" nil
)
93 (sexp :tag
"Menu Path"))
94 :set
'recentf-menu-customization-changed
)
96 (defcustom recentf-menu-before
"open-file"
97 "*Name of the menu before which the recentf menu will be added.
98 If nil add it at end of menu (see also `easy-menu-change')."
100 :type
'(choice (string :tag
"Name")
101 (const :tag
"Last" nil
))
102 :set
'recentf-menu-customization-changed
)
104 (defcustom recentf-menu-action
'recentf-find-file
105 "*Function to invoke with a filename item of the recentf menu.
106 The default action `recentf-find-file' calls `find-file' to edit an
107 existing file. If the file does not exist or is not readable, it is
108 not edited and its name is removed from `recentf-list'. You can use
109 `find-file' instead to open non-existing files and keep them in the
110 list of recently opened files."
113 :set
'recentf-menu-customization-changed
)
115 (defcustom recentf-max-menu-items
10
116 "*Maximum number of items in the recentf menu."
119 :set
'recentf-menu-customization-changed
)
121 (defcustom recentf-menu-filter nil
122 "*Function used to filter files displayed in the recentf menu.
123 Nil means no filter. The following functions are predefined:
125 - - `recentf-sort-ascending' to sort menu items in ascending order.
126 - - `recentf-sort-descending' to sort menu items in descending order.
128 The filter function is called with one argument, the list of filenames to be
129 displayed in the menu and must return a new list of filenames."
132 :set
'recentf-menu-customization-changed
)
134 (defcustom recentf-menu-append-commands-p t
135 "*If not-nil command items are appended to the menu."
138 :set
'recentf-menu-customization-changed
)
140 (defcustom recentf-keep-non-readable-files-p nil
141 "*If nil (default), non-readable files are not kept in `recentf-list'."
144 :set
'(lambda (sym val
)
146 (remove-hook kill-buffer-hook recentf-remove-file-hook
)
147 (add-hook kill-buffer-hook recentf-remove-file-hook
))
148 (custom-set-default sym val
)))
150 (defcustom recentf-mode nil
151 "Toggle recentf mode.
152 When recentf mode is enabled, it maintains a menu for visiting files that
153 were operated on recently.
154 Setting this variable directly does not take effect;
155 use either \\[customize] or the function `recentf-mode'."
156 :set
(lambda (symbol value
)
157 (recentf-mode (or value
0)))
158 :initialize
'custom-initialize-default
163 (defcustom recentf-load-hook nil
164 "*Normal hook run at end of loading the `recentf' package."
169 (defun recentf-mode (&optional arg
)
170 "Toggle recentf mode.
171 With prefix ARG, turn recentf mode on if and only if ARG is positive.
172 Returns the new status of recentf mode (non-nil means on).
174 When recentf mode is enabled, it maintains a menu for visiting files that
175 were operated on recently."
178 (> (prefix-numeric-value arg
) 0)
179 (not recentf-mode
))))
181 (unless recentf-initialized-p
182 (setq recentf-initialized-p t
)
183 (if (file-readable-p recentf-save-file
)
184 (load-file recentf-save-file
))
185 (setq recentf-update-menu-p t
)
186 (add-hook 'find-file-hooks
'recentf-add-file-hook
)
187 (add-hook 'write-file-hooks
'recentf-add-file-hook
)
188 ;; (add-hook 'activate-menubar-hook 'recentf-update-menu-hook)
189 (add-hook 'menu-bar-update-hook
'recentf-update-menu-hook
)
190 (add-hook 'kill-emacs-hook
'recentf-save-list
))
191 (when recentf-initialized-p
192 (setq recentf-initialized-p nil
)
194 (easy-menu-remove-item nil recentf-menu-path recentf-menu-title
)
195 (remove-hook 'find-file-hooks
'recentf-add-file-hook
)
196 (remove-hook 'write-file-hooks
'recentf-add-file-hook
)
197 ;; (remove-hook 'activate-menubar-hook 'recentf-update-menu-hook)
198 (remove-hook 'menu-bar-update-hook
'recentf-update-menu-hook
)
199 (remove-hook 'kill-emacs-hook
'recentf-save-list
)))
200 (setq recentf-mode on-p
)))
202 (defun recentf-add-file-hook ()
203 "Insert the name of the file just opened or written into `recentf-list'."
204 (and buffer-file-name
(recentf-add-file buffer-file-name
))
207 (defun recentf-remove-file-hook ()
208 "When a buffer is killed remove a non readable file from `recentf-list'."
209 (and buffer-file-name
(recentf-remove-if-non-readable buffer-file-name
))
212 (defun recentf-update-menu-hook ()
213 "Update the recentf menu from the current `recentf-list'."
214 (when recentf-update-menu-p
217 (easy-menu-change recentf-menu-path
219 (recentf-make-menu-items)
221 (setq recentf-update-menu-p nil
))
225 (defun recentf-save-list ()
226 "Save the current `recentf-list' to the file `recentf-save-file'."
228 (let ((saved-list (recentf-elements recentf-max-saved-items
)))
231 (insert (format recentf-save-file-header
(current-time-string)))
232 (insert "(setq recentf-list\n '(\n")
234 (insert (format " %S\n" e
)))
237 (if (file-writable-p recentf-save-file
)
238 (write-region (point-min) (point-max) recentf-save-file
))
239 (kill-buffer (current-buffer))))
242 (defvar recentf-edit-selected-items nil
243 "Used by `recentf-edit-list' to hold the list of files to be deleted
244 from `recentf-list'.")
246 (defun recentf-edit-list-action (widget &rest ignore
)
247 "Checkbox widget action used by `recentf-edit-list' to select/unselect a file."
248 (let ((value (widget-get widget
':tag
)))
249 ;; if value is already in the selected items
250 (if (memq value recentf-edit-selected-items
)
253 (setq recentf-edit-selected-items
254 (delq value recentf-edit-selected-items
))
255 (message "%s removed from selection." value
))
258 (setq recentf-edit-selected-items
259 (nconc (list value
) recentf-edit-selected-items
))
260 (message "%s added to selection." value
)))))
263 (defun recentf-edit-list ()
264 "Allow the user to edit the files that are kept in the recent list."
266 (with-current-buffer (get-buffer-create (concat "*" recentf-menu-title
" - Edit list*"))
267 (switch-to-buffer (current-buffer))
268 (kill-all-local-variables)
269 (let ((inhibit-read-only t
))
271 (let ((all (overlay-lists)))
272 ;; Delete all the overlays.
273 (mapcar 'delete-overlay
(car all
))
274 (mapcar 'delete-overlay
(cdr all
)))
275 (setq recentf-edit-selected-items nil
)
276 ;; Insert the dialog header
277 (widget-insert "Select the files to be deleted from the 'recentf-list'.\n\n")
278 (widget-insert "Click on Ok to update the list or on Cancel to quit.\n" )
279 ;; Insert the list of files as checkboxes
280 (mapcar '(lambda (item)
281 (widget-create 'checkbox
282 :value nil
; unselected checkbox
283 :format
"\n %[%v%] %t"
285 :notify
'recentf-edit-list-action
))
287 (widget-insert "\n\n")
288 ;; Insert the Ok button
289 (widget-create 'push-button
290 :notify
(lambda (&rest ignore
)
291 (if recentf-edit-selected-items
292 (progn (kill-buffer (current-buffer))
293 (mapcar '(lambda (item)
295 (delq item recentf-list
)))
296 recentf-edit-selected-items
)
297 (message "%S file(s) removed from the list"
298 (length recentf-edit-selected-items
))
299 (setq recentf-update-menu-p t
))
300 (message "No file selected.")))
303 ;; Insert the Cancel button
304 (widget-create 'push-button
305 :notify
(lambda (&rest ignore
)
306 (kill-buffer (current-buffer))
307 (message "Command canceled."))
309 (use-local-map widget-keymap
)
313 (defun recentf-cleanup ()
314 "Remove all non-readable and excluded files from `recentf-list'."
318 (mapcar '(lambda (filename)
319 (and (file-readable-p filename
)
320 (recentf-include-p filename
)
323 (setq recentf-update-menu-p t
))
325 (defun recentf-open-more-files-action (widget &rest ignore
)
326 "Button widget action used by `recentf-open-more-files' to open a file."
327 (kill-buffer (current-buffer))
328 (funcall recentf-menu-action
(widget-value widget
)))
331 (defun recentf-open-more-files ()
332 "Allow the user to open files that are not in the menu."
334 (with-current-buffer (get-buffer-create (concat "*" recentf-menu-title
" - More*"))
335 (switch-to-buffer (current-buffer))
336 (kill-all-local-variables)
337 (let ((inhibit-read-only t
))
339 (let ((all (overlay-lists)))
340 ;; Delete all the overlays.
341 (mapcar 'delete-overlay
(car all
))
342 (mapcar 'delete-overlay
(cdr all
)))
343 ;; Insert the dialog header
344 (widget-insert "Click on a file to open it or on Cancel to quit.\n\n")
345 ;; Insert the list of files as buttons
346 (mapcar '(lambda (menu-element)
347 (let ((menu-item (car menu-element
))
348 (file-path (cdr menu-element
)))
349 (widget-create 'push-button
350 :button-face
'default
352 :help-echo
(concat "Open " file-path
)
354 :notify
'recentf-open-more-files-action
356 (widget-insert "\n")))
357 (funcall (or recentf-menu-filter
'identity
)
358 (mapcar '(lambda (item) (cons item item
))
359 (nthcdr recentf-max-menu-items recentf-list
))))
361 ;; Insert the Cancel button
362 (widget-create 'push-button
363 :notify
(lambda (&rest ignore
)
364 (kill-buffer (current-buffer))
365 (message "Command canceled."))
367 (use-local-map widget-keymap
)
370 (defvar recentf-menu-items-for-commands
371 (list ["Cleanup list" recentf-cleanup t
]
372 ["Edit list..." recentf-edit-list t
]
373 ["Save list now" recentf-save-list t
]
374 (vector "Recentf Options..." '(customize-group "recentf") t
))
375 "List of menu items for recentf commands.")
377 (defun recentf-make-menu-items ()
378 "Make menu items from `recentf-list'."
380 (mapcar '(lambda (entry)
381 (vector entry
(list recentf-menu-action entry
) t
))
382 (funcall (or recentf-menu-filter
'identity
)
383 (recentf-elements recentf-max-menu-items
)))))
384 (append (or file-items
(list ["No files" t nil
]))
385 (and (< recentf-max-menu-items
(length recentf-list
))
386 (list ["More..." recentf-open-more-files t
]))
387 (and recentf-menu-append-commands-p
388 (cons ["---" nil nil
]
389 recentf-menu-items-for-commands
)))))
391 (defun recentf-add-file (filename)
392 "Add or move FILENAME at the beginning of `recentf-list'.
393 Does nothing if FILENAME matches one of the `recentf-exclude' regexps."
394 (when (recentf-include-p filename
)
395 (setq recentf-list
(cons filename
(delete filename recentf-list
)))
396 (setq recentf-update-menu-p t
)))
398 (defun recentf-remove-if-non-readable (filename)
399 "Remove FILENAME from `recentf-list' if not readable."
400 (unless (file-readable-p filename
)
401 (setq recentf-list
(delete filename recentf-list
))
402 (setq recentf-update-menu-p t
)))
404 (defun recentf-find-file (filename)
405 "Edit file FILENAME using `find-file'.
406 If FILENAME is not readable it is removed from `recentf-list'."
407 (if (file-readable-p filename
)
410 (message "File `%s' not found." filename
)
411 (setq recentf-list
(delete filename recentf-list
))
412 (setq recentf-update-menu-p t
))))
414 (defun recentf-include-p (filename)
415 "Return t if FILENAME matches none of the `recentf-exclude' regexps."
416 (let ((rl recentf-exclude
))
417 (while (and rl
(not (string-match (car rl
) filename
)))
421 (defun recentf-elements (n)
422 "Return a list of the first N elements of `recentf-list'."
423 (let ((lh nil
) (l recentf-list
))
424 (while (and l
(> n
0))
425 (setq lh
(cons (car l
) lh
))
430 (defun recentf-sort-ascending (l)
431 "Sort the list of strings L in ascending order."
432 (sort l
'(lambda (e1 e2
) (string-lessp e1 e2
))))
434 (defun recentf-sort-descending (l)
435 "Sort the list of strings L in descending order."
436 (sort l
'(lambda (e1 e2
) (string-lessp e2 e1
))))
440 (run-hooks 'recentf-load-hook
)
442 ;;; recentf.el ends here.