Ctags are more fun then you think

This post is dedicated to people who are already familiar with Ctags, and aims to show you how I use them. If you’ve never heard of Ctags before, and you use a code editor (not an IDE) I HIGHLY encourage you to read about it, then install Universal Ctags.

Now that you know all about Ctags, continue reading! you’ll love it. I think.

Vim

autotag.vim

autotag.vim makes sure your tag files are always up to date.

… using ctags -a will only change existing entries in a tags file or add new ones. It doesn’t delete entries that no longer exist. Should you delete an entity from your source file that’s represented by an entry in a tags file, that entry will remain after calling ctags -a.

autotag.vim fixes this issue by deleting all entries in the tags file referencing the source file that’s just been saved, and then executing ctags -a on that source file.

This is my current configuration:

" put the tags file in the git directory
let g:autotagTagsFile=".git/tags"

Tagbar

Tagbar is a Vim plugin that provides an easy way to browse the tags of the current file and get an overview of its structure. It does this by creating a sidebar that displays the ctags-generated tags of the current file, ordered by their scope…

If you’re a taglist.vim user, you should really check it out.

This is my current configuration:

let g:tagbar_autofocus = 1
" auto open tagbar when opening a tagged file
" does the same as taglist.vim's TlistOpen.
autocmd VimEnter * nested :call tagbar#autoopen(1)

fzf.vim

fzf is a general-purpose command-line fuzzy finder.

I’ve already written about fzf before, and said that it has complementing vim plugin, fzf.vim.

fzf.vim has a neat :Tags command that allows fuzzy finding tags. cool right?

Git

Tim Pope (aka: tpope) wrote a great blog post a few years ago about automatic ctag generation using git hooks.

Instead of manual copy-pasting the steps from his blog post, I wrote a script that does that automatically (including updating all current git projects):

  • Adds a ~/.git_template directory for git templates
  • Copy and configure all ctags hooks in that directory
  • Configure git ctags alias to generate ctags in the current directory
  • Recursively walk a given directory and update every folder that’s managed by git, to automatically generate ctags.

After running this script, all current and future git managed projects will have the hooks installed.

#!/usr/bin/env sh

# the directory where you put your code
TARGET_DIR="$1"
# the directory where git templates reside
TEMPLATE_DIR="$HOME/.git_template"

if test -z "$TARGET_DIR" || ! test -d "$TARGET_DIR"; then
echo "Usage: $0 <target-dir>"
exit 1
fi

mkdir -p "$TEMPLATE_DIR/hooks"

# configure the template directory
git config --global init.templatedir "$TEMPLATE_DIR"

# add a git alias: 'git ctags' that generates ctags
git config --global alias.ctags '!.git/hooks/ctags'

# create all the hooks
cat << 'EOF' > "$TEMPLATE_DIR/hooks/ctags"
#!/bin/sh
set -e
PATH="/usr/local/bin:$PATH"
dir="$(git rev-parse --git-dir)"
trap 'rm -f "$dir/$$.tags"' EXIT
git ls-files | \
ctags --tag-relative=yes -L - -f"$dir/$$.tags"
mv "$dir/$$.tags" "$dir/tags"
EOF

for f in "post-checkout" "post-commit" "post-merge"; do
cat << 'EOF' >> "$TEMPLATE_DIR/hooks/$f"
#!/bin/sh
.git/hooks/ctags >/dev/null 2>&1 &
EOF
done

cat << 'EOF' >> "$TEMPLATE_DIR/hooks/post-rewrite"
#!/bin/sh
case "$1" in
rebase) exec .git/hooks/post-merge ;;
esac
EOF

# make all hooks executable
for f in "post-checkout" "post-commit" "post-merge" "post-rewrite" "ctags"; do
chmod u+x "$TEMPLATE_DIR/hooks/$f"
done

# go recursively on all files in the directory
shopt -s globstar
# "re-init" will only copy the template, don't worry.
for dir in $TARGET_DIR/**/.git/; do
(cd "$(dirname "$dir")" || false && git init)
done