Tuesday, April 10, 2012

Source Code Navigation with Vim

I'm a pretty naive user of Vim.  It started while watching a professor of mine do code examples on a projector during class.  I was amazed at the speed of which he was copying and pasting code, editing text, etc... all without using the mouse.

When I asked him how he did that, he talked about how he had been using those techniques for 20 years.  I had watched other people use emacs, but they were still moving their hands away from the home row.  That alone motivated me to pick and learn Vi.  (Yeah yeah, I know, a proficient emacs user could have been just as impressive.  More on that later.)

Learning Vim turned out to be a great investment, as I worked on several unix and embedded systems where it was normal to have some form of Vi installed.  It's even usable in the Dvorak layout! (I'll have to save those details for another post).

Back to the topic at hand: Vim is also great for reading and navigating source code.  I would see people using an IDE that would push a button and it would take them to the declaration of a variable, function, etc... "How did you do that!?"  See, I've never used a "real IDE" on a regular basis, and was unaccustomed to such features (feel free to flog me for this).  I had to figure out how to get those features inside of Vim.  (Another sidenote: I can't use anything besides Vim now.  Even my MS words docs often have rows of jjjjj kkkkk sporadically placed.  Another sidenote #2: One of the eclipse Vim plugins I tried last year made eclipse usable, but I still prefer Vim... I'll have to save that one for another post as well.)

Turns out there were several options to get source code navigation working in Vim.  ctags, cscope, an eclipse ipc method, and one I stumbled on because of a co-worker: gnu global.  Anthony swore by global, and indeed, his source code navigation was impressive... even while using emacs!

In any case, after trying all the above tools, I settled on gnu global, although it took some work to get it working with Vim and my setup.  That's what this post is really about: documenting my usage of Vim and gnu global.

First, some settings for .bashrc:
export MAKEOBJDIRPREFIX=$HOME/wa/globaltags
alias maketags='mkdir -p $MAKEOBJDIRPREFIX/$(pwd -P) && gtags -i $MAKEOBJDIRPREFIX/$(pwd -P)'
alias maketags_cpp='GTAGSFORCECPP=1 $(maketags)'

That will set up the command I use to generate the tags: maketags_cpp

global is great in that you can store the generated tags outside the source tree (in this case $HOME/wa/globaltags/).  Plus, you can be anywhere in your source tree and it correctly finds the tags.  You can also have automatic per project tag databases.  These are some of the main reasons I chose gnu global over the alternatives.  I don't want to jump to spots in an unrelated code base.

Now, for Vim.  First, .vimrc:
let GtagsCscope_Auto_Load = 1
let GtagsCscope_Auto_Map = 1
let GtagsCscope_Quiet = 1
set cscopetag

Apparently Vim doesn't have a plugin architecture for tagging systems.  So, global provides a cscope adapter in order to make Vim think it's using cscope, when it's really using global.

Next, drop gtags-cscope.vim into $HOME/.vim/plugins.  You may need to make sure you have the correct version for the version of global you're using.  global also has "gtags.vim", but like I said, Vim doesn't expose an api to allow different tagging systems, making the tag stack unavailable (which, is one of the best features of this setup).

Another sidenote/tip from Anthony: if you ever google for it, make sure you google "gnu global" and not "global" or "gtags".  I know... "gnu global" is a terrible name marketing wise.

So, after installing global, the basic workflow is like this:

  1. enter source dir root and run "maketags_cpp"
  2. open a file with Vim

Some of the common operations I use:

  • go to function/variable definition of identifier under cursor (pushes onto the tag stack): CTRL-]
  • go back (pops off the tag stack): CTRL-t
  • search for all instances of this identifier: CTRL-\ and then e
    • this brings up a list, and you can enter the number to go to that spot.  You can then do ":tn" and ":tN" from the Vim command line to go forward and backwards through the tag list.
  • global also has a method to find all functions that call the function in question.  I don't remember the shortcut, but honestly, I normally use the above item instead.
  • several other search methods that I can't remember right now
Whenever the tags start getting out of date (because of modified code), you can simply go to the root of source code again and run maketags_cpp.  The updates are fast and incremental, which was another reason for choosing global.

There are some warts that I haven't figured out: sometimes the first "common operation" can't find the definition like it should.  In those cases, I use the 3rd "common operation" as a backup.

There are support for other languages, and some have configured global to work with unsupported languages, but googling for those is left as an exercise for the reader.

That's about it.  Just as my co-worker warned, it's pretty hard to work without this functionality after having enjoyed it.

6 comments:

Muthukumaran said...

Hi Wade,
Thanks for sharing this useful info. I was using ctags for a while but while working with multiple versions of same project it becomes a headache. I've got couple of queries here. Will be great if you can help me out.

1. Does GNU Global takes care of different versions of same project. In other words if I've got same file in different folders will it list out the choices I've so that I can choose the correct version of file.

2. I was trying to prepare my gvim in Windows with the steps you've mentioned. Like replacing commands with windows-specific stuff. Like replacing alias with doskey but could not quite manage it. If you've got it working successfully in windows please assist me with a windows-specific steps to set up gtags.

Your help is much appreciated.

Thanks
Muthukumaran M.

Muthukumaran said...
This comment has been removed by a blog administrator.
Muthukumaran said...

Hi Wade Berrier,
Great post. I've tried to follow the procedure in my windows environment after making necessary changes. But could not quite succeed. I've got couple of queries here. Would be great if you can help me out.

1. Can GNU Global work fine where a project is having different versions. Assuming I've done two versions, one for current development and another for last released, then can GNU Global take care of file name clashes?

2. How to set up GNU Global in Windows environment. I did my best to imitate the commands by replacing them with equivalents ones in Windows. But it doesn't work.

Any suggestion/help from you will save my day.

Thanks
Muthukumaran M.

Wade Berrier said...

Hi Muthukumaran,

GNU global can be used for different versions of the same project. That was one of the main reasons I started using it. It is based solely on the path. Plus, it has very fast incremental updates.

I haven't tried it on windows, but I'm not aware of anything that would prevent it from working. I would suggest crafting up some .bat files that do something similar to what I posted.

I'd be interested to hear if you get it working.

tatung said...

Hi,

Thank you for your tutorial. I have successfully installed and used GNU Global. However, for my case, it is not stable at all. Sometimes GNU Global can jump, but sometimes (I mean most of the time), VIM has just said that

E257: cstags: tag not found

Do you now how to fix it and make it stable?

Wade Berrier said...

Tatung,

It's been a while :)

Sometimes gnu global can't find the tag of interest.

These days I use Emacs in Evil mode with language server protocol (lsp) with all sorts of lanuages.