Working effectively with ctags has always been a topic I missed for a long time because I was too lazy to invest time to learn about the it.
I was working on on my application and was constantly consulting Padrino's API doc in the browser. It would have been more effective if I can do the searching directly the Padrino's code on GitHub. Benefit I don't have to leave the terminal and can focus on my task.
ctags is a tool which make it easy for you to shift through in no time.
It does this with indexing classes, methods, variables and other things. All this information
are stored in a tags
file - one line in this file contains one tag.
ctags makes it easy to browse a large code base like Padrino in which you are unfamiliar where to find what you are searching for. Jumping forward and backward between variables, classes, modules, and methods are essential to get understand a code base.
Depending on your operation system, you can install it with easiness:
$ sudo apt-get install exuberant-ctags
Why I'm installing exuberant-ctags
instead of ctags
? Ctags was originally written by Ken Arnold (the author of the "Rogue video game") and was first introduced in BSD Unix. Exuberant ctags is a variant of ctags
and was distributed with Vim 6.0. The main benefit of exuberant ctags is that it support over 40 languages and has regular expression support which make it easier to create your own custom language parser for creating the tags
file. The tags
contains the collected information about the things you want to know.
Just clone Padrino and run the following command at the root of the project:
$ git clone https://github.com/padrino/padrino-framework
$ cd padrino-framework
$ ctags -R .
The command will run recursively through the directory and will tag all sources and files it can find. During running
the ctags -R .
command I got the following error:
ctags: Warning: ignoring null tag in padrino-admin/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/bootstrap.min.js
Okay, we are actually interested in all the things, except JavaScript. Let's exclude those files with the following command:
$ ctags -R --exclude="*.js" .
If you are done with the command you have tags
file created in the current directory - there is no output if everything went well. Let's open the generated tags
file and try to understand the basic nature of the file:
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
!_TAG_PROGRAM_AUTHOR Darren Hiebert /[email protected]/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
!_TAG_PROGRAM_VERSION 5.9~svn20110310 //
<< padrino-core/lib/padrino-core/logger.rb /^ def <<(message = nil)$/;" f
== padrino-core/lib/padrino-core/mounter.rb /^ def ==(other)$/;" f class:Padrino.Mounter
AbstractFormBuilder padrino-helpers/lib/padrino-helpers/form_builder/abstract_form_builder.rb /^ class AbstractFormBuilder # @private$/;" c class:Padrino.Helpers.FormBuilder
AbstractHandler padrino-helpers/lib/padrino-helpers/output_helpers/abstract_handler.rb /^ class AbstractHandler$/;" c class:Padrino.Helpers.OutputHelpers
AccessControl padrino-admin/lib/padrino-admin/access_control.rb /^ module AccessControl$/;" m class:Padrino.Admin
AccessControlError padrino-admin/lib/padrino-admin/access_control.rb /^ class AccessControlError < StandardError # @private$/;" c class:Padrino.Admin
Account padrino-admin/test/fixtures/data_mapper.rb /^class Account$/;" c
Actions padrino-admin/lib/padrino-admin/generators/actions.rb /^ module Actions$/;" m class:Padrino.Generators.Admin
...
The header of each tag file gives you basic information about the creation of the file:
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
!_TAG_PROGRAM_AUTHOR Darren Hiebert /[email protected]/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
!_TAG_PROGRAM_VERSION 5.9~svn20110310 //
As you can see, the tags are sorted, folded, and you can see the version of ctags in which they were created. If you take a look close in the example above, you can see detect a tag name schema: Let's consider our first tag:
<< padrino-core/lib/padrino-core/logger.rb /^ def <<(message = nil)$/;" f
First of all you have the tagname <<
, then a tab as separator (it isn't visible in the code examples above), then tagfile padrino-core/lib/padrino-core/logger.rb
where the tag can be found, followed by a tab, and finally the tagaddress the exact location of the tag inside the tagfile. The tagaddress is a regular expression - the special characters in a search pattern are ^
(begin-of-line) and $
(indicates the end of the line). Finally a term marked with ;"
which indicates if the tag is either a class (c
), module (m
), or function (f
). The term may differ for which language constructs you are going to create your tags.
Let's open the padrino-core/lib/padrino-core.rb
file and place your cursors under the server
name:
require 'sinatra/base'
require 'padrino-core/version'
require 'padrino-core/support_lite'
require 'padrino-core/application'
require 'padrino-core/caller'
require 'padrino-core/command'
require 'padrino-core/loader'
require 'padrino-core/logger'
require 'padrino-core/mounter'
require 'padrino-core/reloader'
require 'padrino-core/router'
require 'padrino-core/server'
require 'padrino-core/tasks'
require 'padrino-core/module'
# The Padrino environment (falls back to the rack env or finally develop)
PADRINO_ENV = ENV["PADRINO_ENV"] ||= ENV["RACK_ENV"] ||= "development" unless defined?(PADRINO_ENV)
# The Padrino project root path (falls back to the first caller)
PADRINO_ROOT = ENV["PADRINO_ROOT"] ||= File.dirname(Padrino.first_caller) unless defined?(PADRINO_ROOT)
module Padrino
...
end
Now press <C-]>
- you'll directly to the Server
class in the padrino-core/lib/padrino-core/server.rb
file. Awesome,
that is just what you know now what you want to know and you can jump back to your previous position with <C-t>
or
<C-o>
.
You can also jump to the tag you want the commandline mode in Vim and use the :tag
command. Type in the name of the class or method you want to jump look into. For example, if you type in
:tag Padrino
you will jump to a tag. But you allready have a smell in mind that the Padrino
exists more than one
time. You may think this is a mistake but ctags keeps up a list of all the tags you have explored so far.
If you searched after :tag Padrino
again you can message line beyond in your command window: tag 1 of 81 or more
.
There are 81 browsable matchings tags available. Per default Vim will take the first matching tag if your search for the
first time in your vim session after :tag Padrino
. Use :tselect
followed by a number to jump to the tag you want:
# pri kind tag file
> 1 F C m Padrino padrino-admin/lib/padrino-admin.rb
module Padrino
2 F m Padrino padrino-admin/lib/padrino-admin/access_control.rb
module Padrino
3 F m Padrino padrino-admin/lib/padrino-admin/generators/actions.rb
module Padrino
4 F m Padrino padrino-admin/lib/padrino-admin/generators/admin_app.rb
module Padrino
5 F m Padrino padrino-admin/lib/padrino-admin/generators/admin_page.rb
...
So you can chose what you want to have. There a bunch of more commands you can use to navigate multiple tags
:tnext (or :tn)
: goes to the next tag in the:tselect
list:tprev (or :tp)
: goes to the previous tag in the:tselect
list:tfirst (or :tf)
: goes to the first tag in the:tselect
list:tlast (or :tl)
: goes to the last tag in the:tselect
list
Before going to add the tags for the Padrino project as well as any other sources you are using on your project, we need to understand how we can get a big tag file of all our installed gems and after that limit our scope to these parts we only need.
Let's say you are using rbenv to manage your ruby versions and you want to get a global tag file of all your installed gems. Please run the following command:
ctags -R -f gems.tag * ~/.rbenv/versions/<your-ruby-version>/lib
I have 200 installed gems on my system (use gem list | wc -l
) and it took around 7 seconds to generate a tag file with over 200.000 lines. The chances are high that you have errors in your tags
file occur and I really don't want to load such a big tag file into my vim session. Please note, that the generated tag file gems.tag
instead of tags
.
If you think about a ruby project, it is very likely that you will have Gemfile
with all the specified extensions you
need for your project. My friend Andrew Radev has created a small ruby snippet that uses Bundler API for retrieving the used Gemfile in your project and builds a tag file. Instead of using the script you can also
perform the following one-liner on the route of your application:
ctags -R -f gems.tags `bundle show --paths`
Let's say you have a Padrino project (like my Job Vacancy) with the following Gemfile
:
source 'https://rubygems.org'
# Server requirements
gem 'thin', '1.5.1'
# Project requirements
gem 'rake', '10.0.4'
gem 'uglifier', '2.1.1'
gem 'yui-compressor', '0.9.6'
# Component requirements
gem 'erubis', '~> 2.7.0'
gem 'activerecord', '~> 3.2.9', :require => 'active_record'
gem 'sqlite3', '~> 1.3.6'
# Test requirements
group :test do
gem 'rspec' , '2.13.0'
gem 'factory_girl', '4.2.0'
gem 'rack-test', '0.6.2', :require => 'rack/test'
end
gem 'guard-rspec'
gem 'libnotify'
# Security
gem 'bcrypt-ruby', '3.0.1', :require => 'bcrypt'
# Padrino Stable Gem
gem 'wirble', '0.1.3'
gem 'pry', '0.9.12'
gem 'tilt', '1.3.7'
# Padrino edge
gem 'padrino', :git => "git://github.com/padrino/padrino-framework.git"
bundle show --paths
will print the absolute path of the used Gems in the following form:
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/open4-1.3.0
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/bundler/gems/padrino-framework-0c1317b0c897/padrino
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/bundler/gems/padrino-framework-0c1317b0c897/padrino-admin
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/bundler/gems/padrino-framework-0c1317b0c897/padrino-cache
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/bundler/gems/padrino-framework-0c1317b0c897/padrino-core
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/bundler/gems/padrino-framework-0c1317b0c897/padrino-gen
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/bundler/gems/padrino-framework-0c1317b0c897/padrino-helpers
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/bundler/gems/padrino-framework-0c1317b0c897/padrino-mailer
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/bundler/gems/padrino-sprockets-8f4f6150b2d9
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/polyglot-0.3.3
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/pry-0.9.12
/home/helex/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/rack-1.5.2
When you start vim in the root of your project, it will only load the tags
file but not the gems.tags
file. Just
open the commandline in vim and type in :set tags+=gems.tags
.
If you find yourself doing it over and over again for different project, you have to setup the following setting in your
.vimrc
set tags=tags,./tags,gems.tags,./gems.tags
Vim will search for the file named tags
, starting with the directory of the current file and then going to the parent
directory and then recursively to the directory one level above, till it either locates the tags
file or reaches the
root directory. It will do exactly the same for the gems.tags
file.
During the coding you are going to change the name of the method or class and have to generate the tags by hand.
Do you really want to leave the terminal and generate the tags on your own? Tada, there is Autotags.vim. It deletes all tags that are no longer present and calls ctags -a
to append the new tags to your existing tag file. To try it out, just create a new directory with a the following class definition inside the file:
class Ctags
end
Now create the tags with ctags -R .
search after the tag Ctags
with :tag Ctags
, after that change the class name
from Ctags
to VimCtags
. If this doesn't work for you, it means that you don't have python support enabled for
your vim version. You can check it with vim --version | grep python
- a +python
means that you have it.
I have written a blog post about how to install vim with python support for the case you have problems with this.
As we have learned before it is possible that we partialy know the name of the class or method name we want to use. This
is wherre you have to use ctrlp.vim plugin. Ones installed it gives you the
:CtrlPTag
tag command which make it possible to do a fuzzy like tag search.
If you find yourself using this command very often, you have to add the following mapping into your vimrc
:
nnoremap <leader>. :CtrlPTag<cr>
if the Ctrlp doesn't provide you enough hits, please use :tselect
.