I'm going to talk today about vim's quickfix window, and how you can use it to make the whole "build, fix, build, test, fix, build, ..." cycle faster and easier.
If you're like me, a good chunk of your coding time is spent in a cycle that looks like:
- Make some edits
- If step 2 failed, goto 1. Else, goto 4.
- Run automated tests
- Goto 1
Note: I'm purposely ignoring the software-development process that goes on around these things (like when/how you write tests, when/how you commit your work, the size of the units of work). I just want to focus on the logistics of actually doing these things in a painless way.
I used to run these processes in different windows. A vim window for editing, a terminal window for building, and a terminal for running tests. This has some issues:
- You now have to use the information displayed on one window to inform your edits in another. Best case, you can see both windows at once, but often this isn't true.
- Your compiler tells you where the errors are, but you have to manually go to that location.
- Changing windows is precious keystrokes (or worse, the dreaded mouse)!
The solution: automate all the things
Before I talk about what all of this means, let me give you the sneak peek in to what a basic quickfix workflow would look like
- Edit a file -
- Select our compiler -
- Build the project -
- Navigate to first build error -
Those four steps will look something like this:
(Code shown is just a basic Queue implementation. There is a typo, where I tried
this.Typo, instead of
this.Value, which is what the error is
The window at the bottom of the screen there - that's the quickfix window. When
xbuild failed, it popped up with that convenient highlighted error message,
and jumped me directly to the location in the file that needed fixing.
The quickfix window is capable of giving this kind of functionality with any command that outputs file locations (file name, line number, column number). You can ask vim to run the command, parse this information on your behalf, and display it to you in the quickfix window.
How do I do it?
The functionality of running a command and interpreting the output as a series
of file locations is described in some very simple files, which vim calls
(I find the name "compiler" to be a bit loaded and misleading - I guess they were named that because they often correspond to actual compilers, but as I said they can really correlate to any sort of command that outputs information of this type. Compilers, test runners, linting rules, searching tools - all of these are valid commands to be turned into vim compiler plugins.)
Many different compiler plugins are built-in. Typing
:compiler into vim will
print a list of all of them installed on your system. My system lists:
Using one of these is simple. First, type
to select the
xbuild.vim compiler plugin for the current buffer (
:compiler! xbuild will select it for all buffers, so you don't have to set the compiler
again until you close vim). Then call
to run the command specified by the compiler plugin in the current working
directory. You can supply arguments to
:make that will be passed on as well,
When the compile command finishes, your vim window will return. You can open the
quickfix window with
k to navigate lines, then
to jump to the selected error. You can use
:cp[revious], and many
other built-in commands to navigate through the list of errors. Read more about
These commands are very useful, since vim will jump directly to the location of errors, even if those errors are in another file. This can save a lot of time navigating the filesystem and navigating within files, especially if the code you are working on is distributed across several areas.
Better builds and
You might notice that by default,
:make blocks your vim session. So you can't
view or edit files until it returns. And when it returns, it doesn't
automatically open the quickfix window for you.
That's totally lame.
Enter Tim Pope and his magnificent
vim-dispatch plugin. This plugin
introduces a few new commands, chief among them
:make, except that instead of blocking input, the
build process runs elsewhere (in a new pane if you are using tmux, a new tab in
iTerm, a background window, or just invisibly in a background process), then
pops up the quickfix window when it is done (if there were any errors). It never
steals focus, and allows you to build without interrupting your flow at all.
:Dispatch allows you to do the same with other tasks, without having to
specify a compiler. This is great for interleaving a build and the running of
certain tests. Suppose that I have a project that I'm building with the
compiler and testing with a custom
nunit compiler I wrote (the
compiler runs a script called
runNunitTests). Then I can use
:compiler! xbuild so that
:Make always runs xbuild for me. When I want to run a test, I
:Dispatch runNunitTests NameOfA.TestFixture.ToRun
It behaves exactly like
:Make (opening a process in the background and piping
the results to the quickfix window), but will automatically select the
appropriate compiler based on the command supplied, and won't change the setting
:compiler. So I can interleave
If I will be running the same test fixture over and over, I may want to
on it. I start with
:Focus runNunitTests NameOfA.TestFixture.ToRun
And now I can just run
:Dispatch with no arguments to dispatch on my focus.
For even more fun, bind
:Dispatch to function keys. Build and/or
test at the push of a button without interruption!
With just a few commands, you can really make a strong workflow in vim. Not just for editing, but for the whole coding process. But what if...
- You've got an obscure compiler that isn't listed?
- You've got a test suite that you want to run (like the
nunitrunner I talked about above)?
- Your build process is actually a complicated multi-step process (build -> lint -> minify -> test) that you want to all be done and parsed at once?
Then you will just have to write your own vim compiler plugin. This is a lot less terrifying than it sounds. In fact, in my next post I'll show you how to do just that - create a new compiler plugin for running nunit tests.