How I Got "Go To Definition" Working in Vim in 2019
Zev Averbach

Zev Averbach @zev

About: Programmer. I like building things around the spoken word.

Location:
Switzerland
Joined:
Feb 4, 2019

How I Got "Go To Definition" Working in Vim in 2019

Publish Date: Jul 30 '19
24 6
👇👇👇tl;dr 
# .vimrc
set tags=tags
autocmd BufWritePost *.py silent! !ctags -R --python-kinds=-i --languages=python 2> /dev/null &

$ brew install ctags
Enter fullscreen mode Exit fullscreen mode

In the last two years I've spent a significant amount of time in PyCharm and a somewhat shorter time with Visual Studio Code, both with vim keyboard bindings, writing mostly Python and JS. Ultimately, I came back to some flavor of terminal-based vim: First Vim 8, now Neovim. There were too many missing features in "vim mode" in both of the IDEs, and the speed difference—brain-to-screen—was noticeable compared to vim.

What I Miss From IDEs

Because I'm not yet a sed/grep expert in the context of search and replace, I still miss the refactoring tools in PyCharm for renaming and reorganizing code. Automatic imports is also something I haven't replicated in vim.

However, what I was missing most was "go to definition", which I had mapped in VSC and PyCharm to ctrl-] as it is by default in vim.

Getting It To Work + Timesucks to Avoid

As with most of my efforts to make vim more ergonomic/IDE-like, getting "go to definition" working took longer than I had hoped.

For the uninitiated, while vim comes with ctrl-] out of the box, it doesn't actually know where something is defined unless there's at least one tags file and you've told vim where to find it/them. You can run ctags manually, but this can get tiresome if you want vim to always have an updated idea of where all the definitions are in your project: functions, modules, constants, classes, types, etc.

The first option I found for automatically updating the tags file didn't work as advertised: I tried to set up git hooks like Mr. Pope suggested, but for whatever reason the tags file never refreshed on commit. Avoid this rabbit hole! And anyway, don't you want "go to definition" to work between commits too?

What ended up working to refresh the tags file *on every save* was a modified version of something I found in the comments of a StackOverflow answer. However, without python-kinds=-i, "go to definition" didn't work as expected on MacOS (it was fine on an Ubuntu droplet). Inspecting my tags file, it was including imports, which caused my ctrl-] invocations to only jump to the top of the current module, where the import was, not to the definition of the entity.

Final Product + Developer Experience

I can now very quickly navigate to the definition of whatever's under my cursor with a single keystroke. What's nice about this usage of ctags is 1) it runs in the background and never interrupts you, 2) it's fast, and 3) it runs every time you save. I haven't tried this at all with JS yet (will be trying this), but it is lightning fast and accurate for jumping around in large Python-based projects.

Bonus: gf

gf bridges a significant gap that ctags don't cover: It stands for "go to file". Type gf in normal mode when your cursor's over a filename, and it opens it!

Next Thing to Try: Tagging dependencies and the Python standard lib

As alluded to above, you can tell vim about multiple tags files in multiple paths. It doesn't come up as often that I want to "go to definition" of library code, but I can see how it might come in handy.

Comments 6 total

  • Stéphane FEUGA OSHIMA
    Stéphane FEUGA OSHIMAJul 31, 2019

    Hi,

    Maybe a better approach for ctags is to follow this vim video (intelligent navigation with ctags). Part 1 & 2 are also interesting ;).

    Anyway, thanks for sharing this tips

    • Zev Averbach
      Zev AverbachJul 31, 2019

      Thank you for the suggestion! I'm reviewing the video right now.

  • Sérgio Araújo
    Sérgio AraújoOct 14, 2019

    Hi Zev! It is also possible jumping to the target file "gf" or alternative file "Ctrl-6" on a new window: Ctrl-w Ctrl-f and Ctrl-w Ctrl-6

  • d-tork
    d-torkOct 27, 2020

    Hi Zev, thanks for writing this, it was really helpful. You might want to look into how your tldr snippet displays on the blog though, at least to my browser the ending looks like 2> /dev/null &. This caused issues at first and took me a while to figure out that it should be 2>/dev/null &&.

    I also needed to add a semicolon after set tags=tags (per this page), otherwise I was getting the message bash: syntax error: unexpected end of file after every write.

  • Waylon Walker
    Waylon WalkerDec 22, 2020

    My fingers are locked on gd for go to definition and gh for hover and show docs.

    Here are two ways that I have found to work for me with Python.

    Go To Definition

    nnoremap gd :YcmCompleter GoTo<CR>
    nnoremap gd :call CocActionAsync('jumpDefinition')<CR>
    
    Enter fullscreen mode Exit fullscreen mode

    Hover

    nnoremap gd :YcmCompleter GetDoc<CR>
    nnoremap gd :call CocActionAsync('doHover')<CR>
    
    Enter fullscreen mode Exit fullscreen mode
    • mareksamec
      mareksamecMar 18, 2021

      Awesome, thank you for this comment, this does exactly what I need!

Add comment