aliquote.org

Like Vim, Neovim comes with a bunch of preconfigured “compilers” (i.e., general settings for makeprg and errorformat), yet it is possible to define additional compilers when you’re not relying on a language server or, as in my case, when you want to use a specific linter via makeprg. Indeed, when it does not make sense to use a build system, it might be interesting to consider a linter as an alternative for Vim’s make feature. The results of invoking :make are displayed in the quickfix window, which I particularly like. Likewise, the formatprg option allows one to define a fixer for gq‘ing on a selection. We can also define a mapping to fix the whole buffer. This way, we can have linting and fixing facilities without relying on external plugins, like null-ls, ALE or efm-langserver.

Usually, I stand by the already defined compilers, but I added two specifications, one for pytest and the other for shellcheck. The following is for pytest, and it has been proposed by Phelipe:1

if exists('current_compiler')
finish
endif
let current_compiler = 'pytest'

if exists(':CompilerSet') != 2  " older Vim always used :setlocal
command -nargs=* CompilerSet setlocal <args>
endif

CompilerSet makeprg=pytest\ --tb=short\ -vv\ \$*
CompilerSet errorformat=
\%EE\ \ \ \ \ File\ \"%f\"\\,\ line\ %l,
\%CE\ \ \ %p^,
\%ZE\ \ \ %[%^\ ]%\\@=%m,
\%Afile\ %f\\,\ line\ %l,
\%+ZE\ %mnot\ found,
\%CE\ %.%#,
\%-G_%\\+\ ERROR%.%#\ _%\\+,
\%A_%\\+\ %o\ _%\\+,
\%C%f:%l:\ in\ %o,
\%ZE\ %\\{3}%m,
\%EImportError%.%#\'%f\'\.,
\%C%.%#,
\%+G%[=]%\\+\ %*\\d\ passed%.%#,
\%-G%[%^E]%.%#,
\%-G

function! FixColumnNumber()
if b:current_compiler !=? 'pytest'
return
endif

let qflist = getqflist()
for i in qflist
let i.col = i.col + 1
endfor
call setqflist(qflist)
endfunction

augroup FixPytestQuickFix
au!
autocmd QuickFixCmdPost <buffer> call FixColumnNumber()
augroup END


For shellcheck, I have the following in my compiler directory:

CompilerSet makeprg=shellcheck\ -f\ gcc
CompilerSet errorformat=%f:%l:%c:\ %trror:\ %m\ [SC%n],
\%f:%l:%c:\ %tarning:\ %m\ [SC%n],
\%f:%l:%c:\ %tote:\ %m\ [SC%n],
\%-G%.%#


In both cases above, the tricky part is to manage the errorformat. Once you have defined your custom compiler, you can declare it in your filetype file in after/ftplugin and optionally defined an appropriate makeprg command. For instance, in the case of shellcheck I use the following:

compiler shellcheck
setlocal makeprg=shellcheck\ -f\ gcc\ %
nmap <buffer> <silent> g= :!shfmt -i 2 -ln posix -sr -ci -s -w %<cr>:redraw!<cr>


The last line define a mapping to format the whole buffer using shfmt. I use g= for vim.lsp.buf.format() when a language server is available too. Of course I realize that the ALE or null-ls plugins handle all of that in a more elegant and efficient way, especially since it is asynchroneous contrary to the approach presented above and you are limited to one linter unless you write a shell script to gather several commands,2 but that’s my take for exploiting Neovim’s builtins.

I summarized sone of the options I have in my vimrc folder in the following table.

 Filetype LSP Linter Fixer c clangd built-in built-in python pyright built-in black, isort racket racket-langserver built-in built-in haskell hls built-in built-in purescript purescriptls built-in built-in sh not used shellcheck shfmt javascript not used quick-lint-js prettier

♪ Greg Abate • Sunshower

1. It is available on GitHub↩︎

2. I don’t really mind formatting a buffer using a shortcut, although it is possible to use an autocommand to format it on save. ↩︎