Run gofmt on vim without plugin

3.1k views Asked by At

I want to run gofmt on save on vim without installing any plugin. This is what I have tried (inspired by https://gist.github.com/tbrisbout/a91ac3419440cde40c5f54dc32c94427):

function! GoFmt()
  let file = expand('%')
  silent execute "!gofmt -w " . file
  edit!
endfunction

command! GoFmt call GoFmt()
augroup go_autocmd
  autocmd BufWritePost *.go GoFmt
augroup END

This just works when there is no format error. However, if the code contains error, it shows error message at the bottom of the screen and (it seems like) it appears on the buffer as text, so the whole code gets broken. Is there an simple way to handle this kind of work on vim?

2

There are 2 answers

1
Chandan On

You can update the script as below:

function! GoFmt()
  system('gofmt -e -w ' . expand('%'))
  edit!
endfunction

Gofmt is a tool that automatically formats Go source code. Gofmt is both a code formatter and a linter.

From doc

-e Print all (including spurious) errors.

-w Do not print reformatted sources to standard output. If a file's formatting is different from gofmt's, overwrite it with gofmt's version. If an error occurred during overwriting, the original file is restored from an automatic backup.

The above script will update the file if there are no errors


If applicable, create a quickfix list with the errors reported by gofmt.

function! GoFmt()
  cexpr system('gofmt -e -w ' . expand('%'))
  edit!
endfunction

cexpr to create a quickfix list read more about it using vim help doc help :cexpr, system to run system command read more about it using vim help doc help :system, expand Expand wildcards and special keywords into string read more about it using vim help doc help :expand, % is a special keyword which refer to current file.

edit! is used to reload file contents.

Then you can quickly jump over the errors using quickfix list you can learn more about it from this blog post.

2
romainl On

Gofmt is both a code formatter and a linter, a design decision that makes it harder than necessary to integrate it with Vim. In practice, it means that things can go three ways:

  • your code is perfect, nothing happens,
  • your code has formatting issues, it is reformatted,
  • your code has syntax issues, a list of errors is output.

For example, we could naively try to use gofmt for gq and friends by way of :help 'formatprg' but the chances of eventually overwritting our code with crap like:

<standard input>:4:2: expected statement, found '.'
<standard input>:5:3: expected '}', found 'EOF'

are too high. Like in your case, we can do u to undo but that's not fun. I guess we will have to work around gofmt's bad design.

First step: switch to :help BufWritePre. We have seen that gofmt can handle stdin, which allows us to format the buffer as well as the file. That is handy because formatting the file after it was written writes the file a second time for no good reason and forces us to reload it in Vim… and all that seems wasteful. :help BufWritePost is best kept for things that don't affect Vim's state.

function! GoFmt()
  echomsg "hello"
endfunction

command! GoFmt call GoFmt()

augroup go_autocmd
  autocmd BufWritePre *.go GoFmt
augroup END

Second step: filter the whole buffer through gofmt.

function! GoFmt()
  silent %!gofmt
endfunction
  • Best case scenario: nothing happens or the buffer is overwritten with the formatted content.
  • Worst case scenario: the whole buffer is replaced with an error report.

Third step: "handle" the worst case scenario with a basic undo. If the external command returns an error, we can get it via :help v:shell_error and do what needs to be done.

function! GoFmt()
  silent %!gofmt
  if v:shell_error > 0
    silent undo
  endif
endfunction

Fourth step: try to keep the cursor in place.

function! GoFmt()
  let saved_view = winsaveview()
  silent %!gofmt
  if v:shell_error > 0
    silent undo
  endif
  call winrestview(saved_view)
endfunction

See :help winsaveview() and :help winrestview().

Fifth step: if applicable, create a quickfix list with the errors reported by gofmt. :help getline() gives us all the lines of the buffer—therefore all the errors—in a list of which we modify each item so that the file name is the current file name instead of the useless <standard input>. We give that list to :help :cexpr to create a quickfix list before undoing the filter.

function! GoFmt()
  let saved_view = winsaveview()
  silent %!gofmt
  if v:shell_error > 0
    cexpr getline(1, '$')->map({ idx, val -> val->substitute('<standard input>', expand('%'), '') })
    silent undo
  endif
  call winrestview(saved_view)
endfunction

This step has a bit of a "draw the rest of the * howl" vibe but it really is just a simple :help substitute() in a simple :help map(). For the { foo, bar -> baz } syntax, see :help lambda.

Sixth and last step: open the quickfix window if there are any valid errors, with :help :cwindow.

function! GoFmt()
  let saved_view = winsaveview()
  silent %!gofmt
  if v:shell_error > 0
    cexpr getline(1, '$')->map({ idx, val -> val->substitute('<standard input>', expand('%'), '') })
    silent undo
    cwindow
  endif
  call winrestview(saved_view)
endfunction

gofmt