[gnu.emacs] fast-apropos and super-apropos

jbw%bucsf.BU.EDU@BU-CS.BU.EDU (Joe Wells) (04/24/89)

Here is the second release of fast-apropos.  The only change is that
by default, it doesn't look up key bindings, and thus runs twice as
fast.  The default can be changed by setting a variable.

In addition, I am including my version of super-apropos (idea from
Lynn Slater).  It is substantially faster now, since it avoids about 2000
extra system calls by examining the DOC file directly.

fast-apropos displays the documentation for symbols whose names match a
regular expression.  super-apropos displays the documentation for
symbols whose names or documentation match a regular expression.

Enjoy, and as usual, send me bug reports.

--
Joe Wells <jbw@bucsf.bu.edu>
jbw%bucsf.bu.edu@bu-it.bu.edu
...!harvard!bu-cs!bucsf!jbw
----------------------------------------------------------------------
;; Faster apropos commands.
;; Copyright (C) 1989 Free Software Foundation, Inc.

;; This file is not officially part of GNU Emacs, but is being donated
;; to the Free Software Foundation.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.

;; Author: Joe Wells
;; Last changed: Sun Apr 23 22:00:27 1989 by jbw (Joseph Wells) on bucsf
;; jbw%bucsf.bu.edu@bu-it.bu.edu (school year)
;; joew%uswest@boulder.colorado.edu (summer)

;; The ideas for this package were derived from the C code in
;; src/keymap.c and elsewhere.  The functions in this file should
;; always be byte-compiled for speed.  Someone should rewrite this in
;; C (as part of src/keymap.c) for speed.

;; Fixed bug, current-local-map can return nil.
;; Change, doesn't calculate key-bindings unless needed.
;; Added super-apropos capability, changed print functions.
;; Made fast-apropos and super-apropos share code.
;; Sped up fast-apropos again.
;; Added apropos-do-all option.

;; The idea for super-apropos is based on the original implementation
;; by Lynn Slater <address unknown>.

