Emacs Lisp: Find Java Sources
Confession: I really hope someone can tell me I'm doing this wrong. I can't believe there isn't an easier way.
I work with Clojure, in Emacs, almost every day. Navigating the source is usually fairly easy. If I want to navigate to a function definition, all I need to press is
M-., and if I want to navigate back,
does the trick. This works for Clojure that I've written, as well as
Clojure that lives in the libraries that I reference. That's fine the
vast majority of the time, but occasionally I need to navigate to the
Java source of some library I'm using. This is where I can't believe
that no one else has solved this problem.* If I'm in a clj file, my
cursor is on a Java class, I don't know of any way to easily navigate to
the class definition.
Context: I use fig for dependency management (at work). I have my projects set to put sources in ./the-project/lib/sources; therefore, the following solution assumes the sources are in that directory. If you use Lein (for deps), I'm sure the sources are on your local drive somewhere. All you need to do is change the lisp below to point to your source dir.
Additionally, I use Lein, so I have a project.clj at the root of my project; however, there's nothing special or required about "project.clj". You could just as easily put a this.is.a.project.root file in the root of your project, and search for that file.
The following code will search the your source jars for a Java class, and open the first match that it finds (or no match, if no match is found)
(defun find-java-src () (interactive) (er/mark-word) (let* ((project-root (locate-dominating-file (file-name-directory (buffer-file-name)) "project.clj")) (the-str (buffer-substring-no-properties (region-beginning) (region-end)))) (if project-root (progn (grep-string-in the-str (concat project-root "lib/sources")) (switch-to-grep) (sit-for 0.25) (search-forward (concat (expand-file-name project-root) "lib/sources/")) (compile-goto-error) (let* ((current-point (point))) (search-forward-regexp ".*\.jar") (switch-to-buffer (buffer-substring-no-properties current-point (point)))) (search-forward the-str) (archive-extract)) (message (concat "no project.clj found at or below " (buffer-file-name))))))
disclaimer, I'm still an emacs lisp beginner.
The previous code is pretty straightforward. Line 3 uses expand region to mark whatever Java class my cursor is currently on or near. You could type the word instead if you like, this page should help you understand how.
Line 4 and 6 find and verify where the project root lives.
Line 8 (and 9) greps for a string (the Java class) in a directory (my source dir).
Line 10 switches to the grep results.
Line 11 sleeps, waiting for the grep results to return.
Line 12 searches forward, looking for the first match.
Line 13 opens the jar of the first match.
Line 14 assigns the current point to a var named 'current-point'.
Line 15 searches forward to the end of the jar name.
Line 16 grabs the name of the jar and switches to that buffer.
Line 17 searches the jar's dired buffer for the name of the class.
Line 18 opens the first class name match.
(Line 19 lets you know if your project root could not be determined)
That's it, you can now easily find java source (with the occasional conflicting name annoyance). It's not pretty, but it gets the job done.
* I've been told that a few Java modes are good, if I can easily use those to navigate from my Clojure to Java, please leave a link to a manual I can dig into. I assume there are etags solutions, but it's not clear what the best way to go is. I'm sure there's an easy solution for navigating Java from Clojure, I'm just having a hard time finding it.