Mercurial > emacs
changeset 16811:df5765f5f6fd
Initial revision
author | Richard M. Stallman <rms@gnu.org> |
---|---|
date | Thu, 02 Jan 1997 07:19:58 +0000 |
parents | 20dc495230a0 |
children | af96712b7f5d |
files | lisp/=word-help.el |
diffstat | 1 files changed, 717 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/=word-help.el Thu Jan 02 07:19:58 1997 +0000 @@ -0,0 +1,717 @@ +;;; word-help.el --- keyword help for any language doc'd in TeXinfo. + +;; Copyright (c) 1996 Free Software Foundation, Inc. + +;; Maintainer: Jens T. Berger Thielemann, <jensthi@ifi.uio.no> +;; Keywords: help, keyword, languages + +;; This file is part of GNU Emacs. + +;; 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 provides a rather general interface for doing keyword +;; help in most languages. In short, it'll determine which Texinfo +;; file which is relevant for the current mode; cache the index and +;; use regexps to give you help on the keyword you're looking at. + +;; Installation +;; ************ + +;; For the default setup to work for all supported modes, make sure +;; the Texinfo files from the following packages are installed: + +;; Texinfo file | Available in archive or URL | Notes +;; autoconf.info | autoconf-2.10.tar.gz | - +;; bison.info | bison-1.25.tar.gz | - +;; libc.info | glibc-1.09.1.tar.gz | - +;; elisp.info | elisp-manual-19-2.4.tar.gz | - +;; latex.info | ftp://ftp.dante.de/pub/tex/info/latex2e-help-texinfo/latex2e.texi +;; groff.info | groff-1.10.tar.gz | - +;; m4.info | m4-1.4.tar.gz | - +;; make.info | make-3.75.tar.gz | - +;; perl.info | http://www.perl.com/CPAN/doc/manual/info/ +;; simula.info | Mail bjort@ifi.uio.no | Written in Norwegian +;; texinfo.info | texinfo-3.9.tar.gz | - + +;; BTW: We refer to Texinfo files by just their last component, not +;; with an absolute file name. You must thus set up +;; `Info-directory-list' and `Info-default-directory-list' so that +;; these can automatically be located. + +;; Usage +;; ***** +;; +;; Place the cursor over the function/variable/type/whatever you want +;; help on. Type "C-h C-i". `word-help' will then make a suggestion +;; to an index topic; press return to accept this. If not, you may use +;; tab-completion to find the topic you're interested in. + +;; Usually, `word-help' is able to determine the relevant Texinfo +;; file from looking at the buffer's `mode-name'; if not, you can use +;; the interactive function `set-help-file' to set this. + +;; Customizing +;; *********** +;; +;; User interface +;; -------------- +;; +;; Two variables control the behaviour of the user-interface of +;; `word-help': `word-help-split-window' and +;; `word-help-magic-index'. Do C-h v to get more information on +;; these. + +;; Adding more Texinfo files +;; ------------------------- +;; +;; Associations between mode-names and Texinfo files can be done +;; through the `word-help-mode-alist' variable, which defines an +;; `alist' making `set-help-file' able to initialize the necessary +;; variable. + +;; Contacting the author +;; ********************* +;; +;; If you wish to contact me for any reason, please feel free to write +;; to: + +;; Jens Berger +;; Spektrumveien 4 +;; N-0666 Oslo +;; Norway +;; +;; E-mail: <jensthi@ifi.uio.no> + +;; Have fun. + +;; +;;; Code: +;; + +(require 'info) + +;;;-------------------- +;;; USER OPTIONS +;;;-------------------- + +(defvar word-help-split-window t + "*Non-nil means that the info buffer will pop up in a separate window. +If nil, we will just switch to it.") + +(defvar word-help-magic-index t +"*Non-nil means that the keyword will be searched for in the requested node. +This is done by determining whether the line the point is positioned +on after using `Info-goto-node', actually contains the keyword. If +not, we will search for the first occurence of the keyword. This may +help when the info file isn't correctly indexed.") + +;;; ---- end of user configurable variables + +;;;------------------------- +;;; ADVANCED USER OPTIONS +;;;------------------------- + +(defvar word-help-mode-alist + '(("autoconf" + (("autoconf" "Macro Index") ("m4" "Macro index")) + (("AC_\\([A-Za-z0-9_]+\\)" 1) + ("[a-z]+"))) + + ("Bison" + (("bison" "Index") + ("libc" "Type Index" "Function Index" "Variable Index")) + (("%[A-Za-z]+") + ("[A-Za-z]+") + ("[A-Za-z_][A-Za-z0-9_]+"))) + ("YACC" . "Bison") + + ("C" (("libc" "Type Index" "Function Index" "Variable Index"))) + ("C++" . "C") + + ("Emacs-Lisp" + (("elisp" "Index")) + (("[^][ ()\n\t.\"'#]+"))) + + ("LaTeX" + (("latex" "Command Index")) + (("\\\\\\(begin\\|end\\){\\([^}\n]+\\)}" 2 0) + ("\\\\[A-Za-z]+") + ("\\\\[^A-Za-z]") + ("[A-Za-z]+"))) + + ("Nroff" + (("groff" "Macro Index" "Register Index" "Request Index")) + ((".[^A-Za-z]") + (".[A-Za-z]+") + (".\\([A-Za-z]+\\)" 1))) + ("Groff" . "Nroff") + + ("m4" (("m4" "Macro index"))) + + ("Makefile" + (("make" "Name Index" "Concept Index")) + (("\\.[A-Za-z]+") + ("\\$[^()]") + ("\\$([^()= \t]+)") + ("[A-Za-z]+"))) + + ("Perl" + (("perl" "Variable Index" "Function Index")) + (("\\$[^A-Za-z^]") + ("\\$\\^[A-Za-z]?") + ("\\$[A-Za-z][A-Za-z_0-9]+") + ("[A-Za-z_][A-Za-z_0-9]+")) + nil + (("^\\([^ \t\n]+\\)" 1))) + + ("Simula" (("simula" "Index")) nil t) + ("Ifi Simula" . "Simula") + ("SIMULA" . "Simula") + + ("Texinfo" + (("texinfo" "Command and Variable Index")) + (("@\\([A-Za-z]+\\)" 1))) + + ) + "Assoc list between `mode-name' and Texinfo files. +The variable should be initialized with a list of elements with the +following form: + +\(mode-name (word-help-info-files) (word-help-keyword-regexps) + word-help-ignore-case word-help-index-mapper) + +where `word-help-info-files', `word-help-keyword-regexps' and so +forth of course are the values which should be put in these variables +for this mode. Note that `mode-name' doesn't have to be a legal +mode-name; the user may use the call `set-help-file', where +`mode-name' will be used in the `completing-read'. + +Example entry (for C): + +\(\"C\" ((\"libc\" \"Type Index\" \"Function Index\" \"Variable Index\")) + ((\"[A-Za-z_][A-Za-z0-9]+\"))) + +The two first variables must be initialized; the two remaining will +get default values if you omit them or set them to nil. The default +values are: + +word-help-keyword-regexps: (\"[A-Za-z_][A-Za-z0-9]+\") +word-help-ignore-case: nil + +More settings may be defined in the future. + +You may also define aliases, if there are several relevant mode-names +to a single entry. These should be of the form: + +\(MODE-NAME-ALIAS . MODE-NAME-REAL) + +For C++, you would use the alias + +\(\"C++\" . \"C\") + +to make C++ mode use the same help files as C files do. Please note +that you can shoot yourself in the foot with this possibility, by +defining recursive aliases.") + +;;; --- end of advanced user options + +(defvar word-help-ignore-case nil + "Non-nil means that case is ignored when doing lookup.") +(make-variable-buffer-local 'word-help-ignore-case) + +(defvar word-help-info-files nil +"List of infofiles with respective nodes for the current mode. + +This should be a list of the following form: + +\((INFO-FILE-1 NODE-NAME-1 NODE-NAME-2 ...) + (INFO-FILE-1 NODE-NAME-1 NODE-NAME-2 ...) + : : : + (INFO-FILE-1 NODE-NAME-1 NODE-NAME-2 ...)) + +An example entry for e.g. C would be: + +\((\"/local/share/gnu/info/libc\" \"Function Index\" \"Type Index\" + \"Variable Index\")) + +The files and nodes will be searched/cached in the order specified. +This variable is usually set by the `word-help-switch-help-file' +function, which utilizes the `word-help-mode-alist'.") +(make-variable-buffer-local 'word-help-info-files) + +(defvar word-help-keyword-regexps nil + "Regexps for finding keywords in the current mode. + +This is constructed as a list of the following form: + +\((REGEXP SUBMATCH-LOOKUP SUBMATCH-CURSOR) + (REGEXP SUBMATCH-LOOKUP SUBMATCH-CURSOR) + : : : + (REGEXP SUBMATCH-LOOKUP SUBMATCH-CURSOR)) + +The regexps will be searched in order for a match which the cursor is +within. + +submatch-lookup is the submatch number which will be looked for in the +index. May be omitted; defaults to 0 (e.g. the entire pattern). This is +useful in for instance configure lookup; each command is there prefixed +with 'AC_', which must be ignored when doing a lookup. Example regexp +entry for this: + +\(\"AC_\\\\([A-Za-z0-9]+\\\\)\" 1) + +submatch-cursor is the part of the match which the cursor must be within. +May be omitted; defaults to 0 (e.g. the entire pattern).") +(make-variable-buffer-local 'word-help-keyword-regexps) +(set-default 'word-help-keyword-regexps '(("[A-Za-z_][A-Za-z_0-9]*"))) + +(defvar word-help-index-mapper nil + "Regexps to use for massaging index-entries into keywords. +This variable should contain a list of regexps with sub-expressions, +where we will only look for the sub-expression in the user text. + +The regexp list should be formatted as: + + ((REGEXP SUBEXP) (REGEXP SUBEXP) ... ) + +If the index entry does not match any of the regexps, it will be ignored. + +Example: + +Perl has index entries of the following form: + +* abs VALUE: perlfunc. +* accept NEWSOCKET,GENERICSOCKET: perlfunc. +* alarm SECONDS: perlfunc. +* atan2 Y,X: perlfunc. +* bind SOCKET,NAME: perlfunc. + : : : + +We will thus try to extract the first word in the index entry - +\"abs\" from \"abs VALUE\", etc. This is done by the following entry: + +\((\"^\\\\([^ \\t\\n]+\\\\)\" 1))") +(make-variable-buffer-local 'word-help-index-mapper) + +(defvar word-help-main-index nil +"List of all index entries. + +See `word-help-process-indexes' for structure formatting. + +Minor note: This variable is a list if it is initialized, t if +initializing failed and nil if uninitialized.") +(make-variable-buffer-local 'word-help-main-index) + +(defvar word-help-main-obarray nil +"Global work variable for `word-help' system. +Do Not mess with this!") + +(defvar word-help-history nil + "History for `word-help' minibuffer queries.") +(make-local-variable 'word-help-history) + +(defvar word-help-current-help-file nil + "Current help file active for this mode.") + +(defvar word-help-index-alist nil + "An assoc list mapping help files to info indexes. +This means that `word-help-mode-index' can be init'ed faster.") + +(defvar word-help-help-mode nil + "Which mode the help system is bound to for the current mode.") +(make-variable-buffer-local 'word-help-help-mode) + + +;;; Debugging + +;;;###autoload +(defun reset-word-help () + "Clear all cached indexes in the `word-help' system. +You should only need this when installing new info files, and/or +adding more Texinfo files to the `word-help' system." + (interactive) + (setq word-help-index-alist nil + word-help-main-index nil)) + + +;;; Changing help file + +;;;###autoload +(defun set-help-file () + "Change which set of Texinfo files used for word help. + +`word-help' maintains a list over which Texinfo files which are +relevant for each programming language (`word-help-mode-alist'). It +usually selects the correct one, based upon the value of `mode-name'. +If this guess is incorrect, you may also use this function manually to +instruct future `word-help' calls which Texinfo files to use." + (interactive) + (let (helpfile helpguess (case-comp completion-ignore-case)) + (setq helpguess (cond + (word-help-current-help-file) + ((word-help-guess-help-file)))) + + (setq completion-ignore-case t + helpfile (completing-read + (if helpguess + (format "Select help mode (default %s): " helpguess) + "Select help mode: ") + word-help-mode-alist + nil t nil nil)) + (setq completion-ignore-case case-comp) + (if (equal "" helpfile) + (setq helpfile helpguess)) + (if helpfile + (word-help-switch-help-file helpfile)) + ) + ) + +;;; Main user interface + +;;;###autoload +(defun word-help () + "Find documentation on the keyword under the cursor. +The determination of which language the keyword belongs to, is based upon +The relevant info file is selected by matching `mode-name' (the major +mode) against the assoc list `word-help-mode-alist'. + +If this is not possible, `set-help-file' will be invoked for selecting +the relevant info file. `set-help-file' may also be invoked +interactively by the user. + +If the keyword you are looking at is not available in any index, no +default suggestion will be presented. " + (interactive) + (let (helpguess myguess guess index-info case-store) +;; Set necessary variables for later lookup + (if (not word-help-info-files) + (if (setq helpguess (word-help-guess-help-file)) + (word-help-switch-help-file helpguess) + (set-help-file))) +;; Have we previously cached datas? + (word-help-process-indexes) + (if + (atom word-help-main-index) + (message "No help file available for this mode.") +;; First make a guess at what the user is looking for + (setq myguess (word-help-guess + (point) + (cond + ((not (atom word-help-main-index)) + (car word-help-main-index))) + word-help-keyword-regexps)) +;; Ask the user himself + (setq case-store completion-ignore-case + completion-ignore-case word-help-ignore-case) + (setq guess (completing-read + ; Format string + (if myguess + (format "Look up keyword (default %s): " myguess) + "Look up keyword: ") + ; Collection + (car word-help-main-index) + nil t nil 'word-help-history)) + (setq completion-ignore-case case-store) + (if (equal guess "") + (setq guess myguess)) +;; If we've got anything meaningful to lookup, do so + (if (not guess) + (message "Help aborted.") + (setq index-info (word-help-find-index-node + guess + word-help-main-index)) + (if (not index-info) + (message "Oops, I could not find \"%s\" anyway! Bug?" guess) + (word-help-goto-index-node index-info) + ) + ) + ) + ) + ) + +;;; Index mappers + +(defun word-help-map-index-entries (str re-list) + (let ((regexp (car (car re-list))) + (subexp (car (cdr (car re-list)))) + (next (cdr re-list))) + (cond + ((string-match regexp str) + (substring str (match-beginning subexp) (match-end subexp))) + (next + (word-help-map-index-entries str next))))) + + +;;; Mode lookup + +(defun word-help-guess-help-file () + "Guesses a relevant help file based on mode name. +Returns nil if no guess could be made. Uses `word-help-mode-alist'." + (let (guess) + (cond + ((setq guess (assoc mode-name word-help-mode-alist)) + (car guess))))) + + +(defun word-help-switch-help-file (helpfile) + "Changes the help-file to the mode name given. +Uses `word-help-mode-alist'." + (if helpfile + (let (helpdesc) + (if (not (setq helpdesc (assoc helpfile word-help-mode-alist))) + (message "No help defined for \"%s\"." helpfile) + (if (stringp (cdr helpdesc)) + (word-help-switch-help-file (cdr helpdesc)) + (word-help-make-default-map + helpdesc + (list 'word-help-help-mode + 'word-help-info-files + 'word-help-keyword-regexps + 'word-help-ignore-case + 'word-help-index-mapper)))) + (setq word-help-main-index nil)))) + +;;; Default mapping + +(defun word-help-make-default-map (list vars) + "Makes a default mapping for `vars', which must be listed in order. +vars is a list of quoted symbols. If the nth entry in the list is +non-nil, the nth variable will be given this value. If nil, the var +will be given the global default value." + (set (car vars) (cond ((car list)) ((default-value (car vars))))) + (if (cdr vars) + (word-help-make-default-map (cdr list) (cdr vars)))) + + +;;; Index collection + +(defun word-help-extract-index (file-name index-list index-map ignore-case) + "Extract index from filename and the first node name in index list. +`file-name' is the name of the info file, while `index-list' is a list +of node-names to search." + (let (cmd1 cmdlow nodename ob-array next) + (setq nodename (car index-list)) + (setq ob-array (make-vector 211 0)) + (message "Processing \"%s\" in %s..." nodename file-name) + (save-window-excursion + (Info-goto-node (concat "(" file-name ")" nodename)) + (end-of-buffer) + (while (re-search-backward "\\* \\([^\n:]+\\):" nil t) + (setq cmd1 (buffer-substring (match-beginning 1) (match-end 1))) + (setq cmdlow (if ignore-case (downcase cmd1) cmd1)) + (if index-map + (setq cmdlow (word-help-map-index-entries cmdlow + index-map))) +;; We have to do this workaround to support case-insensitive matching + (cond + (cmdlow + (put (intern cmdlow ob-array) 'word-help-real-name cmd1) + (intern cmdlow word-help-main-obarray))))) + (setq next (cond + ((cdr index-list) + (word-help-extract-index file-name (cdr index-list) + index-map ignore-case)))) + (nconc (list (list nodename ob-array)) next))) + + +(defun word-help-collect-indexes (info-file) + "Process all the indexes in an info file. + +Uses `word-help-extract-index' on each node, and returns an entry +suitable for merging into `word-help-process-indexes'. `info-file' +is an entry of the form + +\(FILE-NAME INDEX-NAME-1 INDEX-NAME-2 ...)" + (let ((file (car info-file)) + (nodes (cdr info-file))) + (nconc (list file) (word-help-extract-index file nodes + word-help-index-mapper + word-help-ignore-case)) + ) + ) + +(defun word-help-process-indexes () + "Process all the entries in the global variable `word-help-info-files'. +Returns a list formatted as follows: + +\(all-entries-ob + (file-name-1 (node-name-1 this-node-entries-ob) + (node-name-2 this-node-entries-ob) + : : : + (node-name-n this-node-entries-ob)) + (file-name-2 (node-name-1 this-node-entries-ob) + (node-name-2 this-node-entries-ob) + : : : + (node-name-n this-node-entries-ob)) + : : : : : : : : : + (file-name-n (node-name-1 this-node-entries-ob) + (node-name-2 this-node-entries-ob) + : : : + (node-name-n this-node-entries-ob))) + +The symbols in the obarrays may contain the additional property +`word-help-real-name', which tells the *real* node to go to. + +Note that we use `word-help-index-alist' to speed up the process. Note +that `word-help-switch-help-file' must have been called before this function. + +This structure is then later searched by `word-help-find-index-node'." + (let (index-words old-index) + (if (not word-help-main-index) + (cond + ((setq old-index + (assoc word-help-help-mode word-help-index-alist)) + (setq word-help-main-index (nth 1 old-index))) + (word-help-info-files + (setq word-help-main-obarray (make-vector 307 0) + index-words (mapcar 'word-help-collect-indexes + word-help-info-files) + word-help-main-index + (append (list word-help-main-obarray) index-words)) + (setq word-help-index-alist (cons (list word-help-help-mode + word-help-main-index) + word-help-index-alist))) + (t (setq word-help-main-index t)))))) + + +;;; Keyword lookup + +(defun word-help-guess (cur-point cmd-array re-list) + "Guesses what keyword the user is looking at, and returns that. +CUR-POINT should be the current value of `point', CMD-ARRAY an obarray +of all the keywords which are defined for the current mode, and +RE-LIST a list of regexps use for the hunt. See also +`word-help-keyword-regexps'." + (let (guess pre-guess regexp submatch cursmatch end-point) + (setq regexp (car (car re-list)) + submatch (cond ((nth 1 (car re-list))) (0)) + cursmatch (cond ((nth 2 (car re-list))) (0))) +;; Store where the old point was + (save-excursion + (end-of-line) +;; We won't accept matches after the line + (setq end-point (point)) +;; Start at the beginning + (beginning-of-line) + (setq guess nil) + (while (and (not guess) (re-search-forward regexp end-point t)) + (setq pre-guess (buffer-substring (match-beginning submatch) + (match-end submatch))) +;; Look whether the cursor is within the match + (if (and (<= (match-beginning cursmatch) cur-point) + (>= (match-end cursmatch) cur-point) + (cond + (cmd-array (intern-soft (if word-help-ignore-case + (downcase pre-guess) + pre-guess) cmd-array)) + (t))) + (setq guess pre-guess)) + ) +;; If we found anything, return it, else call ourselves again + (cond + (guess) + ((cdr re-list) (word-help-guess cur-point cmd-array (cdr re-list))) + ) + ) + ) + ) + +;;; Find an index entry + +(defun word-help-find-index-node (node index-reg) + "Finds the node named `node' in the index-register `index-reg'. +`index-reg' has the format as returned (and documented) by the +`word-help-process-indexes' call. In most cases, this will be equal to +`word-help-main-index'. + +Returns a list with format + (file-name index-node-name index-entry) +which contains the file and index where the entry can be found. +Returns nil if the entry can't be found." + (let (file-info node-name) + (setq node-name (cond (word-help-ignore-case (downcase node)) (node))) + (if (intern-soft node-name (car index-reg)) + (setq file-info (word-help-index-search-file node-name + (cdr index-reg)))) + file-info + ) + ) + +(defun word-help-index-search-file (entry file-data) + "Searches a cached file for the index-entry `entry'." + (let (this-file next-files file-name node node-infos) + (setq this-file (car file-data) + next-files (cdr file-data) + file-name (car this-file) + node-infos (cdr this-file) + node (word-help-index-search-nodes entry node-infos)) + (cond + (node + (cons file-name node)) + (next-files (word-help-index-search-file entry next-files)) + ) + ) + ) + +(defun word-help-index-search-nodes (entry node-info) + "Searches a cached list of nodes for the entry `entry'." + (let (this-node next-nodes node-name node-ob node-sym) + (setq this-node (car node-info) + next-nodes (cdr node-info) + node-name (car this-node) + node-ob (car (cdr this-node)) + node-sym (intern-soft entry node-ob)) + (cond + (node-sym + (list node-name (get node-sym 'word-help-real-name))) + (next-nodes (word-help-index-search-nodes entry next-nodes))))) + +;;; Switch to a node in an index + +(defun word-help-goto-index-node (index-info) + "Jumps to an index node. +`index-info' should be a list with the following format: + +\(FILE-NAME INDEX-NODE-NAME INDEX-ENTRY)" + + (let* ((file-name (car index-info)) + (node-name (nth 1 index-info)) + (entry-name (nth 2 index-info)) + (entry-regexp (concat "\\<" (regexp-quote (nth 2 index-info)) "\\>")) + (buffer (current-buffer)) + end-point) + (if word-help-split-window + (pop-to-buffer nil)) + (Info-goto-node (concat "(" file-name ")" node-name)) + (Info-menu entry-name) +;; Do magic keyword search + (cond + (word-help-magic-index + (end-of-line) + (setq end-point (point)) + (beginning-of-line) + (cond + ((not (re-search-forward entry-regexp end-point t)) + (re-search-forward entry-regexp) + (recenter 0))))) + (if word-help-split-window + (pop-to-buffer buffer)))) + +(provide 'word-help) + +;;; word-help.el ends here