"Jump to Definition" in Vim

Did you know Vim has a few built-in features designed to help with the "Jump to Definition" action you see in most IDEs?

"Jump to Definition" in Vim
Photo by Austin Neill / Unsplash

Did you know Vim has a few built-in features designed to help with the "Jump to Definition" action you see in most IDEs?

Level 1: include and define

Since Vim is a "dumb" editor (that is, it doesn't do any static analysis on your text), you'd expect a "Jump to Definition" feature that relies on a simple text search.

include and define are two mechanics to help facilitate that.

Include

include is a pattern to help Vim understand which files are relevant when performing code searches.

Let's take a look at the default value:

:let &include = '^\s*#\s*include'

It's trying to catch a pattern that looks like #include <filename>.

That makes sense! In C, that's how you tell the preprocessor, "I need code in <filename> in this position". If you want to jump to the definition of a macro or whatnot, you'd want to look into all included files.

A good include pattern is crucial to making this work. For example, for Javascript filetypes, I have:

:let &l:include = 'from\|require'

Now I can look for any keyword appearing in any required files!

Demo: asciinema

Further reading: :help include-search in Vim: hotlink.

Define

define is a pattern to help Vim understand which parts of your code are definitions.

Let's take a look at the default value:

let &define = '^\s*#\s*define'

In this case, it's looking for something that looks like #define <keyword>.

This time, it seems to be looking for preprocessor macros. Makes sense, although since there are languages for which no analogue exists (e.g. Javascript), we can repurpose it to match variable definitions instead.

For my Javascript filetype, I have:

let &l:define = '\v(export\s+(default\s+)?)?(var|let|const|function|class)|export\s+'

A bit convoluted, but at least it can catch different permutations such as export default function <name> and const <name> = ....

Now I can jump to definitions in required files!

Demo: asciinema

Further reading: :help definition-search in Vim: hotlink.

Level 2: Ctags

Vim also has builtin support for ctags. Ctags tags important parts of your code so you can jump around the project without the restriction of include files.

To work with ctags, all you have to do is generate a tags file (preferably in project root):

$ ctags -R .

Then open Vim in the same directory. You can now jump to tags!

Demo: asciinema

Universal ctags is an often recommended ctags implementation, since it's actively maintained and has good language coverage.

For Javascript filetypes, I actually rely on es-ctags, which uses the Tern static analysis engine to generate a tags file.

Bonus: maintaining the tags file

Several people have different techniques to keep the tags file up-to-date as you edit.

Tim Pope has a blog post detailing his workflow with Git hooks.

A lot of people also love the popular Gutentags plugin.

I've read some people rely on their task/build runner for such a task.

I myself rely on a simple file watcher that re-runs ctags and appends new tags to the file:

$ watchrun src -- es-ctags -a

Further reading: :help tags-and-searches in Vim: hotlink.

Level 3: Static code analysis

The smartest "Jump to Definition" mechanic would definitely rely on parsing and analyzing the code itself, which is what static analysis engines are for.

There are multiple plugins out there that work to integrate with these engines, e.g. tern_for_vim for Javascript. Try and find one for your language of choice, or ask some of the helpful chaps in r/vim or #vim on freenode!

Many people (myself included) are also hopeful for the Language Server Protocol, which aims to provide a standard for text editors and static analysis engines to work together.

About the Author

Written by Ian Emnace