navigating through source code using tags

[Updated] I was always amazed by the way emacs gurus navigated through source code. It almost seemed like magic. They see some function call, and then with some short keyboard combo jump to another file, on the exact place where the function was defined.

The 'magic' in this case is a tag file. A tag file is a file that maintains an index of all the symbols in source code, where they are used and where they are defined. If you use grep and the like to look for things, you'll find tag files very useful.

There are different programs to create and deal with tag files; emacs ships with etags and there's also ctags. I'm not too familiar with those systems, but instead rely on GNU-Global for my tagging needs; it's available for Unix-like systems. The instructions below assume that you have installed GNU-Global.

tags from the command line


First, let's see how to create and update a tag file from the command line. GNU-Global works recursively on a directory tree; that is, you can run it in the top of your source directory, and it will then index all the files found, traversing through subdirectories. To create a tag file, go to the top of your source tree and run:
$ gtags

This will create a bunch of files (GPATH, GRTAGS, GSYMS and GTAGS) that together form your tagfile.
If you have run gtags before, and only want to update for changes in your source files, run:
$ global -u

This is usually much faster than running gtags. Also note that you can run this from anywhere in your source tree. The program will locate the G*-files by itself.

After that, you can search for symbols etc from the command line; please refer to the global man page for details. However, let's see how we can do all that from within emacs.

Using GNU-Global from emacs


GNU-Global comes with support for emacs, which makes it very convenient to use. It still relies on the command line to use create / update the tagfiles though; but we can do something about that with a bit of elisp. First, we define the following function djcb-gtags-create-or-update in our .emacs:
(defun djcb-gtags-create-or-update ()
"create or update the gnu global tag file"
(interactive)
(if (not (= 0 (call-process "global" nil nil nil " -p"))) ; tagfile doesn't exist?
(let ((olddir default-directory)
(topdir (read-directory-name
"gtags: top of source tree:" default-directory)))
(cd topdir)
(shell-command "gtags && echo 'created tagfile'")
(cd olddir)) ; restore
;; tagfile already exists; update it
(shell-command "global -u && echo 'updated tagfile'")))

This function will check if there is an existing tagfile; if so, it will update it. If not, it asks where to create the tag file; you should provide the name of the top of your source directory there.

Now, we can automatically run this function whenever we open a C/C++/...-file, so we always have an up-to-date tagfile available:

(add-hook 'gtags-mode-hook 
(lambda()
(local-set-key (kbd "M-.") 'gtags-find-tag) ; find a tag, also M-.
(local-set-key (kbd "M-,") 'gtags-find-rtag))) ; reverse tag

(add-hook 'c-mode-common-hook
(lambda ()
(require 'gtags)
(gtags-mode t)
(djcb-gtags-create-or-update)))

I've found GNU-Global fast enough to run djcb-gtags-create-or-update automatically. However, if you work with really huge code bases (for example, the linux kernel sources) it's better to use a tag file for some not-too-big sub-tree, or turn tagging off. To turn off automatic updating/creating tagging for some particular directory, you could do something like:
[Update: fix elisp error, thanks Anatoly]
(add-hook 'c-mode-common-hook
(lambda ()
(require 'gtags)
(gtags-mode t)
(when (not (string-match "/usr/src/linux/" (expand-file-name default-directory)))
(djcb-gtags-create-or-update))))

instead of the above add-hook. You can then still run djcb-gtags-create-or-update by hand for some particular subdirectory.

usage


Now, having done all this, tagging is quite easy - and quickly through your source code is even easier. To find the definition of a function (or symbol), type M-. (Alt + dot). If your cursor is on a function name (or on other symbol), it will be the default target. Otherwise, you type the name; autocompletion is available with the Tab-key.

If you want to do the reverse, ie. finding all the uses of a certain symbol, use M-, (Alt + comma) and we get a list of locations. There are some more functions available, all starting with gtags-; see the built-in documentation for details.

No comments:

Post a Comment

Followers

Popular Posts