diff lisp/hyperbole/wrolo.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 4103f0995bd7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/hyperbole/wrolo.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,1197 @@
+;;!emacs
+;;
+;; FILE:         wrolo.el V2 (Renamed from rolo.el in earlier versions to avoid
+;;                            load path conflicts with the rolo.el written by
+;;                            another author.)
+;; SUMMARY:      Hierarchical, multi-file, easy to use rolodex system
+;; USAGE:        GNU Emacs Lisp Library
+;; KEYWORDS:     hypermedia, matching
+;;
+;; AUTHOR:       Bob Weiner
+;; ORG:          Motorola Inc.
+;;
+;; ORIG-DATE:     7-Jun-89 at 22:08:29
+;; LAST-MOD:     31-Oct-95 at 18:39:54 by Bob Weiner
+;;
+;; This file is part of Hyperbole.
+;; Available for use and distribution under the same terms as GNU Emacs.
+;;
+;; Copyright (C) 1989, '90, '91, '92, '95  Free Software Foundation, Inc.
+;; Developed with support from Motorola Inc.
+;;
+;; DESCRIPTION:  
+;;
+;;  The `put whatever you feel like into it' rolodex.
+;;
+;;  FEATURES:
+;;
+;;   1.  Multiple rolodex files with free text lookup.  No structured
+;;       fields are used.
+;;
+;;   2.  Hierarchical rolodex entries as in:
+;;        *    Company
+;;        **     Manager
+;;        ***      Underlings
+;;
+;;       Searching for Manager turns up all Underlings.  Searching for
+;;       Company retrieves all listed employees.
+;;
+;;       This hierarchical system has proved very effective for retrieving
+;;       computer system administration problem reports by vendor name,
+;;       problem number or by subject area, without having to resort to a
+;;       database system, and also for extraction of relevant text
+;;       sections from reports.
+;;
+;;   3.  String and regular expression searching capabilities.  Matches are
+;;       found anywhere within entries, so entries may be of any format you
+;;       like without the bother of fixed field restrictions.
+;;       Ability to restrict number of matches or to report number of matches
+;;       without displaying entries.
+;;
+;;   4.  Smart addition, editing and sorting of entries by hierarchy level.
+;;
+;;   5.  Support for Hyperbole buttons within rolodex entries.
+;;
+;;   See "wrolo-logic.el" for logical search functions (and, or, not, xor).
+;;   See "wrolo-menu.el" for menu handling functions.  (If you received
+;;   wrolo as part of Hyperbole, this file in unneeded and so not included.)
+;;
+;;
+;;  SETUP:
+;;
+;;   The variable 'rolo-file-list' is a list of files to search for
+;;   matching rolodex entries.  To add personal files to rolo-file-list,
+;;   when you find these functions are useful for any sort of list lookup,
+;;   add the following to your ~/.emacs file (substituting where you see
+;;   <fileN>):
+;;
+;;      (setq rolo-file-list (append rolo-file-list '("<file1>" "<file2>")))
+;;
+;;   We recommend that entries in 'rolo-file-list' have ".otl" suffixes
+;;   so that they do not conflict with file names that other rolodex
+;;   programs might use and so that they are edited in 'outline-mode' by
+;;   default.  If you want the latter behavior, uncomment and add something
+;;   like the following to one of your GNU Emacs initialization files:
+;;
+;;     ;; Add to the list of suffixes that causes automatic mode invocation
+;;     (setq auto-mode-alist
+;;        (append '(("\\.otl$" . outline-mode)) auto-mode-alist))
+;;
+;;   The buffers containing the rolodex files are not killed after a search
+;;   on the assumption that another search is likely to follow within this
+;;   Emacs session.  You may wish to change this behavior with the following
+;;   setting:
+;;
+;;     (setq rolo-kill-buffers-after-use t)
+;;
+;;   After an entry is killed, the modified rolodex file is automatically
+;;   saved.  If you would rather always save files yourself, use this
+;;   setting:
+;;
+;;     (setq rolo-save-buffers-after-use nil)
+;;
+;;   When adding an entry from within a buffer containing a mail
+;;   message, the rolodex add function will extract the sender's name
+;;   and e-mail address and prompt you with the name as a default.  If
+;;   you accept it, it will enter the name and the email address using
+;;   the format given by the 'rolo-email-format' variable.  See its
+;;   documentation if you want to change its value.
+;;
+;;
+;;   If you use Hyperbole V2.3 or greater, then no other rolodex setup
+;;   is necessary, simply select the "Rolo/" menu item from the top
+;;   level Hyperbole menu.  Otherwise, add the following to your
+;;   "~/.emacs" file: 
+;;
+;;     (autoload 'rolo-menu "rolo-menu" "Load wrolo system." t)
+;;     (global-set-key "\C-x4r" 'rolo-menu)
+;;
+;;   And then simply invoke the rolodex menu with {C-x 4 r} after Emacs
+;;   has read those lines in your init file.
+;;
+;;
+;;  SUMMARY OF USE:
+;;
+;;   The rolo menu provides access to the following commands:
+;;
+;;     Menu Item       Function              Description
+;;     ====================================================================
+;;     Add             rolo-add              Adds a rolodex entry
+;;     Display         rolo-display-matches  Displays last matches again
+;;     Edit            rolo-edit             Edits an existing rolodex entry
+;;     Info                                  Displays Rolodex manual entry
+;;     Kill            rolo-kill             Removes an entry from the rolodex
+;;     Order           rolo-sort             Sorts all levels in rolodex
+;;     RegexFind       rolo-grep             Finds all entries containing
+;;                                             a regular expression
+;;     StringFind      rolo-fgrep            Finds all entries containing
+;;                                             a string
+;;     WordFind        rolo-word             Finds all entries containing
+;;                                             a string of whole words
+;;     Yank            rolo-yank             Inserts first matching rolodex
+;;                                             entry at point
+;;
+;;   For any of these commands that prompt you for a name, you may use the form
+;;   parent/child to locate a child entry below a parent entry, e.g.
+;;   from the example near the top, we could give Company/Manager/Underlings.
+;;
+;;   Here is a snippet from our group rolodex file.  The ';'s should be
+;;   removed of course and the '*'s should begin at the start of the
+;;   line.  If a rolodex file begins with two separator lines whose
+;;   first three characters are "===", then these lines and any text
+;;   between them are prepended to the output buffer whenever any
+;;   entries are retrieved from that file.
+;;
+;;=============================================================================
+;;			      GROUP ROLODEX
+;; <Last Name>, <First Name>  <Co/Categ>   W<Work #>   H<Home #>  P<Pager #>
+;;				           F<Fax #>    M<Modem #> C<Cellular #>
+;;        <Address>	   <Miscellaneous Info, Key Words>
+;;=============================================================================
+;;*   EX594, Digital-Systems-Research
+;;**  Weiner, Bob	      Motorola     W2087                  P7-7489
+;;	  FL19, L-1035
+;;
+;;
+;;  FOR PROGRAMMERS:
+;;
+;;   Entries in rolodex files are separated by patterns matching
+;;   'rolo-entry-regexp'.  Each entry may have any number of sub-entries
+;;   which represent the next level down in the entry hierarchy.
+;;   Sub-entries' separator patterns are always longer than their parents'.
+;;   For example, if an entry began with '*' then its sub-entries would begin
+;;   with '**' and so on.  Blank lines in rolodex files will not end up where
+;;   you want them if you use the rolo-sort commands; therefore, blank lines
+;;   are not recommended.  If you change the value of
+;;   'rolo-entry-regexp', you will have to modify 'rolo-sort'.
+;;
+;;   The following additional functions are provided:
+;;
+;;     'rolo-sort-level' sorts a specific level of entries in a rolodex file;
+;;     'rolo-map-level' runs a user specified function on a specific level of
+;;       entries in a rolodex file;
+;;     'rolo-fgrep-file', same as 'rolo-fgrep' but operates on a single file;
+;;     'rolo-grep-file', same as 'rolo-grep' but operates on a single file;
+;;     'rolo-display-matches', display last set of rolodex matches, if any;
+;;     'rolo-toggle-narrow-to-entry' toggles between display of current entry
+;;       and display of all matching entries.
+;;
+;;
+;;  MOD HISTORY:
+;;
+;;   12/17/89
+;;     Added internal 'rolo-shrink-window' function for use in
+;;     compressing/uncompressing the rolo view window to/from a size just
+;;     large enough for the selected entry.  This is useful when a search
+;;     turns up more entries than desired.
+;;
+;;   02/21/90
+;;     Modified 'rolo-grep-file' and 'rolo-map-level' so they only set buffers
+;;     read-only the first time they are read in.  This way, if someone edits a
+;;     rolodex file and then does a rolo-fgrep or other function, the buffer
+;;     will not be back in read-only mode.
+;;
+;;   04/18/91
+;;     Modified 'rolo-grep-file' to expand any hidden entries in rolo file
+;;     before doing a search.
+;;
+;;   12/24/91
+;;     Added Hyperbole button support.
+;;
+;;   12/30/91
+;;     Added convenient support for entry add, edit, kill and yank.
+;;
+;;   01/10/91
+;;     Fixed bug in rolo-to that ended search too early.
+;;
+;; DESCRIP-END.
+
+;;; ************************************************************************
+;;; Other required Elisp libraries
+;;; ************************************************************************
+
+(require 'hversion)
+(require 'hmail)
+
+;;; ************************************************************************
+;;; Public variables
+;;; ************************************************************************
+
+(defvar rolo-email-format "%s\t\t<%s>"
+  "Format string to use when adding an entry with e-mail addr from a mail msg.
+It must contain a %s indicating where to put the entry name and a second
+%s indicating where to put the e-mail address.")
+
+(defvar rolo-file-list
+  (if (memq system-type '(ms-windows windows-nt ms-dos))
+      '("c:/_rolodex.otl") '("~/.rolodex.otl"))
+  "*List of files containing rolodex entries.
+The first file should be a user-specific rolodex file, typically in the home
+directory.  The second file is often a shared, group-specific rolodex file.
+
+A rolo-file consists of:
+   (1) an optional header beginning with and ending with a line which matches
+       rolo-hdr-regexp;
+   (2) one or more rolodex entries which each begin with
+       rolo-entry-regexp and may be nested.")
+
+(defvar rolo-highlight-face nil
+  "*Face used to highlight rolodex search matches.")
+(if rolo-highlight-face
+    nil
+  (setq rolo-highlight-face
+	(cond (hyperb:emacs19-p
+	       (if (fboundp 'make-face)
+		   (progn (make-face 'rolo-highlight-face)
+			  'rolo-highlight-face)))
+	      (hyperb:epoch-p (make-style))
+	      (t (if (fboundp 'make-face)
+		     (face-name (make-face 'rolo-highlight-face))))))
+  (if (fboundp 'hproperty:set-item-highlight)
+      (hproperty:set-item-highlight)))
+
+(defvar rolo-kill-buffers-after-use nil
+  "*Non-nil means kill rolodex file buffers after searching them for entries.
+Only unmodified buffers are killed.")
+
+(defvar rolo-save-buffers-after-use t
+  "*Non-nil means save rolodex file after an entry is killed.")
+
+(defvar wrolo-yank-reformat-function nil
+  "*Value is a function of two arguments, START and END, invoked after a rolo-yank.
+It should reformat the region given by the arguments to some preferred style.
+Default value is nil, meaning no reformmating is done.")
+
+;;; ************************************************************************
+;;; Commands
+;;; ************************************************************************
+
+;;;###autoload
+(defun rolo-add (name &optional file)
+  "Adds a new entry in personal rolodex for NAME.
+Last name first is best, e.g. \"Smith, John\".
+With prefix argument, prompts for optional FILE to add entry within.
+NAME may be of the form: parent/child to insert child below a parent
+entry which begins with the parent string."
+  (interactive
+   (progn
+     (or (fboundp 'mail-fetch-field) (require 'mail-utils))
+     (let* ((lst (rolo-name-and-email))
+	    (name (car lst))
+	    (email (car (cdr lst)))
+	    (entry (read-string "Name to add to rolo: "
+				(or name email))))
+       (list (if (and email name
+		      (string-match (concat "\\`" (regexp-quote entry)) name))
+		 (format rolo-email-format entry email) entry)
+	     current-prefix-arg))))
+  (if (or (not (stringp name)) (string= name ""))
+      (error "(rolo-add): Invalid name: '%s'" name))
+  (if (and (interactive-p) file)
+      (setq file (completing-read "File to add to: "
+				  (mapcar 'list rolo-file-list))))
+  (if (null file) (setq file (car rolo-file-list)))
+  (cond ((and file (or (not (stringp file)) (string= file "")))
+	 (error "(rolo-add): Invalid file: '%s'" file))
+	((and (file-exists-p file) (not (file-readable-p file)))
+	 (error "(rolo-add): File not readable: '%s'" file))
+	((not (file-writable-p file))
+	 (error "(rolo-add): File not writable: '%s'" file)))
+  (set-buffer (or (get-file-buffer file) (find-file-noselect file)))
+  (if (interactive-p) (message "Locating insertion point for '%s'..." name))
+  (let ((parent "") (level "") end)
+    (widen) (goto-char 1)
+    (while (setq end (string-match "/" name))
+      (setq parent (substring name 0 end)
+	    name (substring name (min (1+ end) (length name))))
+      (if (re-search-forward
+	   (concat "\\(" rolo-entry-regexp "\\)[ \t]*" 
+		   (regexp-quote parent)) nil t)
+	  (setq level (buffer-substring (match-beginning 1)
+					(match-end 1)))
+	(error "(rolo-add): '%s' category not found in \"%s\"."
+	       parent file)))
+    (narrow-to-region (point)
+		      (progn (rolo-to-entry-end t level) (point)))
+    (goto-char (point-min))
+    (let* ((len (length name))
+	   (name-level (concat level "*"))
+	   (level-len (length name-level))
+	   (entry "")
+	   (entry-spc "")
+	   (entry-level)
+	   (match)
+	   (again t))
+      (while (and again
+		  (re-search-forward
+		   (concat "\\(" rolo-entry-regexp "\\)\\([ \t]*\\)")
+		   nil 'end))
+	(setq entry-level (buffer-substring (match-beginning 1)
+					    (match-end 1)))
+	(if (/= (length entry-level) level-len)
+	    (rolo-to-entry-end t entry-level)
+	  (setq entry (buffer-substring (point) (+ (point) len))
+		entry-spc (buffer-substring (match-beginning 2)
+					    (match-end 2)))
+	  (cond ((string< entry name)
+		 (rolo-to-entry-end t entry-level))
+		((string< name entry)
+		 (setq again nil) (beginning-of-line))
+		(t ;; found existing entry matching name
+		 (setq again nil match t)))))
+      (setq buffer-read-only nil)
+      (if match
+	  nil
+	(insert (or entry-level (concat level "*"))
+		(if (string= entry-spc "") "   " entry-spc)
+		name "\n")
+	(backward-char 1))
+      (widen)
+      (rolo-to-buffer (current-buffer))
+      ;; Fixes non-display update bug when buf is on screen before
+      ;; interactive command invocation. 
+      (goto-char (point))
+      (if (interactive-p)
+	  (message "Edit entry at point.")))))
+
+;;;###autoload
+(defun rolo-display-matches (&optional display-buf return-to-buffer)
+  "Display optional DISPLAY-BUF buffer of previously found rolodex matches.
+If DISPLAY-BUF is nil, use the value in 'rolo-display-buffer'.
+Second arg RETURN-TO-BUFFER is the buffer to leave point within after the display."
+  (interactive)
+  (or display-buf (setq display-buf (get-buffer rolo-display-buffer)))
+  (if display-buf nil
+    (error "(rolo-display-matches): Search the rolodex first."))
+  ;; Save current window configuration if rolodex match buffer is not
+  ;; displayed in one of the windows already.
+  (or
+   ;; Handle both Emacs V18 and V19 versions of get-buffer-window.
+   (condition-case ()
+       (get-buffer-window display-buf (selected-frame))
+     (error (get-buffer-window display-buf)))
+   (setq *rolo-wconfig* (current-window-configuration)))
+  (rolo-to-buffer display-buf t)
+  (if (eq major-mode 'wrolo-mode) nil (wrolo-mode))
+  (setq buffer-read-only nil)
+  (if (fboundp 'hproperty:but-create) (hproperty:but-create))
+  (rolo-shrink-window)
+  (goto-char (point-min))
+  (set-buffer-modified-p nil)
+  (setq buffer-read-only t)
+  (run-hooks 'wrolo-display-hook)
+  ;; Leave point in match buffer unless a specific RETURN-TO-BUFFER has
+  ;; been specified.  Use {q} to quit and restore display.
+  (if return-to-buffer (rolo-to-buffer return-to-buffer t)))
+
+;;;###autoload
+(defun rolo-edit (&optional name file)
+  "Edits a rolodex entry given by optional NAME within 'rolo-file-list'.
+With prefix argument, prompts for optional FILE to locate entry within.
+With no NAME arg, simply displays FILE or first entry in 'rolo-file-list' in an
+editable mode.  NAME may be of the form: parent/child to edit child below a
+parent entry which begins with the parent string."
+  (interactive "sName to edit in rolo: \nP")
+  (if (string-equal name "") (setq name nil))
+  (and name (not (stringp name))
+       (error "(rolo-edit): Invalid name: '%s'" name))
+  (if (and (interactive-p) current-prefix-arg)
+      (if (= (length rolo-file-list) 1)
+	  (setq file (car rolo-file-list))
+	(setq file (completing-read "Entry's File: "
+				    (mapcar 'list rolo-file-list)))))
+  (let ((found-point) (file-list (if file (list file) rolo-file-list)))
+    (or file (setq file (car file-list)))
+    (if (null name)
+	(progn (if (not (file-writable-p file))
+		  (error "(rolo-edit): File not writable: '%s'" file))
+	       (find-file-other-window file) (setq buffer-read-only nil))
+      (if (setq found-point (rolo-to name file-list))
+	  (progn
+	    (setq file buffer-file-name)
+	    (if (file-writable-p file)
+		(setq buffer-read-only nil)
+	      (message
+	       "(rolo-edit): Entry found but file not writable: '%s'" file)
+	      (beep))
+	    (rolo-to-buffer (current-buffer)))
+	(message "(rolo-edit): '%s' not found." name)
+	(beep)
+	(rolo-to-buffer (or (get-file-buffer (car file-list))
+			    (find-file-noselect (car file-list))))
+	(setq buffer-read-only nil))
+      (widen)
+      ;; Fixes display update bug in some Emacs versions.  When buf is
+      ;; on screen before interactive command invocation, point is not
+      ;; moved to proper location.
+      (if found-point (goto-char found-point)))))
+
+(defun rolo-edit-entry ()
+  "Edit the source entry of the rolodex match buffer entry at point.
+Returns entry name if found, else nil."
+  (interactive)
+  (let ((name (rolo-name-at)))
+    (if name (progn (rolo-edit name (hbut:key-src))
+		    name))))
+
+;;;###autoload
+(defun rolo-fgrep (string
+		    &optional max-matches rolo-file count-only no-display)
+  "Display rolodex entries matching STRING.
+To a maximum of optional prefix arg MAX-MATCHES, in file(s) from optional
+ROLO-FILE or rolo-file-list.  Default is to find all matching entries.  Each
+entry is displayed with all of its sub-entries.  Optional COUNT-ONLY non-nil
+means don't retrieve and don't display matching entries.  Optional NO-DISPLAY
+non-nil means retrieve entries but don't display.
+
+Nil value of MAX-MATCHES means find all matches, t value means find all matches
+but omit file headers, negative values mean find up to the inverse of that
+number of entries and omit file headers.
+
+Returns number of entries matched.  See also documentation for the variable
+rolo-file-list."
+  (interactive "sRolodex string to match: \nP")
+  (let ((total-matches (rolo-grep (regexp-quote string) max-matches
+				  rolo-file count-only no-display)))
+    (if (interactive-p)
+	(message "%s matching entr%s found in rolodex."
+		 (if (= total-matches 0) "No" total-matches)
+		 (if (= total-matches 1) "y" "ies")))
+    total-matches))
+
+;;;###autoload
+(defun rolo-grep (regexp &optional max-matches rolo-bufs count-only no-display)
+  "Display rolodex entries matching REGEXP.
+To a maximum of prefix arg MAX-MATCHES, in buffer(s) from optional ROLO-BUFS or
+rolo-file-list.  Default is to find all matching entries.  Each entry is
+displayed with all of its sub-entries.  Optional COUNT-ONLY non-nil means don't
+retrieve and don't display matching entries.  Optional NO-DISPLAY non-nil
+means retrieve entries but don't display.
+
+Nil value of MAX-MATCHES means find all matches, t value means find all matches
+but omit file headers, negative values mean find up to the inverse of that
+number of entries and omit file headers.
+
+Returns number of entries matched.  See also documentation for the variable
+rolo-file-list."
+  (interactive "sRolodex regular expression to match: \nP")
+  (let ((rolo-file-list
+	 (cond ((null rolo-bufs) rolo-file-list)
+	       ((listp rolo-bufs) rolo-bufs)
+	       ((list rolo-bufs))))
+	(display-buf (if count-only
+			 nil
+		       (set-buffer (get-buffer-create rolo-display-buffer))))
+	(total-matches 0)
+	(num-matched 0)
+	(inserting (or (eq max-matches t)
+		       (and (integerp max-matches) (< max-matches 0))))
+	(file))
+    (if count-only nil
+      (setq buffer-read-only nil)
+      (or inserting (erase-buffer)))
+    (while (and (setq file (car rolo-file-list))
+		(or (not (integerp max-matches))
+		    (< total-matches (max max-matches (- max-matches)))))
+      (setq rolo-file-list (cdr rolo-file-list)
+	    num-matched (rolo-grep-file file regexp max-matches count-only)
+	    total-matches (+ total-matches num-matched))
+      (if (integerp max-matches)
+	  (setq max-matches
+		(if (>= max-matches 0)
+		    (- max-matches num-matched)
+		  (+ max-matches num-matched)))))
+    (if (or count-only no-display inserting (= total-matches 0))
+	nil
+      (rolo-display-matches display-buf))
+    (if (interactive-p)
+	(message "%s matching entr%s found in rolodex."
+		 (if (= total-matches 0) "No" total-matches)
+		 (if (= total-matches 1) "y" "ies")
+		 ))
+    total-matches))
+
+(defun rolo-isearch ()
+  "Interactively search forward for next occurrence of current match regexp.
+Use this to add characters to further narrow the search."
+  (interactive)
+  (if (equal (buffer-name) rolo-display-buffer)
+      (execute-kbd-macro (concat "\e\C-s" rolo-match-regexp))
+    (error "(rolo-isearch): Use this command in the %s match buffer"
+	   rolo-display-buffer)))
+
+;;;###autoload
+(defun rolo-kill (name &optional file)
+  "Kills a rolodex entry given by NAME within 'rolo-file-list'.
+With prefix argument, prompts for optional FILE to locate entry within.
+NAME may be of the form: parent/child to kill child below a parent entry
+which begins with the parent string.
+Returns t if entry is killed, nil otherwise."
+  (interactive "sName to kill in rolo: \nP")
+  (if (or (not (stringp name)) (string= name ""))
+      (error "(rolo-kill): Invalid name: '%s'" name))
+  (if (and (interactive-p) current-prefix-arg)
+      (setq file (completing-read "Entry's File: "
+				  (mapcar 'list rolo-file-list))))
+  (let ((file-list (if file (list file) rolo-file-list))
+	(killed))
+    (or file (setq file (car file-list)))
+    (if (rolo-to name file-list)
+	(progn
+	  (setq file buffer-file-name)
+	  (if (file-writable-p file)
+	      (let ((kill-op
+		     (function (lambda (start level)
+				 (kill-region
+				  start (rolo-to-entry-end t level))
+				 (setq killed t)
+				 (rolo-save-buffer)
+				 (rolo-kill-buffer))))
+		    start end level)
+		(setq buffer-read-only nil)
+		(re-search-backward rolo-entry-regexp nil t)
+		(setq end (match-end 0))
+		(beginning-of-line)
+		(setq start (point)
+		      level (buffer-substring start end))
+		(goto-char end)
+		(skip-chars-forward " \t")
+		(if (interactive-p)
+		    (let ((entry-line (buffer-substring
+				       (point)
+				       (min (+ (point) 60)
+					    (progn (end-of-line) (point))))))
+		      (if (y-or-n-p (format "Kill `%s...' " entry-line))
+			  (progn
+			    (funcall kill-op start level)
+			    (message "Killed"))
+			(message "Aborted")))
+		  (funcall kill-op start level)))
+	    (message
+	     "(rolo-kill): Entry found but file not writable: '%s'" file)
+	    (beep)))
+      (message "(rolo-kill): '%s' not found." name)
+      (beep))
+    killed))
+
+(defun rolo-mail-to ()
+  "Start composing mail addressed to the first e-mail address at or after point."
+  (interactive)
+  (let ((opoint (point)) button)
+    (skip-chars-backward "^ \t\n\r<>")
+    (if (and (re-search-forward mail-address-regexp nil t)
+	     (goto-char (match-beginning 1))
+	     (setq button (ibut:at-p)))
+	(hui:hbut-act button)
+      (goto-char opoint)
+      (beep)
+      (message "(rolo-mail-to): Invalid buffer or no e-mail address found"))))
+
+(defun rolo-next-match ()
+  "Move point forward to the start of the next rolodex search match."
+  (interactive)
+  (if (not (stringp rolo-match-regexp))
+      (error "(rolo-next-match): Invoke a rolodex search expression first"))
+  (let ((start (point))
+	(case-fold-search t))
+    (if (looking-at rolo-match-regexp)
+	(goto-char (match-end 0)))
+    (if (re-search-forward rolo-match-regexp nil t)
+	(goto-char (match-beginning 0))
+      (goto-char start)
+      (error
+       "(rolo-next-match): No following matches for \"%s\"" rolo-match-regexp))))
+
+(defun rolo-previous-match ()
+  "Move point back to the start of the previous rolodex search match."
+  (interactive)
+  (if (not (stringp rolo-match-regexp))
+      (error "(rolo-previous-match): Invoke a rolodex search expression first"))
+  (let ((case-fold-search t))
+    (if (re-search-backward rolo-match-regexp nil t)
+	nil
+      (error
+       "(rolo-previous-match): No prior matches for \"%s\"" rolo-match-regexp))))
+
+(defun rolo-quit ()
+  "Quit from the rolodex match buffer and restore the prior frame display."
+  (interactive)
+  (bury-buffer)
+  (if (and *rolo-wconfig*
+	   (if (fboundp 'window-configuration-p)
+	       (window-configuration-p *rolo-wconfig*)
+	     t))
+      (set-window-configuration *rolo-wconfig*)))
+
+;;;###autoload
+(defun rolo-sort (&optional rolo-file)
+  "Sorts up to 14 levels of entries in ROLO-FILE (default is personal rolo).
+Assumes entries are delimited by one or more '*'characters.
+Returns list of number of groupings at each entry level." 
+  (interactive
+   (list (let ((default "")
+	       (file))
+	 (setq file
+	       (completing-read
+		(format "Sort rolo file (default %s): "
+			(file-name-nondirectory
+			 (setq default
+			       (if (and buffer-file-name
+					(memq
+					 t (mapcar
+					    (function
+					     (lambda (file)
+					       (equal buffer-file-name
+						      (expand-file-name file))))
+					    rolo-file-list)))
+				   buffer-file-name
+				 (car rolo-file-list)))))
+		(mapcar 'list rolo-file-list)))
+	 (if (string= file "") default file))))
+  (if (or (not rolo-file) (equal rolo-file ""))
+      (setq rolo-file (car rolo-file-list)))
+  (if (not (and (stringp rolo-file) (file-readable-p rolo-file)))
+      (error "(rolo-sort): Invalid or unreadable file: %s" rolo-file))
+  (let ((level-regexp (regexp-quote "**************"))
+	(entries-per-level-list)
+	(n))
+    (while (not (equal level-regexp ""))
+      (setq n (rolo-sort-level rolo-file level-regexp))
+      (if (or (/= n 0) entries-per-level-list)
+	  (setq entries-per-level-list
+		(append (list n) entries-per-level-list)))
+      (setq level-regexp (substring level-regexp 0 (- (length level-regexp) 2))))
+    entries-per-level-list))
+
+(defun rolo-sort-level (rolo-file level-regexp &optional max-groupings)
+  "Sorts groupings of entries in ROLO-FILE at hierarchy level LEVEL-REGEXP.
+To a maximum of optional MAX-GROUPINGS.  Nil value of MAX-GROUPINGS means all
+groupings at the given level.  LEVEL-REGEXP should simply match the text of
+any rolodex entry of the given level, not the beginning of a line (^); an
+example, might be (regexp-quote \"**\") to match level two.  Returns number
+of groupings sorted."
+  (interactive "sRolodex file to sort: \nRegexp for level's entries: \nP")
+  (rolo-map-level
+   (function (lambda (start end) (sort-lines nil start end)))
+   rolo-file
+   level-regexp
+   max-groupings))
+
+(defun rolo-toggle-narrow-to-entry ()
+  "Toggle between display of current entry and display of all matched entries.
+Useful when bound to a mouse key."
+  (interactive)
+  (if (rolo-narrowed-p)
+      (widen)
+    (if (or (looking-at rolo-entry-regexp)
+	    (re-search-backward rolo-entry-regexp nil t))
+	(progn (forward-char)
+	       (narrow-to-region (1- (point)) (rolo-display-to-entry-end)))))
+  (rolo-shrink-window)
+  (goto-char (point-min)))
+
+(defun rolo-word (string
+		  &optional max-matches rolo-file count-only no-display)
+  "Display rolodex entries with whole word matches for STRING.
+To a maximum of optional prefix arg MAX-MATCHES, in file(s) from optional
+ROLO-FILE or rolo-file-list.  Default is to find all matching entries.  Each
+entry is displayed with all of its sub-entries.  Optional COUNT-ONLY non-nil
+means don't retrieve and don't display matching entries.  Optional NO-DISPLAY
+non-nil means retrieve entries but don't display.
+
+Nil value of MAX-MATCHES means find all matches, t value means find all matches
+but omit file headers, negative values mean find up to the inverse of that
+number of entries and omit file headers.
+
+Returns number of entries matched.  See also documentation for the variable
+rolo-file-list."
+  (interactive "sRolodex whole words to match: \nP")
+  (let ((total-matches (rolo-grep (format "\\b%s\\b" (regexp-quote string))
+				  max-matches
+				  rolo-file count-only no-display)))
+    (if (interactive-p)
+	(message "%s matching entr%s found in rolodex."
+		 (if (= total-matches 0) "No" total-matches)
+		 (if (= total-matches 1) "y" "ies")))
+    total-matches))
+
+;;;###autoload
+(defun rolo-yank (name &optional regexp-p)
+  "Inserts at point the first rolodex entry matching NAME.
+With optional prefix arg, REGEXP-P, treats NAME as a regular expression instead
+of a string."
+  (interactive "sName to insert record for: \nP")
+  (let ((rolo-display-buffer (current-buffer))
+	(start (point))
+	found)
+    (save-excursion
+      (setq found (if regexp-p
+		      (rolo-grep name -1)
+		    (rolo-grep (regexp-quote name) -1))))
+    ;; Let user reformat the region just yanked.
+    (if (and (= found 1) (fboundp wrolo-yank-reformat-function))
+	(funcall wrolo-yank-reformat-function start (point)))
+    found))
+
+;;; ************************************************************************
+;;; Public functions
+;;; ************************************************************************
+
+(defun rolo-fgrep-file (rolo-buf string &optional max-matches count-only)
+  "Retrieve entries in ROLO-BUF matching STRING to a maximum of optional MAX-MATCHES.
+Nil value of MAX-MATCHES means find all matches, t value means find all matches
+but omit file headers, negative values mean find up to the inverse of that
+number of entries and omit file headers.  Optional COUNT-ONLY non-nil
+means don't retrieve matching entries.
+Returns number of matching entries found."
+  (rolo-grep-file rolo-buf (regexp-quote string) max-matches count-only))
+
+(defun rolo-grep-file (rolo-buf regexp &optional max-matches count-only)
+  "Retrieve entries in ROLO-BUF matching REGEXP to a maximum of optional MAX-MATCHES.
+Nil value of MAX-MATCHES means find all matches, t value means find all matches
+but omit file headers, negative values mean find up to the inverse of that
+number of entries and omit file headers.  Optional COUNT-ONLY non-nil
+means don't retrieve matching entries.
+Returns number of matching entries found."
+  ;;
+  ;; Save regexp as last rolodex search expression.
+  (setq rolo-match-regexp regexp)
+  ;;
+  (let ((new-buf-p) (actual-buf))
+    (if (and (or (null max-matches) (eq max-matches t) (integerp max-matches))
+	     (or (setq actual-buf (rolo-buffer-exists-p rolo-buf))
+		 (if (file-exists-p rolo-buf)
+		     (setq actual-buf (find-file-noselect rolo-buf t)
+			   new-buf-p t))))
+	(let ((hdr-pos) (num-found 0) (curr-entry-level)
+	      (incl-hdr t))
+	  (if max-matches
+	      (cond ((eq max-matches t)
+		     (setq incl-hdr nil max-matches nil))
+		    ((< max-matches 0)
+		     (setq incl-hdr nil
+			   max-matches (- max-matches)))))
+	  (set-buffer actual-buf)
+	  (if new-buf-p (setq buffer-read-only t))
+	  (save-excursion
+	    (save-restriction
+	      (widen)
+	      (goto-char 1)
+	      ;; Ensure no entries in outline mode are hidden.
+	      ;; Uses 'show-all' function from outline.el.
+	      (and (search-forward "\C-m" nil t)
+		   (show-all))
+	      (if (re-search-forward rolo-hdr-regexp nil t 2)
+		  (progn (forward-line)
+			 (setq hdr-pos (cons (point-min) (point)))))
+	      (re-search-forward rolo-entry-regexp nil t)
+	      (while (and (or (null max-matches) (< num-found max-matches))
+			  (re-search-forward regexp nil t))
+		(re-search-backward rolo-entry-regexp nil t)
+		(let ((start (point))
+		      (next-entry-exists))
+		  (re-search-forward rolo-entry-regexp nil t)
+		  (setq curr-entry-level (buffer-substring start (point)))
+		  (rolo-to-entry-end t curr-entry-level)
+		  (or count-only
+		      (if (and (= num-found 0) incl-hdr)
+			  (let* ((src (or (buffer-file-name actual-buf)
+					  actual-buf))
+				 (src-line
+				   (format
+				     (concat (if (boundp 'hbut:source-prefix)
+						 hbut:source-prefix
+					       "@loc> ")
+					     "%s")
+				     (prin1-to-string src))))
+			    (set-buffer rolo-display-buffer)
+			    (goto-char (point-max))
+			    (if hdr-pos
+				(progn
+				  (insert-buffer-substring
+				    actual-buf (car hdr-pos) (cdr hdr-pos))
+				  (insert src-line "\n\n"))
+			      (insert (format rolo-hdr-format src-line)))
+			    (set-buffer actual-buf))))
+		  (setq num-found (1+ num-found))
+		  (or count-only
+		      (rolo-add-match rolo-display-buffer regexp start (point)))))))
+	  (rolo-kill-buffer actual-buf)
+	  num-found)
+      0)))
+
+(defun rolo-map-level (func rolo-buf level-regexp &optional max-groupings)
+  "Perform FUNC on groupings of ROLO-BUF entries at level LEVEL-REGEXP,
+to a maximum of optional argument MAX-GROUPINGS.  Nil value of MAX-GROUPINGS
+means all groupings at the given level.  FUNC should take two arguments, the
+start and the end of the region that it should manipulate.  LEVEL-REGEXP
+should simply match the text of any rolodex entry of the given level, not the
+beginning of a line (^); an example, might be (regexp-quote \"**\") to match
+level two.  Returns number of groupings matched."
+  (let ((actual-buf))
+    (if (and (or (null max-groupings) (< 0 max-groupings))
+	     (or (setq actual-buf (rolo-buffer-exists-p rolo-buf))
+		 (if (file-exists-p rolo-buf)
+		     (progn (setq actual-buf (find-file-noselect rolo-buf t))
+			    t))))
+	(progn
+	  (set-buffer actual-buf)
+	  (let ((num-found 0)
+		(exact-level-regexp (concat "^\\(" level-regexp "\\)[ \t\n]"))
+		(outline-regexp rolo-entry-regexp)
+		(buffer-read-only)
+		(level-len))
+	    ;; Load 'outline' library since its functions are used here.
+	    (if (not (boundp 'outline-mode-map))
+		(load-library "outline"))
+	    (goto-char (point-min))
+	    ;; Pass buffer header if it exists
+	    (if (re-search-forward rolo-hdr-regexp nil t 2)
+		(forward-line))
+	    (while (and (or (null max-groupings) (< num-found max-groupings))
+			(re-search-forward exact-level-regexp nil t))
+	      (setq num-found (1+ num-found))
+	      (let* ((opoint (prog1 (point) (beginning-of-line)))
+		     (grouping-start (point))
+		     (start grouping-start)
+		     (level-len (or level-len (- (1- opoint) start)))
+		     (next-level-len)
+		     (next-entry-exists)
+		     (grouping-end)
+		     (no-subtree))
+		(while (and (progn
+			      (if (setq next-entry-exists
+					(re-search-forward
+					 rolo-entry-regexp nil t 2))
+				  (setq next-level-len
+					(- (point)
+					   (progn (beginning-of-line)
+						  (point)))
+					grouping-end
+					(< next-level-len level-len)
+					no-subtree
+					(<= next-level-len level-len))
+				(setq grouping-end t no-subtree t)
+				(goto-char (point-max)))
+			      (let ((end (point)))
+				(goto-char start)
+				(hide-subtree) ; And hide multiple entry lines
+				;; Move to start of next entry at equal
+				;; or higher level.
+				(setq start
+				      (if no-subtree
+					  end
+					(if (re-search-forward
+					     rolo-entry-regexp nil t)
+					    (progn (beginning-of-line) (point))
+					  (point-max))))
+				;; Remember last expression in 'progn'
+				;; must always return non-nil.
+				(goto-char start)))
+			    (not grouping-end)))
+		(let ((end (point)))
+		  (goto-char grouping-start)
+		  (funcall func grouping-start end)
+		  (goto-char end))))
+	    (show-all)
+	    (rolo-kill-buffer actual-buf)
+	    num-found))
+      0)))
+
+;;; ************************************************************************
+;;; Private functions
+;;; ************************************************************************
+
+(defun rolo-add-match (rolo-matches-buffer regexp start end)
+  "Insert before point in ROLO-MATCHES-BUFFER an entry matching REGEXP from the current region between START to END."
+  (let ((rolo-buf (current-buffer))
+	opoint)
+    (set-buffer (get-buffer-create rolo-matches-buffer))
+    (setq opoint (point))
+    (insert-buffer-substring rolo-buf start end)
+    (rolo-highlight-matches regexp opoint (point))
+    (set-buffer rolo-buf)))
+
+(defun rolo-buffer-exists-p (rolo-buf)
+  "Returns buffer given by ROLO-BUF or nil.
+ROLO-BUF may be a file-name, buffer-name, or buffer."
+  (car (memq (get-buffer (or (and (stringp rolo-buf)
+				  (get-file-buffer rolo-buf))
+			     rolo-buf))
+	     (buffer-list))))
+
+(defun rolo-display-to-entry-end ()
+  "Go to end of current entry, ignoring sub-entries."
+  (if (re-search-forward (concat rolo-hdr-regexp "\\|"
+				 rolo-entry-regexp) nil t)
+      (progn (beginning-of-line) (point))
+    (goto-char (point-max))))
+
+	  
+(defun rolo-format-name (name-str first last)
+  "Reverse order of NAME-STR field given my regexp match field FIRST and LAST."
+  (if (match-beginning last)
+      (concat (substring name-str (match-beginning last) (match-end last))
+	      ", "
+	      (substring name-str (match-beginning first) (match-end first)))))
+
+(defun rolo-highlight-matches (regexp start end)
+  "Highlight matches for REGEXP in region from START to END."
+  (if (fboundp 'hproperty:but-add)
+      (let ((hproperty:but-emphasize-p))
+	(save-excursion
+	  (goto-char start)
+	  (while (re-search-forward regexp nil t)
+	    (hproperty:but-add (match-beginning 0) (match-end 0)
+			       (or rolo-highlight-face
+				   hproperty:highlight-face)))))))
+
+(defun rolo-kill-buffer (&optional rolo-buf)
+  "Kills optional ROLO-BUF if unchanged and 'rolo-kill-buffers-after-use' is t.
+Default is current buffer."
+  (or rolo-buf (setq rolo-buf (current-buffer)))
+  (and rolo-kill-buffers-after-use (not (buffer-modified-p rolo-buf))
+       (kill-buffer rolo-buf)))
+
+(defun rolo-name-and-email ()
+  "If point is in a mail message, returns list of (name email-addr) of sender.
+Name is returned as 'last, first-and-middle'."
+  (let ((email) (name) (from))
+    (save-window-excursion
+      (if (or (hmail:lister-p) (hnews:lister-p))
+	  (other-window 1))
+      (save-excursion
+	(save-restriction
+	  (goto-char (point-min))
+	  (if (search-forward "\n\n" nil t)
+	      (narrow-to-region (point-min) (point)))
+	  (setq email (mail-fetch-field "reply-to")
+		from  (mail-fetch-field "from")))))
+    (if from
+	(cond
+	 ;; Match: email, email (name), email "name"
+	 ((string-match
+	   (concat "^\\([^\"<>() \t\n]+\\)"
+		   "\\([ \t]*[(\"][ \t]*\\([^\"()]+\\)[ \t]+"
+		   "\\([^\" \t()]+\\)[ \t]*[)\"]\\)?[ \t]*$")
+	   from)
+	  (setq name (rolo-format-name from 3 4))
+	  (or email (setq email (substring from (match-beginning 1)
+					   (match-end 1)))))
+	 ;; Match: <email>, name <email>, "name" <email>
+	 ((string-match
+	   (concat "^\\(\"?\\([^\"<>()\n]+\\)[ \t]+"
+		   "\\([^\" \t()<>]+\\)\"?[ \t]+\\)?"
+		   "<\\([^\"<>() \t\n]+\\)>[ \t]*$")
+	   from)
+	  (setq name (rolo-format-name from 2 3))
+	  (or email (setq email (substring from (match-beginning 4)
+					   (match-end 4)))))))
+    (if (or name email)
+	(list name email))))
+
+(defun rolo-name-at ()
+  "If point is within an entry in 'rolo-display-buffer', returns entry, else nil."
+  (if (string-equal (buffer-name) rolo-display-buffer)
+      (save-excursion
+	(if (or (looking-at rolo-entry-regexp)
+		(progn (end-of-line)
+		       (re-search-backward rolo-entry-regexp nil t)))
+	    (progn (goto-char (match-end 0))
+		   (skip-chars-forward " \t")
+		   (if (or (looking-at "[^ \t\n\^M]+ ?, ?[^ \t\n\^M]+")
+			   (looking-at "\\( ?[^ \t\n\^M]+\\)+"))
+		       (buffer-substring (match-beginning 0)
+					 (match-end 0))))))))
+
+(defun rolo-narrowed-p ()
+  (or (/= (point-min) 1) (/= (1+ (buffer-size)) (point-max))))
+
+(defun rolo-save-buffer (&optional rolo-buf)
+  "Saves optional ROLO-BUF if changed and 'rolo-save-buffers-after-use' is t.
+Default is current buffer.  Used, for example, after a rolo entry is killed."
+  (or rolo-buf (setq rolo-buf (current-buffer)))
+  (and rolo-save-buffers-after-use (buffer-modified-p rolo-buf)
+       (set-buffer rolo-buf) (save-buffer)))
+
+(defun rolo-shrink-window ()
+  (let* ((lines (count-lines (point-min) (point-max)))
+	 (height (window-height))
+	 (window-min-height 2)
+	 (desired-shrinkage (1- (min (- height lines)))))
+    (and (>= lines 0)
+	 (/= desired-shrinkage 0)
+	 (> (frame-height) (1+ height))
+	 (shrink-window 
+	   (if (< desired-shrinkage 0)
+	       (max desired-shrinkage (- height (/ (frame-height) 2)))
+  (min desired-shrinkage (- height window-min-height)))))))
+
+(defun rolo-to (name &optional file-list)
+  "Moves point to entry for NAME within optional FILE-LIST.
+'rolo-file-list' is used as default when FILE-LIST is nil.
+Leaves point immediately after match for NAME within entry.
+Switches internal current buffer but does not alter the frame.
+Returns point where matching entry begins or nil if not found."
+  (or file-list (setq file-list rolo-file-list))
+  (let ((found) file)
+    (while (and (not found) file-list)
+      (setq file (car file-list)
+	    file-list (cdr file-list))
+      (cond ((and file (or (not (stringp file)) (string= file "")))
+	     (error "(rolo-to): Invalid file: '%s'" file))
+	    ((and (file-exists-p file) (not (file-readable-p file)))
+	     (error "(rolo-to): File not readable: '%s'" file)))
+      (set-buffer (or (get-file-buffer file) (find-file-noselect file)))
+      (let ((case-fold-search t) (real-name name) (parent "") (level) end)
+	(widen) (goto-char 1)
+	(while (setq end (string-match "/" name))
+	  (setq level nil
+		parent (substring name 0 end)
+		name (substring name (min (1+ end) (length name))))
+	  (cond ((progn
+		   (while (and (not level) (search-forward parent nil t))
+		     (save-excursion
+		       (beginning-of-line)
+		       (if (looking-at
+			    (concat "\\(" rolo-entry-regexp "\\)[ \t]*" 
+				    (regexp-quote parent)))
+			   (setq level (buffer-substring (match-beginning 1)
+							 (match-end 1))))))
+		   level))
+		((equal name real-name));; Try next file.
+		(t;; Found parent but not child
+		 (setq buffer-read-only nil)
+		 (rolo-to-buffer (current-buffer))
+		 (error "(rolo-to): '%s' part of name not found in \"%s\"."
+			parent file)))
+	  (if level
+	      (narrow-to-region (point)
+				(save-excursion
+				  (rolo-to-entry-end t level) (point)))))
+	(goto-char (point-min))
+	(while (and (search-forward name nil t)
+		    (not (save-excursion
+			   (beginning-of-line)
+			   (setq found
+				 (if (looking-at
+				      (concat "\\(" rolo-entry-regexp
+					      "\\)[ \t]*"
+					      (regexp-quote name)))
+				     (point))))))))
+      (or found (rolo-kill-buffer))) ;; conditionally kill
+    (widen)
+    found))
+
+(defun rolo-to-buffer (buffer &optional other-window-flag frame)
+  "Pop to BUFFER."
+  (cond (hyperb:lemacs-p
+	  (pop-to-buffer buffer other-window-flag
+			 ;; default is to use selected frame
+			 (or frame (selected-frame))))
+	(t (pop-to-buffer buffer other-window-flag))))
+
+(defun rolo-to-entry-end (&optional include-sub-entries curr-entry-level)
+"Goes to end of whole entry if optional INCLUDE-SUB-ENTRIES is non-nil.
+CURR-ENTRY-LEVEL is a string whose length is the same as the last found entry
+header.  If INCLUDE-SUB-ENTRIES is nil, CURR-ENTRY-LEVEL is not needed.
+Returns current point."
+  (while (and (setq next-entry-exists
+		    (re-search-forward rolo-entry-regexp nil t))
+	      include-sub-entries
+	      (> (- (point) (save-excursion
+			      (beginning-of-line)
+			      (point)))
+		 (length curr-entry-level))))
+  (if next-entry-exists
+      (progn (beginning-of-line) (point))
+    (goto-char (point-max))))
+
+(defun wrolo-mode ()
+  "Major mode for the rolodex match buffer.
+Calls the functions given by `wrolo-mode-hook'.
+\\{wrolo-mode-map}"
+  (interactive)
+  (setq major-mode 'wrolo-mode
+	mode-name "Rolodex")
+  (use-local-map wrolo-mode-map)
+  ;;
+  ;; Loads menus under non-tty InfoDock, XEmacs or Emacs19; does nothing
+  ;; otherwise.
+  (and (not (featurep 'wrolo-menu)) hyperb:window-system
+       (or hyperb:lemacs-p hyperb:emacs19-p) (require 'wrolo-menu))
+  ;;
+  (if (not (fboundp 'outline-minor-mode))
+      nil
+    (outline-minor-mode 1))
+  (run-hooks 'wrolo-mode-hook))
+
+;;; ************************************************************************
+;;; Private variables
+;;; ************************************************************************
+
+(defvar rolo-display-buffer "*Rolodex*"
+  "Buffer used to display set of last matching rolodex entries.")
+
+(defvar rolo-entry-regexp "^\\*+"
+  "Regular expression to match the beginning of a rolodex entry.
+This pattern must match the beginning of the line.  Entries may be nested
+through the use of increasingly longer beginning patterns.")
+
+(defconst rolo-hdr-format
+  (concat
+   "======================================================================\n"
+   "%s\n"
+   "======================================================================\n")
+  "Header to insert preceding a file's first rolodex entry match when
+file has none of its own.  Used with one argument, the file name."
+)
+
+(defconst rolo-hdr-regexp "^==="
+  "Regular expression to match the first and last lines of rolodex file headers.
+This header is inserted into rolo-display-buffer before any entries from the
+file are added.")
+
+(defconst rolo-match-regexp nil
+  "Last regular expression used to search the rolodex.
+Nil before a search is done.
+String search expressions are converted to regular expressions.")
+
+(defvar *rolo-wconfig* nil
+  "Saves frame's window configuration prior to a rolodex search.")
+
+(defvar wrolo-mode-map nil
+  "Keymap for the rolodex match buffer.")
+
+(if wrolo-mode-map
+    nil
+  (setq wrolo-mode-map (make-keymap))
+  (if (fboundp 'set-keymap-name)
+      (set-keymap-name wrolo-mode-map 'wrolo-mode-map))
+  (suppress-keymap wrolo-mode-map)
+  (define-key wrolo-mode-map "<"        'beginning-of-buffer)
+  (define-key wrolo-mode-map ">"        'end-of-buffer)
+  (define-key wrolo-mode-map "."        'beginning-of-buffer)
+  (define-key wrolo-mode-map ","        'end-of-buffer)
+  (define-key wrolo-mode-map "?"        'describe-mode)
+  (define-key wrolo-mode-map "\177"     'scroll-down)
+  (define-key wrolo-mode-map " "        'scroll-up)
+  (define-key wrolo-mode-map "a"        'show-all)
+  (define-key wrolo-mode-map "b"        'outline-backward-same-level)
+  (define-key wrolo-mode-map "e"        'rolo-edit-entry)
+  (define-key wrolo-mode-map "f"        'outline-forward-same-level)
+  (define-key wrolo-mode-map "h"        'hide-subtree)
+  (define-key wrolo-mode-map "m"        'rolo-mail-to)
+  (define-key wrolo-mode-map "n"        'outline-next-visible-heading)
+  (define-key wrolo-mode-map "p"        'outline-previous-visible-heading)
+  (define-key wrolo-mode-map "q"        'rolo-quit)
+  (define-key wrolo-mode-map "r"        'rolo-previous-match)
+  (define-key wrolo-mode-map "s"        'show-subtree)
+  (define-key wrolo-mode-map "\M-s"     'rolo-isearch)
+  (define-key wrolo-mode-map "t"        'hide-body)
+  (define-key wrolo-mode-map "\C-i"     'rolo-next-match)      ;; {TAB}
+  (define-key wrolo-mode-map "\M-\C-i"  'rolo-previous-match)  ;; {M-TAB}
+  (define-key wrolo-mode-map "u"        'outline-up-heading)
+  )
+
+(provide 'wrolo)