October 11, 2011
Many would say that Real Programmers should need nothing more than a text editor and a compiler, but it's hard to deny that a good IDE is a useful tool for many humans. Whether a project is large or small, I think it wouldn't be a stretch to say that an IDE can double, triple, and perhaps even quadruple productivity. Features like code completion, code generation, automatic refactoring and live syntax error highlighting can make code-writing significantly more fun and productive.
But a little-recognised fact of programming in a team is that you spend most of your time reading and understanding code written by other people. So although IDEs are usually associated with writing code, I'd say that it's their code-reading features that are even more useful. Of the code-reading features that IDEs commonly provide (amongst syntax highlighting, outline views and documentation browsing), the most useful are the code navigation features. Some examples of use-cases are:
Any decent IDE will give you these code navigation features, but unfortunately,
there is not a great range of C++ IDEs to choose from in Linux. This leaves a
gap for text editors like Vim to rule the roost, but it is a far cry from say,
Visual Studio. While there are tools like ctags
and cscope
that purport to
provide these code navigation features, they tend to give quite bad results
because the underlying problem requires the editor to have access to a
fully-fledged C++ parser.
In this post, I will introduce a set of tools that I've been working on, that bring solid code navigation features to Vim, backed by a real C++ parser: Clang. Meet CLIC – the Clang Indexer for C/C++.
There are two main parts to CLIC – the indexer and the Vim plugin. The indexer works by parsing the code tree using libclang and dumping all the symbols to a database, grouped by the entity that they refer to. Once this index is built, the Vim plugin can, when queried, ask the Clang API to parse the loaded file and determine the entity under the cursor. From there, it's a simple matter to find the other references to that entity from the index.
If you want to try it out, you can check out and build my Github project, clang_indexer. Before you start, you'll need Clang, BerkeleyDB, Boost and zlib. If you're getting Clang as a binary package, make sure it includes the libclang.so (libclang.dylib in Mac) dynamic library.
git clone git://github.com/exclipy/clang_indexer.git
cd clang_indexer
mkdir build
cd build
cmake ..
make
This should have built three binaries: clic_add
, clic_rm
and clic_clear
.
These programs will respectively, add a file to the index, remove a file from
the index, and clear the index. I don't expect these to be used directly, but
they are intended to be called via the script clic_update.sh
, which is in the
root directory of the repo.
The first thing to do is ensure the clic_add
, clic_rm
and clic_clear
are
in your $PATH
(the clic_update.sh
script assumes and requires this). To
build the index, it's best to create a separate directory to hold the database.
So suppose you have your C++ source tree in ~/src/project/
, you might want to
place the index in ~/src/project-index/
. Create that directory, change into
it, then invoke clic_update.sh
like so:
mkdir ~/src/project-index
cd ~/src/project-index
clic_update.sh ~/src/project
The only argument to clic_update.sh
is the location of the source. This will
crunch through the .cpp, .hpp, .cxx, .hxx, .cc, .c and .h files in your source
tree (to change the file set, you'll just have to edit the top of clic_update.sh
where the find
command is invoked). When it is done, you'll see a a bunch of
.i.gz
files in the directory, as well as an file called index.db
. The latter
is the main database that the plugin will query.
To build the plugin for Vim that actually does the navigation, I piggy-backed largely on the clang_complete plugin by Rip-Rip. I've implemented the code navigation in a fork of clang_complete.
To install it, I highly suggest you use the
Pathogen plugin management system so
that getting updates will be as easy as a git pull
. If you have Pathogen, just
clone the repository into your bundle directory to install it (alternatively,
use make install
).
cd ~/.vim/bundle
git clone https://github.com/exclipy/clang_complete.git
Now, configure the plugin by editing your .vimrc
file. At the very least, you
need to enable the use of the libclang
library (this is optional for the
normal clang_complete plugin, but compulsory for my fork). Here are the settings
that I recommend:
let g:clang_auto_select=1
let g:clang_complete_auto=0
let g:clang_complete_copen=1
let g:clang_hl_errors=1
let g:clang_periodic_quickfix=0
let g:clang_snippets=1
let g:clang_snippets_engine="clang_complete"
let g:clang_conceal_snippets=1
let g:clang_exec="clang"
let g:clang_user_options=""
let g:clang_auto_user_options="path, .clang_complete"
let g:clang_use_library=1
let g:clang_library_path="**/directory/of/libclang.so/**"
let g:clang_sort_algo="priority"
let g:clang_complete_macros=1
let g:clang_complete_patterns=0
nnoremap <Leader>q :call g:ClangUpdateQuickFix()<CR>
let g:clic_filename="**/path/to/index.db**"
nnoremap <Leader>r :call ClangGetReferences()<CR>
nnoremap <Leader>d :call ClangGetDeclarations()<CR>
nnoremap <Leader>s :call ClangGetSubclasses()<CR>
Paste the above into your .vimrc
file and remember to change the paths as
appropriate (clang_library_path
refers to a directory while clic_filename
refers to a file). The last four lines are the CLIC-specific settings for my
fork of clang_complete.
Finish setting up clang_complete by setting up your .clang_complete
file in
your source directory. This is a newline separated list of arguments to pass to
the compiler. The easiest way to get this is to build your project normally and
copy the arguments that make
passes to the compiler. For example, here is
mine:
-I/opt/local/include
-I/opt/local/include/db52
-std=c++0x
Phew! Now that it's set up, you should be able to open one of the source files
in your project with Vim. With the bindings that I've supplied in my example
.vimrc
settings, there are three shortcut keys for doing code navigation. If
the Leader
key is backslash (as is the default), then you can put the cursor
over a symbol in your source and press \r
to bring up a list of locations
where that symbol is referenced. For example, if you do this on a function name,
you'll be able to find the definition, declarations, all the call sites, and any
places where you take the address of the function.
Below is a screenshot showing the plugin in action in MacVim. Here, I've put the
cursor over the addMultiple
function and pressed \r
. The list of reference
locations at the bottom of the window is clickable, and can also be navigated
using :cnext
, :cprev
, :cfirst
and :clast
.
As well as \r
, you can also use \d
to get only declarations of a symbol –
viola, we have jump-to-definition! Finally, if you focus on a class name, you
can get its subclasses by pressing \s
. This feature is useful if you see some
code calling a method on an interface (perhaps in a dependency injected
architecture) and want to know what it's actually calling through to.
Finally, please note that this is an early stage of a little project that I've been doing just to make my own life easier. As you hopefully would have gathered from these instructions, it has extremely rough edges, and I haven't really invested much effort in making it easy to use. Use at your own peril, it will destroy your code, format your hard drive, eat your children, etc. And I've only tested it in Linux and Mac. Maybe it'll work in Cygwin for Windows, but I wouldn't count on it (let me know!).