SLIME Tips and Techniques - Part 4 (Library Documentation)
Monday, January 29, 2007
If you use
SLIME, the key chord "C-c C-d h" (which brings up
the
Common Lisp Hyperspec (CLHS) documentation for the CL symbol that
is at "point" in the current SLIME buffer) is probably used quite
frequently. However, the hyperspec.el code that ships with SLIME is
limited in that it only looks up documentation for symbols that are
defined in the CL standard.
In my .emacs file, I re-bind "C-c C-d h" to the
cl-lookup function. This is an elisp package created by Yuji
Minejima and it builds on and extends the hyperspec.el functionality
provided by SLIME. Out-of-the-box, cl-lookup provides lookup
for:
- hyperspec symbols (e.g. defun, dolist)
- hyperspec chapters (e.g. [index], [syntax])
- format control characters (e.g. "~C: Character", "~%: Newline")
- reader macro characters (e.g. "(", "#'", "#b", "#+")
- loop (e.g. loop:with, loop:collect)
- arguments (e.g. :test, :key, :eof-error-p)
- concepts (e.g. "lambda lists:", "character names:")
- glossary (e.g. {absolute}, {binding})
- MOP (e.g. add-dependent, ensure-class)
- Implementation-specific help for some CLISP functions (e.g. ext:cd)
- Library-specific help for the CL-PPCRE regular expression package (e.g. cl-ppcre:parse-tree-synonym)
First of all, not all libraries are going to have good, HTML-based documentation (or even documentation at all!). Also, some documentation will not lend itself to being programmatically accessed (if tags are not included in the documentation appropriately). Luckily, a lot of the libraries that I use on a regular basis were written by Edi Weitz and he writes both good documentation and documentation that is easy to programmatically access. Edi uses his own DOCUMENTATION-TEMPLATE utility to generate the base page for the HTML documentation of the libraries he writes. He then annotates and adds additional information to the page. However, the key point is that, when he generates the template, DOCUMENTATION-TEMPLATE grovels over all of the external symbols in his library and writes out the base documenation for the symbols using their documentation strings. Therefore, I know that (a) there will be documentation for all external symbols in the library and (b) how the link to the symbol's documentation will be constructed. So, it was pretty easy to write a function that would automatically generate the cl-lookup mappings for any of Edi's libraries:
(require :asdf)Basically, I'm doing the following:
(defparameter *packages-to-process* '((cl-ppcre "http://www.weitz.de/cl-ppcre/") (cl-fad "http://weitz.de/cl-fad/") (hunchentoot "http://weitz.de/hunchentoot/") (chunga "http://weitz.de/chunga/") (url-rewrite "http://weitz.de/url-rewrite/") (cl-who "http://weitz.de/cl-who/")) "List of packages and their documentation root urls.")
(defvar *base-code* "(require 'cl-lookup) (defvar cl-lookup-~a-root ~s)
(mapc #'(lambda (entry) (destructuring-bind (name path) entry (let ((symbol (intern (downcase name) cl-lookup-obarray))) (if (boundp symbol) (pushnew path (symbol-value symbol) :test #'equal) (set symbol `(,path)))))) '((~s (cl-lookup-~a-root ""))~%")
(defun create-lookup-file (&optional (packages *packages-to-process*)) (dolist (p packages) (let* ((package (progn (asdf:oos 'asdf:load-op (first p)) (first p))) (package-name (string-downcase (symbol-name package))) (url (second p)) (output-file (format nil "cl-lookup-~a.el" package-name)) (symbols (sort (loop for sym being each external-symbol of package collect (list (string-downcase (symbol-name sym)))) #'string-lessp :key #'car))) (with-open-file (s output-file :direction :output :if-exists :supersede) (format s *base-code* package-name url package-name package-name) (mapcar (lambda (sym) (format s "~8t("~a:~a" (cl-lookup-~a-root "#~a"))~%" package-name (string-downcase (car sym)) package-name (string-downcase (car sym)))) symbols) (format s "~8t))~%~%(provide 'cl-lookup-~a)" package-name)))))
- In the *packages-to-process* variable, I set the package names and base URL's (where the documentation for the package is located) for a number of Edi's libraries (in this case, it's for HUNCHENTOOT and its dependencies).
- The *base-code* variable contains the elisp code that needs to be written out to add the library mappings to the cl-lookup object array.
- In the create-lookup-file function:
- I use ASDF to load each library so that I can process the library's externally-visible symbols.
- I write out the base code needed for any documentation that will be added to the cl-lookup documentation lookup.
- I write out a sexp linking each of the library's symbols to the place in the HTML documentation where the documentation for the symbol is located.
;; Set up load path (setq load-path (append (list "/path/to/slime" "/path/to/mcomplete" "/path/to/w3m" "/path/to/cl-lookup") load-path))There are a few points to note:
(autoload 'slime "slime" "Start SLIME." t)
(add-hook 'lisp-mode-hook (lambda () (cond ((not (featurep 'slime)) (require 'slime) (normal-mode)))))
(eval-after-load "slime" '(progn ;; SLIME setup (setq slime-lisp-implementations `((openmcl ("dppccl")) (sbcl ("sbcl")) ,@slime-lisp-implementations)) (slime-setup) ;; Use w3m for viewing HTML documentation (require 'w3m-load) (setq browse-url-browser-function 'w3m-browse-url) ;; Use mcomplete for partial completions (require 'mcomplete) (turn-on-mcomplete-mode) ;; Setup CL-LOOKUP for enhanced documentation access (setq cl-lookup-categories ;; basic CL documentation '(:hyperspec-index :hyperspec-chapters :format-control-characters :reader-macro-characters :loop :arguments :concepts "cl-lookup-glossary" "cl-lookup-mop" ;; non-CLHS library documentation "cl-lookup-cl-ppcre" "cl-lookup-cl-fad" "cl-lookup-chunga" "cl-lookup-cl-who" "cl-lookup-hunchentoot" "cl-lookup-url-rewrite" )) (put 'cl-lookup 'mcomplete-mode '(:method-set (mcomplete-substr-method mcomplete-prefix-method) :exhibit-start-chars 1)) (require 'cl-lookup) (defun cl-lookup (entry) "View the documentation on ENTRY from the Common Lisp HyperSpec, et al." (interactive (list (let ((default (cl-lookup-default-entry))) (completing-read (concat "Look up Common Lisp info" (when default (format " (%s)" default)) ": ") cl-lookup-obarray #'boundp t (thing-at-point 'symbol) 'cl-lookup-history default)))) (let* ((symbol (intern-soft (downcase entry) cl-lookup-obarray)) (paths (if (and symbol (boundp symbol)) (symbol-value symbol) (error "cl-lookup internal error: the path of %s is not defined" name)))) (loop for (path . rest) on paths do (cl-lookup-browse path) if rest do (sleep-for 1.5)))) ;; Override SLIME hyperspec lookup key bindings (define-key slime-mode-map (kbd "C-c C-d h") 'cl-lookup) (define-key slime-repl-mode-map (kbd "C-c C-d h") 'cl-lookup)))
- I use w3m for browsing documentation in SLIME. I generally use Firefox for normal Internet browsing but prefer to use an Emacs-based browser when working with Lisp code in SLIME as it's easier to copy/paste things and I don't have to leave Emacs to view the docs. However, w3m is not really needed if you prefer to use a different browser.
- I use mcomplete.el for minibuffer completion and substring matching with cl-lookup. The reasons for this will become apparent in my comments below. Even though mcomplete is not really needed, it is really convenient to use as it provides partial-string searches, an iswitchb-like interface and is generally useful for many minibuffer-completion requirements (not just cl-lookup).
- In the "non-CLHS library documentation" part of the cl-lookup-categories definition, you'll see that I've specified the names of the 6 libraries that I generated documentation for using my create-lookup-file function. These 6 files can be placed anywhere in the load-path.
- I have overridden the definition of cl-lookup. The only change I made was so that the text at point is displayed in the minibuffer for completion purposes (this makes it easy to do either partial name entry and complete with mcomplete or to enter symbols that aren't prefixed with their package name). The existing cl-lookup function needs to either be patched locally or the new definition loaded (as is done here) after the base cl-lookup is loaded.
- I override the "C-c C-d h" SLIME bindings for hyperspec lookup so that they call cl-lookup instead.

As shown in this image, when I entered the characters "spl" in the REPL buffer and pressed "C-c C-d h", I was presented with a choice of either the standard CLHS function "array-displacement" or the CL-PPCRE function "split". Selecting the CL-PPCRE:SPLIT function results in the HTML documentation for that function (from Edi's web site) being displayed in the w3m browser:

In create-lookup-file, I also generate a shortcut to the documentation page to facilitate easy browsing (for example, if I press "C-c C-d h" and type in "hun", I can select "hunchentoot" from the mcomplete list of completions and be taken to the Hunchentoot page).
I find cl-lookup (now that the documentation elisp files have been generated with my create-lookup-file function) helps me to quickly access the documentation of the libraries that I use most often. Which also means that I use the documenation more often as it is always easily accessible!
Update-2007-01-29: Zach Beane sent me an email mentioning that Edi's libraries already support the Hyperdoc protocol. In the Hyperdoc CVS, there is also a SLIME patch that adds the start of support for documentation lookup via Hyperdoc. If you're interested in reading a bit more on the hyperdoc approach, there's also a CLiki page. Blast! Don't you hate it when you spend time thinking up something that's pretty nifty and then find out that someone else has already done something similar! Oh well, que sera, sera.

