Some people like to run tools such as test runners outside their editor. Perhaps manually or perhaps via some file watching utility. They then review the output, switch back to their editor, and navigate to the appropriate location.
I prefer to explicitly invoke an external program from my editor, review the output, and allow my editor to jump to the first error. As well as storing a list of the locations of any other errors, a list that I may review as well as jump to its items. This tight feedback loop and integration with the editor allow for quick and efficient iteration.
Vim users will know this as :make
. This is a command that runs
a program, displays its output, then parses it according to
errorformat
to populate the quickfix list, and jumps to the first
error location.
This is a feature I rarely see people talk about much or use so
I thought I'd walk through my setup, not as a recommendation but just to
show how I like things. And maybe to encourage you to try :make
if you
don't already use it.
I would like to have the output of :make
take up the whole screen,
rather than being printed at the bottom of the screen.
To do this I'll need to set makeef
(the temporary errorfile used by
:make
) to a static value so its path is known. I can then use an
autocmd to store this in a variable before the file gets removed.
set makeef=errorfile.vim
augroup ErrorFile
autocmd!
autocmd QuickFixCmdPost make let g:LastError = readfile(&makeef)
augroup END
Then a function that will echo that variable out along with the required newlines so that the actual text starts from the top of the screen.
function! Print() abort
echo join(g:LastError, "\n") . repeat("\n", &lines - len(g:LastError))
endfunction
nnoremap <silent> ge :call Print()<CR>
Just re-running the same makeprg
quickly and toggling makeprg
's
would be useful. As would being able to run specific tests. So
a function to do those things will be needed.
It will run :make
suppressing its output in favour of the screen
filling version. When passed a single argument it will look for
a relevant buffer local variable to determine what makeprg
should be
first. And when passed more than one argument it does the same expanding
and appending the remaining arguments to makeprg
.
The reason makeprg
is being set globally is so I can switch between
buffers and still re-run the same thing.
function! Run(...) abort
if a:0 == 1
let &makeprg = eval('b:' . a:1 . 'Prg')
elseif a:0 > 1
let &makeprg = eval('b:' . a:1 . 'Prg') . ' ' . expand(join(a:000[1:-1]))
endif
silent make
call Print()
endfunction
Having some convenient ways to call this seems like a good idea.
nnoremap <silent> <CR> :call Run()<CR>
nnoremap <silent> gl :call Run('Lint')<CR>
nnoremap <silent> gt :call Run('Test')<CR>
command! -nargs=* -complete=file -bar Test call Run('Test', <f-args>)
Some filetype specifics will be needed. Using rust as en example it may look as follows.
Using comipler cargo
sets up errorformat
but it would also set
makeprg
locally which I wish to avoid so we have to unset this after.
compiler cargo
setlocal makeprg=
The buffer local variables used to refer to lint and test programs will need to be filled in.
let b:LintPrg = 'cargo check'
let b:TestPrg = 'cargo test'
Setting makeprg
if it's not already set enables using <CR>
without
using gl
, gt
, or :Test
first.
if empty(&makeprg)
let &makeprg = b:LintPrg
endif
Finally I have a couple of autocmds.
The first writes any modified buffers to disk before :make
runs.
The second cleans up the quickfix list after :make
has been run. Some
may like having additional context for errors in the quickfix list but
I hate it. As such I filter the contents of the quickfix list so that
there are only items with actual error locations, removing superfluous
output from :make
. But recall I can still display the full output as
I've saved it in a variable. This essentially makes the quickfix list
the same as the output given by :clist
.
augroup Make
autocmd!
autocmd QuickFixCmdPre make wall
autocmd QuickFixCmdPost make call setqflist(filter(getqflist(), "v:val['valid']"), 'r')
augroup END