Clementson's Blog

Bits and pieces (mostly Lisp-related) that I collect from the ether.

January 2007
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Dec  Feb

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:

You can configure cl-lookup to not provide lookup on certain things too - for instance, if you don't use CLISP, you don't want lookup for CLISP symbols. The existing hyperspec.el also provides some of these lookups, but not as completely. However, the really nice thing about cl-lookup is that it provides a standardized approach to adding new documentation. So, if there's some library that you use quite a bit that you want to be able to lookup documentation for in the same manner as with the CLHS, it's just a matter of creating the appropriate set of mappings. That said, creating a mapping file for documentation is pretty boring, error-prone, time-consuming, and the mapping needs to be manually updated each time there's a new release of a library. And, we all know about the 3 virtues of a programmer, so, of course, I decided to see what I could do about automating this process.

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)
(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)))))
Basically, I'm doing the following:
  1. 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).
  2. 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.
  3. In the create-lookup-file function:
    1. I use ASDF to load each library so that I can process the library's externally-visible symbols.
    2. I write out the base code needed for any documentation that will be added to the cl-lookup documentation lookup.
    3. 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.
This creates one elisp file for each library. Then, in my .emacs file, I add the following to setup cl-lookup (Note: the following represents the subset of my .emacs file necessary to show how cl-lookup is setup with SLIME and is runnable as a stand-alone .emacs file once the paths are setup correctly):
;; Set up load path
(setq load-path (append (list "/path/to/slime"
			      "/path/to/mcomplete"
			      "/path/to/w3m"
			      "/path/to/cl-lookup")
			load-path))
(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)))
There are a few points to note: So, when I'm in a SLIME buffer, I can enter the name of a symbol in one of Edi's libraries and press "C-c C-d h" to bring up the documentation for that symbol. In fact, I can even enter just part of the name and get partial-name completion (using mcomplete) as is shown here:

cl-lookup

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:

cl-lookup

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.

emacs Copyright © 2007 by Bill Clementson