recentf.el 43.9 KB
Newer Older
1
;;; recentf.el --- setup a menu of recently opened files
Dave Love's avatar
Dave Love committed
2

3 4
;; Copyright (C) 1999, 2000, 2001, 2002, 2003
;;   Free Software Foundation, Inc.
Dave Love's avatar
Dave Love committed
5 6 7

;; Author: David Ponce <david@dponce.com>
;; Created: July 19 1999
8 9 10
;; Maintainer: FSF
;; Keywords: files

Dave Love's avatar
Dave Love committed
11 12 13
;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
14 15 16
;; it under the terms of the GNU General Public License as published
;; by the Free Software Foundation; either version 2, or (at your
;; option) any later version.
Dave Love's avatar
Dave Love committed
17 18 19 20 21 22 23 24 25 26 27 28 29 30

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; This package maintains a menu for visiting files that were operated
31 32
;; on recently.  When enabled a new "Open Recent" submenu is displayed
;; in the "Files" menu.  The recent files list is automatically saved
Dave Love's avatar
Dave Love committed
33 34
;; across Emacs sessions.  You can customize the number of recent
;; files displayed, the location of the menu and others options (see
35 36 37
;; the source code for details).

;;; History:
Dave Love's avatar
Dave Love committed
38 39 40 41 42
;;

