QZ qz thoughts
a blog from Eli the Bearded

vi and tags


The vi editor, and the significant vi-clones, as well as Emacs and other editors, support a thing called a "tags file". It's essentially a set of bookmarks or a book's "Index" section fore text files.

The intended use is you run a program that indexes your source code and creates the tag entries for you. For example, with ctags you can run it and have it scan source code in dozens of languages (not just C-like ones, such as Java and Go, but also Postscript, Fortran, and others) and produce a file that tells the editor how to find function and variable declarations. (For Emacs, use the etags program to similar effect.)

The tags functionality is very useful when editing code. I keep tags files in source directories for all of my multi-year projects. You can begin and edit session by running vi -t pickle to open the editor to the file and line that pickle() is defined. Say inside pickle() you find a call to spices() and want to know what it does, position your cursor in the word and hit <ctrl-]>, you jump there. Return to where you were with a :pop. Without the keyword handy to <ctrl-]> upon, you can instead :tag spices as well.

For people using vim as their vi of choice, it might help to know that the entirety of the :help system is built using tags, just slightly tweaked for where to look for the tags file. If you know how to use help in Vim, you know how to use tags. And :help tags can probably teach you something you didn't already know about them.

The tags files are a little more mysterious. Normally people don't create or edit them by hand, but you can or you can create programs to create them for your own special needs.

The basic format, and all I'll cover here, is a text file with three tab separated columns. The first column is the tag name, eg pickle. The second column is the file name, relative to where the tags file is located. The third column is a ex-mode movement within the file. Usually the movement is a search, something like /^int pickle(recipe_t rec)$/ that will unambigously find a single line in the file. But line numbers also work just fine. And search with line number offset works for niche needs, eg /^int pickle(.*)$/+2 to start out on the variable declarations if your syntax looks like the following sample. With :set scrolloff=3 to put some space above the cursor, it may be more useful for you.

#include "brine.h"

int pickle(recipe_t rec)
{
    long     cuke;
    int      salt;
    spices_t spicing = spices(rec);

    /* ... */
}

In traditional vi any command you could put on a : line would work in the movement column, including things like :! rm *. I know Vim has tightened that, and I believe the other vi-clones have as well. If Vim doesn't like any movement in a tags file, it will ignore the whole file.

One trick I have found useful is programmatically generated tags files created with a wrapper program. Rather than have a giant file with all tags, I have a database that I can query and then it generates a tags file (with a single entry) and invokes vim -t main to jump to the exact file and line for me. This can make my query easier to form than remembering a specific tag keyword and does not involve a large flat file with huge redundancy.