Skip to content

Instantly share code, notes, and snippets.

@cilindrox
Created September 9, 2020 13:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cilindrox/be83e850ecfe21598b64cef9c77d8efb to your computer and use it in GitHub Desktop.
Save cilindrox/be83e850ecfe21598b64cef9c77d8efb to your computer and use it in GitHub Desktop.

Introduction

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.

Managing output

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>

Running :make

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>)

Filetype setup

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

Autocmds

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment