;; This file sets up machinery to build a doc string file from ;; a number of lisp files. Then it allows building of key pointers ;; into that file. These can be used to complete and view documentation ;; in emacs. I have tried to emulate the usage pattern of the tags facility ;; in order to make the doc facility easier to use. ;; edoc <--> etags ;; DOC <--> TAGS ;; visit-doc-file <--> visit-tags-table ;; C-h d <--> M-. ;; To create the doc strings file use edoc. ;; Usage: ;; % edoc *.lisp ;; This creates a DOC file and a DOC-keys.el file. ;; Normally comments which appear where a doc string would have ;; been, will be used instead of the doc string. Also comments ;; preceding or following a defvar will be used depending ;; on the setting of the variable comments-for-defvar. ;; You may set that variable in a .edoc file. ;; For c files you may use the appropriate primitive in emacs/etc ;; in order to create the DOC file. ;; For a lisp system for which you do not have sources (why are you using it!), ;; you may build a DOC file using the common lisp function doc-file ;; provided in this file. You must then use the snarf-doc command, to ;; build the keys into the DOC file you have just created. ;; You may concatenate two DOC files. Again you must use snarf-doc, ;; to build the keys. ;; To use the documentation so created do ;; M-x visit-doc-file to set up for using a particular DOC file. ;; Then C-hd (find-doc) will allow you to query the doc data base. (defvar comments-for-defvar 'after) ;; If nil only use comments inside the defvar, ;; If the symbol 'after use comment following, and if 'before ;; use the comment before. (defvar doc-start "") ;; The special string which starts each doc record. key used (defvar doc-key-length 1) ;; The length of the description immediately following doc-start ;; which says if this is a function,...: This field contains ;; F for function or M for macro V for variable,... (defvar find-doc-name) (defvar find-doc-args) ;Used internally by find-doc-args. (defvar include-all-functions-and-args nil) ;;If t all functions, not just those with documentation, will be included. (defvar include-args t) ;;If t a macro or function's args will be included. ;;Set up the common lisp syntax table. (defvar common-lisp-syntax-table (copy-syntax-table lisp-mode-syntax-table)) (let* ((const "!$%&*+-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz[]^_{}~") (i 0)) (while (< i (length const)) (modify-syntax-entry (aref const i) "w" common-lisp-syntax-table) (setq i (1+ i)))) (defun forward-over-white() (while (looking-at "[ \n]") (forward-char 1))) (defun back-over-white() (let (tem) (while (looking-at "[ \n]") (setq tem t) (forward-char -1)) (if tem (forward-char 1)) )) (defun make-doc (file out) "Create documentation for file" (find-file file) (let ((file-buf (current-buffer))) (if buffer-read-only (toggle-read-only)) (goto-char (point-min)) (set-syntax-table common-lisp-syntax-table) (setq doc-buf (generate-new-buffer "doc-buf")) (while (re-search-forward "^(def" nil t) (condition-case er (parse-one-def out) (error (end-of-line) er))) (set-buffer-modified-p nil) (kill-buffer file-buf) doc-buf )) (defun make-all-doc (out-file file-list) ;Write doc strings to OUT-FILE for all files in FILE-LIST. ;Currently lisp syntax is assumed for files in file-list. (if (file-exists-p out-file) (delete-file out-file)) (while file-list (setq file (car file-list)) (message (format "for %s.."file)) (setq buf (make-doc file nil)) (switch-to-buffer buf) (append-to-file (point-min) (point-max) out-file) (kill-buffer buf) (setq file-list (cdr file-list)) )) (global-set-key "d" 'find-doc) (global-set-key "/" 'apropos-doc) (defun apropos-doc (test) (interactive "sApropos doc string: ") (require-doc-file) (let (ans (alist my-lisp-doc)) (while alist (cond ((string-match test (car (car alist))) (setq ans (cons (car (car alist)) ans)))) (setq alist (cdr alist))) (with-output-to-temp-buffer "*Completions*" (display-completion-list ans)))) (defun string-next-sexp (pt) (save-excursion (goto-char pt) (let ((beg pt) (end (progn (forward-sexp 1) (point))) ) (goto-char beg) (cond ((and (looking-at "(") (re-search-forward "\\b&aux" end t)) (forward-char (- (length "&aux"))) (skip-chars-backward " \n") (concat (buffer-substring beg (point)) ")")) (t(buffer-substring beg end)))))) (defun skip-to-doc (type) (forward-char 2) (setq find-doc-name (progn (forward-sexp 1) (forward-over-white) (point))) (cond ((equal type "V") (forward-sexp 1) ;skip the name (forward-over-white) (or (looking-at ")") (forward-sexp 1)) (forward-over-white) (cond ((and comments-for-defvar (looking-at ")")) (cond ((eq comments-for-defvar 'after) (forward-char 1) (forward-over-white)) ((eq comments-for-defvar 'before) (goto-char find-doc-name) (previous-line 1) (back-over-white) (beginning-of-line) )))) (setq find-doc-args nil)) (t (setq find-doc-args (progn (forward-sexp 1)(forward-over-white) (point))) ;skip name (forward-sexp 1) (forward-over-white) ;skip the args )) (read-doc type) ) (defun parse-one-def (out) (let (name) (beginning-of-line) (cond ((looking-at "(defun") (skip-to-doc "F")) ((looking-at "(defmacro") (skip-to-doc "M")) ((or (looking-at "(defvar") (looking-at "(defconstant") (looking-at "(defparameter")) (skip-to-doc "V")) ) (end-of-line) )) (defvar find-doc-comment-start nil) (defun mark-very-long-comment () (interactive) ; (mm "call mark comment at %d" (point)) (setq comment-start (or find-doc-comment-start comment-start)) (let ((at (point))) (beginning-of-line) (while(and (not (eobp)) (or (looking-at comment-start) (looking-at "[ ]*\n") )) (forward-line 1)) (back-over-white) (set-mark (point)) (goto-char at) (while(and (not (bobp)) (or (looking-at comment-start) (looking-at "[ ]*\n") )) (forward-line -1)) (if (not (looking-at comment-start))(forward-line 1)) (forward-over-white) )) (defmacro mm (&rest b) (list 'progn (list 'message (cons 'format b)) '(sleep-for 1))) ;;narrows to the long-comment, and removes the ; (defun copy-long-comment (to-buf) (mark-very-long-comment) (let ((beg (min (dot) (mark))) (end (max (dot) (mark))) (n 0)m) ; (mm "Beg %d end %d" beg end) (narrow-to-region beg end) (goto-char (point-min)) (forward-over-white) (let ((tem (point))) ; (mm "check at %d" tem) (while (looking-at ";") (forward-char 1)) (setq n (- (point) tem))) (goto-char (point-min)) (while (not (eobp)) (setq m n) (while (> m 0) (cond (;(looking-at ";") (looking-at comment-start) (delete-char 1) (cond ((looking-at " ")(delete-char 1)(setq m 0))) (setq m (- m 1))) (t (setq m 0)))) (forward-line 1))) (my-copy-to-buffer doc-buf (point-min) (point-max)) (widen) ) (defun my-copy-to-buffer (buf beg end) (let ((tem (current-buffer))) (switch-to-buffer buf) (insert-buffer-substring tem beg end) (switch-to-buffer tem))) (defun write-doc (string) (let ((buf (current-buffer))) (switch-to-buffer doc-buf) (goto-char (point-max)) (insert string) (switch-to-buffer buf))) (defun write-doc-string-begin (type) (let ((name (string-next-sexp find-doc-name)) (args (if find-doc-args (string-next-sexp find-doc-args)))) (let ((buf (current-buffer))) (switch-to-buffer doc-buf) (goto-char (point-max)) (insert doc-start type name) (insert (cdr (assoc type '(("F" . "\n Function ") ("M" . "\n Macro ") ("T" . "\n Topic ") ("V" . "\n Variable: "))))) (cond ((and args include-args) (insert "Args: " args "\n")) (t (insert "\n"))) (switch-to-buffer buf) ))) (defun read-doc (type) "Reads the documentation and puts in doc file" (skip-chars-forward " \n" ) (cond ((looking-at comment-start) (write-doc-string-begin type) (copy-long-comment doc-buf)) ((looking-at "\"") (let ((tem (point)) (end (progn (forward-sexp 1)(point)))) (write-doc-string-begin type) (my-copy-to-buffer doc-buf (+ 1 tem) (- end 1)))) (include-all-functions-and-args (write-doc-string-begin type)))) (defun snarf-doc (file) "Takes a doc string file, and records the pointers into that file. It writes out a list of doc pointers into file-keys.el. The list is suitable for the find-doc command." (interactive "FMake -keys.el for file: ") (find-file file) (set-syntax-table common-lisp-syntax-table) (goto-char (point-min)) (let (tem lis) (while (search-forward doc-start nil t) (setq tem (point)) (setq lis (cons (cons (buffer-substring (setq tem (+ doc-key-length tem)) (progn (forward-sexp 1) (point))) (- tem 1) ) lis))) (let ((buf (generate-new-buffer "-keys.el"))(tem lis)) (switch-to-buffer buf) (insert "(setq my-lisp-doc '(") (while tem (prin1 (car tem) buf) (terpri buf) (setq tem (cdr tem)) ) (insert "))") (write-file (concat file "-keys.el"))) (setq my-lisp-doc lis))) (defvar find-doc-buffer nil) ; buffer where the lisp documentation lives (defvar doc-file-name nil) ; File name of the current doc file. Usually ../DOC should be used ; and ../DOC-keys.el will hold the keys to the file. (defun visit-doc-file (file) (interactive (list (read-file-name "Visit doc table: (default DOC) " default-directory (concat default-directory "DOC") t))) (setq file (expand-file-name file)) (if (file-directory-p file) (setq file (concat file "DOC"))) (setq doc-file-name file) (load (concat file "-keys.el"))) (defun require-doc-file() (or doc-file-name (visit-doc-file (read-file-name "Visit doc table: (default DOC) " default-directory (concat default-directory "DOC") t)))) (defvar find-doc-edit nil "If non nil, instead of just printing out a copy of the documentation in the other window, we actually visit the DOC file. This is useful for editing it.") (defun find-doc() (interactive) (require-doc-file) (require 'sshell) (or find-doc-edit(and find-doc-buffer (get-buffer-process find-doc-buffer)) (progn (setq find-doc-buffer (make-shell "find-doc" "/bin/sh" nil "-i")) (sleep-for 2) (send-string (get-buffer-process find-doc-buffer) "PS1=\n \n") )) (let (tem result (completion-ignore-case t)) (save-excursion (condition-case er (progn (forward-sexp -1) (setq tem (buffer-substring (point) (progn (forward-sexp 1) (point))))) (error))) (or (and tem (assoc (setq tem (upcase tem)) my-lisp-doc)) (setq tem nil)) (let ((symbol (completing-read "Describe symbol: " my-lisp-doc nil t tem))) (setq result (assoc symbol my-lisp-doc)) (or result (setq result (assoc (downcase symbol) my-lisp-doc))) (or result (setq result (assoc (upcase symbol) my-lisp-doc))) (or result (error (format "case mix up: %s not in my-lisp-doc keys" symbol)))) (cond (find-doc-edit (find-file-other-window doc-file-name) (goto-char (cdr result)) (set-fill-column 79) (cond ((looking-at (concat "[A-Z]" (car result))) (recenter 0) ) (t (goto-char (point-min)) (re-search-forward (concat "[A-Z\n]" (car result) "\\b")) (recenter 0) )) ) ( t (let ((old (current-buffer))) (switch-to-buffer find-doc-buffer) (erase-buffer) (goto-char (point-max)) (send-string (get-buffer-process find-doc-buffer) "echo Documentation: \n" ) (process-send-string (get-buffer-process find-doc-buffer) (format "print_doc %s %d \n" doc-file-name (cdr result))) (switch-to-buffer old) (display-buffer find-doc-buffer) result))))) ;;common lisp for creating a doc file. (defun doc-file (file packages) ;;Write FILE of doc strings for all symbols in PACKAGES ;;This file is suitable for use with the find-doc function. (with-open-file (st file :direction :output) (sloop:sloop for v in packages do (sloop:sloop for w in-package (if (packagep v) (package-name v) v) when (setq doc (documentation w 'function)) do (format st "F~a~%~a~a" w (cond ((special-form-p w) "Special Form: ") ((functionp w) "Function: ") ((macro-function w) "Macro: ") (t "")) doc) when (setq doc (documentation w 'variable)) do (format st "VVariable:~a~%~a" w doc) ))))