;;; Code:
(require 'easymenu)
(require 'wid-edit)
43
(require 'timer)
Dave Love's avatar
Dave Love committed
44

45 46
;;; Internal data
;;
Dave Love's avatar
Dave Love committed
47 48 49
(defvar recentf-list nil
  "List of recently opened files.")

50 51 52 53 54 55
(defvar recentf-data-cache nil
  "Cache of data used to build the recentf menu.
The menu is rebuilt when this data has changed.")

;;; Customization
;;
Dave Love's avatar
Dave Love committed
56 57 58 59 60 61 62 63 64 65 66
(defgroup recentf nil
  "Maintain a menu of recently opened files."
  :version "21.1"
  :group 'files)

(defgroup recentf-filters nil
  "Group to customize recentf menu filters.
You should define the options of your own filters in this group."
  :group 'recentf)

(defcustom recentf-max-saved-items 20
67 68 69
  "*Maximum number of items of the recent list that will be saved.
nil means to save the whole list.
See the command `recentf-save-list'."
Dave Love's avatar
Dave Love committed
70 71 72
  :group 'recentf
  :type 'integer)

73 74
(defcustom recentf-save-file "~/.recentf"
  "*File to save the recent list into."
Dave Love's avatar
Dave Love committed
75 76 77 78
  :group 'recentf
  :type 'file)

(defcustom recentf-exclude nil
Glenn Morris's avatar
Glenn Morris committed
79 80 81 82 83
"*List of regexps and predicates for filenames excluded from the recent list.
When a filename matches any of the regexps or satisfies any of the
predicates it is excluded from the recent list.
A predicate is a function that is passed a filename to check and that
must return non-nil to exclude it."
Dave Love's avatar
Dave Love committed
84
  :group 'recentf
Glenn Morris's avatar
Glenn Morris committed
85
  :type '(repeat (choice regexp function)))
Dave Love's avatar
Dave Love committed
86

87 88 89 90 91 92 93 94
(defun recentf-menu-customization-changed (variable value)
  "Function called when the recentf menu customization has changed.
Set VARIABLE with VALUE, and force a rebuild of the recentf menu."
  (when (featurep 'recentf)
    ;; Unavailable until recentf has been loaded.
    (recentf-clear-data))
  (set-default variable value))

Dave Love's avatar
Dave Love committed
95 96 97 98 99 100 101 102
(defcustom recentf-menu-title "Open Recent"
  "*Name of the recentf menu."
  :group 'recentf
  :type 'string
  :set 'recentf-menu-customization-changed)

(defcustom recentf-menu-path '("files")
  "*Path where to add the recentf menu.
103
If nil add it at top level (see also `easy-menu-add-item')."
Dave Love's avatar
Dave Love committed
104 105 106 107 108
  :group 'recentf
  :type '(choice (const :tag "Top Level" nil)
                 (sexp :tag "Menu Path"))
  :set 'recentf-menu-customization-changed)

109
(defcustom recentf-menu-before "Open File..."
Dave Love's avatar
Dave Love committed
110
  "*Name of the menu before which the recentf menu will be added.
111
If nil add it at end of menu (see also `easy-menu-add-item')."
Dave Love's avatar
Dave Love committed
112 113 114 115 116 117 118
  :group 'recentf
  :type '(choice (string :tag "Name")
                 (const :tag "Last" nil))
  :set 'recentf-menu-customization-changed)

(defcustom recentf-menu-action 'recentf-find-file
  "*Function to invoke with a filename item of the recentf menu.
119
The default is to call `recentf-find-file' to edit the selected file."
Dave Love's avatar
Dave Love committed
120 121 122 123 124 125 126 127 128 129 130 131
  :group 'recentf
  :type 'function
  :set 'recentf-menu-customization-changed)

(defcustom recentf-max-menu-items 10
  "*Maximum number of items in the recentf menu."
  :group 'recentf
  :type 'integer
  :set 'recentf-menu-customization-changed)

(defcustom recentf-menu-filter nil
  "*Function used to filter files displayed in the recentf menu.
Pavel Janík's avatar
Pavel Janík committed
132
nil means no filter.  The following functions are predefined:
Dave Love's avatar
Dave Love committed
133

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
- `recentf-sort-ascending'
    Sort menu items in ascending order.
- `recentf-sort-descending'
    Sort menu items in descending order.
- `recentf-sort-basenames-ascending'
    Sort menu items by filenames sans directory in ascending order.
- `recentf-sort-basenames-descending'
    Sort menu items by filenames sans directory in descending order.
- `recentf-sort-directories-ascending'
    Sort menu items by directories in ascending order.
- `recentf-sort-directories-descending'
    Sort menu items by directories in descending order.
- `recentf-show-basenames'
    Show filenames sans directory in menu items.
- `recentf-show-basenames-ascending'
    Show filenames sans directory in ascending order.
- `recentf-show-basenames-descending'
    Show filenames sans directory in descending order.
- `recentf-relative-filter'
    Show filenames relative to `default-directory'.
- `recentf-arrange-by-rule'
    Show sub-menus following user defined rules.
- `recentf-arrange-by-mode'
    Show a sub-menu for each major mode.
- `recentf-arrange-by-dir'
    Show a sub-menu for each directory.
- `recentf-filter-changer'
    Manage a ring of filters.

The filter function is called with one argument, the list of menu
elements used to build the menu and must return a new list of menu
elements (see `recentf-make-menu-element' for menu element form)."
Dave Love's avatar
Dave Love committed
166
  :group 'recentf
Juanma Barranquero's avatar
Juanma Barranquero committed
167
  :type '(radio (const nil)
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
                (function-item recentf-sort-ascending)
                (function-item recentf-sort-descending)
                (function-item recentf-sort-basenames-ascending)
                (function-item recentf-sort-basenames-descending)
                (function-item recentf-sort-directories-ascending)
                (function-item recentf-sort-directories-descending)
                (function-item recentf-show-basenames)
                (function-item recentf-show-basenames-ascending)
                (function-item recentf-show-basenames-descending)
                (function-item recentf-relative-filter)
                (function-item recentf-arrange-by-rule)
                (function-item recentf-arrange-by-mode)
                (function-item recentf-arrange-by-dir)
                (function-item recentf-filter-changer)
                function)
Dave Love's avatar
Dave Love committed
183 184
  :set 'recentf-menu-customization-changed)

185 186
(defcustom recentf-menu-append-commands-flag t
  "*non-nil means to append command items to the menu."
Dave Love's avatar
Dave Love committed
187 188 189 190
  :group 'recentf
  :type 'boolean
  :set 'recentf-menu-customization-changed)

191 192 193 194 195 196 197 198
(defvaralias 'recentf-menu-append-commands-p
  'recentf-menu-append-commands-flag)
(make-obsolete-variable 'recentf-menu-append-commands-p
                        'recentf-menu-append-commands-flag
                        "21.4")

(defcustom recentf-keep-non-readable-files-flag nil
  "*non-nil means to keep non readable files in the recent list."
Dave Love's avatar
Dave Love committed
199
  :group 'recentf
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
  :type 'boolean)

(defvaralias 'recentf-keep-non-readable-files-p
  'recentf-keep-non-readable-files-flag)
(make-obsolete-variable 'recentf-keep-non-readable-files-p
                        'recentf-keep-non-readable-files-flag
                        "21.4")

(defcustom recentf-auto-cleanup 'mode
  "*Define when to automatically cleanup the recent list.
The following values can be set:

- `mode'
    Cleanup when turning the mode on (default).
- `never'
    Never cleanup the list automatically.
- A number
    Cleanup each time Emacs has been idle that number of seconds.
- A time string
    Cleanup at specified time string, for example at \"11:00pm\".

Setting this variable directly does not take effect;
use \\[customize].

See also the command `recentf-cleanup', that can be used to manually
cleanup the list."
  :group 'recentf
  :type '(radio (const  :tag "When mode enabled"
                        :value mode)
                (const  :tag "Never"
                        :value never)
                (number :tag "When idle that seconds"
                        :value 300)
                (string :tag "At time"
                        :value "11:00pm"))
  :set (lambda (variable value)
         (set-default variable value)
         (when (featurep 'recentf)
           ;; Unavailable until recentf has been loaded.
           (recentf-auto-cleanup))))
Dave Love's avatar
Dave Love committed
240

241 242 243 244 245 246
(defcustom recentf-initialize-file-name-history t
  "*non-nil means to initialize `file-name-history' with the recent list.
If `file-name-history' is not empty, do nothing."
  :group 'recentf
  :type  'boolean)

Dave Love's avatar
Dave Love committed
247 248 249 250 251
(defcustom recentf-load-hook nil
   "*Normal hook run at end of loading the `recentf' package."
  :group 'recentf
  :type 'hook)

252 253 254 255 256 257 258 259 260
(defcustom recentf-filename-handler nil
  "Function to call to process filename handled by recentf.
It is passed a filename to give a chance to transform it.
If it returns nil, the filename is left unchanged."
  :group 'recentf
  :type 'function)

;;; Utilities
;;
Dave Love's avatar
Dave Love committed
261
(defconst recentf-case-fold-search
Juanma Barranquero's avatar
Juanma Barranquero committed
262
  (memq system-type '(vax-vms windows-nt cygwin))
Dave Love's avatar
Dave Love committed
263 264
  "Non-nil if recentf searches and matches should ignore case.")

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
(defsubst recentf-string-equal (s1 s2)
  "Return non-nil if strings S1 and S2 have identical contents.
Ignore case if `recentf-case-fold-search' is non-nil."
  (if recentf-case-fold-search
      (string-equal (downcase s1) (downcase s2))
    (string-equal s1 s2)))

(defsubst recentf-string-lessp (s1 s2)
  "Return non-nil if string S1 is less than S2 in lexicographic order.
Ignore case if `recentf-case-fold-search' is non-nil."
  (if recentf-case-fold-search
      (string-lessp (downcase s1) (downcase s2))
    (string-lessp s1 s2)))

(defun recentf-string-member (elt list)
  "Return non-nil if ELT is an element of LIST.
The value is actually the tail of LIST whose car is ELT.
ELT must be a string and LIST a list of strings.
Ignore case if `recentf-case-fold-search' is non-nil."
  (while (and list (not (recentf-string-equal elt (car list))))
    (setq list (cdr list)))
  list)

(defsubst recentf-trunc-list (l n)
  "Return from L the list of its first N elements."
  (let (nl)
    (while (and l (> n 0))
      (setq nl (cons (car l) nl)
            n  (1- n)
            l  (cdr l)))
    (nreverse nl)))

(defun recentf-dump-variable (variable &optional limit)
  "Insert a \"(setq VARIABLE value)\" in the current buffer.
When the value of VARIABLE is a list, optional argument LIMIT
specifies a maximum number of elements to insert.  By default insert
the full list."
  (let ((value (symbol-value variable)))
    (if (atom value)
        (insert (format "\n(setq %S %S)\n" variable value))
      (when (and (integerp limit) (> limit 0))
        (setq value (recentf-trunc-list value limit)))
      (insert (format "\n(setq %S\n      '(" variable))
      (dolist (e value)
        (insert (format "\n        %S" e)))
      (insert "\n        ))\n"))))

(defvar recentf-auto-cleanup-timer nil
  "Timer used to automatically cleanup the recent list.
See also the option `recentf-auto-cleanup'.")

(defun recentf-auto-cleanup ()
  "Automatic cleanup of the recent list."
  (when (timerp recentf-auto-cleanup-timer)
    (cancel-timer recentf-auto-cleanup-timer))
  (when recentf-mode
    (setq recentf-auto-cleanup-timer
          (cond
           ((eq 'mode recentf-auto-cleanup)
            (recentf-cleanup)
            nil)
           ((numberp recentf-auto-cleanup)
            (run-with-idle-timer
             recentf-auto-cleanup t 'recentf-cleanup))
           ((stringp recentf-auto-cleanup)
            (run-at-time
             recentf-auto-cleanup nil 'recentf-cleanup))))))

;;; File functions
;;
(defsubst recentf-push (filename)
  "Push FILENAME into the recent list, if it isn't there yet.
If it is there yet, move it at the beginning of the list.
If `recentf-case-fold-search' is non-nil, ignore case when comparing
filenames."
  (let ((m (recentf-string-member filename recentf-list)))
    (and m (setq recentf-list (delq (car m) recentf-list)))
    (push filename recentf-list)))

(defsubst recentf-expand-file-name (name)
  "Convert filename NAME to absolute, and canonicalize it.
See also the function `expand-file-name'.
If defined, call the function `recentf-filename-handler' to post
process the canonical name."
  (let* ((filename (expand-file-name name)))
    (or (and recentf-filename-handler
             (funcall recentf-filename-handler filename))
        filename)))

Glenn Morris's avatar
Glenn Morris committed
354 355 356 357 358 359 360
(defsubst recentf-file-readable-p (filename)
  "Return t if file FILENAME exists and you can read it.
Like the function `file-readable-p' but return nil on error."
  (condition-case nil
      (file-readable-p filename)
    (error nil)))

Dave Love's avatar
Dave Love committed
361
(defun recentf-include-p (filename)
Glenn Morris's avatar
Glenn Morris committed
362 363
  "Return non-nil if FILENAME should be included in the recent list.
That is, if it doesn't match any of the `recentf-exclude' checks."
Dave Love's avatar
Dave Love committed
364
  (let ((case-fold-search recentf-case-fold-search)
Glenn Morris's avatar
Glenn Morris committed
365 366 367 368 369 370 371 372 373 374 375 376
        (checks recentf-exclude)
        (keepit t)
        check)
    (while (and checks keepit)
      (setq check  (car checks)
            checks (cdr checks)
            keepit (not (if (stringp check)
                            ;; A regexp
                            (string-match check filename)
                          ;; A predicate
                          (funcall check filename)))))
    keepit))
Dave Love's avatar
Dave Love committed
377

378 379
(defsubst recentf-add-file (filename)
  "Add or move FILENAME at the beginning of the recent list.
Glenn Morris's avatar
Glenn Morris committed
380 381
Does nothing if the name satisfies any of the `recentf-exclude' regexps or
predicates."
382 383 384
  (setq filename (recentf-expand-file-name filename))
  (when (recentf-include-p filename)
    (recentf-push filename)))
Dave Love's avatar
Dave Love committed
385

386 387 388
(defsubst recentf-remove-if-non-readable (filename)
  "Remove FILENAME from the recent list, if file is not readable.
Return non-nil if FILENAME has been removed."
Glenn Morris's avatar
Glenn Morris committed
389
  (unless (recentf-file-readable-p filename)
390 391 392
    (let ((m (recentf-string-member
              (recentf-expand-file-name filename) recentf-list)))
      (and m (setq recentf-list (delq (car m) recentf-list))))))
Dave Love's avatar
Dave Love committed
393 394 395

(defun recentf-find-file (filename)
  "Edit file FILENAME using `find-file'.
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
If the file does not exist or is non readable, and
`recentf-keep-non-readable-files-flag' is nil, it is not edited and
its name is removed from the recent list."
  (if (and (not recentf-keep-non-readable-files-flag)
           (recentf-remove-if-non-readable filename))
      (message "File `%s' not found" filename)
    (find-file filename)))

(defsubst recentf-directory-compare (f1 f2)
  "Compare absolute filenames F1 and F2.
First compare directories, then filenames sans directory.
Return non-nil if F1 is less than F2."
  (let ((d1 (file-name-directory f1))
        (d2 (file-name-directory f2)))
    (if (recentf-string-equal d1 d2)
        (recentf-string-lessp (file-name-nondirectory f1)
                              (file-name-nondirectory f2))
      (recentf-string-lessp d1 d2))))

;;; Menu building
;;
(defvar recentf-menu-items-for-commands
  (list ["Cleanup list"
         recentf-cleanup
         :help "Remove all non-readable and excluded files from the recent list"
         :active t]
        ["Edit list..."
         recentf-edit-list
         :help "Edit the files that are kept in the recent list"
         :active t]
        ["Save list now"
         recentf-save-list
         :help "Save the list of recently opened files now"
         :active t]
        ["Options..."
         (customize-group "recentf")
         :help "Customize recently opened files menu and options"
         :active t]
        )
  "List of menu items for recentf commands.")
Dave Love's avatar
Dave Love committed
436

437 438 439 440 441 442 443 444
(defvar recentf-menu-filter-commands nil
  "This variable can be used by menu filters to setup their own command menu.
If non-nil it must contain a list of valid menu-items to be appended
to the recent file list part of the menu.  Before calling a menu
filter function this variable is reset to nil.")

(defsubst recentf-elements (n)
  "Return a list of the first N elements of the recent list."
Dave Love's avatar
Dave Love committed
445 446
  (recentf-trunc-list recentf-list n))

447
(defsubst recentf-make-menu-element (menu-item menu-value)
Dave Love's avatar
Dave Love committed
448
  "Create a new menu-element.
449 450 451 452 453 454
A menu element is a pair (MENU-ITEM . MENU-VALUE), where MENU-ITEM is
the menu item string displayed.  MENU-VALUE is the file to be open
when the corresponding MENU-ITEM is selected.  Or it is a
pair (SUB-MENU-TITLE . MENU-ELEMENTS) where SUB-MENU-TITLE is a
sub-menu title and MENU-ELEMENTS is the list of menu elements in the
sub-menu."
Dave Love's avatar
Dave Love committed
455 456
  (cons menu-item menu-value))

457
(defsubst recentf-menu-element-item (e)
Dave Love's avatar
Dave Love committed
458 459 460
  "Return the item part of the menu-element E."
  (car e))

461
(defsubst recentf-menu-element-value (e)
Dave Love's avatar
Dave Love committed
462 463 464
  "Return the value part of the menu-element E."
  (cdr e))

465
(defsubst recentf-set-menu-element-item (e item)
Dave Love's avatar
Dave Love committed
466 467 468
  "Change the item part of menu-element E to ITEM."
  (setcar e item))

469
(defsubst recentf-set-menu-element-value (e value)
Dave Love's avatar
Dave Love committed
470 471 472
  "Change the value part of menu-element E to VALUE."
  (setcdr e value))

473
(defsubst recentf-sub-menu-element-p (e)
Dave Love's avatar
Dave Love committed
474 475 476
  "Return non-nil if menu-element E defines a sub-menu."
  (consp (recentf-menu-element-value e)))

477 478 479 480
(defsubst recentf-make-default-menu-element (file)
  "Make a new default menu element with FILE.
This a menu element (FILE . FILE)."
  (recentf-make-menu-element file file))
Dave Love's avatar
Dave Love committed
481

482 483
(defsubst recentf-menu-elements (n)
  "Return a list of the first N default menu elements from the recent list.
Dave Love's avatar
Dave Love committed
484
See also `recentf-make-default-menu-element'."
Dave Love's avatar
Dave Love committed
485 486 487 488
  (mapcar 'recentf-make-default-menu-element
          (recentf-elements n)))

(defun recentf-apply-menu-filter (filter l)
Dave Love's avatar
Dave Love committed
489 490
  "Apply function FILTER to the list of menu-elements L.
It takes care of sub-menu elements in L and recursively apply FILTER
Dave Love's avatar
Dave Love committed
491
to them.  It is guaranteed that FILTER receives only a list of single
Dave Love's avatar
Dave Love committed
492
menu-elements (no sub-menu)."
493
  (if (and l (functionp filter))
Dave Love's avatar
Dave Love committed
494
      (let ((case-fold-search recentf-case-fold-search)
495 496 497 498 499 500 501 502 503 504 505 506 507
            elts others)
        ;; split L into two sub-listes, one of sub-menus elements and
        ;; another of single menu elements.
        (dolist (elt l)
          (if (recentf-sub-menu-element-p elt)
              (push elt elts)
            (push elt others)))
        ;; Apply FILTER to single elements.
        (when others
          (setq others (funcall filter (nreverse others))))
        ;; Apply FILTER to sub-menu elements.
        (setq l nil)
        (dolist (elt elts)
Dave Love's avatar
Dave Love committed
508
          (recentf-set-menu-element-value
509 510 511 512 513
           elt (recentf-apply-menu-filter
                filter (recentf-menu-element-value elt)))
          (push elt l))
        ;; Return the new filtered menu element list.
        (nconc l others))
Dave Love's avatar
Dave Love committed
514 515 516
    l))

(defun recentf-make-menu-items ()
517
  "Make menu items from the recent list."
Dave Love's avatar
Dave Love committed
518 519 520 521 522 523
  (setq recentf-menu-filter-commands nil)
  (let ((file-items
         (mapcar 'recentf-make-menu-item
                 (recentf-apply-menu-filter
                  recentf-menu-filter
                  (recentf-menu-elements recentf-max-menu-items)))))
524 525 526
    (append (or file-items (list ["No files" t
                                  :help "No recent file to open"
                                  :active nil]))
Dave Love's avatar
Dave Love committed
527
            (and (< recentf-max-menu-items (length recentf-list))
528 529 530
                 (list ["More..." recentf-open-more-files
                        :help "Open files that are not in the menu"
                        :active t]))
Dave Love's avatar
Dave Love committed
531 532 533
            (and recentf-menu-filter-commands
                 (cons "---"
                       recentf-menu-filter-commands))
534
            (and recentf-menu-append-commands-flag
Dave Love's avatar
Dave Love committed
535 536 537
                 (cons "---"
                       recentf-menu-items-for-commands)))))

538 539 540 541 542 543 544 545
(defsubst recentf-make-menu-item (elt)
  "Make a menu item from menu element ELT."
  (let ((item  (recentf-menu-element-item  elt))
        (value (recentf-menu-element-value elt)))
    (if (recentf-sub-menu-element-p elt)
        (cons item (mapcar 'recentf-make-menu-item value))
      (vector item (list recentf-menu-action value)
              :help (concat "Open " value)
546
              :active t))))
Dave Love's avatar
Dave Love committed
547

548 549 550 551
(defsubst recentf-menu-bar ()
  "Return the keymap of the global menu bar."
  (lookup-key global-map [menu-bar]))

552 553 554
(defun recentf-clear-data ()
  "Clear data used to build the recentf menu.
This force a rebuild of the menu."
555 556
  (easy-menu-remove-item (recentf-menu-bar)
                         recentf-menu-path recentf-menu-title)
557 558 559 560 561
  (setq recentf-data-cache nil))

;;; Predefined menu filters
;;
(defsubst recentf-sort-ascending (l)
Dave Love's avatar
Dave Love committed
562 563 564
  "Sort the list of menu elements L in ascending order.
The MENU-ITEM part of each menu element is compared."
  (sort (copy-sequence l)
565 566 567 568
        #'(lambda (e1 e2)
            (recentf-string-lessp
             (recentf-menu-element-item e1)
             (recentf-menu-element-item e2)))))
Dave Love's avatar
Dave Love committed
569

570
(defsubst recentf-sort-descending (l)
Dave Love's avatar
Dave Love committed
571 572 573
  "Sort the list of menu elements L in descending order.
The MENU-ITEM part of each menu element is compared."
  (sort (copy-sequence l)
574 575 576 577
        #'(lambda (e1 e2)
            (recentf-string-lessp
             (recentf-menu-element-item e2)
             (recentf-menu-element-item e1)))))
Dave Love's avatar
Dave Love committed
578

579
(defsubst recentf-sort-basenames-ascending (l)
Dave Love's avatar
Dave Love committed
580
  "Sort the list of menu elements L in ascending order.
581
Only filenames sans directory are compared."
Dave Love's avatar
Dave Love committed
582
  (sort (copy-sequence l)
583 584 585 586
        #'(lambda (e1 e2)
            (recentf-string-lessp
             (file-name-nondirectory (recentf-menu-element-value e1))
             (file-name-nondirectory (recentf-menu-element-value e2))))))
Dave Love's avatar
Dave Love committed
587

588
(defsubst recentf-sort-basenames-descending (l)
Dave Love's avatar
Dave Love committed
589
  "Sort the list of menu elements L in descending order.
590
Only filenames sans directory are compared."
Dave Love's avatar
Dave Love committed
591
  (sort (copy-sequence l)
592 593 594 595 596 597
        #'(lambda (e1 e2)
            (recentf-string-lessp
             (file-name-nondirectory (recentf-menu-element-value e2))
             (file-name-nondirectory (recentf-menu-element-value e1))))))

(defsubst recentf-sort-directories-ascending (l)
Dave Love's avatar
Dave Love committed
598 599 600
  "Sort the list of menu elements L in ascending order.
Compares directories then filenames to order the list."
  (sort (copy-sequence l)
601 602 603 604
        #'(lambda (e1 e2)
            (recentf-directory-compare
             (recentf-menu-element-value e1)
             (recentf-menu-element-value e2)))))
Dave Love's avatar
Dave Love committed
605

606
(defsubst recentf-sort-directories-descending (l)
Dave Love's avatar
Dave Love committed
607 608 609
  "Sort the list of menu elements L in descending order.
Compares directories then filenames to order the list."
  (sort (copy-sequence l)
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
        #'(lambda (e1 e2)
            (recentf-directory-compare
             (recentf-menu-element-value e2)
             (recentf-menu-element-value e1)))))

(defun recentf-show-basenames (l &optional no-dir)
  "Filter the list of menu elements L to show filenames sans directory.
When a filename is duplicated, it is appended a sequence number if
optional argument NO-DIR is non-nil, or its directory otherwise."
  (let (filtered-names filtered-list full name counters sufx)
    (dolist (elt l (nreverse filtered-list))
      (setq full (recentf-menu-element-value elt)
            name (file-name-nondirectory full))
      (if (not (member name filtered-names))
          (push name filtered-names)
        (if no-dir
            (if (setq sufx (assoc name counters))
                (setcdr sufx (1+ (cdr sufx)))
              (setq sufx 1)
              (push (cons name sufx) counters))
          (setq sufx (file-name-directory full)))
        (setq name (format "%s(%s)" name sufx)))
      (push (recentf-make-menu-element name full) filtered-list))))

(defsubst recentf-show-basenames-ascending (l)
  "Filter the list of menu elements L to show filenames sans directory.
Filenames are sorted in ascending order.
This filter combines the `recentf-sort-basenames-ascending' and
Dave Love's avatar
Dave Love committed
638
`recentf-show-basenames' filters."
Dave Love's avatar
Dave Love committed
639 640
  (recentf-show-basenames (recentf-sort-basenames-ascending l)))

641 642 643 644
(defsubst recentf-show-basenames-descending (l)
  "Filter the list of menu elements L to show filenames sans directory.
Filenames are sorted in descending order.
This filter combines the `recentf-sort-basenames-descending' and
Dave Love's avatar
Dave Love committed
645
`recentf-show-basenames' filters."
Dave Love's avatar
Dave Love committed
646 647 648
  (recentf-show-basenames (recentf-sort-basenames-descending l)))

(defun recentf-relative-filter (l)
649 650 651 652 653 654 655 656
  "Filter the list of menu-elements L to show relative filenames.
Filenames are relative to the `default-directory'."
  (mapcar #'(lambda (menu-element)
              (let* ((ful (recentf-menu-element-value menu-element))
                     (rel (file-relative-name ful default-directory)))
                (if (string-match "^\\.\\." rel)
                    menu-element
                  (recentf-make-menu-element rel ful))))
Dave Love's avatar
Dave Love committed
657
          l))
658 659 660

;;; Rule based menu filters
;;
Dave Love's avatar
Dave Love committed
661 662 663 664 665 666 667
(defcustom recentf-arrange-rules
  '(
    ("Elisp files (%d)" ".\\.el$")
    ("Java files (%d)"  ".\\.java$")
    ("C/C++ files (%d)" "c\\(pp\\)?$")
    )
  "*List of rules used by `recentf-arrange-by-rule' to build sub-menus.
Dave Love's avatar
Dave Love committed
668
A rule is a pair (SUB-MENU-TITLE . MATCHER).  SUB-MENU-TITLE is the
Dave Love's avatar
Dave Love committed
669
displayed title of the sub-menu where a '%d' `format' pattern is
Dave Love's avatar
Dave Love committed
670 671
replaced by the number of items in the sub-menu.  MATCHER is a regexp
or a list of regexps.  Items matching one of the regular expressions in
Dave Love's avatar
Dave Love committed
672 673 674 675 676 677
MATCHER are added to the corresponding sub-menu."
  :group 'recentf-filters
  :type '(repeat (cons string (repeat regexp)))
  :set 'recentf-menu-customization-changed)

(defcustom recentf-arrange-by-rule-others "Other files (%d)"
Dave Love's avatar
Dave Love committed
678 679 680 681 682
  "*Title of the `recentf-arrange-by-rule' sub-menu.
This is for the menu where items that don't match any
`recentf-arrange-rules' are displayed.  If nil these items are
displayed in the main recent files menu.  A '%d' `format' pattern in
the title is replaced by the number of items in the sub-menu."
Dave Love's avatar
Dave Love committed
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
  :group 'recentf-filters
  :type '(choice (const  :tag "Main menu" nil)
                 (string :tag "Title"))
  :set 'recentf-menu-customization-changed)

(defcustom recentf-arrange-by-rules-min-items 0
  "*Minimum number of items in a `recentf-arrange-by-rule' sub-menu.
If the number of items in a sub-menu is less than this value the
corresponding sub-menu items are displayed in the main recent files
menu or in the `recentf-arrange-by-rule-others' sub-menu if
defined."
  :group 'recentf-filters
  :type 'number
  :set 'recentf-menu-customization-changed)

(defcustom recentf-arrange-by-rule-subfilter nil
699 700 701
  "*Function called by a rule based filter to filter sub-menu elements.
nil means no filter.  See also `recentf-menu-filter'.
You can't use another rule based filter here."
Dave Love's avatar
Dave Love committed
702
  :group 'recentf-filters
Dave Love's avatar
Dave Love committed
703
  :type '(choice (const nil) function)
704 705 706 707 708 709 710 711 712
  :set (lambda (variable value)
         (when (memq value '(recentf-arrange-by-rule
                             recentf-arrange-by-mode
                             recentf-arrange-by-dir))
           (error "Recursive use of a rule based filter"))
         (recentf-menu-customization-changed variable value)))

(defun recentf-match-rule-p (matcher filename)
  "Return non-nil if the rule specified by MATCHER match FILENAME.
Dave Love's avatar
Dave Love committed
713 714
See `recentf-arrange-rules' for details on MATCHER."
  (if (stringp matcher)
715
      (string-match matcher filename)
Dave Love's avatar
Dave Love committed
716
    (while (and (consp matcher)
717
                (not (string-match (car matcher) filename)))
Dave Love's avatar
Dave Love committed
718 719 720 721
      (setq matcher (cdr matcher)))
    matcher))

(defun recentf-arrange-by-rule (l)
Dave Love's avatar
Dave Love committed
722 723
  "Filter the list of menu-elements L.
Arrange them in sub-menus following rules in `recentf-arrange-rules'."
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
  (if (not recentf-arrange-rules)
      l
    (let ((menus (mapcar #'(lambda (r) (list (car r)))
                         recentf-arrange-rules))
          menu others min file rules elts count)
      (dolist (elt l)
        (setq file  (recentf-menu-element-value elt)
              rules recentf-arrange-rules
              elts  menus
              menu  nil)
        (while (and (not menu) rules)
          (when (recentf-match-rule-p (cdar rules) file)
            (setq menu (car elts))
            (recentf-set-menu-element-value
             menu (cons elt (recentf-menu-element-value menu))))
          (setq rules (cdr rules)
                elts  (cdr elts)))
        (unless menu
          (push elt others)))
      
      (setq l nil
            min (if (natnump recentf-arrange-by-rules-min-items)
                    recentf-arrange-by-rules-min-items 0))
      (dolist (menu menus)
        (when (setq elts (recentf-menu-element-value menu))
          (setq count (length elts))
          (if (< count min)
              (setq others (nconc elts others))
            (recentf-set-menu-element-item
             menu (format (recentf-menu-element-item menu) count))
            (recentf-set-menu-element-value
             menu (recentf-apply-menu-filter
                   recentf-arrange-by-rule-subfilter (nreverse elts)))
            (push menu l))))
      
      (if (and (stringp recentf-arrange-by-rule-others) others)
          (nreverse
           (cons
            (recentf-make-menu-element
             (format recentf-arrange-by-rule-others (length others))
             (recentf-apply-menu-filter
              recentf-arrange-by-rule-subfilter (nreverse others)))
            l))
        (nconc
         (nreverse l)
         (recentf-apply-menu-filter
          recentf-arrange-by-rule-subfilter (nreverse others)))))
    ))

;;; Predefined rule based menu filters
;;
Dave Love's avatar
Dave Love committed
775
(defun recentf-build-mode-rules ()
776 777
  "Convert `auto-mode-alist' to menu filter rules.
Rules obey `recentf-arrange-rules' format."
Dave Love's avatar
Dave Love committed
778
  (let ((case-fold-search recentf-case-fold-search)
779 780 781 782
        regexp rule-name rule rules)
    (dolist (mode auto-mode-alist)
      (setq regexp (car mode)
            mode   (cdr mode))
Dave Love's avatar
Dave Love committed
783 784 785 786
      (when (symbolp mode)
        (setq rule-name (symbol-name mode))
        (if (string-match "\\(.*\\)-mode$" rule-name)
            (setq rule-name (match-string 1 rule-name)))
787 788
        (setq rule-name (concat rule-name " (%d)")
              rule (assoc rule-name rules))
Dave Love's avatar
Dave Love committed
789 790
        (if rule
            (setcdr rule (cons regexp (cdr rule)))
791
          (push (list rule-name regexp) rules))))
Dave Love's avatar
Dave Love committed
792 793 794
    ;; It is important to preserve auto-mode-alist order
    ;; to ensure the right file <-> mode association
    (nreverse rules)))
Juanma Barranquero's avatar
Juanma Barranquero committed
795

Dave Love's avatar
Dave Love committed
796
(defun recentf-arrange-by-mode (l)
797
  "Split the list of menu-elements L into sub-menus by major mode."
Dave Love's avatar
Dave Love committed
798 799 800 801 802
  (let ((recentf-arrange-rules (recentf-build-mode-rules))
        (recentf-arrange-by-rule-others "others (%d)"))
    (recentf-arrange-by-rule l)))

(defun recentf-build-dir-rules (l)
803 804
  "Convert directories in menu-elements L to menu filter rules.
Rules obey `recentf-arrange-rules' format."
Dave Love's avatar
Dave Love committed
805
  (let (dirs)
806 807 808 809 810 811 812 813 814 815
    (mapcar #'(lambda (e)
                (let ((dir (file-name-directory
                            (recentf-menu-element-value e))))
                  (or (recentf-string-member dir dirs)
                      (push dir dirs))))
            l)
    (mapcar #'(lambda (d)
                (cons (concat d " (%d)")
                      (concat "\\`" d)))
            (nreverse (sort dirs 'recentf-string-lessp)))))
Dave Love's avatar
Dave Love committed
816 817

(defun recentf-file-name-nondir (l)
818
  "Filter the list of menu-elements L to show filenames sans directory.
Dave Love's avatar
Dave Love committed
819 820
This simplified version of `recentf-show-basenames' does not handle
duplicates.  It is used by `recentf-arrange-by-dir' as its
Dave Love's avatar
Dave Love committed
821
`recentf-arrange-by-rule-subfilter'."
822 823 824 825
  (mapcar #'(lambda (e)
              (recentf-make-menu-element
               (file-name-nondirectory (recentf-menu-element-value e))
               (recentf-menu-element-value e)))
Dave Love's avatar
Dave Love committed
826 827 828
          l))

(defun recentf-arrange-by-dir (l)
829
  "Split the list of menu-elements L into sub-menus by directory."
Dave Love's avatar
Dave Love committed
830 831 832 833
  (let ((recentf-arrange-rules (recentf-build-dir-rules l))
        (recentf-arrange-by-rule-subfilter 'recentf-file-name-nondir)
        recentf-arrange-by-rule-others)
    (nreverse (recentf-arrange-by-rule l))))
834 835 836

;;; Ring of menu filters
;;
Dave Love's avatar
Dave Love committed
837 838 839 840 841 842 843 844 845 846
(defvar recentf-filter-changer-state nil
  "Used by `recentf-filter-changer' to hold its state.")

(defcustom recentf-filter-changer-alist
  '(
    (recentf-arrange-by-mode . "*Files by Mode*")
    (recentf-arrange-by-dir  . "*Files by Directory*")
    (recentf-arrange-by-rule . "*Files by User Rule*")
    )
  "*List of filters managed by `recentf-filter-changer'.
847 848 849
Each filter is defined by a pair (FUNCTION . LABEL), where FUNCTION is
the filter function, and LABEL is the menu item displayed to select
that filter."
Dave Love's avatar
Dave Love committed
850 851
  :group 'recentf-filters
  :type '(repeat (cons function string))
852
  :set (lambda (variable value)
Dave Love's avatar
Dave Love committed
853
         (setq recentf-filter-changer-state nil)
854
         (recentf-menu-customization-changed variable value)))
Dave Love's avatar
Dave Love committed
855 856

(defun recentf-filter-changer-goto-next ()
857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
  "Go to the next filter available.
See `recentf-filter-changer'."
  (setq recentf-filter-changer-state (cdr recentf-filter-changer-state))
  (recentf-clear-data))

(defsubst recentf-filter-changer-get-current ()
  "Get the current filter available.
See `recentf-filter-changer'."
  (unless recentf-filter-changer-state
    (setq recentf-filter-changer-state recentf-filter-changer-alist))
  (car recentf-filter-changer-state))

(defsubst recentf-filter-changer-get-next ()
  "Get the next filter available.
See `recentf-filter-changer'."
  ;; At this point the current filter is the first element of
  ;; `recentf-filter-changer-state'.
  (car (or (cdr recentf-filter-changer-state)
           ;; There is no next element in
           ;; `recentf-filter-changer-state', so loop back to the
           ;; first element of `recentf-filter-changer-alist'.
           recentf-filter-changer-alist)))
Juanma Barranquero's avatar
Juanma Barranquero committed
879

Dave Love's avatar
Dave Love committed
880
(defun recentf-filter-changer (l)
881
  "Manage a ring of menu filters.
Dave Love's avatar
Dave Love committed
882
`recentf-filter-changer-alist' defines the filters in the ring.
883 884 885
Filtering of L is delegated to the current filter in the ring.  A
filter menu item is displayed allowing to dynamically activate the
next filter in the ring.  If the filter ring is empty, L is left
Dave Love's avatar
Dave Love committed
886
unchanged."
887 888 889 890 891 892 893 894 895
  (let ((filter (recentf-filter-changer-get-current)))
    (when filter
      (setq l (recentf-apply-menu-filter (car filter) l)
            filter (recentf-filter-changer-get-next))
      (when filter
        (setq recentf-menu-filter-commands
              (list (vector (cdr filter)
                            '(recentf-filter-changer-goto-next)
                            t)))))
Dave Love's avatar
Dave Love committed
896
    l))
897 898 899

;;; Common dialog stuff
;;
Dave Love's avatar
Dave Love committed
900
(defun recentf-cancel-dialog (&rest ignore)
Dave Love's avatar
Dave Love committed
901
  "Cancel the current dialog.
902 903
Used internally by recentf dialogs.
IGNORE arguments."
Dave Love's avatar
Dave Love committed
904 905
  (interactive)
  (kill-buffer (current-buffer))
906
  (message "Dialog canceled"))
Dave Love's avatar
Dave Love committed
907

908 909 910 911 912 913 914
(defvar recentf-dialog-mode-map
  (let ((km (make-sparse-keymap)))
    (define-key km "q" 'recentf-cancel-dialog)
    (define-key km [down-mouse-1] 'widget-button-click)
    (set-keymap-parent km widget-keymap)
    km)
  "Keymap used in recentf dialogs.")
Dave Love's avatar
Dave Love committed
915 916

(defun recentf-dialog-mode ()
917
  "Major mode of recentf dialogs.
Dave Love's avatar
Dave Love committed
918

919
\\{recentf-dialog-mode-map}"
Dave Love's avatar
Dave Love committed
920 921 922 923
  (interactive)
  (setq major-mode 'recentf-dialog-mode)
  (setq mode-name "recentf-dialog")
  (use-local-map recentf-dialog-mode-map))
924 925 926 927 928 929 930 931

;;; Hooks
;;
(defun recentf-track-opened-file ()
  "Insert the name of the file just opened or written into the recent list."
  (and buffer-file-name
       (recentf-add-file buffer-file-name))
  ;; Must return nil because it is run from `write-file-functions'.
Dave Love's avatar
Dave Love committed
932 933
  nil)

934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
(defun recentf-track-closed-file ()
  "Update the recent list when a buffer is killed.
That is, remove a non readable file from the recent list, if
`recentf-keep-non-readable-files-flag' is nil."
  (and buffer-file-name
       (not recentf-keep-non-readable-files-flag)
       (recentf-remove-if-non-readable buffer-file-name)))

(defun recentf-update-menu ()
  "Update the recentf menu from the current recent list."
  (let ((cache (cons default-directory recentf-list)))
    ;; Does nothing, if nothing has changed.
    (unless (equal recentf-data-cache cache)
      (setq recentf-data-cache cache)
      (condition-case err
949 950 951 952 953
          (easy-menu-add-item
           (recentf-menu-bar) recentf-menu-path
           (easy-menu-create-menu recentf-menu-title
                                  (recentf-make-menu-items))
           recentf-menu-before)
954 955 956
        (error
         (message "recentf update menu failed: %s"
                  (error-message-string err)))))))
Dave Love's avatar
Dave Love committed
957

958 959 960 961 962 963 964 965 966
(defconst recentf-used-hooks
  '(
    (find-file-hook       recentf-track-opened-file)
    (write-file-functions recentf-track-opened-file)
    (kill-buffer-hook     recentf-track-closed-file)
    (menu-bar-update-hook recentf-update-menu)
    (kill-emacs-hook      recentf-save-list)
    )
  "Hooks used by recentf.")
Dave Love's avatar
Dave Love committed
967

968 969 970 971 972 973
(defsubst recentf-enabled-p ()
  "Return non-nil if recentf mode is currently enabled."
  (memq 'recentf-update-menu menu-bar-update-hook))

;;; Commands
;;
Dave Love's avatar
Dave Love committed
974
(defvar recentf-edit-selected-items nil
975 976
  "List of files to be deleted from the recent list.
Used internally by `recentf-edit-list'.")
Dave Love's avatar
Dave Love committed
977 978

(defun recentf-edit-list-action (widget &rest ignore)
979 980 981
  "Checkbox WIDGET action that toogles a file selection.
Used internally by `recentf-edit-list'.
IGNORE other arguments."
Dave Love's avatar
Dave Love committed
982 983 984 985 986 987 988
  (let ((value (widget-get widget ':tag)))
    ;; if value is already in the selected items
    (if (memq value recentf-edit-selected-items)
        ;; then remove it
        (progn
          (setq recentf-edit-selected-items
                (delq value recentf-edit-selected-items))
989
          (message "%s removed from selection" value))
Dave Love's avatar
Dave Love committed
990
      ;; else add it
991 992
      (push value recentf-edit-selected-items)
      (message "%s added to selection" value))))
Juanma Barranquero's avatar
Juanma Barranquero committed
993

Dave Love's avatar
Dave Love committed
994
(defun recentf-edit-list ()
995 996
  "Show a dialog buffer to edit the recent list.
That is to select files to be deleted from the recent list."
Dave Love's avatar
Dave Love committed
997
  (interactive)
998 999
  (with-current-buffer
      (get-buffer-create (format "*%s - Edit list*" recentf-menu-title))
Dave Love's avatar
Dave Love committed
1000
    (switch-to-buffer (current-buffer))
1001
    ;; Cleanup buffer
Dave Love's avatar
Dave Love committed
1002
    (kill-all-local-variables)
1003 1004 1005
    (let ((inhibit-read-only t)
          (ol (overlay-lists)))
      (erase-buffer)
Dave Love's avatar
Dave Love committed
1006
      ;; Delete all the overlays.
1007 1008
      (mapc 'delete-overlay (car ol))
      (mapc 'delete-overlay (cdr ol)))
Dave Love's avatar
Dave Love committed
1009 1010
    (setq recentf-edit-selected-items nil)
    ;; Insert the dialog header
1011 1012 1013 1014 1015
    (widget-insert
     "\
Select the files to be deleted from the recent list.\n\n\
Click on Ok to update the list. \
Click on Cancel or type \"q\" to quit.\n")
Dave Love's avatar
Dave Love committed
1016
    ;; Insert the list of files as checkboxes
1017 1018 1019 1020 1021 1022 1023
    (dolist (item recentf-list)
      (widget-create
       'checkbox
       :value nil                       ; unselected checkbox
       :format "\n %[%v%]  %t"
       :tag item
       :notify 'recentf-edit-list-action))
Dave Love's avatar
Dave Love committed
1024 1025
    (widget-insert "\n\n")
    ;; Insert the Ok button
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
    (widget-create
     'push-button
     :notify (lambda (&rest ignore)
               (if recentf-edit-selected-items
                   (let ((i 0))
                     (kill-buffer (current-buffer))
                     (dolist (e recentf-edit-selected-items)
                       (setq recentf-list (delq e recentf-list)
                             i (1+ i)))
                     (message "%S file(s) removed from the list" i))
                 (message "No file selected")))
     "Ok")
Dave Love's avatar
Dave Love committed
1038 1039
    (widget-insert " ")
    ;; Insert the Cancel button
1040 1041 1042 1043
    (widget-create
     'push-button
     :notify 'recentf-cancel-dialog
     "Cancel")
Dave Love's avatar
Dave Love committed
1044 1045 1046 1047 1048
    (recentf-dialog-mode)
    (widget-setup)
    (goto-char (point-min))))

(defun recentf-open-files-action (widget &rest ignore)
1049 1050 1051
  "Button WIDGET action that open a file.
Used internally by `recentf-open-files'.
IGNORE other arguments."
Dave Love's avatar
Dave Love committed
1052 1053 1054 1055
  (kill-buffer (current-buffer))
  (funcall recentf-menu-action (widget-value widget)))

(defvar recentf-open-files-item-shift ""
1056 1057
  "Amount of space to shift right sub-menu items.
Used internally by `recentf-open-files'.")
Dave Love's avatar
Dave Love committed
1058 1059

(defun recentf-open-files-item (menu-element)
1060 1061 1062 1063 1064
  "Insert an item widget for MENU-ELEMENT in the current dialog buffer.
Used internally by `recentf-open-files'."
  (let ((item (car menu-element))
        (file (cdr menu-element)))
    (if (consp file)               ; This is a sub-menu
Dave Love's avatar
Dave Love committed
1065 1066
        (let* ((shift recentf-open-files-item-shift)
               (recentf-open-files-item-shift (concat shift "  ")))
1067 1068 1069 1070 1071 1072
          (widget-create
           'item
           :tag item
           :sample-face 'bold
           :format (concat shift "%{%t%}:\n"))
          (mapc 'recentf-open-files-item file)
Dave Love's avatar
Dave Love committed
1073
          (widget-insert "\n"))
1074 1075 1076 1077 1078 1079 1080 1081
      (widget-create
       'push-button
       :button-face 'default
       :tag item
       :help-echo (concat "Open " file)
       :format (concat recentf-open-files-item-shift "%[%t%]")
       :notify 'recentf-open-files-action
       file)
Dave Love's avatar
Dave Love committed
1082 1083 1084
      (widget-insert "\n"))))

(defun recentf-open-files (&optional files buffer-name)
1085 1086 1087 1088 1089 1090 1091
  "Show a dialog buffer to open a recent file.
If optional argument FILES is non-nil, it specifies the list of
recently-opened files to choose from.  It is the whole recent list
otherwise.
If optional argument BUFFER-NAME is non-nil, it specifies which buffer
name to use for the interaction.  It is \"*`recentf-menu-title'*\" by
default."
Dave Love's avatar
Dave Love committed
1092
  (interactive)
1093 1094 1095 1096
  (unless files
    (setq files recentf-list))
  (unless buffer-name
    (setq buffer-name (format "*%s*" recentf-menu-title)))
Dave Love's avatar
Dave Love committed
1097 1098
  (with-current-buffer (get-buffer-create buffer-name)
    (switch-to-buffer (current-buffer))
1099
    ;; Cleanup buffer
Dave Love's avatar
Dave Love committed
1100
    (kill-all-local-variables)
1101 1102 1103
    (let ((inhibit-read-only t)
          (ol (overlay-lists)))
      (erase-buffer)
Dave Love's avatar
Dave Love committed
1104
      ;; Delete all the overlays.
1105 1106
      (mapc 'delete-overlay (car ol))
      (mapc 'delete-overlay (cdr ol)))
Dave Love's avatar
Dave Love committed
1107 1108 1109 1110 1111
    ;; Insert the dialog header
    (widget-insert "Click on a file to open it. ")
    (widget-insert "Click on Cancel or type \"q\" to quit.\n\n" )
    ;; Insert the list of files as buttons
    (let ((recentf-open-files-item-shift ""))
1112 1113 1114 1115
      (mapc 'recentf-open-files-item
            (recentf-apply-menu-filter
             recentf-menu-filter
             (mapcar 'recentf-make-default-menu-element files))))
Dave Love's avatar
Dave Love committed
1116 1117
    (widget-insert "\n")
    ;; Insert the Cancel button
1118 1119 1120 1121
    (widget-create
     'push-button
     :notify 'recentf-cancel-dialog
     "Cancel")
Dave Love's avatar
Dave Love committed
1122 1123 1124 1125 1126
    (recentf-dialog-mode)
    (widget-setup)
    (goto-char (point-min))))

(defun recentf-open-more-files ()
1127
  "Show a dialog buffer to open a recent file that is not in the menu."
Dave Love's avatar
Dave Love committed
1128 1129
  (interactive)
  (recentf-open-files (nthcdr recentf-max-menu-items recentf-list)
1130
                      (format "*%s - More*" recentf-menu-title)))
Dave Love's avatar
Dave Love committed
1131

1132 1133 1134 1135 1136 1137 1138 1139
(defconst recentf-save-file-header
  ";;; Automatically generated by `recentf' on %s.\n"
  "Header to be written into the `recentf-save-file'.")

(defun recentf-save-list ()
  "Save the recent list.
Write data into the file specified by `recentf-save-file'."
  (interactive)
Glenn Morris's avatar
Glenn Morris committed
1140
  (with-temp-buffer
1141 1142 1143 1144
    (erase-buffer)
    (insert (format recentf-save-file-header (current-time-string)))
    (recentf-dump-variable 'recentf-list recentf-max-saved-items)
    (recentf-dump-variable 'recentf-filter-changer-state)
Glenn Morris's avatar
Glenn Morris committed
1145
    (write-file (expand-file-name recentf-save-file))
1146 1147 1148 1149
    nil))

(defun recentf-load-list ()
  "Load a previously saved recent list.
1150 1151 1152
Read data from the file specified by `recentf-save-file'.
When `recentf-initialize-file-name-history' is non-nil, initialize an
empty `file-name-history' with the recent list."
1153 1154 1155
  (interactive)
  (let ((file (expand-file-name recentf-save-file)))
    (when (file-readable-p file)
1156 1157 1158 1159 1160
      (load-file file)
      (and recentf-initialize-file-name-history
           (not file-name-history)
           (setq file-name-history (mapcar 'abbreviate-file-name
                                           recentf-list))))))
1161 1162

(defun recentf-cleanup ()
Glenn Morris's avatar
Glenn Morris committed
1163
  "Remove all excluded or non-readable files from the recent list."
1164 1165 1166 1167
  (interactive)
  (message "Cleaning up the recentf list...")
  (let (newlist)
    (dolist (f recentf-list)
Glenn Morris's avatar
Glenn Morris committed
1168
      (if (and (recentf-include-p f) (recentf-file-readable-p f))
1169 1170 1171 1172
          (push f newlist)
        (message "File %s removed from the recentf list" f)))
    (setq recentf-list (nreverse newlist))
    (message "Cleaning up the recentf list...done")))
1173

Dave Love's avatar
Dave Love committed
1174
;;;###autoload
1175
(define-minor-mode recentf-mode
Dave Love's avatar
Dave Love committed
1176
  "Toggle recentf mode.
1177 1178
With prefix argument ARG, turn on if positive, otherwise off.
Returns non-nil if the new state is enabled.
Dave Love's avatar
Dave Love committed
1179

1180 1181
When recentf mode is enabled, it maintains a menu for visiting files
that were operated on recently."
1182 1183
  :global t
  :group 'recentf
1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195