Follow @learnvim for more Vim tips and tricks!
One useful ability in coding is to jump to any function definitions quickly. It helps you to understand and visualize project codes faster.
Vim uses a tag file to jump to a function definition quickly. In this article, I will show you how tag works and how to manually set up tags. I will also show you how to use a plugin to automate your tag creation.
These are some things I will cover:
Tags overview
If you are reading a large source code and see a function you don't understand, you can jump to its definition to understand it.
If I saw this code snippet and want to know what Account
model does:
Models::Account.new(
account_name: from_json(['whatever']
...
With tags, I can put my cursor on "Account"
, press Ctrl-]
, and jump to definition.
Vim jumps to Account
class inside a file:
module MyModule
module Users
module Deserializers
class Account < Iggy::Core::Deserializer
def initialize()
...
With this, I can read what is being initialized in a new account. I can see at other methods inside this class and understand more what this class can do.
Tags give you a quick project visualization. The more you know about the code, the more you understand the project.
The way tag system works in Vim, is that Vim relies on a tag generator to generate a tag file (normally called tags
in root directory) that contains a list of definitions. To see where Vim expects to find a tags
file, you can run :set tags?
. Depending on what tags generator you use, the content of your tags
file may look different. At minimum, a tag file must have either one of these formats:
1. {tagname} {TAB} {tagfile} {TAB} {tagaddress}
2. {tagname} {TAB} {tagfile} {TAB} {tagaddress} {term} {field} ..
I will go over the content later.
Setting up tags
Here is how you can get started creating tags. To use Vim tags, we need a tag generator. There are several options:
ctags # C only. Available in most unix
exuberant ctags # Good, supports many file types
etags # Emacs. Hmm...
JTags # Java
ptags.py # Python
ptags # Perl
gnatxref # Ada
The popular option is exuberant ctags. It supports 41 programming languages. I personally use that. For this tutorial, we will go with exuberant ctags.
If you have mac and homebrew installed, you can run brew install ctags
. Once installed, you can check it with ctags --version
.
For this demo, I will use dev.to github project. You can use whatever project you want.
To generate ctags, run:
ctags -R .
By default, ctags
generates a tags
file in your current directory. The -R
is recursive option (which you probably want to do most of the time). If you noticed, your tag file contains a very long list. Mine has over 170000 entries. This is because ctags generate tags from our node_modules
too! We need to exclude it. Delete our tag file (rm tags
) and run this instead:
ctags -R --exclude=node_modules .
This time it takes less than a second. I only got around 6900 entries. Much better.
By the way, you can use exclude option multiple times, for example:
ctags -R --exclude=.git--exclude=vendor --exclude=node_modules --exclude=db --exclude=log .
Vim tags in action
Let me show you an example of a tags file:
...
!_TAG_PROGRAM_VERSION 5.8 //
Abstract config/initializers/sidekiq.rb /^ module Abstract$/;" m class:Rack.Session
Accept elasticsearch-7.5.2/jdk.app/Contents/Home/include/jdwpTransport.h /^ jdwpTransportError (JNICALL *Accept)(jdwpTransportEnv* env,$/;" m struct:jdwpTransportNativeInterface_
Accept elasticsearch-7.5.2/jdk.app/Contents/Home/include/jdwpTransport.h /^ jdwpTransportError Accept(jlong accept_timeout, jlong handshake_timeout) {$/;" f struct:_jdwpTransportEnv
ActionController config/initializers/persistent_csrf_token_cookie.rb /^module ActionController$/;" m
ActsAsFollowerMigration db/migrate/20170208152018_acts_as_follower_migration.rb /^class ActsAsFollowerMigration < ActiveRecord::Migration[4.2]$/;" c
...
This file is generated from dev.to repo. It may look like gibberish, but it isn't that bad. The top lines (that start with !_TAG_
are metadata. I will not go over that). The important part is the line that looks like this:
ActionController config/initializers/persistent_csrf_token_cookie.rb /^module ActionController$/;" m
ActionController
is our definition. Think of it like a key in key-value pair. When we press Ctrl-]
on "ActionController"
string anywhere in our code, Vim looks at our tags
file, searches for "ActionController"
, and looks for the location to jump to, in this case, it is config/initializers/persistent_csrf_token_cookie.rb
.
Once Vim finds the location, it looks for the location using this pattern: /^module ActionController$/
. ^
is regex pattern for start of line. $
is regex for end of line.
Using tags
You can get good milage using only Ctrl-]
. But let's learn a few more tricks with tags.
We just learned that we could put our cursor on "ActionController"
string and jump to definition with Ctrl-]
. You can do the same with :tag {name}
.
:tag ActionController
You can autocomplete :tag
argument. If you do:
:tag A{TAB}
Vim lists all tags that starts with A...
You can have multiple definitions for one keyword. For example, I have several "User"
definitions in tags
file:
User app/models/user.rb /^class User < ApplicationRecord$/;" c
User app/services/data_sync/elasticsearch/user.rb /^ class User < Base$/;" c class:DataSync.Elasticsearch
User app/services/search/query_builders/user.rb /^ class User < QueryBase$/;" c class:Search.QueryBuilders
User app/services/search/user.rb /^ class User < Base$/;" c class:Search
By default, ctags always jumps to first definition. To jump to Nth definition, do:
:Ntag User
To jump to 3rd User
definition (the one inside query_builders/user.rb
), we do :3tag User
. In normal mode, we can do 3 Ctrl-]
while cursor is on User
.
You may ask, "This is nice, but how do I know if there are multiple tags for User
?"
You can use :tselect
(or g]
in normal mode) to see all definitions for that string. If you do :tselect User
, you'll see a list of all tags generated for "User". You also can put cursor on User
and do g]
. From there, you can choose where to jump.
Tag stack
Vim keeps a list of all tags we've jumped to in a stack (max 20). You can see the stack with :tags
. It looks something like this:
# TO tag FROM line in file/text
1 1 banned 64 app/controllers/application_controller.rb
2 1 submission_template_customized 39 submission_template = @tag.submission_template_customized(@user.name).to_s
>
The upper stacks are older stacks. The lower stacks are newer stacks. I started out with "banned", then I searched for "submission_template_customized".
To "pop" the stack, do Ctrl-T
or :pop
.
Let's say you're working on a new project and you want to investigate a call graph. You can use tag stack to strategically jump to relevant functions.
func0 --> func1a --> func1b
--> func2a
--> func3a
Starting at func0
, you observe that it calls func1a
, func2a
, and func3a
. You go to func1a
with Ctrl-]
. You see func1a
calls func1b
, so you go to func1b
(ctrl-]
). Once you understand func1a
and func1b
, you return (Ctrl-T
twice) back at func0. Back at func0
, jump to func2a
with ctrl-]
, see what it does, and "pop" back to func0
. Finally, jump to func3a
to complete the call graph.
Plugins
When your project change, your tags need to change. If you removed User
's first definition inside app/models/user.rb
, the tags still think it is inside app/models/user.rb
. You have to tell them that it has changed.
You can regenerate tags (ctasg -R .
), but Vim doesn't do this automatically. Luckily, there are plugins to automate tags generation. I use vim-gutentags and it works right out of the box. Many plugins can do this automatically.
Some alternatives:
Conclusion
I think this is a good place to stop. We learned how tag works. We learned how to set up tags for our project. We learned how to jump, pop back, and view tag stack. Understanding how Vim tags work will boost your productivity.
Thanks for reading. Happy coding!
Top comments (2)
Hi! Thanks for this article.
But, with lsp, are tags still usefull?
Here is the discussion on reddit.
TL;DR Yes.