;; -*- Emacs-Lisp -*- ;;; picklist.el --- Maintain menu of recently opened files. ;;; ;;; Copyright (C) 1998-2003 Claus Brunzema ;;; ;;; This is based on recent-files.el[1.9]. ;;; -- recent-files.el is Copyright (C) 1994, 1995 ;;; Juergen Nickelsen ;;; -- recent-files.el is part of XEmacs 19.15 available at ;;; http://www.xemacs.org. ;;; ;;; Thanks go to Joerg Schreiber for testing and for suggestions ;;; Christoph Conrad for GNU emacs testing and suggestions ;; Keywords: menu, file ;;; picklist.el is free software; you can redistribute it and/or ;;; modify 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. ;;; ;;; It 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 picklist.el; see the file COPYING. If not, write to the ;;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. ;;; ------------------------------------------------------------------ ;;; Commentary: ;; ;; picklist.el is an enhanched version of recent-files.el. It stores ;; not only filenames, but the point positions in the files, ;; too. picklist.el tries to be smarter than recent-files.el when used ;; with more than one XEmacs process, it checks and merges ;; modifications in the save file before saving the file again. ;; ;; To install picklist, put the following statements in your startup ;; file: ;; ;; (load "picklist") ;; (picklist-initialize) ;; ;; and place the file picklist.el in a directory in your XEmacs's ;; load-path. In order to use picklist.el with dired, dired has to ;; be loaded first. ;; ;; WARNING: this version of picklist.el uses a new file format, it ;; will give a warning and picklist-save-list-on-exit will be disabled ;; if it finds an old save file (from < 2.0.0 versions). If you ;; want to keep your old entries, make a backup and edit your save ;; file, just erase the "(setq picklist-list '" (note the single ;; quote) at the beginning and a ")" at the end to convert your file ;; to the new format. ;; ;; picklist.el adds the menu "Picklist" (or whatever name you choose, see ;; "Customization:" below) to XEmacs's menubar. Its entries are the ;; files (and directories) that have recently been opened by ;; XEmacs. You can open one of these files again by selecting its ;; entry in the "Picklist" menu. The point is placed on the spot where ;; you left the file. The list of file entries in this menu is ;; preserved from one XEmacs session to another. You can prevent ;; XEmacs from saving this list by unselecting "Save picklist list on ;; exit" from the menu. ;; ;; The menu has permanent and non-permanent entries. Permanent ;; entries are marked with an asterisk in front of the filename. ;; ;; Each time you open a file in XEmacs, it is added as a non-permanent ;; entry to the menu. The value of `picklist-number-of-entries' ;; determines how many non-permanent entries are held in the ;; menu. When the number of non-permanent entries reaches this value, ;; the least recently added non-permanent entry is removed from the ;; menu when another non-permanent entry is added. It is not removed ;; from the list, though; it may reappear when entries are deleted ;; from the list. The number of entries saved to disk is the value of ;; the variable `picklist-number-of-saved-entries'. ;; ;; Permanent entries are not removed from the menu. You can make a ;; file entry permanent by selecting "Make permanent" (where ;; is the name of the current buffer) when the current ;; buffer holds this file. "Make non-permanent" makes the ;; file entry of the current buffer non-permanent. ;; ;; The command "Kill buffer and delete entry" is handy when ;; you have accidently opened a file but want to keep neither the ;; buffer nor the entry. ;; ;; You can erase the list of non-permanent entries by selecting ;; "Erase non-permanent entries" from the menu. ;; ;; You can erase all non-permanent entries that refer to files which ;; no longer exist by selecting "Erase stale entries" from the menu. ;; ;; picklist.el tries to be smart when it saves its list on exit. If the ;; save file was modified by a second XEmacs process, picklist.el rereads ;; the file and merges the entries before saving the list again. ;; ;; ;; Customization: ;; ;; There are lots of variables to control the behaviour and ;; appearence of picklist.el. They are accessible via menu: ;; Options/Customize/Emacs/Environment/Menu/Picklist. You do not have ;; to change any of them if you like it as it comes out of the ;; box. However, you may want to look at the options to make it ;; behave and look different. ;; ;; ;; KNOWN BUGS / STRANGE THINGS: ;; - If you have two XEmacs processes running, the one exiting later ;; will try to merge in the in the new entries from the other XEmacs ;; process. This merging is not safe against race conditions, but I ;; think this doesn't matter that much in real life. ;; - picklist.el sets the point in dired-buffers always to the ;; beginning of the buffer, if the buffer has to be loaded. ;; - If you have a buffer (and a picklist entry) on a file, then ;; erase that file (outside XEmacs), you can't switch to the (still ;; existing) buffer via the picklist menu. Use the buffer menu ;; instead. Save the buffer (resurrecting the file), and everything ;; is fine. If you have changed `picklist-find-file-command' to ;; something different from `find-file', this behaviour may ;; disappear... ;;; Code: (require 'easymenu) (require 'cl) ;;; User options --------------------------------------------------------- (defgroup picklist nil "Maintain a menu of recently opened files." :link '(url-link :tag "Homepage" "http://www.cbrunzema.de/software.html#picklist") :link '(emacs-commentary-link :tag "Commentary in picklist.el" "picklist.el") :prefix "picklist-" :group 'files :group 'menu) (defgroup picklist-menu nil "Menu options of picklist." :link '(url-link :tag "Homepage" "http://www.cbrunzema.de/software.html#picklist") :link '(emacs-commentary-link :tag "Commentary in picklist.el" "picklist.el") :prefix "picklist-" :group 'picklist) (defcustom picklist-menu-title "Picklist" "*Name to be displayed as title of the picklist menu." :type 'string :group 'picklist-menu) (defcustom picklist-menu-path nil "*Path where to add the picklist menu. A list of strings containing menu titles forming a path through the menu structure. A value of nil means add it as top-level menu." :type '(choice (const :tag "Top Level" nil) (sexp :tag "Menu Path")) :group 'picklist-menu) (defcustom picklist-add-menu-before nil "*Name of the menu before which the picklist menu shall be added. A value of nil means add it as the last menu in `picklist-menu-path'." :type '(choice (string :tag "Name") (const :tag "Last" nil)) :group 'picklist-menu) (defcustom picklist-number-of-entries 100 "*Maximum of entries in the picklist menu." :type 'integer :group 'picklist) (defcustom picklist-number-of-menu-entries 25 "*Maximum of entries shown in one (sub)menu." :type 'integer :group 'picklist) (defcustom picklist-number-of-saved-entries 100 "*Maximum of non-permanent entries saved to `picklist-save-file'." :type 'integer :group 'picklist) (defcustom picklist-set-point-after-find-file t "*If non-nil, try to set the point position after every `find-file'." :type 'boolean :group 'picklist) (defcustom picklist-save-file (expand-file-name "~/.picklist.el") "*File to save the picklist list in." :type 'file :group 'picklist) (defcustom picklist-save-list-on-exit t "*If non-nil, save the picklist list on exit." :type 'boolean :group 'picklist) (defcustom picklist-filename-replacements (list (cons (expand-file-name "~") "~")) "*List of regexp/replacement pairs for filenames. If a filename matches one of the regexps, it is replaced by the corresponding replacement for display. Only the first match is replaced. If you have several entries in this list that may match a filename simultaneously, put the most special in front of the others." :type '(repeat (cons regexp (string :tag "Replacement"))) :group 'picklist) (defcustom picklist-dont-include nil "*List of regexps for filenames *not* to keep in picklist. For instance, a list to exclude all .newsrc files, all auto-save-files, and all files in the /tmp directory (but not the /tmp directory itself) would look like this: '(\"\\.newsrc\" \"~$\" \"^/tmp/\\.\"))" :type '(repeat regexp) :group 'picklist) (defcustom picklist-use-full-names t "*If non-nil, use the full pathname of a file in the picklist menu. Otherwise use only the filename part. The `picklist-filename-replacements' are not applied in the latter case." :type 'boolean :group 'picklist) (defcustom picklist-permanent-submenu nil "*If non-nil, put the permanent entries of picklist into a submenu." :type 'boolean :group 'picklist-menu) (defcustom picklist-non-permanent-submenu nil "*If non-nil, put the non-permanent entries of picklist into a submenu." :type 'boolean :group 'picklist-menu) (defcustom picklist-permanent-first 'sort "*Control the placement of entries in the picklist menu. If the value is t, permanent entries are put first. If the value is nil, non-permanent entries are put first. If the value is neither, the entries are mixed following `picklist-sort-function' if neither appear in a submenu." :type '(choice (const :tag "Permanent First" t) (const :tag "Non-Permanent First" nil) (sexp :tag "Mixed")) :group 'picklist-menu) (defcustom picklist-commands-submenu nil "*If non-nil, put the commands of picklist into a submenu." :type 'boolean :group 'picklist-menu) (defcustom picklist-commands-submenu-title "Commands..." "*Title of the commands submenu of picklist." :type 'string :group 'picklist-menu) (defcustom picklist-commands-on-top nil "*If non-nil, put the commands on top of the picklist menu." :type 'boolean :group 'picklist-menu) (defcustom picklist-sort-function (function picklist-dont-sort) "*Function to sort the picklist list with. The value `picklist-dont-sort' means to keep the \"most recent on top\" order. The value `picklist-sort-alphabetically' means, well, to sort the list alphabetically." :type 'function :group 'picklist) (defcustom picklist-find-file-command (function find-file) "*Command to invoke when an entry of the picklist menu is selected. This is usually `find-file'." :type 'function :group 'picklist) ;;; Internal variables --------------------------------------------------- (defconst picklist-cvs-name "$Name: v2_0_5 $" "Version control info.") (defvar picklist-list nil "List of recently opened files. Entries are lists like ( ). If is non-nil, the file stays permanently in the list.") (defconst picklist-save-file-header ";; This file is generated by picklist.el. ;; The car of each entry is to appear in the `picklist' menu. ;; Saved at %s. " "Header to be written into the `picklist-save-file'.") (defvar picklist-save-file-mtime nil "Modification time of the save file when read on startup.") (defvar picklist-non-permanent-menu-cache nil "Cache for the menu structure for the non-permanent entries.") (defvar picklist-permanent-menu-cache nil "Cache for the menu structure for the permanent entries.") (defvar picklist-all-mixed-menu-cache nil "Cache for the menu structure for all entries.") ;;; Module initialization ------------------------------------------------ (defun picklist-initialize () "Initialize the picklist menu." (interactive) (if picklist-list (message "picklist is already initialized.") (progn (add-hook 'find-file-hooks #'picklist-find-file-hook) (add-hook 'dired-after-readin-hook #'picklist-find-file-hook) (add-hook 'write-file-hooks #'picklist-write-file-hook) (add-hook 'kill-buffer-hook #'picklist-kill-buffer-hook) (add-hook 'kill-emacs-hook #'picklist-kill-emacs-hook) (setq picklist-list (picklist-load-object picklist-save-file)) (unless (picklist-picklist-p picklist-list) (setq picklist-save-list-on-exit nil) (setq picklist-list nil) (let ((msg "picklist file format error, picklist-save-list-on-exit disabled")) (if (featurep 'xemacs) (display-warning 'file-format-error msg) (message msg)))) (when picklist-list (setq picklist-save-file-mtime (nth 5 (file-attributes picklist-save-file)))) (picklist-list-changed) (easy-menu-change picklist-menu-path picklist-menu-title (list :filter #'(lambda (&optional menu) (picklist-make-menu picklist-list))) picklist-add-menu-before) (when (featurep 'xemacs) ;; push it into the global menubar (set-menubar current-menubar))))) ;;; Hook functions ------------------------------------------------------- (defun picklist-find-file-hook () "Find-file-hook and dired-mode-hook for picklist. Inserts the name of the file just opened or written into the `picklist-list' and sets point when `picklist-set-point-after-find-file' is not nil." (let ((filename (picklist-get-current-buffer-filename))) (picklist-add-entry filename) (when picklist-set-point-after-find-file (let ((entry (picklist-retrieve-entry filename))) (when (picklist-entry-get-point entry) (set-window-point (get-buffer-window (current-buffer)) (picklist-entry-get-point entry)))))) nil) (defun picklist-write-file-hook () "Hook for write-file. Registers the filename to catch filenames created with 'Save As'." (picklist-add-entry (picklist-get-current-buffer-filename)) nil) (defun picklist-kill-buffer-hook () "Hook for kill-buffer. Saves the point position." (picklist-entry-set-point (picklist-retrieve-current) (point))) (defun picklist-kill-emacs-hook () "Hook for kill-emacs. Saves the picklist list if `picklist-save-file-on-exit' is non-nil." (when picklist-save-list-on-exit (picklist-save-the-list))) ;;; the famous misc functions section ------------------------------------ (defun picklist-version () "Return a string identifying the current version of picklist. If called interactively, show it in the echo area. The version string is derived from `picklist-cvs-name'." (interactive) (let ((version (concat "picklist.el " (substitute ?\. ?_ (remove-if #'(lambda (char) (find char "Name:$v ")) picklist-cvs-name)) " by Claus Brunzema "))) (if (interactive-p) (message version) version))) (defun picklist-get-current-buffer-filename () "Return the filename of the current buffer or nil, if there is none. This functions is supposed to do \"the right thing\" also for some modes with no buffer-file-name. Currently supported: dired-mode." (cond (buffer-file-name buffer-file-name) ((eq major-mode 'dired-mode) (dired-current-directory)))) (defun picklist-replace-filename (filename replacements) "Replace the part of FILENAME that matches a regexp in REPLACEMENTS. If FILENAME does not match any regular expression, return it unchanged. Only the first matching regexp/replacement pair is applied." (do ((replacements replacements (cdr replacements)) (result nil)) ((or result (null replacements)) (or result filename)) (when (string-match (caar replacements) filename) (setq result (concat (substring filename 0 (match-beginning 0)) (cdar replacements) (substring filename (match-end 0))))))) (defun picklist-dont-sort (pick-list) "Return PICK-LIST. This is a dummy sorting function for the pick-list. It can be used as value for `picklist-sort-function'" pick-list) (defun picklist-sort-alphabetically (pick-list) "Return PICK-LIST sorted alphabetically by filenames. It can be used as value for `picklist-sort-function'" (sort (copy-list pick-list) #'(lambda (e1 e2) (string-lessp (picklist-entry-get-filename e1) (picklist-entry-get-filename e2))))) (defun picklist-load-object (filename) "Load a lisp object from FILENAME." (when (file-readable-p filename) (with-temp-buffer (insert-file-contents filename) (read (current-buffer))))) ;;; Functions dealing with entries ---------------------------------------- (defun picklist-entry-p (entry) (and (consp entry) (= 3 (length entry)) (stringp (first entry)) (or (eq t (second entry)) (null (second entry))) (or (null (third entry)) (numberp (third entry))))) (defun picklist-entry-get-filename (entry) (when entry (first entry))) (defun picklist-entry-set-filename (entry filename) (when entry (setf (first entry) filename))) (defun picklist-entry-permanent-p (entry) (when entry (second entry))) (defun picklist-entry-set-permanent (entry perm) (when entry (setf (second entry) perm))) (defun picklist-entry-get-point (entry) (when entry (third entry))) (defun picklist-entry-set-point (entry p) (when entry (setf (third entry) p))) (defun picklist-entries-equal-by-filename-p (entry1 entry2) (equal (picklist-entry-get-filename entry1) (picklist-entry-get-filename entry2))) (defun picklist-entry-get-lastname (entry) "Return the last part of the filename in ENTRY." (let ((filename (picklist-entry-get-filename entry))) (when filename (cond ((and (file-directory-p filename) (equal (substring filename -1) "/")) (concat (file-name-nondirectory (substring filename 0 -1)) "/")) ((file-directory-p filename) (concat (file-name-nondirectory filename) "/")) (t (file-name-nondirectory filename)))))) (defun picklist-entry-get-file-buffer (entry) "Return the buffer belonging to ENTRY, if any." (when entry (get-file-buffer (picklist-entry-get-filename entry)))) (defun picklist-entry-to-menu-entry (entry) "Build a menu entry from ENTRY." (vector (concat (if (picklist-entry-permanent-p entry) "* " " ") (if picklist-use-full-names (picklist-replace-filename (picklist-entry-get-filename entry) picklist-filename-replacements) (picklist-entry-get-lastname entry))) (list picklist-find-file-command (picklist-entry-get-filename entry)) :active t)) (defun picklist-make-commands-menu (entry) "Construct the picklist commands menu according to ENTRY." (list (vector (concat "Make " (picklist-entry-get-lastname entry) " permanent") #'picklist-make-permanent :active (and (picklist-entry-get-lastname entry) (not (picklist-entry-permanent-p entry)))) (vector (concat "Make " (picklist-entry-get-lastname entry) " non-permanent") #'picklist-make-non-permanent :active (and (picklist-entry-get-lastname entry) (picklist-entry-permanent-p entry))) (vector "Erase non-permanent entries" #'picklist-erase-non-permanent :active t) (vector "Erase stale entries" #'picklist-erase-stale :active t) (vector "Save picklist list on exit" #'picklist-toggle-save-list-on-exit :style 'toggle :selected picklist-save-list-on-exit) (vector "Save picklist list now" #'picklist-save-the-list :active t) (vector (concat "Kill buffer " (picklist-entry-get-lastname entry) " and delete entry") #'picklist-kill-buffer-delete-entry :active (picklist-entry-get-lastname entry)))) ;;; Functions dealing with a picklist ------------------------------------ (defun picklist-picklist-p (pick-list) "Checks if PICK-LIST is a list of proper picklist entries." (and (listp pick-list) (every #'picklist-entry-p pick-list))) (defun picklist-separate-menu (pick-list) "Make a list of menu-entries (with submenus) from PICK-LIST." (when pick-list (if (<= (length pick-list) picklist-number-of-menu-entries) (mapcar #'picklist-entry-to-menu-entry pick-list) (cons (cons "More..." (picklist-separate-menu (nthcdr picklist-number-of-menu-entries pick-list))) (picklist-separate-menu (butlast pick-list (- (length pick-list) picklist-number-of-menu-entries))))))) (defun picklist-make-file-menu-entries (pick-list pred) "Make file menu entries from PICK-LIST. Only entries that are accepted by PRED are used." (picklist-separate-menu (funcall picklist-sort-function (remove-if #'(lambda (entry) (not (funcall pred entry))) (picklist-truncate pick-list picklist-number-of-entries))))) (defun picklist-make-menu (pick-list) "Construct a picklist menu from PICK-LIST." (let ((commands (picklist-make-commands-menu (picklist-retrieve-current))) (permanent (or picklist-permanent-menu-cache (setq picklist-permanent-menu-cache (picklist-make-file-menu-entries pick-list #'picklist-entry-permanent-p)))) (non-permanent (or picklist-non-permanent-menu-cache (setq picklist-non-permanent-menu-cache (picklist-make-file-menu-entries pick-list #'(lambda (entry) (not (picklist-entry-permanent-p entry))))))) (all-mixed (or picklist-all-mixed-menu-cache (setq picklist-all-mixed-menu-cache (picklist-make-file-menu-entries pick-list #'identity))))) (when picklist-commands-submenu (setq commands (list (cons picklist-commands-submenu-title commands)))) (when picklist-permanent-submenu (setq permanent (list (cons "Permanent entries..." permanent)))) (when picklist-non-permanent-submenu (setq non-permanent (list (cons "Non-permanent entries..." non-permanent)))) (append (when picklist-commands-on-top (append commands (list "-----"))) (if (or picklist-permanent-submenu picklist-non-permanent-submenu) (if picklist-permanent-first (nconc permanent non-permanent) (nconc non-permanent permanent)) (cond ((eq t picklist-permanent-first) (nconc permanent non-permanent)) ((null picklist-permanent-first) (nconc non-permanent permanent)) (t all-mixed))) (unless picklist-commands-on-top (cons "-----" commands))))) (defun picklist-truncate (pick-list n) "Return a list of all permanent and the first N non-permanent entries of PICK-LIST. Preserve the order of the entries." (let ((count 0) (result '())) (dolist (entry pick-list) (if (picklist-entry-permanent-p entry) (push entry result) (when (< count n) (push entry result) (incf count)))) (nreverse result))) (defun picklist-merge-lists (pick-list-old pick-list-new) "Merge entries from PICK-LIST-OLD and PICK-LIST-NEW." (append ;; first the new unique elements... (remove-if #'(lambda (entry) (member* entry pick-list-old :test #'picklist-entries-equal-by-filename-p)) pick-list-new) ;; then the old unique elements... (remove-if #'(lambda (entry) (member* entry pick-list-new :test #'picklist-entries-equal-by-filename-p)) pick-list-old) ;; and finally elements existing in both lists (remove-if #'(lambda (entry) (not (member* entry pick-list-old :test #'picklist-entries-equal-by-filename-p))) pick-list-new))) ;;; Functions dealing with THE (global) picklist ------------------------- (defun picklist-adjust-pos () "Adjust positions according to live buffers in `picklist-list'." (mapc #'(lambda (entry) (when (picklist-entry-get-file-buffer entry) (picklist-entry-set-point entry (with-current-buffer (picklist-entry-get-file-buffer entry) (point))))) picklist-list)) (defun picklist-list-changed () "This must be called after the `picklist-list' has been changed." (setq picklist-non-permanent-menu-cache nil) (setq picklist-permanent-menu-cache nil) (setq picklist-all-mixed-menu-cache nil)) (defun picklist-save-the-list () "Save the current `picklist-list' to the file `picklist-save-file'." (interactive) (picklist-adjust-pos) (when (and picklist-save-file-mtime (file-readable-p picklist-save-file) (not (equal (nth 5 (file-attributes picklist-save-file)) picklist-save-file-mtime))) (let ((old-picklist (picklist-load-object picklist-save-file))) (when (picklist-picklist-p old-picklist) (setq picklist-list (picklist-merge-lists old-picklist picklist-list)) (picklist-list-changed)))) (let ((list-to-save (picklist-truncate picklist-list picklist-number-of-saved-entries))) (when list-to-save (with-temp-buffer (insert (format picklist-save-file-header (current-time-string))) (pp list-to-save (current-buffer)) (insert "\n") (when (file-writable-p picklist-save-file) (write-region (point-min) (point-max) picklist-save-file) (setq picklist-save-file-mtime (nth 5 (file-attributes picklist-save-file)))))))) (defun picklist-add-entry (filename) "Add entry with FILENAME to `picklist-list'. FILENAME is not really added if it matches one of the regexps in `picklist-dont-include'." (unless (some #'(lambda (re) (string-match re filename)) picklist-dont-include) (setq picklist-list (cons (or (picklist-retrieve-entry filename) (list filename nil nil)) (remove-if #'(lambda (entry) (equal filename (picklist-entry-get-filename entry))) picklist-list))) (picklist-list-changed))) (defun picklist-retrieve-entry (filename) "Retrieve an entry from the picklist list." (assoc filename picklist-list)) (defun picklist-retrieve-current () "Retrieve the picklist entry belonging to the current buffer." (picklist-retrieve-entry (picklist-get-current-buffer-filename))) ;;; Menu commands -------------------------------------------------------- (defun picklist-make-permanent () "Make the file in current buffer a permanent entry in picklist." (interactive) (picklist-entry-set-permanent (picklist-retrieve-current) t) (picklist-list-changed)) (defun picklist-make-non-permanent () "Make the file in current buffer a non-permanent entry in picklist." (interactive) (picklist-entry-set-permanent (picklist-retrieve-current) nil) (picklist-list-changed)) (defun picklist-kill-buffer-delete-entry () "Kill the current buffer and delete its entry in the picklist menu." (interactive) (setq picklist-list (remove (picklist-retrieve-current) picklist-list)) (kill-buffer (current-buffer)) (picklist-list-changed)) (defun picklist-erase-non-permanent () "Erase all non-permanent entries from the picklist menu." (interactive) (setq picklist-list (remove-if #'(lambda (entry) (not (picklist-entry-permanent-p entry))) picklist-list)) (picklist-list-changed)) (defun picklist-erase-stale () "Erase all stale nonpermanent entries from the picklist menu." (interactive) (setq picklist-list (remove-if #'(lambda (entry) (and (not (file-exists-p (picklist-entry-get-filename entry))) (not (picklist-entry-permanent-p entry)))) picklist-list)) (picklist-list-changed)) (defun picklist-toggle-save-list-on-exit () "Toggle state of `picklist-save-list-on-exit'." (interactive) (setq picklist-save-list-on-exit (not picklist-save-list-on-exit))) (provide 'picklist) ;;; picklist.el ends here