(defvar apropos-do-all nil
  "*Should fast-apropos and super-apropos do everything that they can
by default.  Makes them run 2 or 3 times slower.")

(defvar doc-file-name (concat exec-directory "DOC")
  "The complete pathname of the documentation file that contains all
documentation for functions and variables defined before Emacs is
dumped.")

(defun fast-apropos (regexp &optional do-all)
  "Show all symbols whose names contain matches for REGEXP.  If
optional argument DO-ALL is non-nil, does more (time-consuming) work
such as showing key bindings.  Returns list of symbols and
documentation found."
  "Show all symbols whose names contain match for REGEXP.
If optional arg PRED is non-nil, (funcall PRED SYM) is done
for each symbol and a symbol is mentioned if that returns non-nil.
Returns list of symbols found; if third arg NOPRINT is non-nil,
does not display them, just returns the list."
  (interactive "sFast-Apropos: \nP")
  (setq do-all (or apropos-do-all do-all))
  (let ((apropos-accumulate (apropos regexp nil t)))
    (fast-apropos-get-doc apropos-accumulate)
    (with-output-to-temp-buffer "*Help*"
      (apropos-print-matches apropos-accumulate regexp nil do-all))
    apropos-accumulate))

(defun fast-apropos-get-doc (list)
  "Takes LIST of symbols and adds documentation.  Modifies LIST in
place, resulting alist is of form ((symbol fn-doc var-doc) ...).
Should only be called by fast-apropos."
  (let (fn-doc var-doc symbol)
    (while (consp list)
      (setq symbol (car list)
	    fn-doc (and (fboundp symbol)
			(documentation symbol))
	    var-doc (documentation-property symbol 'variable-documentation)
	    fn-doc (and fn-doc
			(substring fn-doc 0 (string-match "\n" fn-doc)))
	    var-doc (and var-doc
			 (substring var-doc 0 (string-match "\n" var-doc))))
      (setcar list (list symbol fn-doc var-doc))
      (setq list (cdr list))))
  nil)

(defun super-apropos (regexp &optional do-all)
  "Show symbols whose names/documentation contain matches for REGEXP.
If optional argument DO-ALL is non-nil, does more (time-consuming)
work such as showing key bindings and documentation that is not stored
in the documentation file.  Returns list of symbols and documentation
found."
  (interactive "sSuper Apropos: \nP")
  (setq do-all (or apropos-do-all do-all))
  (let (apropos-accumulate fn-doc var-doc item)
    (setq apropos-accumulate (super-apropos-check-doc-file regexp))
    (if do-all (mapatoms 'super-apropos-accumulate))
    (with-output-to-temp-buffer "*Help*"
      (apropos-print-matches apropos-accumulate nil t do-all))
    apropos-accumulate))

(defun super-apropos-check-doc-file (regexp)
  "Finds all documentation related to REGEXP in doc-file-name.
Returns an alist of form ((symbol fn-doc var-doc) ...)."
  (let ((doc-buffer (or (get-file-buffer doc-file-name)
			(find-file-noselect doc-file-name)))
	type symbol doc sym-list)
    (save-excursion
      (set-buffer doc-buffer)
      (goto-char (point-min))
      (while (re-search-forward regexp nil t)
	(search-backward "\C-_")
	(setq type (if (eq ?F (char-after (1+ (point))))
		       1
		     2)
	      symbol (progn
		       (forward-char 2)
		       (read doc-buffer))
	      doc (buffer-substring
		   (point)
		   (progn
		     (if (search-forward "\C-_" nil 'move)
			 (1- (point))
		       (point))))
	      item (assq symbol sym-list))
	(or item
	    (setq item (list symbol nil nil)
		  sym-list (cons item sym-list)))
	(setcar (nthcdr type item) doc)))
    sym-list))

(defun super-apropos-accumulate (symbol)
  "DON'T USE THIS!
Helping function for super-apropos.  This is passed as the argument to
map-atoms, so it is called once for every symbol in obarray.  Takes
one argument SYMBOL, and finds any memory-resident documentation on
that symbol if it matches a variable regexp.  WARNING: this function
depends on the symbols fn-doc var-doc regexp and item being bound
correctly when it is called!"
  ;; Uses these variables: fn-doc var-doc regexp item
  (cond ((string-match regexp (symbol-name symbol))
	 (setq item (apropos-get-accum-item symbol))
	 (setcar (cdr item) (or (safe-documentation symbol)
				(nth 1 item)))
	 (setcar (nthcdr 2 item) (or (safe-documentation-property symbol)
				     (nth 2 item))))
	(t
	 (and (setq fn-doc (safe-documentation symbol))
	      (string-match regexp fn-doc)
	      (setcar (cdr (apropos-get-accum-item symbol)) fn-doc))
	 (and (setq var-doc (safe-documentation-property symbol))
	      (string-match regexp var-doc)
	      (setcar (nthcdr 2 (apropos-get-accum-item symbol)) var-doc))))
  nil)

(defun apropos-print-matches (matches &optional regexp spacing do-all)
  "Helping function for fast-apropos and super-apropos.  Prints the
symbols and documentation in alist MATCHES of form ((symbol fn-doc
var-doc) ...).  Uses optional argument REGEXP to speed up searching
for keybindings.  The names of all symbols in MATCHES must match
REGEXP.  Displays in the buffer pointed to by standard-output.
Optional argument SPACING means put blank lines in between each
symbol's documentation.  Optional argument DO-ALL means do more
time-consuming work.  Should only be called within a
with-output-to-temp-buffer."
  (setq matches (sort matches (function
			       (lambda (a b)
				 (string-lessp (car a) (car b))))))
  (save-excursion
    (set-buffer standard-output)
    (let ((p matches)
	  item keys-done symbol)
      (while (consp p)
	(setq item (car p)
	      symbol (car item)
	      p (cdr p))
	(or (not spacing) (bobp) (terpri))
	(prin1 symbol nil)		;print symbol name
	;; don't calculate key-bindings unless needed
	(cond ((and do-all (commandp symbol) (not keys-done))
	       (apropos-match-keys matches regexp)
	       (setq keys-done t)))
	(cond ((and do-all
		    (or (setq tem (nthcdr 3 item))
			(commandp symbol)))
	       (indent-to 30 1)
	       (if tem
		   (princ (mapconcat 'key-description tem ", "))
		 (insert "(not bound to any keys)"))))
	(terpri)
	(if (setq tem (nth 1 item))
	    (insert "  Function: " (substitute-command-keys tem)))
	(or (bolp) (terpri))
	(if (setq tem (nth 2 item))
	    (insert "  Variable: " (substitute-command-keys tem)))
	(or (bolp) (terpri)))))
  nil)

(defun apropos-match-keys (alist &optional regexp)
  "Find key bindings for symbols that are cars in ALIST.  Optionally,
first match the symbol name against REGEXP.  Each key binding is added
as a string to the end of the list in ALIST whose car is the
corresponding symbol.  The pointer to ALIST is returned."
  (let* ((current-local-map (current-local-map))
	 (maps (append (and current-local-map
			    (accessible-keymaps current-local-map))
		       (accessible-keymaps (current-global-map))))
	 map				;map we are now inspecting
	 sequence			;key sequence to reach map
	 i				;index into vector map
	 command			;what is bound to current keys
	 key				;last key to reach command
	 local				;local binding for sequence + key
	 item)				;symbol data item in alist
    ;; examine all reachable keymaps
    (while (consp maps)
      (setq map (cdr (car maps))
	    sequence (car (car maps))	;keys to reach this map
	    maps (cdr maps))
      (cond ((consp map)
	     (setq map (cdr map))))	;skip keymap symbol
      (setq i 0)
      (while (and map (< i 128))	;vector keymaps have 128 entries
	(cond ((consp map)
	       (setq command (cdr (car map))
		     key (car (car map))
		     map (cdr map)))
	      ((vectorp map)
	       (setq command (aref map i)
		     key i
		     i (1+ i))))
	;; if is a symbol, and matches optional regexp, and is a car
	;; in alist, and is not shadowed by a different local binding,
	;; record it
	(and (symbolp command)
	     (if regexp (string-match regexp (symbol-name command)))
	     (setq item (assq command alist))
	     (setq key (concat sequence (char-to-string key)))
	     ;; checking if shadowed by local binding.
	     ;; either no local map, no local binding, or runs off the
	     ;; binding tree (number), or is the same binding
	     (or (not current-local-map)
		 (not (setq local (lookup-key current-local-map key)))
		 (numberp local)
		 (eq command local))
	     ;; add this key binding to the item in alist
	     (nconc item (cons key nil))))))
  alist)

(defun apropos-get-accum-item (symbol)
  "DON'T USE THIS!
Get an alist item in alist apropos-accumulate whose car is SYMBOL.
Creates the item if not already present."
  (or (assq symbol apropos-accumulate)
      (progn
	(setq apropos-accumulate
	      (cons (list symbol nil nil) apropos-accumulate))
	(assq symbol apropos-accumulate))))

(defun safe-documentation (function)
  "Like documentation, except it will return nil instead of calling
get_doc_string()."
  (while (symbolp function)
    (setq function (if (fboundp function)
		       (symbol-function function)
		     0)))
  (if (not (consp function))
      nil
    (if (eq (car function) 'macro)
	(setq function (cdr function)))
    (if (not (memq (car function) '(lambda autoload)))
	nil
      (setq function (nth 2 function))
      (if (stringp function)
	  function
	nil))))

(defun safe-documentation-property (symbol)
  "Like documentation-property, except it will return nil instead of
calling get_doc_string()."
  (setq symbol (get symbol 'variable-documentation))
  (if (numberp symbol)
      nil
    symbol))