Clementson's Blog

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

November 2008
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
Oct  Dec

Clojure SLIME Mods for Java Documentation

Thursday, November 20, 2008

As I mentioned in a previous post, I use a combination of Emacs and SLIME when I'm working with Clojure code. SLIME provides nice functionality for jumping to Clojure documentation interactively - you just press "C-c C-d C-d" when your cursor is on a Clojure symbol and you get a brief description with an argument list. For example, if my cursor is on "defn" and I press "C-c C-d C-d", I get the following:

-------------------------
clojure.core/defn
([name doc-string? attr-map? [params*] body] [name doc-string? attr-map? ([params*] body) + attr-map?])
Macro
  Same as (def name (fn [params* ] exprs*)) or (def
    name (fn ([params* ] exprs*)+)) with any doc-string or attrs added
    to the var metadata
However, there is no standard way in clojure-mode to view Java documentation. So, I used a couple of neat functions that I came across recently plus a bit of elisp code to roll my own. Now, if I press "C-c C-d C-d", I still get the standard Clojure documentation. However, if I'm on a Java class name or instance and press "C-c d", I'll get a summary of the methods for that class. So, for "Object", I'll get:
===  java.lang.Object  ===
[ 0] ()
[ 1] equals : boolean (1)
[ 2] getClass : class java.lang.Class (0)
[ 3] hashCode : int (0)
[ 4] notify : void (0)
[ 5] notifyAll : void (0)
[ 6] toString : class java.lang.String (0)
[ 7] wait : void (0)
[ 8] wait : void (1)
[ 9] wait : void (2)
And, if I'm on the integer value "42", I'll get:
===  java.lang.Integer  ===
[ 0] static MAX_VALUE : int
[ 1] static MIN_VALUE : int
[ 2] static SIZE : int
[ 3] static TYPE : class java.lang.Class
[ 4] static bitCount : int (1)
[ 5] static decode : class java.lang.Integer (1)
etc ...
If I want to read the actual JavaDoc documentation for a Java class, I can press "C-c D" and get a separate Swing window with the Java documentation in it. So, for example, pressing "C-c D" when the cursor is on "Thread" produces the following:

Javadoc for Thread

