;;; filemenu.el --- Mode for buffers that present menus of files to visit ;; Copyright (C) 1998 Will Mengarini ;; Author: Will Mengarini ;; URL: ;; Created: Sa 26 Jul 97 ;; Version: 0.53 ;; Keywords: abbrev, files, filemenu, menu ;; This program 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. ;; This program 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 lets you set up one or more menus of names of files you visit ;; frequently, so you can select from that menu. A mouse is supported but ;; not required. The menus are stored in text files; they can have several ;; columns of file names, & can apply different default directories to ;; different groups of lines in the same file. File names that contain ;; spaces, such as occur on Macs & on Windows {95,NT}, are supported. ;; To use this package, you need to hack 2 files: a file you'll create ;; containing a menu of names of files you access often, & your .emacs file, ;; where you'll autoload filemenu-mode & map loading your menu file to a key. ;; By default, the name of your menu file is "~/filemenu". I suggest that to ;; get started, you copy this text to that file: ;; ~/ ;; my-novel.txt "My Invention.txt" my-checklist.chl ;; c:\emacs\emacs-19.34\lisp\simple.el ;; Delete the leading semicolons by putting the cursor in the top left corner ;; of the file, pressing C-SPC to set the mark, moving it to line 3 column 5 ;; over the "c" in "c:", & pressing C-x r k to run (kill-rectangle). Then ;; modify the file names to name files you really have on your system. You ;; might as well leave the quotes around the middle-column file name just to ;; remind yourself that that format is available (you may need it on ;; Microsoft systems). On a Microsoft OS, you can use either a virgule or a ;; bash as a directory delimiter; the bash need not be doubled or quadrupled ;; (as it is to cope with multiple levels of quoting in some contexts). On a ;; Unix system, use a virgule. "~/" works with all systems. Note that ;; because it's alone on a line, it sets the default directory for all file ;; names on all following lines until another alone-on-a-line path ending in ;; a directory separator occurs; however, files that have their own absolute ;; directory specifiers are unaffected. To edit the third line of that file ;; to reflect where your ELisp library actually is, type C-h v load-path; the ;; correct value is in there someplace; copy it. That'll be enough to let ;; you experiment with filemenu; later you can edit ~/filemenu to add all the ;; files you visit frequently. (The simple.el library file defines the most ;; frequently used Lisp functions that are standard in GNU Emacs, such as ;; `newline-and-indent'. If you're a Lisp hacker you'll want to read these ;; occasionally just to understand how Emacs works. This is also a ;; convenient way of accessing other library files, since once you've loaded ;; this one, its directory becomes the default directory for find-file, and ;; you can use completion to name other files. (Using dired on the ELisp ;; library can be a bad idea except on fast machines because the library is ;; so big; I've seen building the dired buffer take > 30 s.) Sparse package ;; documentation often makes it necessary to "Use the Source, Luke" to figure ;; out how to use Emacs features effectively.) ;; Next, put these lines in your .emacs (_emacs on Microsoft systems) file: ;; (autoload 'filemenu "filemenu" ;; "Load the file menu named by the variable filemenu-file-name." ;; t) ;; (autoload 'filemenu-mode "filemenu" ;; "Major mode for picking a file to edit from a buffer offering a menu." ;; t) ;; (add-to-list 'auto-mode-alist '("[:/\\]filemenu\\'" . filemenu-mode)) ;; (global-set-key (read-kbd-macro "C-c f") 'filemenu) ;; You can modify the argument to (read-kbd-macro) to whatever is convenient ;; for you. ;; Next, move the cursor to the first of those (autoload) lines, type C-SPC ;; to set the mark, move it to the beginning of the line after the ;; `global-set-key', & type M-x eval-region. You should then see "nil" in ;; the echo area; that's the value of the final form evaluated. If you see ;; an error message, there's a typo. You won't need to do this `eval-region' ;; again; in the future, when you load Emacs, that region will be evaluated ;; along with the rest of your .emacs at load time. This is just for adding ;; lines to .emacs during a running session. ;; Now you're ready to try filemenu. Type C-c f (or whatever you modified ;; the `read-kbd-macro' arg to). You should find yourself in ~/filemenu, ;; which is the buffer into which you typed those 3 sample lines; the mode ;; line should show "Filemenu" ("T:Filemenu" on Microsoft systems) as the ;; major mode. If anything else happens, review the instructions to be sure ;; you followed them correctly; if it still doesn't work, send me hate mail. ;; Now you're ready to select from the menu. Try it first just using the ;; keyboard: use the usual motion keys to put point before or inside any of ;; those file names, and press RET. The effect will be to visit the file. ;; Type C-x k to kill the buffer you just visited, & C-c f again (or ;; whatever) to get back to the filemenu buffer; then type v instead of RET, ;; & the effect will be to visit the file in view-mode. Kill it again. If ;; you're familiar with dired, you can try positioning point on the first ;; line (the "~/" line) & pressing RET; the effect will be to run dired on ;; your home directory & put you in the dired buffer. ;; If you're on a system that has a mouse, now try it. Type C-c f or ;; whatever to get back to the filemenu buffer, move the mouse pointer to ;; some file, & press mouse-1 (which is usually the left button). The effect ;; will be to visit the file for editing. Kill it, C-c f again, & press ;; mouse-2 (which is usually the middle button, or a click on the mouse wheel ;; if you have an IntelliMouse, or the left & right buttons simultaneously if ;; you only have a 2-button mouse). You'll visit the file in view-mode. ;; That's it; filemenu is installed. Hack ~/filemenu to add the names of ;; files you visit frequently. Watch out for a gotcha there: when you're ;; visiting that file, you're not in text-mode, you're in filemenu-mode, so ;; RET & v don't insert characters, they visit files. Get around this by ;; preceding them with C-q. If you're typing in a large # of file names at ;; once this could be a nuisance; in that case, you could type them into the ;; *scratch* buffer & cut-&-paste them to ~/filemenu. ;; Some customizations are possible; most people won't need these. ;; If you don't like the name "~/filemenu", put a line like ;; (setq filemenu-file-name "c:/My Emacs Stuff/My Filemenu File.fmn") ;; or whatever pleases you in your .emacs or _emacs file. Note that if you ;; used bashes instead of virgules as your directory separators there (that's ;; Lisp code, not filemenu buffer text) you'd need to double them: ;; (setq filemenu-file-name "c:\\My Emacs Stuff\\My Filemenu File.fmm") ;; Virgules work with Emacs on Microsoft systems. ;; If you don't like the keymappings of filemenu-mode, put code like ;; (defun 'my-filemenu-mode-hook () ;; (local-set-key (read-kbd-macro "H-M-f") 'filemenu-find-file-at-point) ;; (local-set-key [down-mouse-3] 'mouse-set-point) ;; (local-set-key [mouse-3] 'filemenu-view-file-at-point)) ;; (add-hook 'filemenu-mode-hook 'my-filemenu-mode-hook) ;; in your .emacs file. ;; Finally, if you want multiple filemenu files, take a deep breath, then ;; copy all of this code literally to the top of your .emacs file: ;; ;; (require 'cl) ;; ;; (defmacro interactive-lambda (argstring &rest list) ;; (if (equal argstring "") ;; `(lambda () (interactive) ,@list) ;; `(lambda ,(car list) (interactive ,argstring) ,@(cdr list)))) ;; ;; (defmacro global-set-key-to-interactive-lambda (key argstring &rest list) ;; `(global-set-key ,key (interactive-lambda ,argstring ,@list))) ;; ;; (fset 'K 'global-set-key-to-interactive-lambda) ;; ;; (defun multiple-actions-for-multiple-taps-on (key-sequence actions) ;; (if (stringp key-sequence) (callf read-kbd-macro key-sequence)) ;; (let (keys) ;; (while ;; (progn ;; (eval (car actions)) ;; (callf cdr actions) ;; (setq keys (read-key-sequence nil)) ;; (and (equal keys key-sequence) ;; actions))) ;; (setq unread-command-events (listify-key-sequence keys)))) ;; ;; (put 'multiple-actions-for-multiple-taps-on ;; 'lisp-indent-function ;; 'defun) ;; ;; Then do an `eval-region' as before on the code you just inserted. ;; ;; Next choose a way of recognizing files that are filemenu files. On ;; Microsoft systems, this'll be the "extension" of the file, the chars that ;; follow its final dot; I'll assume that's your situation, since if you're ;; on a Unix system you must be smart enough to figure out for yourself how ;; to modify these instructions appropriately. Let's say you chose ".fmn" ;; as the extension. Add this line to your .emacs after the other ;; filemenu-related add-to-list line that you already put in: ;; (add-to-list 'auto-mode-alist '("\\.fmn\\'" . filemenu-mode)) ;; Then replace the global-set-key line you already put in with lines ;; equivalent to these (modify the find-file arguments to name your files): ;; (K "\C-cf" "" (multiple-actions-for-multiple-taps-on "f" ;; '((filemenu) ;; (find-file "~/WorkMenu.fmm") ;; (find-file "~/PlayMenu.fmm") ;; (find-file "~/HomeMenu.fmm")))) ;; Do an `eval-region' on the `add-to-list' line & the `K' lines. ;; The effect will be that pressing C-c f will invoke (filemenu) as before, ;; but each subsequent press of f will move to one of the files in ;; "~/{Work,Play,Home}Menu.fmm". Pressing any other key than f returns f to ;; its normal meaning; f also regains its normal meaning after the final ;; file in the list, "~/HomeMenu.fmm", has been visited. ;; As Ross Perot would say, "it's that simple". ;;; Code: ;; I know about ffap.el, but it has a different feature set. (defvar filemenu-file-name "~/filemenu" "*Path to default file menu used by the filemenu command.") (defvar filemenu-mode-hook nil "Normal hook run after loading a buffer in filemenu mode.") (eval-when-compile (require 'cl)) ;;;###autoload (defun filemenu-mode () "Major mode for picking a file to edit from a buffer offering a menu. The intended use is on a file you will create listing the files you visit so frequently you want them on a menu. The command 'filemenu visits that file in filemenu-mode. An example of the format: /etc/passwd /home/myself/ diary \"File with spaces in its name.Doc\" c:\\DOS\\Nibbles.Bas foo.spoo woo.hoo /usr/local/bin/ballyhoo.u oops.oof /etc/hosts\\ Any # of file names may appear on a line. If the name contains no directory separator characters (virgules or backslashes), any preceding line that ended with such a character is used as the directory. Indentation is optional & ignored. You pick a file to visit by positioning point on its name & typing \\[filemenu-find-file-at-point], or by mouse-clicking it. Turning on filemenu mode calls the value of the variable `filemenu-mode-hook', if that value is non-nil. \\{filemenu-map}" ;;Note that the above documentation is an ELisp string; the bashes are ;;doubled there only because of that; use single bashes in ~/filemenu. (interactive) (kill-all-local-variables) (setq major-mode 'filemenu-mode mode-name "Filemenu") (use-local-map filemenu-map) (run-hooks 'filemenu-mode-hook)) ;;;###autoload (defun filemenu () "Load the file named by the variable filemenu-file-name." (interactive) (find-file filemenu-file-name) (filemenu-mode)) (defvar filemenu-map nil "Keymap for filemenu mode.") (if filemenu-map () (setq filemenu-map (make-sparse-keymap)) ;; Remember that M-o whatever can still be used for orthodox functions, ;; if you're using my orthodox.el package. (define-key filemenu-map "\C-m" 'filemenu-find-file-at-point) (define-key filemenu-map [mouse-1] 'filemenu-find-file-at-point) ;; That alone isn't good enough because the global [down-mouse-1] mapping ;; can eat the [mouse-1] event, so it must be modally overridden: (define-key filemenu-map [down-mouse-1] 'mouse-set-point) ;; I find that I'm accustomed to using [mouse-2] as in *Apropos* & dired, ;; but I'd also like the option of view-mode, so: (define-key filemenu-map "v" 'filemenu-view-file-at-point) ;; Use C-q v to enter a literal "v" (define-key filemenu-map [down-mouse-2] 'mouse-set-point) (define-key filemenu-map [mouse-2] 'filemenu-view-file-at-point) ) (defun filemenu-find-file-at-point () (interactive) (filemenu-visit-file-at-point-using 'find-file)) (defun filemenu-view-file-at-point () (interactive) (filemenu-visit-file-at-point-using 'view-file)) (defun filemenu-visit-file-at-point-using (function) (or (symbolp function) (error "Internal failure 3322")) (if (and (bolp) (eolp)) (error "Empty line")) (let (point-bol point-start point-end file) (save-excursion (beginning-of-line) (if (looking-at "[ \t]+$") (error "Line is all whitespace")) (setq point-bol (point))) (save-excursion ;; Now we must get inside any quotes. Because the quote search starts ;; with a search-backward, it's OK to be on the trailing quote of ;; "file", but not on the leading one; so if we're on a quote followed ;; by nonwhitespace we must move forward. Also, we could be invoked ;; after the last quoted string on the line; in that case we must move ;; backward to the closing quote, or the search-backward intended ;; to find the opening quote will find the closing one instead. (when (< (current-column) (current-indentation)) (back-to-indentation)) (if (looking-at "\"[^ \t\n]") (forward-char) (while (looking-at "[ \t\]*$") (backward-char))) ;; Buglettino not worth fixing: in a line like ;; "file1" "file2" "file3" ;; this works if dot is before or after the nonwhitespace on the line, ;; but if it's invoked *between* files, it'll interpret the surrounding ;; quotes as quoting a file whose name is whitespace. It could avoid ;; that by counting quotes & realizing an even # preceded dot, but hey. (if (search-backward "\"" point-bol t) (progn ;; There was one quote, so require the other (forward-char) (setq point-start (point)) (search-forward "\"") (backward-char) (setq point-end (point))) (re-search-backward "^\\|[ \t]") (if (looking-at "[ \t]") (forward-char)) (setq point-start (point)) (re-search-forward "\\([ \t]\\)\\|$") (if (match-beginning 1) (backward-char)) (setq point-end (point))) (setq file (buffer-substring point-start point-end)) (if (= (length file) 0) (error "No file name specified")) ;; If this file name included no directory, search backward in the ;; menu buffer for a line defining the directory & prepend it. (if (string-match "[/\\]" file) () (when (re-search-backward "[/\\]$" nil t) (beginning-of-line) (setq point-bol (point)) (end-of-line) (setq file (concat (buffer-substring point-bol (point)) file))))) (if (string-match "[*?]" file) (progn (message "(dired %S)" file) (dired file)) (message "(%S %S)" function file) (let ((jiggle-enabled nil));see my jiggle.el (bury-buffer)) (funcall function file)))) (provide 'filemenu) ;;; filemenu.el ends here