Clickable Ruby Stacktraces with iTerm & TextMate
UPDATED: Rev’d the script to strip out “stupid rails /../ crap”. Thanks, Ryan.
When I’m working in Ruby on my delightful new MacBook, I sometimes miss a feature of VisualStudio: the ability to double-click a line of a stacktrace in a test failure and open that file and line number in the editor.
I started thinking about how it might be possible at RubyEast on Friday, and tonight I hacked something together that works for TextMate and iTerm, my tools of choice. Here’s the scoop:
iTerm has a feature where you can Command-Click a URL in terminal window and “follow” it using the app registered for that protocol. TextMate has a txmt:// URL scheme that lets you open files in textmate.
I wrote a ruby script that filters input to convert stacktrace lines to the txmt::// format. So here is a normal test failure (without processing through the filter):
[code]
$ /usr/local/bin/ruby -I.:lib:test -rtest/unit -e "%w[test/unit/some_test.rb].each { |f| require f }"
Loaded suite -e
Started
.....F...
Finished in 0.128739 seconds.
1) Failure:
test_spec {Someone, in general, } 006 [should say something](Someone, in general, )
[/Users/lmelia/devprojects/secret/config/../vendor/plugins/test_spec_on_rails/lib/test/spec/rails/test_spec_ext.rb:6:in `equal'
./test/unit/blog_entry_factory_test.rb:68:in `test_spec {Someone, in general, } 006 [should say something]'
/Users/lmelia/devprojects/secret/config/../vendor/gems/mocha-0.5.3/lib/mocha/test_case_adapter.rb:19:in `__send__'
/Users/lmelia/devprojects/secret/config/../vendor/gems/mocha-0.5.3/lib/mocha/test_case_adapter.rb:19:in `run']:
<"Someone's clever response goes where?"> expected but was
<"Someone's clever response goes here.">.
9 tests, 11 assertions, 1 failures, 0 errors
$
[/code]
And here it is running through the textmate_urls Ruby script:
[code]
$ /usr/local/bin/ruby -I.:lib:test -rtest/unit -e "%w[test/unit/blog_entry_factory_test.rb].each { |f| require f }" | textmate_urls
Loaded suite -e
Started
.....F...
Finished in 0.111423 seconds.
1) Failure:
test_spec {Someone, in general, } 006 [should say something](Someone, in general, )
[txmt://open?url=file:///Users/lmelia/devprojects/secret/vendor/plugins/test_spec_on_rails/lib/test/spec/rails/test_spec_ext.rb&line=6 :in `equal'
txmt://open?url=file:///Users/lmelia/devprojects/secret/test/unit/blog_entry_factory_test.rb&line=68 :in `test_spec {Someone, in general, } 006 [should say something]'
txmt://open?url=file:///Users/lmelia/devprojects/secret/config/../vendor/gems/mocha-0.5.3/lib/mocha/test_case_adapter.rb&line=19 :in `__send__'
txmt://open?url=file:///Users/lmelia/devprojects/secret/config/../vendor/gems/mocha-0.5.3/lib/mocha/test_case_adapter.rb&line=19 :in `run']:
<"Someone's clever response goes where?"> expected but was
<"Someone's clever response goes here.">.
9 tests, 11 assertions, 1 failures, 0 errors
$
[/code]
Now I can mouseover any of the stacktrace lines above, hold down command and click, and I’m automatically switched to TextMate with the specified file and line showing.
Yay!
My next step is to figure out how best to integrate it with autotest. If you have ideas, please leave a comment!
Here’s the script:
[ruby]
#!/usr/local/bin/ruby -ws
#
# textmate_urls – by Luke Melia
#
# usage:
# test.rb | textmate_urls
############################################################
class TextmateUrls
def self.urlize
trap ‘INT’ do exit 1 end
TextmateUrls.new.urlize
end
##
# Scans input looking for stacktrace lines and rewrite them with textmate urls in the output
def urlize(input=ARGF, output=$stdout)
cwd = `pwd`.chomp #remember the current working directory to rewrite relative paths in the stacktrace
old_sync = output.sync
output.sync = true
while line = input.gets
case line
when %r{(^[a-z_]+\([A-Za-z]+\)) \[\.?(/?.*):(\d+)\]:}
line = “#{$1}\ntxmt://open?url=file://#{File.join(cwd, $2)}&line=#{$3}”
line.gsub!(%r|/[^/]+/\.\./|, ‘/’)
when %r{^\s*(\[?)(/.+):(\d+):(.*)$} # a stacktrace line
line = "#{$1}txmt://open?url=file://#{$2}&line=#{$3} #{$4}"
line.gsub!(%r|/[^/]+/\.\./|, ‘/’)
when %r{^\s*(\[?)\.?(/?.+):(\d+):(in .*)$} # a stacktrace line with a relative file reference
line = "#{$1}txmt://open?url=file://#{File.join(cwd, $2)}&line=#{$3} #{$4}"
line.gsub!(%r|/[^/]+/\.\./|, ‘/’)
end
output.puts line
end
output.sync = old_sync
end
end
TextmateUrls.urlize
[/ruby]
Happy stack-walking!

you get this for free when using Netbeans :-)
October 3rd, 2007 at 1:48 am
Reinier: yeah, but then he’d be running netbeans. My guess is textmate + iterm + rails + the rest of the OS runs within the memory footprint of netbeans…
Luke: while I’m glad you now have clickable links, isn’t there some way to keep them readable? Maybe even cleaning them up even more over your default (remove full paths and the stupid rails /../ crap)?
I’ve got all that and autotest integration and more within emacs. It truly is helpful.
October 4th, 2007 at 3:07 pm
The same thing works in Leopard’s Terminal.app. If you right click a link, you can choose “Open URL”. I confirmed this works for txmt:// URLs.
December 29th, 2007 at 5:20 pm
I am new to RoR and this has helped me immensely when using autotest. I am trying to figure out how to hook this into the browser’s stack trace. If anyone has any suggestions, please let me know.
Thanks again for this killer script!
February 28th, 2008 at 10:25 pm
Did you ever get this hooked into autotest?
August 6th, 2008 at 7:22 pm
[...] Luke Melia ยป Clickable Ruby Stacktraces with iTerm & TextMate (tags: ruby textmate autotest iterm testing) [...]
December 30th, 2008 at 1:04 am