Using rtags ang gtags for coding Ruby

Boring intro

When I was a C and Java developer (not that long ago), I always used the GNU GLOBAL source code tag system gtags.

Despite it’s awful, impossible-to-find-in-google name, it’s a wonderful system to navigate around your code: find function definitions, where they are used, fast “grepping” of code, you name it.

And, most important, it’s emacs support is wonderful!!

You can check their tutorial. It’s a little bit scary at the beginning, but, really, you should try it.

I want to show you how i use gtags when coding Ruby. Hopefully it will be useful to somebody else.

First problem: gtags only supports C, C++, Java, PHP, YACC ¿¿?? “out of the box”.

Fortunately, it’s developers are very smart and have thought of a system where you can use any program that outputs a pretty established format out of the code you want to tag (anything that emits output like ctags -x does).

So, you can use, for example, Exuberant ctags, to parse your Ruby code and then use gtags to navigate it.

Exuberant ctags it great for parsing lot’s of languages, but not so good with Ruby. But don’t despair, there’s this guy who has wrote rtags, a parser designed to extract code tags from Ruby code. This is more Ruby-especific, so it usually gets much more information (for example, attr_accessor :foo , alias :foo :bar).

Also, with rtags, it’s possible to tag not only my_method, but also MyClass#my_method, so you can list, for example, every method in one class.

Problems: rtags is slow. Painfully slow, compared with ctags. But gtags is pretty smart, I told you. The first time you index your sources it’ll be slow. But, after changing and changing your code, it’ll just reindex the files that have changed since it’s last reindex.

Get the code

First, you should have installed gtags (apt-get install global)

Now, you need to get a rtags version that can output cros xref format:

I have patched rtags, so you can use it with gtags (I am not that smart, it was easy, since the rtags code is easily read).

I have placed it in github, in http://github.com/gaizka/rtags/tree/master.

If you don’t care about git, you can get the file at: http://github.com/gaizka/rtags/tree/master%2Fbin%2Frtags?raw=true

Once you have, place anywhere in your disk, chmod +x, and add that direcotry to your $PATH.

Update The rtags mantainer has merged my modifications into the official repo, so can install directly with gems:

    $ gem install rtags --version 0.97

Just make sure it’s versión 0.97 or higher.

Configure it

With the installation of gtags comes a sample configuration file that you have to use to extend, to use it with rtags. First copy the sample:

    cp /usr/share/doc/global/examples/gtags.conf $HOME/.globalrc

And then add this lines. Careful with it, this format is awful, and you cannot have any blank lines on it.

