Combining vim with ctags yields a powerful combination for working with large or unfamiliar codebases.
What is ctags?
Ctags is a tool that will sift through your code, indexing methods, classes,
variables, and other identifiers, storing the index in a tags
file. The tags
file contains a single tag per line. Depending on command line arguments and the
language ctags is run against, a lot of information can be obtained from this
index.
Ctags currently supports 41 programming languages, and it’s relatively easy to add definitions for more.
Ctags makes it much easier to navigate a larger project, particularly if the code you’re working with is unfamiliar. If you’re unsure of what a method does or how it’s supposed to be called, you can jump straight to it’s definition. If you’re in the downward spiral of a 500+ line Perl script and want to know where a variable was defined three hours ago, you can jump right back to it. And afterwards, you can jump right back to where you were working.
Installing ctags
You can install ctags using a package manager.1 If you’re on OS X, use homebrew:
brew install ctags
OS X comes with a ctags
executable, but it’s not exuberant-ctags, and is
missing most of the useful features.
Using ctags
If you’re currently sitting in the directory you want to index, just run:
ctags -R .
Ctags will walk through the directory recursively, tagging all source files it encounters. For very large projects, this might take a while, but normally it’s pretty fast.
I normally don’t like having a tags
file in plain sight in the source
directory, so I keep it a little bit more hidden, in the .git
folder:
ctags -R -f ./.git/tags .
This can be a pain in the ass to run regularly, so you might like to bind it to a vim keyboard shortcut so you can run it every so often, or add some git hooks to run ctags every time you check out, commit, or fetch with git.
Using ctags from vim
If you’ve already run ctags in the current project folder, vim will
automatically pick up your tags file (even in the .git
directory), and you can
start using it right away. Take this bit of Ruby code, for example:
class TestCase
def assert_equal(expected, value)
# do_stuff_with_args
end
end
class ModelTest < TestCase
assert_equal true, model_function
end
If you put your cursor over ModelTest
‘s assert_true
call in normal mode and
press <C-]>
, vim will jump to assert_true
‘s definition in the TestCase
class,
even if they’re in different files. You can continue down this rabbit hole, if
you choose, and when you’re done, press <C-t>
to climb back up the tree.2
You can also go directly to a tag’s definition by entering one of the following in vim’s command mode:
:tag function_name
:ta function_name
These commands will also accept regular expressions, so, for example, :tag /^asserts_*
would find all tags that start with ‘asserts_’. By default vim will
jump to the first result, but a number of commands can be used to sort through
the list of tags:
-
:ts
or:tselect
shows the list -
:tn
or:tnext
goes to the next tag in that list -
:tp
or:tprev
goes to the previous tag in that list -
:tf
or:tfirst
goes to the first tag of the list -
:tl
or:tlast
goes to the last tag of the list
To show the tags you’ve traversed since you opened vim, run :tags
.
Vim + Ctags + Ctrlp
If you’re using the Ctrlp plugin for vim, running :CtrlPTag
will let you
search through your tags
file and jump to where tags are defined. Very useful
when you need to jump around a project in a hurry.
It’s also handy to bind this to a keyboard shortcut. I use this in my ~/.vimrc
:
nnoremap <leader>. :CtrlPTag<cr>
Vim + Ctags + Tagbar
Tagbar is another useful vim plugin for working with a tags
file. Install it,
and map a key to it (I use ,b
):
nnoremap <silent> <Leader>b :TagbarToggle<CR>
When the tagbar is toggled, it will pop up on the right side of the vim window
and show the tags picked up by ctags
for the current file, organized by tag
type, e.g. function, variable, class, etc. This effectively gives you
a birds-eye view of the code you’re working on.
.ctags
You can put extra configuration for ctags in a ~/.ctags
file, and ctags will
use the contents as arguments every time it’s run. You can use this to establish
basic options, define regular expressions for additional languages, and set
options for specific languages.
For example, here’s my ~/.ctags
file:
# Basic options
--recurse=yes
--tag-relative=yes
--exclude=.git
# Regex for Clojure
--langdef=Clojure
--langmap=Clojure:.clj
--regex-clojure=/\([ \t]*create-ns[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/n,namespace/
--regex-clojure=/\([ \t]*def[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/d,definition/
--regex-clojure=/\([ \t]*defn-?[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/f,function/
--regex-clojure=/\([ \t]*defmacro[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/m,macro/
--regex-clojure=/\([ \t]*definline[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/i,inline/
--regex-clojure=/\([ \t]*defmulti[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/a,multimethod definition/
--regex-clojure=/\([ \t]*defmethod[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/b,multimethod instance/
--regex-clojure=/\([ \t]*defonce[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/c,definition (once)/
--regex-clojure=/\([ \t]*defstruct[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/s,struct/
--regex-clojure=/\([ \t]*intern[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/v,intern/
--regex-clojure=/\([ \t]*ns[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/n,namespace/
# PHP
--langmap=php:.engine.inc.module.theme.install.php --PHP-kinds=+cf-v