The mods I did for this were:
  1. I created a user.clj file and added it to my startup classpath. This file is automatically loaded by Clojure on startup if it is in the classpath. I put the following code (thanks to Chris Houser and Christophe Grand for the code) in my user.clj file:
    ;; From: http://groups.google.com/group/clojure/msg/96ed91f823305f02
    ;; usage:
    ;; (show Object)   ; give it a class
    ;; (show Object 1) ; a class and a method number to see details
    ;; (show {})       ; or give it an instance
    (import '(java.io LineNumberReader InputStreamReader PushbackReader) '(java.lang.reflect Modifier Method Constructor) '(clojure.lang RT))
    (defn show ([x] (show x nil)) ([x i] (let [c (if (class? x) x (class x)) items (sort (for [m (concat (.getFields c) (.getMethods c) (.getConstructors c))] (let [static? (bit-and Modifier/STATIC (.getModifiers m)) method? (instance? Method m) ctor? (instance? Constructor m) text (if ctor? (str "(" (apply str (interpose ", " (.getParameterTypes m))) ")") (str (if (pos? static?) "static ") (.getName m) " : " (if method? (str (.getReturnType m) " (" (count (.getParameterTypes m)) ")") (str (.getType m)))))] [(- static?) method? text (str m) m])))] (if i (last (nth items i)) (do (println "=== " c " ===") (doseq [[e i] (map list items (iterate inc 0))] (printf "[%2d] %s\n" i (nth e 2))))))))

    ;; From: http://clj-me.blogspot.com/2008/05/jumping-to-javadocs-from-repl.html ;; usage: ;; (javadoc Throwable) opens a window displaying Throwable's javadoc ;; hint: (javadoc (class some-object))
    (defn open-url [url] (let [htmlpane (new javax.swing.JEditorPane url)] (.setEditable htmlpane false) (.addHyperlinkListener htmlpane (proxy [javax.swing.event.HyperlinkListener] [] (hyperlinkUpdate [#^javax.swing.event.HyperlinkEvent e] (when (= (.getEventType e) (. javax.swing.event.HyperlinkEvent$EventType ACTIVATED)) (if (instance? javax.swing.text.html.HTMLFrameHyperlinkEvent e) (.. htmlpane getDocument (processHTMLFrameHyperlinkEvent e)) (try (.setPage htmlpane (.getURL e)) (catch Throwable t (.printStackTrace t)))))))) (doto (new javax.swing.JFrame) (setContentPane (new javax.swing.JScrollPane htmlpane)) (setBounds 32 32 700 900) (show))))
    (defn javadoc [c] (let [url (str "http://java.sun.com/javase/6/docs/api/" (.. c getName (replace \. \/) (replace \$ \.)) ".html")] (open-url url)))
    ; usage: ; (javadoc Throwable) opens a window displaying Throwable's javadoc ; hint: (javadoc (class some-object))
  2. I then added the following lines to my .emacs file (Note: I already have SLIME setup, this is just additional code for the show/javadoc functionality):
    (defun slime-java-describe (symbol-name)
      "Get details on Java class/instance at point."
      (interactive (list (slime-read-symbol-name "Java Class/instance: ")))
      (when (not symbol-name)
        (error "No symbol given"))
      (save-excursion
        (set-buffer (slime-output-buffer))
        (unless (eq (current-buffer) (window-buffer))
          (pop-to-buffer (current-buffer) t))
        (goto-char (point-max))
        (insert (concat "(show " symbol-name ")"))
        (when symbol-name
          (slime-repl-return)
          (other-window 1))))
    (defun slime-javadoc (symbol-name) "Get JavaDoc documentation on Java class at point." (interactive (list (slime-read-symbol-name "JavaDoc info for: "))) (when (not symbol-name) (error "No symbol given")) (set-buffer (slime-output-buffer)) (unless (eq (current-buffer) (window-buffer)) (pop-to-buffer (current-buffer) t)) (goto-char (point-max)) (insert (concat "(javadoc " symbol-name ")")) (when symbol-name (slime-repl-return) (other-window 1)))
    (add-hook 'slime-connected-hook (lambda () (interactive) (slime-redirect-inferior-output) (define-key slime-mode-map (kbd "C-c d") 'slime-java-describe) (define-key slime-repl-mode-map (kbd "C-c d") 'slime-java-describe) (define-key slime-mode-map (kbd "C-c D") 'slime-javadoc) (define-key slime-repl-mode-map (kbd "C-c D") 'slime-javadoc)))
Much more convenient then having to switch to a browser everytime I want to lookup some information on a Java class!

Update-2008-11-21: There was a breaking change to "doto" that would result in a Java error if you used the "openurl" code as defined above. The following link describes the change to "doto": http://groups.google.com/group/clojure/msg/5f65e55b902751f8
You'll need to update the "openurl" code if you use it in a later Clojure svn version. Thanks to Juergen Gmeiner for the bug report!

Update-2008-11-22: Kei Suzuki prefers to view his javadoc in a browser using local copies of the Java documentation. He sent me the following nice elisp snippet:
(setq slime-browse-local-javadoc-root "/Users/Shared/SDKs/J2SE/5.0")
(defun slime-browse-local-javadoc (ci-name) "Browse local JavaDoc documentation on Java class/Interface at point." (interactive (list (slime-read-symbol-name "Class/Interface name: "))) (when (not ci-name) (error "No name given")) (let ((name (replace-regexp-in-string "\\$" "." ci-name)) (path (concat (expand-file-name slime-browse-local-javadoc-root) "/docs/api/"))) (with-temp-buffer (insert-file-contents (concat path "allclasses-noframe.html")) (let ((l (delq nil (mapcar #'(lambda (rgx) (let* ((r (concat "\\.?\\(" rgx "[^./]+\\)[^.]*\\.?$")) (n (if (string-match r name) (match-string 1 name) name))) (if (re-search-forward (concat "<A HREF=\"\\(.+\\)\" +.*>" n "<.*/A>") nil t) (match-string 1) nil))) '("[^.]+\\." ""))))) (if l (browse-url (concat "file://" path (car l))) (error (concat "Not found: " ci-name)))))))
(add-hook 'slime-connected-hook #'(lambda () (define-key slime-mode-map (kbd "C-c b") 'slime-browse-local-javadoc) (define-key slime-repl-mode-map (kbd "C-c b") 'slime-browse-local-javadoc)))
Kei also noted:
"You may already know this, but just in case: when you open a local JavaDoc html file on Mac, you may get the "Are you sure you want to open it?" dialog box. You won't see it next time you open the same html file, but it's really annoying to see it for any html files being opened first time. You can stop it by running the xattr command like this:
xattr -d com.apple.quarantine `find . -name "*.html"`
Unfortunately xattr has a limited buffer for the file names to process so that you can't run the above command on the docs directory once for all or it'll fail. Instead, run the command on the java, javax, and org directories under the docs/api directory separately."

emacs Copyright © 2008 by Bill Clementson