My philosophy has always been to maximize productivity with minimal effort. I think this is why I prefer Vim over Emacs :smile:. Find a tool that works, stick with it, that’s what I do. I haven’t distro hopped in 10 years and I’ve been using vim as my text editor and tcsh as my login shell for more than 10 years. If it ain’t broken, don’t fix it!

My job has me working on dinosaur code bases where running grep to find stuff isn’t exactly the most practical thing to do. Some of the tools I’ve integrated into my environment over the years are ctags and cscope, which, while requiring me to learn a couple of new tricks, do make the archaeologia cōdicis phase go a lot faster.

So let’s see how ctags and cscope work with Vim.


You need to have some sort of ctags package installed. E.g.

pkg_add ectags
pkg_add universal-ctags
apt install exuberant-ctags

Note: Once a package is installed, the actual binary name may vary. Replace ctags in the shell invocations below with the actual binary name of the thing you have installed. In my case, I run *ectags which is the exubernat ctags implementation.*

Vim used to ship with exuberant ctags, which has somewhat decent C++ support, plus a whole bunch of features related to local symbols, who calls what, etc. If exuberant ctags isn’t available, there’s universal ctags, which is mostly CLI compatible (though it might complain that some --c-kinds are not available). The much older UNIX-y ctags is still floating around, that one doesn’t do much. It can still be useful if it’s all you got; it supports basically no flags.

For a lot of people, running ctags . is enough. But I like having a giant database for an entire source tree (sometimes for multiple source trees if I’m working on a tool that uses some library). I invoke it like this:

# go to command
ctags --extra=+fq --fields=+amnStail --c-kinds=+cdefglpx --c++-kinds=+lpx -f tags -R .

# or, for a more exuberant version:
# --tag-relative=yes  <-- files in tags are relative to tags file and not to cwd
# --fields: enable everything I know of
# --extra: extra stuff for C++
# --c&c++-kinds: enable everything I know of
ctags -f tags --tag-relative=yes --fields=+a+f+i+k+l+m+n+s+S+z+t --extra=+f+q --c-kinds=+c+d+e+f+g+l+m+n+p+s+t+u+v+x --c++-kinds=+c+d+e+f+g+l+m+n+p+s+t+u+v+x -R .

I remember going through the exuberant ctags man page and picking everything that I might ever find interesting. I may have enabled all flags, I don’t remember.


Vim has neat ctags integration, and I use almost none of it.

To add tags, set your 'tags': set tags=tags. Note, this is stored as a string and possibly looked up relative to cwd. This feature is meant to support having a bunch of ctags databases and only loading the subset for the current folder (or whatever). You can have absolute paths.

I prefer to have a giant database with an entire source tree, so I tend to set tags=~/Projects/thing/tags,~/Projects/otherlib/tags.

The magical command you need to remember is :ts (tag-select). There’s a shortcut g] which takes the string under the cursor and passes it to :ts. It then brings up a menu with all the matches.

using ctags to find where a function is implemented

And if you weren’t aware, ^O and ^I are Vim’s back and forward buttons (like a web browser). I recall ctags support in Vim having a concept of a tag stack , but that’s one of the many features I don’t use :smile:. I just stick with ol’ reliable ^O. Just go see :help tags and :help tagsrch.txt, there’s a lot of functionality there…


sometimes ctags isn’t enough; for example, there are many projects that provide helpful macros to help devs define or use stuff, and that confuses C parsers.

Again, you need cscope installed, e.g.

pkg_add cscope
apt install cscope

cscope comes in and provides its own search tools, and the features I mostly use are “who calls this”, “where is this text string”, “where is this regex”, “where is this macro defined”.

cscope -bqR
cscope -bqR -s../include -s../drv          # additional include dirs
cscope -bqR -u                             # force update the database

cscope isn’t as neatly integrated into Vim, but it’s good enough. cscope actually runs as a background process and Vim drives it.

Use :cs add cscope.out to enable.

Use :cs to show the help.

cscope showing its help

Then, you can use :cs f g symbol, :cs f c who_calls_this, :cs f t text and so on.

cs f c Dfix cs f c Dfix

cs f t __OpenBSD__ cs f t __OpenBSD__

If you want to pass a complicated and long symbol name, and you don’t want to type it (I usually don’t), you can use Vim registers to hold it. Interactively, yank the thing in a register (e.g. "qye or visual select + "qy etc), then type :cs f t ^Rq. You can automate e.g. in visual mode with :vremap or related functions.

I don’t believe cscope works well with C++, but since I mostly use it as a complementary text or grep tool to ctags, it works well enough :smile:.


Vim is a powerful IDE, but we knew that already. Especially when you pair ctags and cscope with the somewhat recent :Termdebug feature which makes Vim be a front-end to gdb; now, while debugging, if you stumble over a macro like LIBPATH, you can jump to it to see what it was compiled as :smile:.

I couldn't get :Termdebug to work on OpenBSD, but I did get :terminal to work; here's looking for a tag

The thing is, you can use these tools without Vim. cscope in particular has a menu drive text interface (one of the b or q flags disables this). You can use them with any text editor / IDE.

cscope run standalone

The provide a massive amount of value on large or old or complicated projects/products. And they are considerably faster than running grep repeatedly, or trying to guess where everything should be. There’s just the initial setup time that can be annoying, but it usually finishes by the time you grab some coffee; and if the code base is large enough, ctags should have more than enough time to finish while the product builds :smile:. Keep these tools handy.

There is at least one more tool, GNU GLOBAL, but I haven’t had fun with that one, so I never use it. Visual Studio Code also tries to do similar things to ctags and cscope, and it mostly works, but who wants to run a text editor that eats up as much RAM as Chrome does? And I’m willing to wager someone wrote a cool clang-* thing that does the same thing as these two tools, but I’m too lazy to check (or to change my ways).