(You can get the whole file here: http://pastie.org/290127

    # We need to change some of the output that rtags dumps
    #
    # Module::Submodule is NOT found by global (don't know why)
    # Module:Submodule IS found
    # So we replace :: with :
    #
    # Also, dotnames are not allowed in tags
    # So, when rtag parses this
    #   class User ;  def self.validate ; end ; end
    # as:
    #   User.validate  line_no file_name line
    # global doesn't find it with: global -c User
    # So we need to replace any dots in the firts column with #
    #   User#validate line_no file_name line
    #
    # So we need to to this:
    # rtags -x %s | awk '{ sub(/^::/, "") ; sub(/::/, ":"); sub(/\\./, "#", $1); print}'
    #
    rtags:\
    	:tc=common:\
    	:suffixes=s,a,sa,asm,C,H,cpp,cxx,hxx,hpp,cc,c,h,y,rb:\
    	:extractmethod:\
    	:GTAGS=rtags -x %s | awk '{ sub(/^\:\:/, "") ; sub(/\:\:/, "\:"); sub(/\\./, "#", $1); print}':\
    	:GRTAGS=gtags-parser --langmap=java\:.rb -dtr  %s:\
    	:GSYMS=gtags-parser --langmap=java\:.rb -dts  %s:
    #

Last, you need to tell gtags that you want to use the rtags parser. To do so, yo need to set the environment variable:

    export GTAGSLABEL=rtags

Test it

OK, we now should be able to roll!

Let’s index, for example, activerecord. Go to your gems directory, and try it (you should adapt your paths accordingly):

 
    cd $HOME/gem_repository/gems/activerecord-2.0.2
    export GTAGSLABEL=rtags
    $ gtags -v
    [Fri Oct 03 19:54:48 CEST 2008] Gtags started.
     Using config file '/home/gaizka/.globalrc'.
    [Fri Oct 03 19:54:48 CEST 2008] Creating 'GTAGS'.
     [1] extracting tags of lib/active_record.rb
     [2] extracting tags of lib/active_record/attribute_methods.rb
     ...
     ...
     [174/174] extracting tags of test/serialization_test.rb
    [Fri Oct 03 19:55:43 CEST 2008] Done.

List methods starting with find_:

    $ global -x "^find_*" 
    find_all_ordered  337 test/associations/eager_test.rb def find_all_ordered
    find_by_sql       531 lib/active_record/base.rb def find_by_sql
    find_every       1230 lib/active_record/base.rb def find_every
    find_first         22 lib/active_record/associations/has_and_belongs_to_many_association.rb    def find_first
    ...

ActiveRecord errors:

    $ global -x "^ActiveRecord:Errors*"
    ActiveRecord:Errors   19 lib/active_record/validations.rb class Errors
    ActiveRecord:Errors#[]  119 lib/active_record/validations.rb alias :[]
    ActiveRecord:Errors#add   64 lib/active_record/validations.rb def add
    ActiveRecord:Errors#add_on_blank   79 lib/active_record/validations.rb def add_on_blank
    ActiveRecord:Errors#add_on_empty   70 lib/active_record/validations.rb def add_on_empty
    ...

Command line is great, but when you are editing, you are editing, so you shouldn’t have to drop to the console when you are using your $EDITOR.

Vim support

I just use vim for quick edits, so i cannot help you here. Just point you to gtags documentation: http://www.gnu.org/software/global/globaldoc.html#SEC26

Emacs support

If you’re using the one true editor, you’re lucky. Gtags support is great.

If you want to use gtags in all your ruby coding, then add this lines to your .emacs:

  (autoload 'gtags-mode "gtags" "" t)
 
  (add-hook 'ruby-mode-hook (lambda () 
    (gtags-mode 1)
    (setq gtags-symbol-regexp "[A-Za-z_:][A-Za-z0-9_#.:?]*")
    (define-key ruby-mode-map "\e." 'gtags-find-tag)
    (define-key ruby-mode-map "\e," 'gtags-find-with-grep)
    (define-key ruby-mode-map "\e:" 'gtags-find-symbol)
    (define-key ruby-mode-map "\e_" 'gtags-find-rtag)))

Now, open one of the files of activerecord.

    cd $HOME/gem_repository/gems/activerecord-2.0.2
    emacs lib/active_record.rb

We need to tell gtags where to look for it’s parsed files:

    M-x gtags-visit-rootdir

And point it to $HOME/gem_repository/gems/activerecord-2.0.2

After this, let’s look for tags starting with Time

    M-x gtags-find-tag <RETURN> rename <TAB><TAB>

You should get the completions:

    rename_column    rename_table

Choose, for example, rename_table and you should get a window with contents like:

    rename_table      110 abstract/schema_statements.rb def rename_table
    rename_table      419 mysql_adapter.rb def rename_table
    rename_table      584 postgresql_adapter.rb def rename_table
    rename_table      212 sqlite_adapter.rb def rename_table
    rename_table      373 sqlite_adapter.rb def rename_table

Choose the line you want to go to, and bingo!, you should be there.

You can search inside modules by “tabbing” into them.

    M-x gtags-find-tag ActiveRecord<TAB><TAB>:Callbacks<TAB><TAB>#create_with_callbacks

One think i haven’t managed to is to get rtags emit it’s format so you can find both ActiveRecord:Callbacks#create_with_callbacks and Callbacks#create_with_callbacks. I’ll try to do it, so stay tunned!

You CAN find both ActiveRecord:Callbacks#create_with_callbacks and just create_with_callbacks, though.

Finding not just functions/classes definitions

Finding function definitions is great. But sometimes you need to know where one method is called, for example.

You can then use one of the next functions:

  • gtags-find-rtag . This one finds function references. It does not that good with Ruby, usually it finds more lines than desired. Works great with C or Java, though.

  • gtags-find-with-grep. This one uses grep for finding occurences in the lines of the code.

  • gtags-find-symbol. This one finds where “symbols” (not Ruby symbols, more like “tokens”)are used.

For example, to look where NotImplementedError are raised, you can do:

    M-x gtags-find-symbol NotImplementedError
 
    NotImplementedError   34 database_statements.rb         raise NotImplementedError, "select_rows is an abstract method"
    NotImplementedError   39 database_statements.rb         raise NotImplementedError, "execute is an abstract method"
    ....

Or, where ALTER TABLE is used:

    M-x gtags-find-with-grep ALTER TABLE
 
    ALTER%20TABLE     122 schema_statements.rb         add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
    ALTER%20TABLE     131 schema_statements.rb         execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
    ....

Reparsing files

Ok, you have coded loads of new functionality, and you want it to get tagged. You don’t need to execute gtags again, it’s too slow. You need to call this (in the directory where GTAGS file is).

    $ global -u -v

And it will only parse modified files since last parse, so it is pretty fast (don’t forget to export GTAGSLABEL=rtags to tell it to use rtags).

The end

I hope this is useful to somebody. If you need any aclaration, don’t hesitate in leaving a comment, i’ll try to help you with it.

Thanks to all the great developers out there that release free-as-in-freedom software. ¡¡Thanks for making our coding sessions easier!!

Discussion Area - Leave a Comment