Getting started with Ctags with Vim and Git.
Ctags with Vim are incredibly useful for quickly navigating around code. With them, Vim can jump between symbols to quickly go to the definition of symbols or find keywords within the document. Ctags + Vim end up working very similar to how Visual Studio Code or other IDEs “go to definition” and “find all symbols works.”
From the ctags website:
Ctags generates an index (or tag) file of language objects found in source files that allows these items to be quickly and easily located by a text editor or other utility. A tag signifies a language object for which an index entry is available (or, alternatively, the index entry created for that object).
Ctags with Vim
Ctags can be annoying to use because you have to run the ctags
program to generate a tag file, which then needs to be found by Vim to be used.
If the code changes, then the tag file will need to be re-updated.
One way to set up ctags is to map a Vim keypress to generate the ctags.
In my vimrc
, I set it up to regenerate ctags when I press <F5> while not in a file.
More on the ctags command later.
" Set f5 to generate tags for non-latex files
augroup TexTags
autocmd! TexTags
autocmd FileType tex let b:latex=1
augroup end
if !exists("b:latex")
nnoremap <f5> :!ctags -R<CR>
endif
In addition to generating the ctags, you also need to tell Vim where to find the generated tag files.
" Ctags search
set tags=./.tags;$HOME
This tags string says to search for the file .tags
in the current file’s directory and recursively search upward until the user’s home directory is reached.
Ctags with Vim and Git
However, I do not manually generate the ctags file with <F5>.
I store all my source code in Git, so instead of manually generating the ctags file, I use githooks to automatically generate the ctags files whenever I checkout, commit, merge, or rewrite.
To setup githooks to be added automatically to newly cloned or created git repos, you make a git template with the correct files.
The git template will be copied into the new repositories .git
folder on creation.
First, setup the git_template
structure.
~/.git_template/
└── hooks
├── ctags
├── post-checkout
├── post-commit
├── post-merge
└── post-rewrite
1 directory, 5 files
ctags:
#!/bin/sh
set -e
dir="`git rev-parse --show-toplevel`"
trap 'rm -f "$dir/.$$.tags"' EXIT
ctags -R --tag-relative --extra=+f -f"$dir/.$$.tags" --languages=-javascript,sql
mv "$dir/.$$.tags" "$dir/.tags"
post-checkout:
#!/bin/sh
dir=$(git rev-parse --git-dir)
$dir/hooks/ctags >/dev/null 2>&1 &
post-commit:
#!/bin/sh
dir=$(git rev-parse --git-dir)
$dir/hooks/ctags >/dev/null 2>&1 &
post-merge:
#!/bin/sh
dir=$(git rev-parse --git-dir)
$dir/hooks/ctags >/dev/null 2>&1 &
post-rewrite:
#!/bin/sh
dir=$(git rev-parse --git-dir)
case "$1" in
rebase) exec $dir/hooks/post-merge ;;
esac
You also have to add the template to your .gitconfig
.
[init]
templatedir = ~/.git_template
Now more about the ctags command.
To use the command, you first have to install ctags.
I use exuberant-ctags which can be installed with sudo apt install exuberant-ctags
on Ubuntu or Debian based Linux.
The ctags command I use is
ctags -R --tag-relative --extra=+f -f"$dir/.$$.tags" --languages=-javascript,sql
The -R
recurses into directories.
The --tag-relative
sets file paths relative to the tag file.
The --extra=+f
includes the entry for the base file name of every source file.
The -f
specifies that the tag file should be saved at the root of the git repository as .tags
.
Finally, the --languages
removes javascript and SQL from the languages which get tagged.
As mentioned before, this template will only apply to new git repositories; therefore, I also created two fish functions to reload git hooks based on the git template. One file reloads hooks in a git repo without submodules, and the other one recursively updates the hooks of all submodules.
git-reload-hooks.fish
:
function git-reload-hooks --description 'Reload git hooks'
rm -f (git rev-parse --git-dir)/hooks/*
git init
end
git-reload-hooks-all.fish
:
function git-reload-hooks-all --description 'Reload all git hooks'
git submodule foreach --recursive 'rm -f $(git rev-parse --git-dir)/hooks/*;git init'
end
If all of this seems like a lot to setup, I recommend storing all your linux dotfiles in a git repository with a script to symlink the files to the right location. I plan to create a future post with more detail on how this is done.
References
I followed https://tbaggery.com/2011/08/08/effortless-ctags-with-git.html when I was setting up ctags for the first time. I have since modified my setup to work better with git submodules and other edge cases.
Leave a comment