Ergonomic mappings for code formatting in Vim
Vim allows you to format text with an arbitrary external program with the :h gq operator. For example, say you want to format with prettier, you just
need to set :h 'formatprg' option to npx prettier --stdin-filepath %, and
gqip will format the paragraph with it (it also works if you select the text
first and then use gq — vipgq).
While this feature is great as is, it’s still lacking
  How to customize gq  
We can customize gq by remapping it to use the g@ operator instead, which
will run whatever function you pass to the :h 'operatorfunc' option. See :h :map-operator for more information.
function! s:Format(...)
  " Some logic here to do the formatting
endfunction
nmap <silent> gq :set operatorfunc=<SID>Format<CR>g@
vmap <silent> gq :<C-U>set operatorfunc=<SID>Format<CR>gvg@Now our job will consist of writing what the s:Format function does.
The most innocuous implementation is to simply execute the gq behavior,
unchanged:
function! s:Format(...)
  normal! '[v']gq
endfunctionOf course, this is pointless, so let’s add to that function to enhance our experience:
Avoid changing the jumplist
The first small tweak is to avoid changing the jumplist when we format text,
which we do by prefixing the command with :h :keepjumps.
function! s:Format(...)
  keepjumps normal! '[v']gq
endfunctionSilent execution
I also make the command execute silently, so I won’t be interrupted by hit-enter prompts and error messages will not show up in the message history:
function! s:Format(...)
  silent keepjumps normal! '[v']gq
endfunctionError handling
The external program might fail to format the file — e.g. there is a syntax
error and prettier refuses to format it. The default experience is bad
because your code will be replaced by error messages, which is absolutely not
something anyone would want.
I originally learned about how to work around this in a GitHub gist by romainl:
function! s:Format(...)
  silent keepjumps normal! '[v']gq
  if v:shell_error > 0
    silent undo
    echohl ErrorMsg
    echomsg 'formatprg "' . &formatprg . '" exited with status ' . v:shell_error
    echohl None
  endif
endfunctionAfter gq is used, we can check if an error occurred during the executing of
formatprg with the v:shell_error special variable, which holds the
program’s exit code. If it’s non-zero, it means the command failed so we undo
the operation and show up an error message.
Format file preserving cursor position
I also learned this from romainl’s GitHub gist.
The procedure remains almost unchanged, if only slightly refactored into one
function, mapped to gQ:
function! s:FormatFile() abort
  let w:view = winsaveview()
  keepjumps normal! gg
  set operatorfunc=<SID>Format
  keepjumps normal! g@G
  keepjumps call winrestview(w:view)
  unlet w:view
endfunction
nmap <silent> gQ :call <SID>FormatFile()<CR>It consists of saving the current window view with :h winsaveview(), running
the operator as usual but moving over the entire file with ggg@G (but tries
not to modify the jumplist), then restore the window view with :h winrestview().
Integration with coc.nvim
Since I use coc.nvim as my LSP
client, I wish I could reuse gq to format using LSP, if it was available.
It turns out it was surprisingly simple to implement it:
function! s:Format(type, ...)
  if CocHasProvider('formatRange')
    call CocAction('formatSelected', a:type)
    return
  endif
  " ...
endfunctionAs you can see, I first check if check if the current buffer has an LSP server
attached to it and is able to format text ranges with the CocHasProvider
function.
I then use the formatSelected action, which receives the type of visual mode
last used (either line, char or block, the output of :h visualmode()),
which fortunately the operatorfunc function also receives as the first
argument (see :h :map-operator).