"Jump to Definition" in Emacs

Navigating from a variable's usage to its definition (or declaration) and back is an important part of any IDE. I haven't found a standard name for this behavior: Visual Studio calls it "Go to Definition"; Xcode calls it "Jump to Definition" (depending on the implementation it can also be called tagging/indexing source code). Emacs has two ways, that I know of, to achieve this feature: etags.el (now deprecated) and its successor xref.el.

etags.el

The standard way to achieve code navigation used to be to generate an index of all source code for your project. The index would basically map a name to a line number of a particular file. ctags was the original program that did this, and was written for Unix in the 90's. I think the origin of its name is that it generates tags for the C language (it also supported Pascal, Fortran, LISP, yacc and lex, but defaulted to C)1. There are two modern implementations of ctags: exuberant-ctags (no longer maintained) and universal-ctags (an active fork of exuberant-ctags).

For some reason, Emacs developed its own version of ctags called etags that generates a tag table in a different format. I'm not sure why emacs didn't just use ctags and its table format. The modern forks of ctags can generate a tags table for Emacs with the -e parameter.

etags.el has been deprecated since Emacs 25.1 in favor of xref. It provides M-x find-tag for jumping to definitions.

I decided to try both ctags and etags with Clojure files. I found a stackoverflow question that suggested using this code to generate tags:

find . \! -name '.*' -name '*.clj' | xargs etags --regex='/[ \t\(]*def[a-z]* \([a-z-!]+\)/\1/' --regex='/[ \t\(]*ns \([a-z.]+\)/\1/'

Running this failed with etags: Invalid range end while compiling pattern. I had to transpose the ! and the - to get it to work:

find . \! -name '.*' -name '*.cljs' | xargs etags --regex='/[ \t\(]*def[a-z]* \([a-z!-]+\)/\1/' --regex='/[ \t\(]*ns \([a-z.]+\)/\1/'

It also provided a way with ctags:

(defvar path-to-ctags "/usr/local/bin/ctags")

;; Recursively generate tags for all *.clj files, 
;; creating tags for def* and namespaces
(defun create-clj-tags (dir-name)
  "Create tags file."
  (interactive "DDirectory: ")
  (shell-command
   (format "%s  --langdef=Clojure --langmap=Clojure:.clj --regex-Clojure='/[ \t\(]*def[a-z]* \([a-z!-]+\)/\1/'  --regex-Clojure='/[ \t\(]*ns \([a-z.]+\)/\1/' -f %s/TAGS -e -R %s" path-to-ctags dir-name (directory-file-name dir-name))))

Now M-x find-tag can use these tables to jump to definitions. Keep in mind that using tags tables for source navigating may require some tuning of your workflow. For example, navigating from a project to its libraries can be complicated2 for some languages (and easy for others3) and you have to plan for the tags table to get invalidated as you edit your code.

xref.el

xref.el uses a backend provided by a major mode to find identifiers (and more). A backend can be implemented using a tags table, language server, or anything else:

Plain old tags table

I tried using xref-find-definitions with the two above tags tables, but only the etags table worked. Not sure why.

Language Servers

There are at least two emacs clients for the Language Server Protocol: eglot.el and lsp.el. To use these packages, install a language servers for the desired programming language. For C++, e.g., there are ccls and clangd.

There are also other language servers such as rtags.

CIDER

For Clojure, you can use CIDER which implements an xref backend:

https://github.com/clojure-emacs/cider/commit/15b6d205b6311453349144afc20d7ac4a820b0ab

Once a CIDER connection has been established and a source buffer compiled, xref-find-definitions can accurately jump to symbol definitions regardless of where they are (just as long as they have successfully compiled).

Other Emacs Packages

Footnotes:

1

From the ctags man page:

File names ending with any other suffixes are first examined to see if they contain any Pascal or FORTRAN routine definitions. If not, they are processed again as C-language source code. Files without a . (dot) suffix are processed as C-language source code.

hi