Quick error navigating with Vim QuickFix
Here I’ll show how I setup vim’s quickfix together with the output of rspec and pronto to minimize keystrokes.
A lot of us spending a lot of time writing tests, running them, and fixing them. For me, since I use vim and tmux, the steps goes something like this:
- Change some code
- Trigger test (in vim normal mode:
mtn
) - Test gets executed in tmux pane, either in the background or in an unfocused pane
- Read the error message, decide which file:line is the culprit
- Navigate to that file with
Ctrl+P
- Navigate to that line with
<n> gg
- Rinse and repeat
And then there’s a post-commit lint script that checks the new code that I just committed. That would output any issues to the stdout of the tmux side pane. The steps involed would look something similar to:
- Trigger lint check (either by committing cod, or manually with
mrr
in normal mode) - Read the error message, decide which file:line is the culprit
- Navigate to that file with
Ctrl+P
- Navigate to that line with
<n> gg
- Rinse and repeat
In both cases, I’ve become keenly aware that there’re 3 common steps that looks like an inefficientcy, arising from suboptimal flow of information: the terminal already has the file name and line number, but those information still had to travel through my eyes, brain, fingers and keyboard back to the vim terminal.
QuickFix comes to the rescue. I’ve been unknowingly using this feature with project-wide search(using ag.vim or ack.vim), where I can navigate to a result list, hit Enter and go to the listed location. It turns out we can load any file into this QuickFix list, as long as the format follows the right errorformat:
<filename_with_relative_path>:<line_number>:<message_about_error>
Since I’m lazy I decided to customize the error output to fit the default format instead.
Rspec quickfix output
See details in this blog post. I modified his examples though to have less personalization in the shared codebase.
1. Create a rspec formatter
In ~/rspec_quickfix_formatter.rb
, have something like:
# Format rspec output for vim quickfix
class QuickfixFormatter
RSpec::Core::Formatters.register self, :example_failed
def initialize(output)
@output = output
end
def example_failed(notification)
@output << format(notification) + "\n"
end
private
def format(notification)
rtn = "%s: %s" % [notification.example.location, notification.exception.message]
rtn.gsub("\n", ' ')[0,160]
end
end
2. Tell rspec to use it by default
RSpec allows global options which are overrided in order of specificity, so ~/.rspec can be overrided by $PROJECT_PATH/.rspec, which in turn can be overrided by command line options. So what I have in my global config file is as bellow:
# ~/.rspec
--color
--require /Users/<you>/rspec_quickfix_formatter.rb
--format QuickfixFormatter --out tmp/.quickfix_list
--format progress
This would output the format into a tmp file, but still show the progress format in stdout for more detailed error messages.
3. Tell vim to load the output
I map mre
to load quickfix list from rspec output, and open the list as well:
nnoremap <Leader>re :cf tmp/.quickfix_list<CR>:copen<CR>
Voila!
Pronto quickfix output
Make pronto output the right format
For pronto, the output format could be confiugred as well in a .pronto.yml
, but since the default output is just missing one :
, I have a small sed
running to fix the output instead.
The following is my post-commit script:
# .git/hooks/post-commit
#!/bin/bash
./bin/pronto > tmp/.pronto_output
if [ $? != 0 ]; then
echo "Fix offences! See errors in ./tmp/.quickfix_list"
sed -E 's/ /: /' tmp/.pronto_output > tmp/.quickfix_list
else
# Try to remove the quickfix_list, but supress the errors in case the
# file isn't there if previous run was also error-free
rm tmp/.quickfix_list 2> /dev/null
fi
I spend quite a big chunk of work writing in vim and tmux, so optimizing that feedback cycle can add up to many seconds saved. And what can I do with all the saved, you ask? Ask Kevin.