Vim errorformat Demystified

Vim's errorformat is on of the more arcane part's of vim. It has constantly had me tearing my hair out, if you've found this page there's a good chance you are too. Judging from the google results I've seen this is quite a common reaction.

Simple, single line formats are easy enough to work out with a little trial and error, the problems start mounting when you need multiple formats simultaneously, or worse, multi line error formats.

I believe this is more to do with the documentation than anything else. Despite being extensive and containing many examples it actually does very little to explain things. Once you understand a few key things though, it's actually much simpler than it seems.

An Array of Formats

The efm variable is a csv of error formats. Personally I prefer to visualize it as an array. Let's look at an example from vim's documentation example like:

:set efm=%EError\ %n,%Cline\ %l,%Ccolumn\ %c,%Z%m

You might intuitively think that this is a single error format, which is exactly what I thought. It is in fact four completely separate error format's, delineated by commas. Vim will loop through each pattern and see if the current line matches it, breaking if it finds a match.

Let me repeat that, these are four separate error formats, understanding this was my eureka moment, when I finally understood how it worked. The above format could be written as:

:set efm=%Cline\ %l,%Ccolumn\ %c,%Z%m,%EError\ %n

Which would work exactly the same, at least in this limited scenario. The format I prefer however is this:

set efm=%EError\ %n
set efm+=%Cline\ %l
set efm+=%Ccolumn\ %c
set efm+=%Z%m

This allows the precedence order to be adjusted by moving the lines up and down. When you have to deal with multiple error formats this is incredibly important.

Step by Step

Now for a real world example, recently I wanted to write a format that works with the nunit/nant combo, the output looks like this:

[exec] 1) Test Failure : TestClass.IsTrue
[exec]   Custom error message goes here.
[exec]   Expected: True
[exec]   But was:  False
[exec]
[exec] at TestClass.IsTrue() in c:\projects\book projects\src\app\Test.cs:l ine 9

The first step to creating an error format is to get the file name and line:

set efm=%.%#\ at\ %.%#\ in\ %f:line\ %l

This is saying:

  1. Find any text (%.%# is a wild card) until we get to " at ".
  2. Then any text until we get to " in ".
  3. Then the filename (%f) followed by a colon (:)
  4. Finally the word "line " (whitespace sensitive) followed by the line number (%l)

If it's run against a failing test then the output will be:

47 src\app\Test.cs:9:

This works, we can easily jump to the file and location but there is no other information. To improve this we have to use a multiple formats and use multi line syntax. First we update the format:

set efm=%Z%.%#\ at\ %.%#\ in\ %f:line\ %l

"%Z" has been added to the start. This means it will only be considered if a flag is set indicating we are in the middle of a multi line error. It is also considered the end of the multi line error, the flag is turned off if this is a match. Running it now will not produce a match.

To set the multi line flag we need to add another format:

set efm=%E%.%#Test\ Failure\ :%.%#
set efm+=%Z%.%#\ at\ %.%#\ in\ %f:line\ %l

The second line is mostly the same (note the += at the start). The first line is a format saying the following:

  1. Set the multi line flag (%E) if this is a match
  2. Find any text until we get to "Test Failure : "
  3. Followed by any text.

If we run this now it still won't work. The multi line flag is turned off if a non matching line is found. The simplest way to do this is to match any line:

set efm=%E%.%#Test\ Failure\ :%.%#
set efm+=%Z%.%#\ at\ %.%#\ in\ %f:line\ %l
set efm+=%C%.%#

The %C means it only matches if the multi line flag is on. It then uses a wild card to match anything. Because this matches practically anything it is placed last, giving it the lowest precedence. If the second and third lines were switched then every line after the first error would be considered part of the error, the file name and line number would never get matched.

The last part is to get all the information in between, we add a fourth pattern:

set efm=%E%.%#Test\ Failure\ :%.%#
set efm+=%Z%.%#\ at\ %.%#\ in\ %f:line\ %l
set efm+=%C%.%#[exec]\ %#%m
set efm+=%C%.%#

The added third line is saying:

  1. Only apply this if the multi line flag is set (%C)
  2. Find any text up to [exec]
  3. Find any space characters
  4. Append the rest of the line to the error message (%m)

The output will look like:

47 src\app\Test.cs:9: Custom error message goes here. Expected: true But was: false

To make this easier to manage it is a good idea to keep the error line it is matching in a comment:

"[exec] 1) Test Failure : TestClass.IsTrue
set efm=%E%.%#Test\ Failure\ :%.%#
"[exec] at TestClass.IsTrue() in c:\projects\book projects\src\app\Test.cs:l ine 9
set efm+=%Z%.%#\ at\ %.%#\ in\ %f:line\ %l
"[exec]   Custom error message goes here.
"[exec]   Expected: True
"[exec]   But was:  False
set efm+=%C%.%#[exec]\ %#%m
"[exec]
set efm+=%C%.%#

More Errors

The above assumes that nunit errors are all we care about but there are many others, from the compiler itself and the build script itself being the two obvious ones. If the build script itself throws an error it looks like this:

c:\projects\project1\nant.build(23,5):
Error loading buildfile.

    The 'cscvffe' start tag on line 11 position 4 does not match the end tag of 'csc'. Line 23, position 5.

The error format for this is rather straight forward:

"c:\projects\project1\nant.build(23,5):
set efm=%E%f(%l\\,%c):
"Error loading buildfile.
"
"    The 'cscvffe' start tag on line 11 position 4 does not match the end tag of 'csc'. Line 23, position 5.
set efm+=%C%m

To get it to work with other errors though, it need to be arranged in order of specificity:

"[exec] 1) Test Failure : TestClass.IsTrue
set efm=%E%.%#Test\ Failure\ :%.%#
"c:\projects\project1\nant.build(23,5):
set efm=%E%f(%l\\,%c):
"[exec] at TestClass.IsTrue() in c:\projects\book projects\src\app\Test.cs:l ine 9
set efm+=%Z%.%#\ at\ %.%#\ in\ %f:line\ %l
"[exec]   Custom error message goes here.
"[exec]   Expected: True
"[exec]   But was:  False
set efm+=%C%.%#[exec]\ %#%m
"Error loading buildfile.
"
"    The 'cscvffe' start tag on line 11 position 4 does not match the end tag of 'csc'. Line 23, position 5.
set efm+=%C%m
"[exec]
set efm+=%C%.%#

The start format goes near the top because it signifies the start of a new error no matter what. The message line is very generic though, so it has to go right near the bottom.

Error formats, and the quick list itself is one of those features that really makes vim shine, It just needs some customization for your particular tool set. Hopefully I've straightened out some misconceptions here.