For the past several weeks I have been using MacVim as my primary editor for Ruby coding. My workflow has been to edit some code, Command-Tab over to a Terminal window and run a test by either typing the command, or using the up-arrow to run the same test I had already typed in.
This worked pretty well, but it was annoying needing to type in the full path to the test file when running a new test. And I would often Command-Tab just once thinking this would take me back to a Terminal window, but end up looking at a browser forgetting that I had been reading some documentation in between test runs.
I played around with some techniques for running the test file “inside” of MacVim, but did not like how they would lock up my editor, or added new buffer splits to show the output.
To improve my workflow I put together a few Vim functions for running a test file in a specific Terminal or iTerm tab using an AppleScript helper script (executed with the
osascript command line tool). When I start working on a project I set a global variable specifying the TTY name of the Terminal tab I want to run the test in. Running the
tty command in Terminal or iTerm will print out the value that needs to be set. I use the following mapping as a shortcut for setting the global variable, including a hardcoded default value.
Three functions are provided in the following Vimscript. One runs the entire file, one runs the file at a specific line, and the last re-runs a previously run file.
function! RunInTerminal(file) if match(a:file, '_spec\.rb') != -1 let l:command = 'bundle exec rspec' elseif match(a:file, '\.feature') != -1 let l:command = 'bundle exec cucumber' elseif match(a:file, '\.rb') != -1 let l:command = 'ruby' endif if exists("l:command") let g:last_run_in_terminal = a:file let l:run_script = "!osascript ~/.vim/tools/run_command.applescript" silent execute ":up" silent execute l:run_script . " '" . l:command . " " . a:file . "' " . g:vim_terminal . " &" silent execute ":redraw!" else echo "Couldn't figure out how to run " . a:file end endfunction function! RunFileInTerminal() if exists("g:vim_terminal") call RunInTerminal(expand("%")) else echo "You need to set g:vim_terminal to a valid TTY (e.g. /dev/ttys000)" end endfunction function! RunFileAtLineInTerminal() if exists("g:vim_terminal") call RunInTerminal(expand("%") . ":" . line(".")) else echo "You need to set g:vim_terminal to a valid TTY (e.g. /dev/ttys000)" endif endfunction function! ReRunLastFileCommand() if exists("g:vim_terminal") && exists("g:last_run_in_terminal") call RunInTerminal(g:last_run_in_terminal) endif endfunction command! RunFileInTerminal call RunFileInTerminal() command! RunFileAtLineInTerminal call RunFileAtLineInTerminal() command! ReRunLastFileCommand call ReRunLastFileCommand()
The following AppleScript looks pretty awful, but it seems to work and I just can’t muster up the motivation to spend the time learning the language well enough to make it any better. The Vim functions above expect this script to be reside in
~/.vim/tools/run_command.applescript. It looks through all of the Terminal.app and iTerm.app tabs trying to find one that matches a given TTY name.
on appIsRunning(appName) tell application "System Events" to (name of processes) contains appName end appIsRunning on execInItermTab(_terminal, _session, _command) tell application "iTerm" activate set current terminal to _terminal tell _session select _session write text _command end tell end tell end selectTerminalTab on execInTerminalTab(_window, _tab, _command) tell application "Terminal" activate set frontmost of _window to true set selected of _tab to true do script _command in _tab end end execInTerminalTab on run argv set _command to item 1 of argv set _foundTab to false -- Second argument should be the tty to look for if length of argv is 2 if appIsRunning("iTerm") then tell application "iTerm" repeat with t in terminals tell t repeat with s in sessions set _tty to (tty of s) if _tty = (item 2 of argv) then set _foundTab to true set _session to s set _terminal to t exit repeat end if end repeat end tell if _foundTab then exit repeat end if end repeat end tell if _foundTab then execInItermTab(_terminal, _session, _command) end if end if if not _foundTab and appIsRunning("Terminal") then tell application "Terminal" repeat with w in windows tell w repeat with t in tabs set _tty to (tty of t) if _tty = (item 2 of argv) then set _foundTab to true set _window to w set _tab to t exit repeat end if end repeat end tell if _foundTab then exit repeat end if end repeat end tell if _foundTab then execInTerminalTab(_window, _tab, _command) end if end if end if end run
If there is interest I am thinking about packaging this up as a Vim plugin, but for now you can just download the files and fit them into your Vim configuration however you see fit.
One final note: I tried this out in the native Vim that comes with Mac OS X and it seems to work, but I haven’t really tested it much in anything other than the latest MacVim (7.3).
I think you should really check out Guard. It makes writing all those vim scripts obsolete.
I will frequently run only a single test within a file, especially when working on system/integration tests that take longer to run. I have always preferred having control over which test file, or even which test within a file, I want to run after making code changes. But perhaps I should give something like guard-rspec just to see how it goes.
Thanks for the feedback.
@Patric running just a single file from a test is something that Guard excels at, like it was made just for that. But it’s oh so much more powerful…
@Patric But, of course, you cannot make it run a single test :)
Sorry for the spam, but apparently RSpec supports a :focus option added to each spec and with a special config set in spec_herlp it lets you run only the selected spec! See http://railscasts.com/episodes/285-spork :)
Comments are closed.