英文:
Run gofmt on vim without plugin
问题
我想在保存时在vim上运行gofmt,而不安装任何插件。这是我尝试过的方法(受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
这只在没有格式错误时起作用。然而,如果代码包含错误,它会在屏幕底部显示错误消息,并且(似乎)以文本形式出现在缓冲区中,导致整个代码被破坏。
有没有一种简单的方法来处理vim上的这种工作?
英文:
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?
答案1
得分: 3
Gofmt既是一个代码格式化工具,也是一个代码检查工具,这个设计决策使得将其与Vim集成变得比必要的困难。实际上,这意味着有三种情况可能发生:
- 你的代码完美无误,什么都不会发生。
- 你的代码存在格式问题,它会被重新格式化。
- 你的代码存在语法问题,会输出一系列错误。
例如,我们可以尝试通过gq
等命令使用gofmt
,通过:help 'formatprg'
来实现,但是这样很有可能最终用垃圾覆盖我们的代码,例如:
<standard input>:4:2: expected statement, found '.'
<standard input>:5:3: expected '}', found 'EOF'
这种情况的可能性太高了。就像你的情况一样,我们可以使用u
来撤销,但这并不好玩。我想我们将不得不解决gofmt
的设计问题。
第一步:切换到:help BufWritePre
。我们已经知道gofmt
可以处理stdin
,这使得我们可以格式化缓冲区以及文件。这很方便,因为在写入文件后格式化文件会无故地再次写入文件,并强制我们在Vim中重新加载它...所有这些似乎都是浪费。最好将:help BufWritePost
用于不影响Vim状态的事物。
function! GoFmt()
echomsg "hello"
endfunction
command! GoFmt call GoFmt()
augroup go_autocmd
autocmd BufWritePre *.go GoFmt
augroup END
第二步:将整个缓冲区通过gofmt
进行过滤。
function! GoFmt()
silent %!gofmt
endfunction
- 最好的情况:什么都不会发生,或者缓冲区被格式化的内容覆盖。
- 最坏的情况:整个缓冲区被替换为错误报告。
第三步:通过基本的撤销来“处理”最坏的情况。如果外部命令返回错误,我们可以通过:help v:shell_error
获取错误,并执行必要的操作。
function! GoFmt()
silent %!gofmt
if v:shell_error > 0
silent undo
endif
endfunction
第四步:尝试保持光标位置不变。
function! GoFmt()
let saved_view = winsaveview()
silent %!gofmt
if v:shell_error > 0
silent undo
endif
call winrestview(saved_view)
endfunction
参见:help winsaveview()
和:help winrestview()
。
第五步:如果适用,使用gofmt
报告的错误创建一个快速修复列表。:help getline()
可以将缓冲区的所有行(因此所有错误)作为列表给出,我们修改每个项目,使文件名成为当前文件名,而不是无用的<standard input>
。然后,我们将该列表提供给:help :cexpr
在撤销过滤之前创建一个快速修复列表。
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
这一步有点像“画出其余部分的*嚎叫”感觉,但实际上只是一个简单的:help substitute()
在一个简单的:help map()
中。关于{ foo, bar -> baz }
语法,请参见:help lambda
。
第六步,也是最后一步:如果有任何有效的错误,使用: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 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
答案2
得分: 0
你可以按照以下方式更新脚本:
function! GoFmt()
system('gofmt -e -w ' . expand('%'))
edit!
endfunction
Gofmt
是一个自动格式化 Go 代码的工具。Gofmt
同时也是一个代码格式化工具和代码检查工具。
从文档中可以了解到:
> -e
打印所有(包括错误)错误。
> -w
不将重新格式化的源代码打印到标准输出。
如果文件的格式与 gofmt 不同,将其覆盖为 gofmt 的版本。如果在覆盖过程中发生错误,将从自动备份中恢复原始文件。
如果没有错误,上述脚本将更新文件。
如果适用,可以使用 gofmt 报告的错误创建一个快速修复列表。
function! GoFmt()
cexpr system('gofmt -e -w ' . expand('%'))
edit!
endfunction
cexpr
用于创建快速修复列表,可以通过 vim 帮助文档 :help :cexpr
了解更多信息,system
用于运行系统命令,可以通过 vim 帮助文档 :help :system
了解更多信息,expand
将通配符和特殊关键字扩展为字符串,可以通过 vim 帮助文档 :help :expand
了解更多信息,%
是一个特殊关键字,指代当前文件。
edit!
用于重新加载文件内容。
然后,你可以使用快速修复列表快速跳转到错误位置,可以从这篇博客文章
中了解更多信息。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论