I am trying to write a function to lint buffers in neovim. It seems that the usual recommendation for achieving such a thing is to run the command over the entire range (ie %!lint). This is however, problematic if the file is not properly formatted. In (neo)vim's current behaviour, it appears that even stderr output from the lint program will replace the entire buffer.

I have attempted to fix this by running the lint command separately and checking for shell errors, like so:

" example usage:
" :call RunBuf("python -m json.tool")
function! RunBuf(cmd) abort
  redir => output
  silent execute ":w !" . a:cmd
  redir END
  if v:shell_error
    echo output
  let pos = getcurpos()
  execute "%!" . a:cmd
  call setpos('.', pos)

This is still not optimal as the lint command needs to be executed twice. I am thinking if there is a way to replace the entire buffer with output, or if there's a simpler way of achieving the same?


2 Answers 2


One option would be to

  • run the linter on the file and shove the output in a temporary file (relevant docs: :range!, :make, shell, shellredir, shellcmdflag, shellpipe, tempname())
  • if no error, simply replace everything with that file: :%delete | execute 'read' tempfile | 0delete
  • delete the tempfile (:call delete(tempfile))

I think this would look like (but I haven't tested it yet):

function RunBuf(cmd) abort
  let temp = tempname()
  silent execute 'write !' cmd &shellredir temp
  if v:shell_error is# 0
    execute '1read' temp
  call delete(temp)

However, you might be better off integrating with something like ALE (which, AFAIK, handles all this nonsense for you) or another linter plugin.

I think the reason stderr is usually included is because shellpipe and shellredir are set to include stderr when the shell supports it.

  • 2
    Note that this is most of what :range! does, just with the extra error checking
    – D. Ben Knoble
    Commented Aug 20, 2020 at 21:16

D Ben Knoble's answer looks promising, but I hated the idea of using temporary files. After some digging, I found another way to achieve the desired behaviour, largely stolen from the dart vim plugin fmt iimplementation:

function! RunBuf(cmd) abort
  let buffer_content = getline(1, '$')
  let lines = systemlist(a:cmd, join(buffer_content, "\n"))
  if v:shell_error
    echo join(lines, "\n")
  silent keepjumps call setline(1, lines)
  if line('$') > len(lines)
    silent keepjumps execute string(len(lines)+1).',$ delete'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.