‘Go to Definition’ in Vim for Python using Ctags, Done Right

How to set up and configure Vim to use tags for Python development so that it doesn’t suck.

Install Ctags

Get the latest version of ctags, put it on your PATH. Recent releases are much improved for Python.

Creating or updating tags files

You’ll probably want one tags file at the root of your project, which will need to be created or updated whenever you make significant changes. Either get used to manually running the following command a lot:

ctags -R .

or bind it to a key in your ~/.vimrc:

map <f12> :!start /min ctags -R .<cr>

I like to set Vim’s current working directory equal to the root of whatever project I’m working in, so now I can press f12 to update the tags file for the project. The ‘start /min’ part is a Windows-specific way to run the command in the background, so Vim isn’t locked up waiting for it to finish.

Test it out

Now, in Vim, ctrl-] will jump to the definition of the symbol under your text cursor. Hooray, etc. If there is more than one definition of that symbol, it presents a menu for you to choose from.

Turn off useless tags

By default, ctags generates tags for Python functions, classes, class members, variables and imports. The last two are useless to me, and they actually make ctrl-] more inconvenient, because they increase the likelyhood of finding duplicate definitions of a tag, causing the menu to inconveniently pop up, rather than just jumping to the tag you want.

To fix this, create a ~/.ctags file:

--python-kinds=-iv
--exclude=build
--exclude=dist

The first line turns off tags generation for variables and imports. Personally, I never need to jump to these, and including them in the tags file just clutters up the results with lots of needless duplicates, requiring me to pick from a menu every time I try to jump to a definition.

The second and third lines turn off generation of tags in the named dirs, since you almost certainly want to ignore source code in those directories.

Case insensitive tag matching

If your .vimrc requests case-insensitive searching by setting ignorecase (aka ic), then the above tag matching will also be case insensitive. This is irksome, because searching for the definition of property .matrix will present you with a menu asking you to choose between property .matrix and class Matrix, rather than just jumping to the property.

To fix this, add this to your .vimrc:

" go to defn of tag under the cursor
fun! MatchCaseTag()
    let ic = &ic
    set noic
    try
        exe 'tjump ' . expand('<cword>')
    finally
       let &ic = ic
    endtry
endfun
nnoremap <silent> <c-]> :call MatchCaseTag()<CR>

This maps your ctrl-] key to turn off case-insensitivity while it does the jump to tag, then turn it back on again. Now pressing ctrl-] will jump directly to your property, only presenting menus on the occasion when the tag you search for is defined in more than one place using precisely the same name.

Much better.

Update: The final Vim script was suggested in a comment by James Vega, in order to reliably restore the state of ‘ignorecase’ after doing the tag jump. Many thanks!

7 thoughts on “‘Go to Definition’ in Vim for Python using Ctags, Done Right

  1. Hey @Lionel. Thanks for the suggestion – I haven’t seen pyscope. Searching, there are several projects called pyscope, and I haven’t yet found one with a README. Which one do you mean?

  2. As for ensuring ‘ignorecase’ is restored properly, you could wrap it all in a function.


    fun! MatchCaseTag()
      let ic = &ic
      set noic
      try
        exe 'tjump ' . expand('<cword>')
      finally
        let &ic = ic
      endtry
    endfun

    nnoremap <silent> :call MatchCaseTag()<CR>

  3. Hey Marius.

    It’s just that I never seem to have a problem instantly finding variable definitions. If they aren’t local or otherwise obvious, then I just hit one or more of * or # or 0g to find it, hopefully at the top of the file.

    Consequentially, the very marginal utility of being able to use tags to find them doesn’t seem worth the downside to me, of cluttering up the tags file. I like it when tags only have a single match, so removing everything from the tags file that I don’t really need seems like a win to me.

    As for Michael: Quiet, you.

  4. I’m curious: why do you find variable tags useless? IIRC they’re only generated for globals, and it’s nice to be able to find those sometimes.

Leave a Reply