Tuesday, August 9, 2011

vim: How to setup awesome autocomplete

Recently I moved to a company which has a huge codebase and loves long names! All vim/emacs users will sympathize with such a situation. Eclipse would autocomplete names in a jiffy. I was sorely missing good autocomplete on my favourite editor: vim.

Typically by setting the "dict" variable appropriately, one can autocomplete english words, but what about C/C++ code?
One fine morning, I had an epiphany. Why don't people use the ctags/etags database to autocomplete. The database already has lots of semantic information about the code and it would have all the project specific keywords too. Now, I am not a guy who has too many epiphanies, so I was sure someone had implemented it even before I could spell "vim". With this in mind, I googled the specific terms "vim autocomplete using ctags". The first stackoverflow result had a brilliant suggestion: omniautocomplete.

Before I go into what omniautocomplete (a plug-in for vim) does, let me first list down my requirements from an autocomplete tool
1) Autocomplete sensibly: Use the knowledge that I am editing "c++" code and give sensible suggestions.
2) It should *not* be enabled by default when I run vim i.e. do not blindly putt crao in my .vim. The reason is that I work on multiple projects at the same time and I don't want suggestions from one project to be mixed with other.
3) No lag in autocompleting.
4) Provide a quick way to update the autocomplete "database".

Some vim scripting + omnicomplete provides all that and more. What more, you might ask. Well, it provides an eclipse-like pop-up when asked to autocomplete. It can also fire when I enter a "." or "->". It also has an option to show "function prototypes" alongwith autocomplete suggestions. This is awesome!!!

To get you excited, here is a screenshot. It looks exactly the same.

Setting up omnicomplete is super-easy. Installation instructions are outlined here. All it involves is unzipping a zip file to ~/.vim

The next part is more interesting:
Since ctags database is used, ctags needs to be compiled with some specific options. The commandline I picked up from their documentation is this:

$ ctags -R --c++-kinds=+p --fields=+iaS --extra=+q .

Now, the "tags" variable in vim needs to point to the tags of your project. Setting this up each time you open vim can be irritating. Very irritating. So, I wrote the following vim function to alleviate the issue:

function! SetProj(proj)
        if a:proj == "mythread"
                map <C-F12> :!ctags -R --c++-kinds=+p --fields=+iaS --extra=+q -f /home/jitesh/repos/mythread_lib/tags /home/jitesh/repos/mythread_lib<CR>
                set tags+=/home/jitesh/repos/mythread_lib/tags
                let OmniCpp_ShowPrototypeInAbbr = 1
                let OmniCpp_MayCompleteDot = 1

Now, when I open vim, all I have to do is:

:call SetProj("mythread")

Note - Re-generating the ctags database is also pretty easy, Just press Ctrl+F12. Notice the keymapping in the vim function above.
So, when I define a new function in my code, I just do a Ctrl+F12 and the tags are updated and ready to be auto-completed.

Pressing ctrl+P or ctrl+N invokes the autocompletion.

When I  start vim, none of this stuff is loaded, so there is no load-time penalty/bloat + I can work on multiple projects just by calling "SetProj" with the appropriate parameter.

I sign-off a happy user! vim FTW


Salil said...

awesomeee!! A great find!

Sandip Gangakhedkar said...

Looks very promising. I'm about to start working on a large C project in linux, and this is very handy. I'm quite new to vim and I have a rather basic question: Where do I put the MyProj function code? In the .vimrc file? Also, I understand that the argument to this func will be the base dir of the project and so by giving different arguments for different projects with the same base path, you can generate ctags for each project...? Correct me if I'm wrong.


Jitesh Shah said...

Yes, put the MyProj fuction inside the .vimrc file.

The argument to the function is a text string (which can be anything). There is an if conditional inside the function which takes an action depending on what string is passed as the argument.

If you are working on a single project, you can remove this argument (and the if conditional) completely.

Otherwise, you can just write a if-elseif-else structure. You can find an example of how to write a if-else statement in vim script here:

Let me know how it works out for you!

Anonymous said...

It's easy to press CTRL+F12 if you have an F12.

Makis said...

Couldn't you just use .localrc to include the project name? Have a parameter there that, if set, does this stuff automatically?