Managing Ctags for Vim

(Day 23 of 30 Days of Blogging)

Ctags is the plugin-free way to do code navigation in vim.

For single projects it works great out of the box. Just build your ctags file and point vim at it. But what about when you have multiple distinct project directories but still need to frequently jump around between them? That calls for multiple ctags index files, and managing that can be a bit of a hassle.

So I wrote a little script called ctags-update that builds an index and puts it in a special cache directory. Vim uses that cache directory to load tags. When I’m done working on a project, I run ctags-remove to remove it from the cache.

Here’s ctags-update:

#!/bin/sh -e
# Adds project tags to the global index.

# project_root=`git rev-parse --show-toplevel`
flattened=`echo $project_root | sed 's!/!%!g'`

# It's in a weird subdirectory with a single file called tags because the vim "tags" option doesn't
# support wildcards in the form ~/.cache/vim-ctags/*
mkdir -p "$tags_db"/"$flattened"

# Remember: Paths in the tags file must be absolute
if [ -d "$project_root/.git" ]; then
    # Include only files tracked in git
    git ls-files | awk -v pwd="$PWD/" '{print pwd $0}'
    # Otherwise include ALL files
    find "$project_root"/ -type f
fi | ctags -L - -f "$tags_db"/"$flattened"/tags

echo "Updated ctags for $project_root, to undo run ctags-remove."

Running this will place a tags file in ~/.cache/vim-ctags. To remove it, just delete it, or use ctags-remove:

# project_root=`git rev-parse --show-toplevel`

flattened=`echo $project_root | sed 's!/!%!g'`

rm -r "$tags_db"/"$flattened"

And lastly configure vim to use the ctags cache directory:

set tags=$HOME/.cache/vim-ctags/*/tags

Now you just need to call ctags-update whenever your code changes, but even that can be automated away with git hooks. Once that’s all set up, ctags fades into the background and you never have think about it.