I’m a heavy Vim user and demand speedy navigation between files. I rely on Exuberant Ctags and tag navigation (usually Ctrl-]) to move quickly around the codebase.
There were times, however, when I wasn’t in Vim but wanted to use tags to
access information quickly; most noticeable was time spent in my shell,
searching the codebase with ag
.
As a zsh
user, I was already aware of introducing tab completion by way
of compdef
and compadd
:
_fn_completion() {
if (( CURRENT == 2 )); then
compadd foo bar baz
fi
}
compdef _fn_completion fn
In this example, fn
is the binary we want to add tab completion to, and we
only attempt to complete after typing fn
and then TAB. By checking
CURRENT == 2
, we’re verifying the position of the cursor as the second field
in the command. This will complete with options foo
, bar
, and baz
, and
filter the options accordingly as you start typing and hit TAB again.
Now that we understand how to configure tab completion for commands, next up is
determining how to extract useful information from the tags
file. Here’s the
first few lines of the file from a project I worked on recently:
== ../app/models/week.rb /^ def ==(other)$/;" f class:Week
AccessToken ../app/models/access_token.rb /^class AccessToken < ActiveRecord::Base$/;" c
AccessTokensController ../app/controllers/access_tokens_controller.rb /^class AccessTokensController < ApplicationController$/;" c
The tokens we want to use for tab completion are the first set of characters per
line, so we can use cut -f 1 path/to/tags
to grab the first field. We then use
grep -v
to ignore autogenerated ctags metadata we don’t care about. With a
bit of extra work (like writing stderr
to /dev/null
in the instance where
the tags
file doesn’t exist yet), the end result looks like this:
_ag() {
if (( CURRENT == 2 )); then
compadd $(cut -f 1 .git/tags tmp/tags 2>/dev/null | grep -v '!_TAG')
fi
}
compdef _ag ag
With this in place, we can now ag
a project and tab complete from the
generated tags
file. With ag Acc
TAB:
$ ag AccessToken
AccessToken AccessTokensController
And the result:
[ ~/dev/thoughtbot/project master ] ✔ ag AccessToken
app/controllers/access_tokens_controller.rb
1:class AccessTokensController < ApplicationController
15: @project = AccessToken.find_project(params[:id])
app/models/access_token.rb
1:class AccessToken < ActiveRecord::Base
db/migrate/20140416195446_create_access_tokens.rb
1:class CreateAccessTokens < ActiveRecord::Migration
db/migrate/20140718175701_add_index_on_access_tokens_project_id.rb
1:class AddIndexOnAccessTokensProjectId < ActiveRecord::Migration
spec/models/access_token_spec.rb
3:describe AccessToken, 'Associations' do
7:describe AccessToken, '.find_project' do
12: result = AccessToken.find_project(access_token.to_param)
20: expect { AccessToken.find_project('unknown') }.
26:describe AccessToken, '.generate' do
40:describe AccessToken, '#to_param' do
50: expect(AccessToken.find(access_token.to_param)).to eq(access_token)
Voila! Tab completion with ag
based on the tags
file.
If you’re using thoughtbot’s dotfiles, you already have this behavior.