How I Got "Go To Definition" Working in Vim in 2019
Tags: vim, ctags, ide
Published: 2019-07-30 15:07:33

---
title: How I Got "Go To Definition" Working in Vim in 2019
published: true
description: Make vim more IDE-like by using ctags and automagically refreshing them on save.
tags: vim, ctags, ide
---


```
👇👇👇tl;dr 
# .vimrc
set tags=tags
autocmd BufWritePost *.py silent! !ctags -R --python-kinds=-i --languages=python 2&gt; /dev/null &amp;

$ brew install ctags
```
<p>
  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.
</p>

<h2>What I Miss From IDEs</h2>

<p>
Because I'm not yet a <code>sed</code>/<code>grep</code> 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.
</p>

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

<h2>Getting It To Work + Timesucks to Avoid</div>

<p>
	As with most of my efforts to make vim more ergonomic/IDE-like, getting "go to definition" working took
	longer than I had hoped. 
</p>
<p>
	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 <code>tags</code> file and you've told vim
	where to find it/them. You can run <code>ctags</code> 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.
</p>
<p>
	The first option I found for automatically updating the tags file didn't work as advertised: I tried to <a
		href="https://tbaggery.com/2011/08/08/effortless-ctags-with-git.html">set up git hooks like Mr.
		Pope suggested</a>, 
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?
</p>

<p>
  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
		href="https://stackoverflow.com/a/155515/4386191">a StackOverflow answer</a>.
	 However, without <code>python-kinds=-i</code>, "go to definition"
			didn't work as expected on MacOS (it was fine on an Ubuntu droplet).  Inspecting my
			<code>tags</code> file,
			it was including imports, which caused my <code>ctrl-]</code> invocations to only jump to the top of the current
	module, where the import was, not to the definition of the entity.
</p>

<h2>Final Product + Developer Experience</h2>

<p>
	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 <a
																														href="https://github.com/artemave/js-editor-tags">this</a>), but it is lightning fast and accurate for jumping around in large Python-based
	projects.
</p>

<h2">Bonus: <code>gf</code></h2>

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

<h2>Next Thing to Try: Tagging dependencies and the Python standard lib</h2>
<p>
  As alluded to above, you can tell vim about multiple <code>tags</code> 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.
</p>