davis@scr.slb.com (Paul Davis) (04/26/89)
Next batch: Code to do provide `popup help' from a file, and a simple, multi-key database command. Enclosed are: generic-help.el (elisp code) db-mode.texinfo (texinfo `manual' for db) some of ~/db (a sample `database' file) You may notice an overlap between the idea of this and the rolo-mode I recently posted. Its not an accident - I prefer rolo for simple reference card stuff, but the generic help function provides a way into some very useful facilites (RMS: this is the basis of the help-on-thing system I mentioned quite a while back). I began developing this for C, and LaTeX, but gave up. If anyone wants the bits I have ... I have NO doubt that someone can clean up my code A LOT .... enjoy: Paul Paul Davis at Schlumberger Cambridge Research <davis%scr.slb.com@relay.cs.net> "to shatter tradition makes us feel free ..." =====> generic-help.el ;; GNU Emacs help/database facility ;; Paul Davis <davis%scr.slb.com@relay.cs.net>, May & July 1988 ;; this file is subject to the conditions of the GNU Emacs general ;; public license. ;; Not an interface to dbm, and not much use with BIG databases, but ;; for on-line (non-Emacs) help, and quick database queries, this e-lisp code seems quite ;; adequate. Again, brought about due to an inferiority complex over ;; Unipress, although this does regexp's, and will handle any number ;; of keys. It does lose a bit in speed however, since Emacs has to ;; read each database visited into a buffer first (!!). (require 'elib) ;;---------------------------------------------------------- ;; `global variables' - use set-default to set your default ;; help path ;; ;; eg; (set-default 'help-default-path "/a /b/c /d/e/f") ;; ;;----------------------------------------------------------- (defvar help-last-file nil "File last visited by \[help]") (defvar help-last-start 0 "Start of line where last match occured in this buffer") (defvar help-last-regexp nil "previous regexp passed to help in this buffer") (defvar help-default-path nil "*default file used by help") (defvar help-regexp-prefix "^\\(\\S +.*" "*regexp used by help to filter key lines from a help file. This should always incude an open alternative specifier \\(") (defvar help-keyline-identifier-regexp "^\\S " "*Regexp used for locating a key line in a help file") (defvar help-buffer-to-add-to nil "Holds name of file from which help-add-item is called") (defvar help-case-fold-search nil "*If non-nil, help/db commands ignore case when searching") (defvar help-modeline-prefix-string " GNU-DBASE Database:[%b]%3 current target: " "Used for modeline format in help-mode, suffixed by help-last-regexp") ;; -------------------------------------------------------------------- ;; local variables - this is so that each buffer (and each mode ?) can ;; have their own search path and their own previous items searched for. ;; We don't really want to have to make require-final-newline global ;; so its recast here as a local variable. We need it on when adding ;; new entries to the database files. ;; -------------------------------------------------------------------- (make-local-variable 'help-last-regexp) (make-local-variable 'help-last-start) (make-local-variable 'help-default-path) (make-local-variable 'require-final-newline) ;; ------------------------------------------------ ;; help - the function ;; ------------------------------------------------ (defun help (key path &optional repeat reverse) "Display text on KEY from a file in PATH. KEY is a regexp combined with help-regexp-prefix to filter out key lines from text. Optional third argument non-nil specifies repeated search for KEY from current position in PATH. Optional fourth argument if non-nil specifies reverse direction for search. The format of FILE is simple: keys are on a line which begins with a non-space character, text is on lines beginning with a whitespace character. An entry runs from the start of its key line to the start of the next key line. The file *must* have newlines both *before* the first and *after* the last entry, if these are not to be lost. This function is really designed for building other, more specific help and db functions , and not for calling via M-x." (interactive "sKey (regexp): \nsFile (search path): ") (setq path (expand-path-names (resolve-string path))) (if repeat (setq path (pop-this-file path help-last-file))) ;; rotate current file to front of path (cond ((null path) ;; empty search path (message "no further matching entries for \"%s\"" key) ;; tell us with words (ding)) ;; tell us with sound ((setq entry (help-entry key (car path) repeat reverse)) ;; search for it (show-help entry key (car path))) ;; found it - now display it (t (help key (cadr path))))) ;; otherwise, recurse down the search path ;; ------------------------------------------------------- ;; second order functions (not to be called interactively) ;; ------------------------------------------------------- (defun show-help (entry key file) "Do some formatting of a help file entry." (if (string-match "\n" entry) (progn (setq key-list (substring entry 0 (match-beginning 0))) (setq entry (substring entry (match-end 0)))) (setq key-list nil)) (if (and key-list (string-match "{" key-list)) (progn (setq primary-key (substring key-list 0 (match-beginning 0))) (setq key-list (substring key-list (match-end 0) (1- (length key-list))))) (setq primary-key (eval key-list)) (setq key-list nil)) (with-output-to-temp-buffer "*DBASE*" (pop-to-buffer "*DBASE*") (help-mode) (insert " " primary-key "\n\n") (insert entry) (if key-list (insert "\n\n Cross references: " key-list)) (setq mode-line-format (list (concat "Emacs-DBASE database:[" (file-name-nondirectory file) "] current target: " key))))) (defun help-entry (help-regexp file repeat reverse) "Return a strng containing an entry with a key matching HELP-REGEXP found in FILE. Third and fourth arguments are as for \[help]. Returns NIL if no match is found." (save-window-excursion (pop-to-buffer (find-file-noselect file)) (help-locate-search-start repeat reverse) (if help-case-fold-search (setq case-fold-search t)) (if (help-search) (progn (beginning-of-line) (setq help-last-file (buffer-file-name)) (setq help-last-regexp help-regexp) (setq help-last-start (point)) (buffer-to-next-key)) nil))) (defun help-search () "Encapsulation of search commands for help-mode" (if reverse (re-search-backward (concat help-regexp-prefix help-regexp "\\|" help-regexp "\\)") (point-min) t) (re-search-forward (concat help-regexp-prefix help-regexp "\\|" help-regexp "\\)") (point-max) t))) (defun help-locate-search-start (repeat reverse) "Set up position of point for search" (widen) (if repeat (progn (goto-char help-last-start) (if reverse (beginning-of-line 0) (beginning-of-line 2))) (if reverse (goto-char (point-max)) (goto-char (point-min))))) (defun buffer-to-next-key () "Return current buffer from point to the start of the next key line or EOB if there are no futher key lines in the buffer" (setq start (save-excursion (beginning-of-line) (point))) (save-excursion (beginning-of-line 2) (if (null (re-search-forward help-keyline-identifier-regexp (point-max) t)) (goto-char (point-max))) (beginning-of-line 1) (buffer-substring start (point)))) ;; --------------------------- ;; interactive "key" commands ;; --------------------------- (defun db (key) "Master help function" (interactive "sKey (regexp): ") (help key help-default-path)) (defun help-next-match () "Display the next entry in the current help file having a key matching the current target" (interactive) (help help-last-regexp help-default-path 1 nil)) (defun help-previous-match () "Display the previous entry in the current help file having a key matching the current target" (interactive) (help help-last-regexp help-default-path 1 t)) (defun help-this-file (item) (interactive "sItem (regexp): ") (help item (buffer-file-name))) ;; ------------------------------------- ;; adding new entries to a database file ;; ------------------------------------- (defun help-edit-this-item () "Edit the current help/db item being viewed." (interactive) (pop-to-buffer (file-name-nondirectory help-last-file)) (beginning-of-line 1) (narrow-to-region (point) (save-excursion (forward-char 1) (if (null (re-search-forward help-keyline-identifier-regexp (point-max) t)) (goto-char (point-max)) (beginning-of-line)) (point)))) (defun help-add-item (key xref) "Add a new entry to the file from which the current entry came. This function simply sets up a buffer for the user to add the entry - the actual save is done by \[help-save-item]." (interactive "sStore under keys: \nsCross-reference by: ") (setq help-buffer-to-add-to (file-name-nondirectory help-last-file)) (with-output-to-temp-buffer "help-item" (set-buffer "help-item") (indented-text-mode) (local-set-key "\C-x\C-s" 'help-save-item) (local-set-key "\C-x\C-q" 'help-quit-add) (message "C-x C-s to add the entry, C-x C-q to quit") (insert (concat key " {" xref "}\n"))) (pop-to-buffer "help-item") (goto-char (point-min)) (forward-line 2) (narrow-to-region (point) (point-max))) (defun help-save-item () "append the current buffer (assumed to have been created using \[help-add-item]) to that of the help file from which this was created, and save the buffer, ensuring the prescence of a final newline." (interactive) (indent-region (point-min) (point-max) 4) (widen) (save-window-excursion (pop-to-buffer help-buffer-to-add-to) (widen) (goto-char (point-max))) (append-to-buffer help-buffer-to-add-to (point-min) (point-max)) (save-window-excursion (pop-to-buffer help-buffer-to-add-to) (setq require-final-newline t) (save-buffer 1) (bury-buffer)) (kill-buffer "help-item")) (defun help-quit-add () "Exit from an item creating session by killing the temporary buffer in which its going on." (interactive) (kill-buffer "help-item")) (defun help-add-key (key) (interactive "sKey to add: ") (save-window-excursion (pop-to-buffer (file-name-nondirectory help-last-file)) (beginning-of-line) (cond ((re-search-forward "}" (save-excursion (end-of-line) (point)) t) (backward-char 1) (insert " " key)) (t (insert "{" key "}"))))) (defun help-set-default-path (path) "Set help-default-path to FILE so that future calls to help use FILE instead of current default." (interactive (list (read-string "Help search path: " help-default-path))) (setq help-default-path path)) (defun help-mode () "Mode for Emacs help/database mode \\{help-mode-map}." (interactive) (kill-all-local-variables) (use-local-map help-mode-map) (define-abbrev-table 'text-mode-abbrev-table ()) (set-syntax-table text-mode-syntax-table) (setq major-mode 'help-mode) (setq mode-name "HELP") (run-hooks 'help-mode-hook)) (defvar help-mode-map (make-sparse-keymap) "*Keymap for help mode") (define-key help-mode-map "\e[B" 'help-next-match) ;; ANSI arrow key DOWN (define-key help-mode-map "\e[A" 'help-previous-match) ;; ANSI arrow key UP (define-key help-mode-map "f" 'db) (define-key help-mode-map "n" 'help-next-match) (define-key help-mode-map "p" 'help-previous-match) (define-key help-mode-map "c" 'help-this-file) (define-key help-mode-map "a" 'help-add-item) (define-key help-mode-map "s" 'help-set-default-path) (define-key help-mode-map "k" 'help-add-key) (define-key help-mode-map "e" 'help-edit-this-item) ;; ---------------------------------------------------------- ;; this one's for doing Norton Guide type pop-up info ;; on something in a buffer. Needs a good hyperkey to be ;; bound to and mode-specific setting of the help search path ;; ---------------------------------------------------------- (defun help-on-thing () "Provide help on thing before point, using current buffer's help-default-path" (interactive) (if (eobp) (open-line 1)) (let* ((char (char-after (point))) (syntax (char-syntax char))) (cond ((eq syntax ?\ ) (backward-sexp 1) (set-mark (point)) (forward-sexp 1)) ((eq syntax ?w) ; word. (forward-word 1) (set-mark (point)) (forward-word -1)) ((eq syntax ?\( ) ; open paren. (mark-sexp 1)) ((eq syntax ?\) ) ; close paren. (forward-char 1) (mark-sexp -1) (exchange-point-and-mark)) ((eolp) ; mark line if at end. (set-mark (1+ (point))) (beginning-of-line 1)) (t ; mark character (set-mark (1+ (point))))) (help (buffer-substring (region-beginning) (region-end)) help-default-path))) ;; BibTeX/rolodex database (defun bibtex-db (bibtex-database key) (interactive "Fbibtex database: \nskey: ") (help key bibtex-database) (pop-to-buffer "*DBASE*") (goto-char (point-min)) (replace-regexp "^\\|}$" "") (setq old-regexp help-regexp-prefix help-xref-string "Cite code: " help-default-path bibtex-database show-help-hook '(lambda () (goto-char (point-min)) (replace-regexp "^\\|}$" "")))) (provide 'generic-help) ===> db-mode.texinfo \input texinfo @tex \global\chapno=29 @end tex @chapter Databases in GNU Emacs The command @code{M-x db} is the entry to the world of simple databases within Emacs. The function @code{db} searches each file in @code{help-default-path} for lines containing matches for a regular expression given as input. If it finds a match, it will display the entry in a special buffer called "*DBASE*". This buffer is in @code{dbase-mode}, and has a special modeline to tell you what's going on: its looks like this: @example Emacs-DBASE database:[@var{name}] current target: @var{target} @end example where @var{name} tells you the name of the file in which the current match has been found, and @var{target} describes the regular expression that @code{db} is currently using (ie; the one used to find this match). The *DBASE* buffer also has a number of useful commands for finding subsequent and previous matches of the same target, and for altering the information held in your databases. Note that this database system is a multi-key one, unlike @code{dbm}-based systems. This feature can be used to create a very flexible means of searching through a database of, for example, names and addresses. Each entry can have additional keys besides that person's name describing things with which they are involved or interested: then, to find someone who might be able help us on a particular topic, just search using that topic, and then step through each match in turn. Alternatively, you can just find entries based on name alone. [Technical aside: you may notice that many of the functions described below begin with the word ``help''. This is because Emacs' database commands are based on a more wide-ranging facility for providing rapid ``pop-up'' help whilst editing.] @section Keys and Entries Your database entries are defined by a series of one or more @var{keys}. These are simply words you wish to be able to use in order to retrieve the entry. If the database was just a list of names and addresses, then typically, the keys would consist of the name of each person, and the entry would be the address. However, you can also include cross-references on the key line by surrounding them with braces. These are not actually functional, but will provide hints on what other keys someone might wish to use to find other, related entries. @section What a Database file looks like Each file in the search path defined by the variable @code{help-search-path} should have the following format: Keys should be listed on a line starting with a non-white space character. Cross-reference hints are surrounded by braces ({ and }), although they still count as keys for the search function. Each key line should have at least one `primary' key not enclosed by braces. The entry for a given set of keys should consist of a number of lines beginning with at least one white space character (a space or a tab). Here a sample item from a database file: @smallexample Richard Stallman @{fsf gnu emacs MIT@} c/o Free Software Foundation 675 Mass Ave Cambridge, MA 02139 USA @end smallexample This entry could be retrieved using any of the following topics, amongst others: @smallexample Stallman, gnu, MIT, Rich, stall\|emacs @end smallexample @section variables affecting database operations @code{help-default-search-path} is a string containing a list of space-separated filenames to be used by @code{db} in its hunt for matching entries. It is local to each dbase-mode buffer, so that if you wish you can have different *DBASE* buffers accessing different databases. You should set this in your @var{.emacs} file using @code{set-default}. Here is an example: @smallexample (set-default 'help-default-path "/usr/davis/db /usr/mygroup/db") @end smallexample @code{help-case-fold-search} determines whether or not case should be ignored when looking for database entries. Its default value, @code{nil}, means that database searches are case-insensitive; you can reset to @code{t} in this in your @var{.emacs} file if you want them to be case-sensitive. @section Commands to find database entries @table @kbd @item M-x db @var{key} find an entry filed under @var{key} and display it in a buffer called "*DBASE*". @end table The following commands are avaiable only within the ``*DBASE*'' buffer: @table @kbd @item c find another item within current database file only (@code{help-this-file}) @item f find an entry filed under a different key (@code{db}) @item n find the next match for the current target key(@code{help-next-match}) @item p find the previous match for the current target key(@code{help-previous-match}) @end table On keyboards whose arrow (cursor) keys generate ANSI-standard sequences, the UP arrow is equivalent to "p" and the DOWN arrow to "n". @section Commands to alter or add to a database @table @kbd @item a add a new entry to the current database file (@code{help-add-item}) @item e edit the entry currently being viewed (@code{help-edit-this-item}) @item k add a key to the current database entry (@code{help-add-key}) @end table @code{help-add-item} prompts for a primary key and cross-references, then creates a temporary buffer for you to create a new entry in the current database file. It looks after all formatting for you, so you can put whatever you want in this buffer without worrying about white-spaces or key entry formats. When you're finished creating the entry, just save as normal using @code{C-x C-s} and the item will be formatted, added to the database and written to a file. @code{help-edit-this-item} switches to a buffer containing the current database file. Point will be on the key line of the entry, and the buffer will be narrowed to the text of this entry. You can carry out all normal editing operations on this buffer -- its a good idea to save it after you're finished though. @code{help-add-key} allows you to add an extra key to the current entry. @bye =====> ~/db (sample) Wolfram Research {mathematica Steven Wolfram} PO Box 6059 Champaign IL 61821 tel: 217 398 0700 Charles Lachlan {dentist} 4 Cross Street Cambridge CB1 2HW tel: 315680 Cooperative Bank {} 134-138 The Grove Stratford E15 1NX 01-555-7241 Travel Store {usa flights barry} Suite 6 Strode House 46-48 Osnaburgh St. London Nw1 3ND Maria Tuck {Independent sun operations} work: 40 City Road London EC1 tel:01 956 1677 Harlow Ski School {} tel: 0279 21792 Travel Corona {Mark travel flight} 81 George St London W1H 5PL tel: 01-935-7433/7355 Chas Roberts {mountain bikes} tel: 01-684-3370 Bromwich Cycles {mountain bikes} tel: 0203 664459 Egg {chainrings bicycles} tel: 0272 770626 Ben Hayward {bicycles} 69 Trumpington Street Cambridge CB2 1RJ Tel: 0223 352294 Contact: robin Spanish Tourist Board {spain} tel: 01 499 0901 Karrimor International {backpacks panniers} tel: 0254 385911 Citadel {Bike locks} tel: 01-208-2106 STA Travel {travel flights} tel: 01-581-1022 (intercontinental) London Student Travel {travel flights} tel: 01-730-3402