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):
-
$ /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
-
$
And here it is running through the textmate_urls Ruby script:
-
$ /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
-
$
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:
-
#!/usr/local/bin/ruby -ws
-
#
-
# textmate_urls - by Luke Melia <luke@lukemelia.com>
-
#
-
# 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
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