Get by in C

(With a Little Help from Etags and Eldoc)

Tim Daly, Jr.
tim@tenkan.org
We explain how to use etags and eldoc to make emacs automatically display a C function's signature — its name, parameter names and types, and return type — whenever your cursor is over the function's name.

Introduction

Recently, a friend of mine asked if I knew a good way to make emacs display function signatures in the echo area while you write code, somewhat like (I assume) Visual C's intellisense feature. Sure, no problem, I thought. I'm sure that's trivial.

Well, I spent several hours googling the various approaches and trying them out. Nothing was really satisfying. CEDET is massive and complex and made my emacs slow. It did the job, but it did too many other jobs at the same time. I was not able to get c-eldoc mode to work. The poor-man's eldoc approach that I found on usenet was still fairly complex, and required that you maintain a separate database of function signatures.

Heck, reinventing the wheel is more fun anyway. I've learned from my friends Luke Gorrie and Tony Martinez that, if you're not programming emacs, then you're not even scratching the surface of what emacs can do. So I set out to write eldoc mode for C, using etags to grab the function signature straight from the source file. The result works well enough that I enjoy using it, and all it requires is a little modification to your .emacs, and a TAGS file. (A TAGS file is very important for efficiently dealing with a large C project, so I think many programmers will already be maintaining one.)

Extending eldoc

Eldoc mode is what elisp programmers use when they want emacs to show documentation for symbols in elisp code. The documentation is printed in the echo area, (the bottom line of your emacs window), and it's printed in a way that avoids being annoying — it waits until you stop typing for a moment. As soon as the documentation is no longer relevant to what's under your cursor, it goes away. If we could extend eldoc mode, we would get a lot for free.

Emacs makes it easy to extend any of its built-in functionality using a feature called advice. Advice makes every function in emacs extensible like a Common Lisp generic function. You can add code that runs before the function, code that runs after the function, and even replace the function entirely. We use advice to wrap the function which eldoc mode uses to display documentation, and make it delegate instead to our own function if the user is in c-mode:

(defadvice eldoc-print-current-symbol-info
    (around eldoc-show-c-tag activate)
  (if (eq major-mode 'c-mode)
      (show-tag-in-minibuffer)
      ad-do-it))
  

override eldoc-mode's doc printer thingy

Using etags to Find Tags

If you're going to work with C in emacs, you must use tags. For a long time, I used to grep through the source code to find definitions. If you know the source code like the back of your hand, that might be an infrequent enough occurrence that it doesn't matter. But, especially if you are working in source code that's mostly new to you, hunting for definitions is an unwanted distraction. By using tags, you can hop to a definition (M-.) and back (M-*) instantly.

To use etags, you need to maintain a TAGS file, also called a tags table. The file can usually be created and left alone; you only need to update it if you've changed the set of definitions that you'd like to be able to look up. To make a new tags table, you pass the set of files that you want to scan for function and variable definitions to the etags command. Often people create a makefile target to regenerate the tags table. In my current project, I'm using a shell script:

#!/bin/sh

etags `find . -name '*.[ch]' | grep -v SCCS |grep -v Decls.h`    
  

maketags.sh

Once created, the tags table can be loaded into emacs using the visit-tags-table command. If you update it, emacs will see that and prompt to load the new one.

In this case, we use etags functionality to check if there is a function synopsis for the thing that the user is currently looking at. There is a bit of a wrinkle, however. After failing to find a tag that's an exact match, etags backs off and tries tags that are close. I was unable to find a simple way to disable that behavior, but I did discover that you can use a regular expression to constrain what matches.

(defun show-tag-in-minibuffer ()
  (when tags-table-list
    (save-excursion
      ;; shadow some etags globals so they won't be modified
      (let ((tags-location-ring (make-ring find-tag-marker-ring-length))
            (find-tag-marker-ring (make-ring find-tag-marker-ring-length))
            (last-tag nil))
        (let* ((tag (funcall 
                     (or find-tag-default-function
                         (get major-mode 'find-tag-default-function)
                         'find-tag-default)))
               ;; we try to keep M-. from matching any old tag all the
               ;; time
               (tag-regex (format "\\(^\\|[ \t\n*]\\)%s\\($\\|(\\)" 
                                  (regexp-quote tag))))
          (set-buffer (find-tag-noselect tag-regex nil t))
          (let ((synopsis (or (thing-at-point 'function-synopsis)
                              (thing-at-point 'line))))
            (when synopsis
              (eldoc-message "%s" 
                             (cleanup-function-synopsis synopsis)))))))))

fetch a tag, jump to it, grab what looks like a function synopsis, and output it in the minibuffer (echo area).

The Things at Point

If a tag is found, we hop to it, grab the function synopsis there, clean it up, and display it. The point is the current location in an emacs buffer. Emacs lets you ask for the thing at the point. You just need to tell it what kind of thing you want. So if we define a new kind of thing called a function-synopsis, we'll have an easy way to grab it:

(put 'function-synopsis 'beginning-op
     (lambda () 
       (if (bolp) (forward-line -1) (beginning-of-line))
       (skip-chars-forward "^{")
       (dotimes (i 3) (backward-sexp))))

(put 'function-synopsis 'end-op
     (lambda () (skip-chars-forward "^{")))
  

make 'function-synopsis a new thing for thing-at-point

Housekeeping

People format their C code in the most astounding ways. I think it's because they can't hit C-M-q and have it automatically fixed, but I'm not sure. In order to avoid displaying big multi-line things in the minibuffer, we strip all the extra whitespace, newlines, and comments out of the function signature. Emacs regular expressions are perfect for this:

(defun cleanup-function-synopsis (f)
  ;; nuke newlines
  (setq f (replace-regexp-in-string "\n" " " f))
  ;; nuke comments (note non-greedy *? instead of *)
  (setq f (replace-regexp-in-string "/\\*.*?\\*/" " " f))
  ;; (just-one-space)
  (setq f (replace-regexp-in-string "[ \t]+" " " f))
  f)
  

strip whitespace and comments from a function signature

Finally, if we want to put our code into .emacs, we would do well to ensure that etags and eldoc are loaded first:

(require 'eldoc)
(require 'etags)
  

ensure the eldoc and etags will be loaded

We can also automatically enable eldoc mode when visiting a C file. (Note that it still won't display any signatures until a tags file is loaded):

(add-hook 'c-mode 'turn-on-eldoc-mode)    
  

turn it on

Conclusion

All the code required to present C function signature hints in the emacs info area has been presented in this article, but if you'd like to download it all at once, you can get it here: c-function-signature.el. I recommend simply pasting the file's contents into your .emacs.

If you have any comments, suggestions, etc., I would love to hear from you! Please send an email to the address at the top of the page, and mention something like function-signature in the subject. Thank you for taking the time.