Tutorial for vim-go - 转载

https://www.5axxw.com/wiki/content/wo6s81#alternate-files

( 如需查看英文版本,请 点击这里 )

存档项目。无需维护。

此项目不再维护,已存档。如果需要的话,你可以自由选择,做出自己的改变。更多细节请阅读我的博客文章:从我的项目中无限期休假

感谢大家的宝贵反馈和贡献。

vim-go教程。一个关于如何安装和使用vim-go的简单教程。

目录

  1. Quick Setup
  2. Hello World
  3. Build it
  4. Cover it
  5. Edit it
  1. Understand it

Quick Setup

我们将使用vim-plug来安装vim-go。请随意使用其他插件管理器。我们将创建一个最小的~/.vimrc,并在继续的过程中添加它。

首先获取并安装vim-plugvim-go

1
2
3
curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
git clone https://github.com/fatih/vim-go.git ~/.vim/plugged/vim-go

使用以下内容创建~/.vimrc

1
2
3
4
call plug#begin()
Plug 'fatih/vim-go', { 'do': ':GoInstallBinaries' }
call plug#end()

或者打开Vim并执行:GoInstallBinaries。这是一个vim-go命令,它为您安装所有vim-go依赖项。它不下载预编译的二进制文件,而是在后台调用go get,因此二进制文件都在主机中编译(这既安全又简化了安装过程,因为我们不需要为多个平台提供二进制文件)。如果您已经有一些依赖项(例如gurugoimports),请调用:GoUpdateBinaries来更新二进制文件。

在本教程中,我们所有的例子都在GOPATH/src/github.com/fatih/vim-go-tutorial/下。请确保您在该文件夹中。这将使您更容易地遵循教程。如果已经有GOPATH设置,请执行:

1
2
go get github.com/fatih/vim-go-tutorial

或者根据需要创建文件夹。

Hello World!

从终端打开main.go文件:

1
2
vim main.go

这是一个将vim-go打印到stdout的非常基本的文件。

Run it

您可以使用:GoRun %轻松运行该文件。在引擎盖下,它为当前文件调用go run。您应该看到它打印了vim-go

对于使用:GoRun运行的整个包。

Build it

vim-go替换为Hello Gophercon。让我们编译这个文件,而不是运行它。我们有:GoBuild。如果您调用它,您应该看到以下消息:

1
2
vim-go: [build] SUCCESS

在引擎盖下面它叫go build,但它更聪明一点。它做了一些不同的事情:

  • 不创建二进制文件;您可以多次调用:GoBuild,而不会污染您的工作区。
  • 它会自动cds到源包的目录中
  • 它分析任何错误并在快速修复列表中显示它们
  • 它自动检测GOPATH并在需要时修改它(检测诸如gbGodeps、etc..)等项目
  • 如果在Vim8.0.xxx或NeoVim中使用,则运行异步

Fix it

让我们通过添加两个编译错误来介绍两个错误:

1
2
3
4
5
6
7
var b = foo()

func main() {
fmt.Println("Hello GopherCon")
a
}

保存文件并再次调用:GoBuild

这次将打开quickfix视图。要在错误之间跳转,可以使用:cnext:cprevious。让我们修复第一个错误,保存文件并再次调用:GoBuild。您将看到quickfix列表更新了一个错误。同时删除第二个错误,保存文件并再次调用:GoBuild。现在,因为不再有错误,vim-go会自动关闭quickfix窗口。

让我们稍微改进一下。Vim有一个名为autowrite的设置,如果您调用:make,它会自动写入文件的内容。vim-go也使用此设置。打开.vimrc并添加以下内容:

1
2
set autowrite

现在,当您调用:GoBuild时,您不必再保存文件了。如果我们重新引入这两个错误并调用:GoBuild,我们现在可以通过只调用:GoBuild来更快地迭代。

:GoBuild跳转到遇到的第一个错误。如果您不想跳转附加!(bang)符号::GoBuild!

在所有go命令中,例如:GoRun:GoInstall:GoTest、etc..,每当出现错误时,quickfix窗口总是会弹出。

vimrc improvements

  • 您可以添加一些快捷方式,以便在快速修复列表中的错误之间切换:
1
2
3
4
map <C-n> :cnext<CR>
map <C-m> :cprevious<CR>
nnoremap <leader>a :cclose<CR>

  • 我还使用这些快捷方式来构建和运行一个带有<leader>b<leader>r的Go程序:
1
2
3
autocmd FileType go nmap <leader>b  <Plug>(go-build)
autocmd FileType go nmap <leader>r <Plug>(go-run)

  • Vim中有两种类型的错误列表。一个叫做location list,另一个叫quickfix。不幸的是,每个列表的命令是不同的。所以:cnext只适用于quickfix列表,而location lists则必须使用:lnextvim-go中的一些命令打开一个位置列表,因为位置列表与一个窗口相关联,每个窗口都可以有一个单独的列表。这意味着您可以有多个窗口和多个位置列表,一个用于Build,一个用于Check,一个用于Tests,等等。。

但是有些人喜欢只使用quickfix。如果将以下内容添加到vimrc中,则所有列表的类型都为quickfix

1
2
let g:go_list_type = "quickfix"

Test it

让我们编写一个简单的函数并对该函数进行测试。添加以下内容:

1
2
3
4
func Bar() string {
return "bar"
}

打开一个名为main_test.go的新文件(无论您如何打开它,从Vim内部,一个单独的Vim会话,等等)。。这取决于你)。让我们使用当前缓冲区并通过:edit main_test.go从Vim打开它。

当你打开新文件时,你会注意到一些东西。文件将自动添加包声明:

1
2
package main

这是由vim-go自动完成的。它检测到文件在一个有效的包中,因此基于包名创建了一个文件(在我们的例子中,包名是main)。如果没有文件,vim-go会自动用一个简单的主包填充内容。

使用以下代码更新测试文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"testing"
)

func TestBar(t *testing.T) {
result := Bar()
if result != "bar" {
t.Errorf("expecting bar, got %s", result)
}
}

打电话:GoTest。您将看到以下消息:

1
2
vim-go: [test] PASS

:GoTest在引擎盖下调用go test。它具有与:GoBuild相同的改进。如果有任何测试错误,将再次打开快速修复列表,您可以轻松地跳转到该列表。

另一个小的改进是您不必打开测试文件本身。你自己试试:打开main.go然后打:GoTest。您将看到测试也将为您运行。

:GoTest默认10秒后超时。这很有用,因为Vim在默认情况下不是异步的。您可以使用let g:go_test_timeout = '10s'更改超时值

我们还有两个命令,可以方便地处理测试文件。第一个是:GoTestFunc。这只测试光标下的函数。让我们将测试文件(main_test.go)的内容更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"testing"
)

func TestFoo(t *testing.T) {
t.Error("intentional error 1")
}

func TestBar(t *testing.T) {
result := Bar()
if result != "bar" {
t.Errorf("expecting bar, got %s", result)
}
}

func TestQuz(t *testing.T) {
t.Error("intentional error 2")
}

现在,当我们调用:GoTest时,一个快速修复窗口将打开,并显示两个错误。但是,如果进入TestBar函数并调用:GoTestFunc,您将看到我们的测试通过了!如果您有很多需要时间的测试,而您只想运行某些测试,那么这非常有用。

另一个test-related命令是:GoTestCompile。测试不仅需要成功地通过,而且还必须毫无问题地进行编译。:GoTestCompile编译测试文件,就像:GoBuild一样,并在出现错误时打开快速修复程序。但这并不能运行测试。这是非常有用的,如果你有一个大的测试,你要编辑很多。调用:GoTestCompile在当前测试文件中,您应该看到以下内容:

1
2
vim-go: [test] SUCCESS

vimrc improvements

  • :GoBuild一样,我们可以添加一个映射,用组合键轻松调用:GoTest。在.vimrc中添加以下内容:
1
2
autocmd FileType go nmap <leader>t  <Plug>(go-test)

现在您可以通过<leader>t轻松测试文件

  • 让我们简化building Go文件。首先,删除前面添加的以下映射:
1
2
autocmd FileType go nmap <leader>b  <Plug>(go-build)

我们将添加一个改进的映射。为了使任何Go文件无缝,我们可以创建一个简单的Vim函数来检查Go文件的类型,并执行:GoBuild:GoTestCompile。下面是可以添加到.vimrc中的帮助函数:

1
2
3
4
5
6
7
8
9
10
11
12
" run :GoBuild or :GoTestCompile based on the go file
function! s:build_go_files()
let l:file = expand('%')
if l:file =~# '^\f\+_test\.go$'
call go#test#Test(0, 1)
elseif l:file =~# '^\f\+\.go$'
call go#cmd#Build(0)
endif
endfunction

autocmd FileType go nmap <leader>b :<C-u>call <SID>build_go_files()<CR>

现在只要你点击<leader>b,它就会生成你的Go文件,或者无缝地编译你的测试文件。

  • 默认情况下,leader快捷方式被定义为:\我已经将我的leader映射到,,因为我发现下面的设置更有用(将它放在.vimrc的开头):
1
2
let mapleader = ","

因此,通过这个设置,我们可以轻松地用,b构建任何测试和非测试文件。

Cover it

让我们深入到测试的世界。测试真的很重要。Go有一种非常好的方式来显示源代码的覆盖率。vim-go可以很容易地看到代码覆盖率,而不必以非常优雅的方式离开Vim。

让我们首先将main_test.go文件改回:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"testing"
)

func TestBar(t *testing.T) {
result := Bar()
if result != "bar" {
t.Errorf("expecting bar, got %s", result)
}
}

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

func Bar() string {
return "bar"
}

func Foo() string {
return "foo"
}

func Qux(v string) string {
if v == "foo" {
return Foo()
}

if v == "bar" {
return Bar()
}

return "INVALID"
}

现在让我们打电话给:GoCoverage。在引擎盖下面,这叫做go test -coverprofile tempfile。它解析概要文件中的行,然后动态更改源代码的语法以反映覆盖率。如您所见,因为我们只对Bar()函数进行了测试,这是唯一一个绿色的函数。

要清除语法突出显示,可以调用:GoCoverageClear。让我们添加一个测试用例,看看覆盖率是如何变化的。将以下内容添加到main_test.go

1
2
3
4
5
6
7
8
9
10
11
12
func TestQuz(t *testing.T) {
result := Qux("bar")
if result != "bar" {
t.Errorf("expecting bar, got %s", result)
}

result = Qux("qux")
if result != "INVALID" {
t.Errorf("expecting INVALID, got %s", result)
}
}

如果我们再次调用:GoCoverage,您将看到Quz函数现在也经过了测试,并且覆盖范围更广。再次调用:GoCoverageClear清除语法高亮显示。

因为调用:GoCoverage:GoCoverageClear经常一起使用,所以有另一个命令可以方便地调用和清除结果。您也可以使用:GoCoverageToggle。它充当一个开关并显示覆盖范围,当再次调用时,它将清除覆盖范围。如何使用它们取决于您的工作流程。

最后,如果您不喜欢vim-go’s内部视图,也可以调用:GoCoverageBrowser。它使用go tool cover创建一个HTML页面,然后在默认浏览器中打开它。有些人更喜欢这个。

使用:GoCoverageXXX命令不会创建任何类型的临时文件,也不会污染您的工作流。所以你不必每次都处理删除不需要的文件。

vimrc improvements

.vimrc中添加以下内容:

1
2
autocmd FileType go nmap <Leader>c <Plug>(go-coverage-toggle)

有了这个,你可以很容易地用<leader>c调用:GoCoverageToggle

Edit it

Imports

让我们从一个main.go文件开始:

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
fmt.Println("gopher" )
}

让我们从我们已经知道的事情开始吧。如果我们保存文件,你会看到它会自动格式化。默认情况下,它是启用的,但是如果需要,可以使用let g:go_fmt_autosave = 0禁用它(不确定为什么要禁用:)。我们还可以选择提供:GoFmt命令,它在引擎盖下运行gofmt

让我们用大写字母打印"gopher"字符串。为此,我们将使用strings包。要更改定义:

1
2
fmt.Println(strings.ToUpper("gopher"))

当你建立它时,你会得到一个错误,当然:

1
2
main.go|8| undefined: strings in strings.ToUpper

您将看到我们得到一个错误,因为strings包没有导入。vim-go有两个命令可以方便地操作导入声明。

我们可以很容易地去编辑文件,但是我们将使用Vim命令:GoImport。此命令将给定的包添加到导入路径。{via::GoImport strings}运行。您将看到正在添加strings包。这个命令最棒的地方是它还支持完成。所以你只需输入:GoImport s然后点击tab。

我们还需要:GoImportAs:GoDrop来编辑导入路径。:GoImportAs:GoImport相同,但它允许更改包名。例如:GoImportAs str strings,将使用包名str.导入strings

最后,:GoDrop可以很容易地从导入声明中删除任何导入路径。:GoDrop strings将从导入声明中删除它。

当然,操纵导入路径是2010年的事了。我们有更好的工具来处理这个案子。如果你还没听说过,那就叫goimportsgoimportsgofmt的替代品。你有两种使用方法。第一种(也是推荐的)方法是告诉vim-go在保存文件时使用它:

1
2
let g:go_fmt_command = "goimports"

现在每当您保存文件时,goimports将自动格式化并重写导入声明。有些人不喜欢goimports,因为它在非常大的代码基上可能很慢。在本例中,我们还有:GoImports命令(注意末尾的s)。这样,您就可以显式地调用goimports

Text objects

让我们展示更多编辑技巧。我们可以使用两个文本对象来更改函数。它们是ifafif表示内部函数,它允许您选择函数外壳的内容。将main.go文件更改为:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
fmt.Println(1)
fmt.Println(2)
fmt.Println(3)
fmt.Println(4)
fmt.Println(5)
}

将光标放在func关键字上,现在在normal模式下执行以下操作,然后看看会发生什么:

1
2
dif

您将看到函数体被移除。因为我们使用了d运算符。使用u撤消更改。最棒的是,光标可以是从func关键字开始到右大括号}的任意位置。它使用引擎盖下的刀具运动。我为vim-go显式地编写了motion来支持这样的特性。它具有很强的感知能力,因此它的性能非常好。你可能会问什么?将main.go改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func Bar() string {
fmt.Println("calling bar")

foo := func() string {
return "foo"
}

return foo()
}

以前我们使用regexp-based文本对象,这会导致问题。例如,在这个例子中,将光标放在匿名函数func关键字上,并以normal模式执行dif。您将看到只有匿名函数的主体被删除。

到目前为止,我们只使用了d运算符(delete)。但这取决于你。例如,您可以通过vif选择它,或者使用yif来拖动(复制)。

我们还有af,意思是a function。此文本对象包括整个函数声明。将main.go更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

// bar returns a the string "foo" even though it's named as "bar". It's an
// example to be used with vim-go's tutorial to show the 'if' and 'af' text
// objects.
func bar() string {
fmt.Println("calling bar")

foo := func() string {
return "foo"
}

return foo()
}

所以这是一件伟大的事情。由于motion,我们对每个语法节点都有充分的了解。将光标放在func关键字的上方或下方或上方的任何位置(无所谓)。如果现在执行vaf,您将看到函数声明和doc注释都被选中了!例如,您可以用daf删除整个函数,您将看到注释也消失了。继续将光标放在注释的顶部,执行vif,然后执行vaf。您将看到它选择了函数体,即使光标在函数之外,或者它也选择了函数注释。

这真的很强大,这一切都要归功于我们从let g:go_textobj_include_function_doc = 1motion学到的知识。如果不希望注释成为函数声明的一部分,可以使用以下方法轻松禁用它:

1
2
let g:go_textobj_include_function_doc = 0

如果您有兴趣了解关于motion的更多信息,请查看我写的博客文章:将Go类型视为vim中的对象

(可选问题:不看go/ast包,doc注释是否是函数声明的一部分?)

结构拆分和联接

有一个很好的插件可以让你拆分或连接Go结构。它实际上不是一个Go插件,但它支持Go结构。要启用它,请将plug定义之间的plugin指令添加到vimrc中,然后在vim编辑器中执行:source ~/.vimrc并运行:PlugInstall。例子:

1
2
3
4
5
call plug#begin()
Plug 'fatih/vim-go'
Plug 'AndrewRadev/splitjoin.vim'
call plug#end()

安装插件后,将main.go文件更改为:

1
2
3
4
5
6
7
8
9
10
11
12
package main

type Foo struct {
Name string
Ports []int
Enabled bool
}

func main() {
foo := Foo{Name: "gopher", Ports: []int{80, 443}, Enabled: true}
}

将光标放在与结构表达式相同的行上。现在输入gS。这将split将结构表达式分成多行。你甚至可以逆转它。如果光标仍在foo变量上,请在normal模式下执行gJ。您将看到字段定义都已联接。

这不使用任何AST-aware工具,因此例如,如果您在字段顶部键入gJ,您将看到只有两个字段被联接。

Snippets

Vim-go支持两个流行的snippet插件。Ultisnips和neosnippet。默认情况下,如果您安装了Ultisnips,它就可以工作了。让我们先安装ultisnips。在vimrc中的plug指令之间添加它,然后在vim编辑器中执行:source ~/.vimrc,然后运行:PlugInstall。例子:

1
2
3
4
5
call plug#begin()
Plug 'fatih/vim-go'
Plug 'SirVer/ultisnips'
call plug#end()

有许多有用的片段。要查看完整列表,请查看我们当前的片段:https://github.com/fatih/vim-go/blob/master/gosnippets/UltiSnips/go.snippets

UltiSnips和YouCompleteMe可能在[tab]按钮上发生冲突

让我展示一下我用得最多的一些片段。将main.go内容更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "encoding/json"

type foo struct {
Message string
Ports []int
ServerName string
}

func newFoo() (*foo, error) {
return &foo{
Message: "foo loves bar",
Ports: []int{80},
ServerName: "Foo",
}, nil
}

func main() {
res, err := newFoo()

out, err := json.Marshal(res)
}

让我们把光标放在newFoo()表达式之后。如果错误是non-nil,让我们在这里惊慌失措。在insert模式下输入errp,然后点击tab。您将看到它将被展开并将光标放在“panic()`”函数中:

1
2
3
4
5
6
if err != nil {
panic( )
^
cursor position
}

err填充恐慌,然后转到json.Marshal语句。做同样的事情。

现在让我们打印变量out。由于变量打印非常流行,因此我们有几个片段:

1
2
3
4
5
fn -> fmt.Println()
ff -> fmt.Printf()
ln -> log.Println()
lf -> log.Printf()

这里fflf是特殊的。它们还动态地将变量名复制到格式字符串中。你自己试试吧。将光标移到main函数的末尾,输入ff,然后点击tab。展开代码段后,可以开始键入。输入string(out),您将看到格式字符串和变量参数都将用您键入的相同字符串填充。

这对于快速打印用于调试的变量非常方便。使用:GoRun运行文件,您将看到以下输出:

1
2
string(out) = {"Message":"foo loves bar","Ports":[80],"ServerName":"Foo"}

伟大的。现在让我展示最后一个我认为非常有用的片段。正如您在输出中看到的,字段MessagePorts以大写字符开头。为了解决这个问题,我们可以在struct字段中添加一个json标记。vim-go使添加字段标记变得非常容易。将光标移到字段中Message字符串行的末尾:

1
2
3
4
5
type foo struct {
Message string .
^ put your cursor here
}

insert模式下,输入json并点击tab。您将看到它将自动扩展为有效的字段标记。字段名将自动转换为小写,并放在那里。现在应该可以看到以下内容:

1
2
3
4
type foo struct {
Message string `json:"message"`
}

真是太神奇了。但我们可以做得更好!继续为ServerName字段创建一个代码段扩展。您将看到它被转换为server_name。太棒了对吧?

1
2
3
4
5
6
type foo struct {
Message string `json:"message"`
Ports []int
ServerName string `json:"server_name"`
}

vimrc improvements

  • 别忘了把gofmt改成goimports
1
2
let g:go_fmt_command = "goimports"

  • 当您保存文件时,gofmt在解析文件期间显示任何错误。如果有任何解析错误,它会在快速修复列表中显示它们。这是默认启用的。有些人不喜欢。要禁用它,请添加:
1
2
let g:go_fmt_fail_silently = 1

  • 您可以在转换时更改应应用的大小写。默认情况下,vim-go使用snake_case。但如果您愿意,也可以使用camelCase。例如,如果要将默认值更改为大小写,请使用以下设置:
1
2
let g:go_addtags_transform = "camelcase"

Beautify it

默认情况下,我们只启用了有限的语法高亮显示。主要有两个原因。首先,人们不喜欢太多的颜色,因为它会让人分心。第二个原因是它对Vim的性能影响很大。我们需要显式地启用它。首先将以下设置添加到.vimrc

1
2
let g:go_highlight_types = 1

这突出显示了barfoo

1
2
3
4
5
6
type foo struct{
quz string
}

type bar interface{}

添加以下内容:

1
2
let g:go_highlight_fields = 1

将突出显示下面的quz

1
2
3
4
5
6
7
type foo struct{
quz string
}

f := foo{quz:"QUZ"}
f.quz # quz here will be highlighted

如果我们添加以下内容:

1
2
let g:go_highlight_functions = 1

我们现在还在声明中突出显示函数和方法名。Foomain现在将突出显示,但是Println不是因为这是一个调用:

1
2
3
4
5
6
func (t *T) Foo() {}

func main() {
fmt.Println("vim-go")
}

如果还想突出显示函数和方法调用,请添加以下内容:

1
2
let g:go_highlight_function_calls = 1

现在,Println也将突出显示:

1
2
3
4
5
6
func (t *T) Foo() {}

func main() {
fmt.Println("vim-go")
}

如果添加let g:go_highlight_operators = 1,它将突出显示以下运算符,例如:

1
2
3
4
5
6
- + % < > ! & | ^ * =
-= += %= <= >= != &= |= ^= *= ==
<< >> &^
<<= >>= &^=
:= && || <- ++ --

如果添加let g:go_highlight_extra_types = 1,以下额外类型也将突出显示:

1
2
3
4
5
bytes.(Buffer)
io.(Reader|ReadSeeker|ReadWriter|ReadCloser|ReadWriteCloser|Writer|WriteCloser|Seeker)
reflect.(Kind|Type|Value)
unsafe.Pointer

让我们继续了解更多有用的亮点。构建标签呢?不查看go/build文档就不容易实现它。让我们首先添加以下内容:let g:go_highlight_build_constraints = 1并将main.go文件更改为:

1
2
3
// build linux
package main

你会看到它是灰色的,所以它是无效的。将+添加到build单词并再次保存:

1
2
3
// +build linux
package main

你知道为什么吗?如果您阅读go/build包,您将看到以下内容隐藏在文档中:

… 前面只有空行和其他行注释。

让我们再次更改内容并将其保存到:

1
2
3
4
// +build linux

package main

您将看到它以有效的方式自动高亮显示它。真的很棒。如果您将linux更改为某个内容,您将看到它还检查有效的官方标记(例如darwinraceignore等)

另一个类似的特性是突出显示Go指令//go:generate。如果将let g:go_highlight_generate_tags = 1放入vimrc,它将突出显示使用go generate命令处理的有效指令。

我们有更多的亮点设置,这些只是一个偷窥。如需更多信息,请通过:help go-settings查看设置

vimrc improvements

  • 有些人不喜欢标签的显示方式。默认情况下,Vim为单个选项卡显示8个空格。然而,如何在Vim中表示取决于我们。以下内容将更改为将单个选项卡显示为4个空格:
1
2
autocmd BufNewFile,BufRead *.go setlocal noexpandtab tabstop=4 shiftwidth=4

此设置不会将选项卡展开为空格。它将用4空格显示一个选项卡。它将使用4空格来表示单个缩进。

  • 很多人要我的配色方案。我用的是稍加修改的molokai。要启用它,请在Plug定义之间添加Plug指令:
1
2
3
4
5
call plug#begin()
Plug 'fatih/vim-go'
Plug 'fatih/molokai'
call plug#end()

同时添加以下内容,以启用原始配色方案和256色版本的molokai:

1
2
3
4
let g:rehash256 = 1
let g:molokai_original = 1
colorscheme molokai

然后重启Vim并调用:source ~/.vimrc,然后调用:PlugInstall。这将拉插件并为您安装。安装插件后,您需要重新启动Vim。

Check it

从前面的示例中,您看到我们有许多命令,当出现问题时,这些命令将显示quickfix窗口。例如,:GoBuild显示编译输出中的错误(如果有)。或者例如:GoFmt显示当前文件格式化时的解析错误。

我们有许多其他命令,允许我们调用然后收集错误、警告或建议。

例如:GoLint。在幕后,它调用golint,这是一个建议更改以使Go代码更具惯用性的命令。还有一个:GoVet,它在引擎盖下调用go vet。有许多其他工具可以检查某些东西。为了让它更简单,有人决定创建一个调用所有这些跳棋的工具。这个工具叫做gometalinter。vim-go通过命令:GoMetaLinter支持它。那么它有什么作用呢?

如果您只是调用:GoMetaLinter来获取给定的Go源代码。默认情况下,它将同时运行go vetgolinterrcheckgometalinter收集所有输出并将其规范化为通用格式。因此,如果您调用:GoMetaLinter,vim-go将在快速修复列表中显示所有这些跳棋的结果。然后,您可以轻松地在lint、vet和errcheck结果之间切换。此默认设置如下:

1
2
let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']

还有许多其他工具,您可以轻松地自定义此列表。如果您调用:GoMetaLinter,它将自动使用上面的列表。

因为:GoMetaLinter通常很快,vim-go也可以在每次保存文件时调用它(就像:GoFmt)。要启用它,您需要将以下内容添加到.vimrc:

1
2
let g:go_metalinter_autosave = 1

最棒的是,autosave的跳棋与:GoMetaLinter不同。这很好,因为您可以自定义它,所以在保存文件时只调用快速检查程序,但如果您调用:GoMetaLinter,则调用其他检查程序。下面的设置允许您为autosave特性定制方格。

1
2
let g:go_metalinter_autosave_enabled = ['vet', 'golint']

如您所见,默认情况下启用vetgolint。最后,为了防止:GoMetaLinter运行太长时间,我们有一个设置,在给定的超时之后取消它。默认为5 seconds,但可以通过以下设置进行更改:

1
2
let g:go_metalinter_deadline = "5s"

Navigate it

到目前为止,我们只跳过了main.gomain_test.go两个文件。如果在同一个目录中只有两个文件,那么切换非常容易。但是如果项目随着时间的推移变得越来越大呢?或者如果文件本身太大,以至于您很难导航它呢?

Alternate files

vim-go有几种改进导航的方法。首先让我展示一下如何在Go源代码和它的测试文件之间快速跳转。

假设您有一个foo.go及其等价的测试文件foo_test.go。如果您有前面示例中的main.go及其测试文件,您也可以打开它。打开后,只需执行以下Vim命令:

1
2
:GoAlternate

您将看到您立即切换到main_test.go。如果您再次执行它,它将切换到main.go:GoAlternate起到切换的作用,如果您有一个包含许多测试文件的包,则非常有用。这个想法非常类似于plugina.vim命令名。这个插件在.c.h文件之间跳跃。在我们的例子中,:GoAlternate用于在测试和non-test文件之间切换。

转到定义

最常用的特性之一是go to definition。从一开始,vim-go有一个:GoDef命令,该命令跳转到任何标识符的声明。让我们首先创建一个main.go文件来显示它的实际操作。使用以下内容创建它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

type T struct {
Foo string
}

func main() {
t := T{
Foo: "foo",
}

fmt.Printf("t = %+v\n", t)
}

现在我们有几种跳到声明的方法。例如,如果您将光标放在T表达式的顶部,紧跟在main函数之后并调用:GoDef,它将跳转到类型声明。

如果您将光标放在t变量声明的顶部,紧跟在main函数之后并调用:GoDef,您将看到不会发生任何事情。因为没有地方可去,但是如果您向下滚动几行并将光标放在fmt.Printf()中使用的t变量并调用:GoDef,您将看到它跳转到变量声明。

:GoDef不仅适用于本地范围,而且在全局范围内(跨GOPATH)工作。例如,如果您将光标放在Printf()函数的顶部并调用:GoDef,它将直接跳转到fmt包。因为这是如此频繁地使用,vim-go也覆盖了内置的Vim快捷方式gdctrl-]。因此,您可以轻松地使用gdctrl-]来代替:GoDef

一旦我们跳转到一个声明,我们可能还想回到以前的位置。默认情况下,Vim快捷键ctrl-o会跳转到上一个光标位置。当它运行得很好时,它会很好地工作,但是如果您在Go声明之间导航,就不够好了。例如,如果您跳转到:GoDef的文件,然后向下滚动到底部,然后可能到顶部,ctrl-o也会记住这些位置。因此,如果你想在调用:GoDef时跳回上一个位置,你必须多次点击ctrl-o。这真的很烦人。

不过,我们不需要使用这个快捷方式,因为vim-go为您提供了更好的实现。有一个命令:GoDefPop正是这样做的。vim-go为使用:GoDef访问的所有位置保留一个内部堆栈列表。这意味着您可以通过:GoDefPop轻松地跳回原来的位置,即使您在文件中向下/向上滚动也可以。因为这也被使用了很多次,所以我们有一个快捷方式ctrl-t,它在引擎盖下调用:GoDefPop。所以回顾一下:

  • 使用ctrl-]gd跳转到本地或全局定义
  • 使用ctrl-t跳回上一个位置

让我们继续问另一个问题,假设你跳得太远,只想回到你开始的地方?如前所述,vim-go保存通过:GoDef调用的所有位置的历史记录。有一个命令显示了所有这些,它名为:GoDefStack。如果您调用它,您将看到一个带有旧位置列表的自定义窗口。只需导航到所需的位置,然后按enter键。最后随时调用:GoDefStackClear清除堆栈列表。

在函数之间移动

从前面的例子中,我们看到:GoDef如果您知道您想跳转到哪里,那么:GoDef是很好的。但是如果你不知道你的下一个目的地呢?或者你只知道一个函数的名字?

在我们的Edit it部分中,我提到了一个名为motion的工具,它是一个专门为vim-go定制的工具。motion还有其他功能。motion解析Go包,因此对所有声明都有很好的理解。我们可以利用这个特性在声明之间跳转。有两个命令,在安装某个插件之前是不可用的。命令包括:

1
2
3
:GoDecls
:GoDeclsDir

首先,让我们通过安装必要的插件来启用这两个命令。这个插件叫做ctrlp。Long-timeVim用户已经安装了它。要安装它,请在plug指令之间添加以下行,然后在vim编辑器中执行:source ~/.vimrc,并调用:PlugInstall来安装它:

1
2
Plug 'ctrlpvim/ctrlp.vim'

安装后,请使用以下main.go内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

type T struct {
Foo string
}

func main() {
t := T{
Foo: "foo",
}

fmt.Printf("t = %+v\n", t)
}

func Bar() string {
return "bar"
}

func BarFoo() string {
return "bar_foo"
}

以及一个main_test.go文件,其中包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"testing"
)

type files interface{}

func TestBar(t *testing.T) {
result := Bar()
if result != "bar" {
t.Errorf("expecting bar, got %s", result)
}
}

func TestQuz(t *testing.T) {
result := Qux("bar")
if result != "bar" {
t.Errorf("expecting bar, got %s", result)
}

result = Qux("qux")
if result != "INVALID" {
t.Errorf("expecting INVALID, got %s", result)
}
}

打开main.go并调用:GoDecls。您将看到:GoDecls为您显示了所有类型和函数声明。如果您输入ma,您将看到ctrlp为您过滤列表。如果你点击enter,它将自动跳转到它。模糊搜索功能与motion的AST功能相结合,给我们带来了一个非常简单但功能强大的特性。

例如,调用:GoDecls并写入foo。您将看到它将为您过滤BarFoo。Go解析器速度非常快,可以很好地处理包含数百个声明的大型文件。

有时仅仅在当前文件中搜索是不够的。一个Go包可以有多个文件(例如测试)。类型声明可以在一个文件中,而特定于一组功能的某些函数可以在另一个文件中。这就是:GoDeclsDir有用的地方。它解析给定文件的整个目录,并列出给定目录(但不是子目录)中文件的所有声明。

打电话:GoDeclsDir。这次您将看到它还包括来自main_test.go文件的声明。如果您输入Bar,您将看到BarTestBar函数。如果您只想获得所有类型和函数声明的概述,并跳转到它们,这真是太棒了。

让我们继续问一个问题。如果你只想转到下一个或上一个函数呢?如果当前函数体很长,则很可能看不到函数名。或者在当前函数和其他函数之间还有其他声明。

Vim已经有了像w表示单词或b表示向后单词的运动操作符。但是如果我们可以加上行动计划呢?例如函数声明?

vim-go提供(重写)两个运动对象在函数之间移动。这些是:

1
2
3
]] -> jump to next function
[[ -> jump to previous function

默认情况下,Vim有这些快捷方式。但这些都适用于C源代码和大括号之间的跳转。我们可以做得更好。就像我们前面的例子一样,motion是在引擎盖下用于这个操作的

打开main.go并移动到文件的顶部。在normal模式下,输入]],然后看看会发生什么。您将看到您跳转到main()函数。另一个]]将跳转到Bar(),如果你点击[[,它将跳回main()函数。

]][[也接受counts。例如,如果您再次移动到顶部并点击3]],您将看到它将跳转到源文件中的第三个函数。接下来,因为这些都是有效的运动,你也可以对它应用操作符!

如果您将文件移到顶部并点击d]],您将看到它在下一个函数之前删除了任何内容。例如,一个有用的用法是输入v]],然后再次点击]]来选择下一个函数,直到完成选择为止。

.vimrc improvements

  • 我们可以改进它来控制它如何打开备用文件。在.vimrc中添加以下内容:
1
2
3
4
5
autocmd Filetype go command! -bang A call go#alternate#Switch(<bang>0, 'edit')
autocmd Filetype go command! -bang AV call go#alternate#Switch(<bang>0, 'vsplit')
autocmd Filetype go command! -bang AS call go#alternate#Switch(<bang>0, 'split')
autocmd Filetype go command! -bang AT call go#alternate#Switch(<bang>0, 'tabe')

这将添加新的命令,称为:A:AV:AS:AT。这里:A的工作方式与:GoAlternate相同,它用备用文件替换当前缓冲区。:AV将使用备用文件打开一个新的垂直拆分。:AS将在新的拆分视图中打开备用文件,在新选项卡中打开:AT。这些命令的效率非常高,这取决于您如何使用它们,所以我认为拥有它们很有用。

  • “go to definition”命令系列非常强大,但使用起来却很简单。默认情况下,它使用工具guru(以前是oracle)。guru有很好的可预测性记录。它适用于点导入、供应商化导入和许多其他non-obvious标识符。但有时对于某些查询来说,它非常慢。以前vim-go使用的是godef,它在解决查询方面非常快。在最新版本中,可以很容易地使用或切换:GoDef的底层工具。要将其更改回godef,请使用以下设置:
1
2
let g:go_def_mode = 'godef'

  • 当前默认情况下,:GoDecls:GoDeclsDir显示类型和函数声明。这可以使用g:go_decls_includes设置进行自定义。默认情况下,它的形式是:
1
2
let g:go_decls_includes = "func,type"

如果只想显示函数声明,请将其更改为:

1
2
let g:go_decls_includes = "func"

Understand it

编写/编辑/更改代码通常只有在我们首先了解代码在做什么时才能做。vim-go有几种方法可以使您更容易地理解代码的全部内容。

Documentation lookup

让我们从基础知识开始。Go文档非常well-written,并且高度集成到goast中。如果只编写一些注释,解析器可以轻松地解析它并与AST中的任何节点关联。所以这意味着我们可以很容易地以相反的顺序找到文档。如果您有一个AST节点,那么您可以轻松地从该节点读取它!

我们有一个名为:GoDoc的命令,它显示与光标下标识符相关的任何文档。让我们将main.go的内容更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
fmt.Println("vim-go")
fmt.Println(sayHi())
fmt.Println(sayYoo())
}

// sayHi() returns the string "hi"
func sayHi() string {
return "hi"
}

func sayYoo() string {
return "yoo"
}

将光标放在Println函数的顶部,紧跟main函数,然后调用:GoDoc。您将看到它vim-go自动打开一个草稿窗口,为您显示文档:

1
2
3
4
5
6
7
8
9
import "fmt"

func Println(a ...interface{}) (n int, err error)

Println formats using the default formats for its operands and writes to
standard output. Spaces are always added between operands and a newline is
appended. It returns the number of bytes written and any write error
encountered.

它显示导入路径、函数签名,最后是标识符的doc注释。最初vim-go使用的是普通的go doc,但它有一些缺点,例如不能基于字节标识符进行解析。go doc非常适合终端使用,但是很难集成到编辑器中。幸运的是,我们有一个非常有用的工具gogetdoc,它解析并检索底层节点的AST节点,并输出相关的doc注释。

这就是:GoDoc适用于任何类型的标识符的原因。如果您将光标放在sayHi()下并调用:GoDoc,您将看到它也显示了它。如果你把它放在sayYoo()下,你会看到它只输出no documentation作为AST节点,没有doc注释。

与其他特性一样,我们重写默认的普通快捷方式K,以便它调用:GoDoc,而不是man(或其他东西)。很容易找到文档,只需在正常模式下点击K

:GoDoc只显示给定标识符的文档。但是它不是一个文档浏览器,如果你想浏览文档,有一个third-party插件来完成它:go-explorer。在vim-go中包含了一个开放的bug。

Identifier resolution

有时您想知道函数接受或返回的是什么。或者光标下的标识符是什么。像这样的问题很常见,我们有命令来回答。

使用相同的main.go文件,检查Println函数并调用:GoInfo。您将看到函数签名正在状态行中打印。这真的很好的看到它在做什么,因为你不必跳到定义和检查签名是什么。

但是每次打:GoInfo都很乏味。我们可以做一些改进来更快地调用它。一如既往,加快速度的一种方法是添加快捷方式:

1
2
autocmd FileType go nmap <Leader>i <Plug>(go-info)

现在只需按一下<leader>i,就可以轻松地调用:GoInfo。但仍有改进的余地。vim-go支持在移动光标时自动显示信息。要启用它,请在.vimrc中添加以下内容:

1
2
let g:go_auto_type_info = 1

现在,只要将光标移到有效标识符上,就会看到状态行自动更新。默认情况下,它每更新一次800ms。这是一个vim设置,可以用updatetime设置进行更改。要将其更改为100ms,请将以下内容添加到.vimrc

1
2
set updatetime=100

Identifier highlighting

有时我们只想快速看到所有匹配的标识符。例如变量、函数等。。假设您有以下Go代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
fmt.Println("vim-go")
err := sayHi()
if err != nil {
panic(err)
}
}

// sayHi() returns the string "hi"
func sayHi() error {
fmt.Println("hi")
return nil
}

如果您将光标放在err上并调用:GoSameIds,您将看到所有的err变量都会高亮显示。将光标放在sayHi()函数调用上,您将看到sayHi()函数标识符都高亮显示。要清除它们,请致电:GoSameIdsClear

如果我们不必每次都手动调用它,这会更有用。vim-go可以自动突出显示匹配的标识符。在vimrc中添加以下内容:

1
2
let g:go_auto_sameids = 1

重新启动vim之后,您将看到不再需要手动调用:GoSameIds。匹配的标识符变量现在会自动为您高亮显示。

依赖项和文件

如您所知,一个包可以由多个依赖项和文件组成。即使目录中有许多文件,也只有正确包含package子句的文件才是包的一部分。

要查看组成包的文件,可以调用以下命令:

1
2
:GoFiles

将输出(my$GOPATH设置为~/Code/Go):

1
2
['/Users/fatih/Code/go/src/github.com/fatih/vim-go-tutorial/main.go']

如果你有其他文件,这些也会列出。请注意,此命令仅用于列出属于构建一部分的Go文件。将不列出测试文件。

为了显示文件的依赖关系,可以调用:GoDeps。如果你叫它,你会看到:

1
2
3
4
['errors', 'fmt', 'internal/race', 'io', 'math', 'os', 'reflect', 'runtime',
'runtime/internal/atomic', 'runtime/internal/sys', 'strconv', 'sync',
'sync/atomic ', 'syscall', 'time', 'unicode/utf8', 'unsafe']

Guru

前一个特性是在引擎盖下使用guru工具。让我们来谈谈古鲁。那么什么是古鲁?Guru是一个用于导航和理解Go代码的编辑器集成工具。有一本用户手册显示了所有的特性:https://golang.org/s/using-guru

让我们使用手册中的相同示例来展示我们集成到vim-go中的一些功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"log"
"net/http"
)

func main() {
h := make(handler)
go counter(h)
if err := http.ListenAndServe(":8000", h); err != nil {
log.Print(err)
}
}

func counter(ch chan<- int) {
for n := 0; ; n++ {
ch <- n
}
}

type handler chan int

func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-type", "text/plain")
fmt.Fprintf(w, "%s: you are visitor #%d", req.URL, <-h)
}

将光标放在handler上,然后调用:GoReferrers。这将调用referrers模式guru,它查找对所选标识符的引用,扫描工作区内所有必需的包。结果将是一个位置列表。

guru的模式之一也是describe模式。它就像:GoInfo,但它更高级一些(它提供了更多信息)。例如,它显示类型的方法集(如果有)。如果选中,则显示包的声明。

让我们继续使用相同的main.go文件。将光标放在URL字段或req.URL(在ServeHTTP函数内)的顶部。打电话给:GoDescribe。您将看到一个包含以下内容的位置列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
main.go|27 col 48| reference to field URL *net/url.URL
/usr/local/go/src/net/http/request.go|91 col 2| defined here
main.go|27 col 48| Methods:
/usr/local/go/src/net/url/url.go|587 col 15| method (*URL) EscapedPath() string
/usr/local/go/src/net/url/url.go|844 col 15| method (*URL) IsAbs() bool
/usr/local/go/src/net/url/url.go|851 col 15| method (*URL) Parse(ref string) (*URL, error)
/usr/local/go/src/net/url/url.go|897 col 15| method (*URL) Query() Values
/usr/local/go/src/net/url/url.go|904 col 15| method (*URL) RequestURI() string
/usr/local/go/src/net/url/url.go|865 col 15| method (*URL) ResolveReference(ref *URL) *URL
/usr/local/go/src/net/url/url.go|662 col 15| method (*URL) String() string
main.go|27 col 48| Fields:
/usr/local/go/src/net/url/url.go|310 col 2| Scheme string
/usr/local/go/src/net/url/url.go|311 col 2| Opaque string
/usr/local/go/src/net/url/url.go|312 col 2| User *Userinfo
/usr/local/go/src/net/url/url.go|313 col 2| Host string
/usr/local/go/src/net/url/url.go|314 col 2| Path string
/usr/local/go/src/net/url/url.go|315 col 2| RawPath string
/usr/local/go/src/net/url/url.go|316 col 2| RawQuery string
/usr/local/go/src/net/url/url.go|317 col 2| Fragment string

您将看到,我们可以看到字段的定义、方法集和URL结构的字段。这是一个非常有用的命令,如果您需要它并想理解周围的代码,它就在那里。尝试通过在其他各种标识符上调用:GoDescribe来测试输出是什么。

被问得最多的问题之一是如何知道一个类型正在实现的接口。假设您有一个类型和一个由多个方法组成的方法集。您想知道它可能实现哪个接口。guru的模式implement就是这样做的,它有助于找到一个类型实现的接口。

只需继续前一个main.go文件。将光标放在handler标识符上main()函数之后。Call:GoImplements您将看到一个位置列表,其中包含以下内容:

1
2
3
main.go|23 col 6| chan type handler
/usr/local/go/src/net/http/server.go|57 col 6| implements net/http.Handler

第一行是我们选择的类型,第二行是它实现的接口。因为一个类型可以实现许多接口,所以它是一个位置列表。

guru模式中可能有帮助的是whicherrs。如你所知,错误只是价值观。所以它们可以被编程,因此可以代表任何类型。看看guru手册上说的:

whichers模式报告可能出现在类型error值中的一组可能的常量、全局变量和具体类型。在处理错误时,这些信息可能很有用,以确保所有重要的案件都得到处理。

那么我们如何使用它呢?很简单。我们仍然使用相同的main.go文件。将光标放在从http.ListenAndServe返回的err标识符的顶部。调用:GoWhicherrs,您将看到以下输出:

1
2
3
4
5
6
main.go|12 col 6| this error may contain these constants:
/usr/local/go/src/syscall/zerrors_darwin_amd64.go|1171 col 2| syscall.EINVAL
main.go|12 col 6| this error may contain these dynamic types:
/usr/local/go/src/syscall/syscall_unix.go|100 col 6| syscall.Errno
/usr/local/go/src/net/net.go|380 col 6| *net.OpError

您将看到err值可能是syscall.EINVAL常量,也可能是动态类型syscall.Errno*net.OpError。如您所见,这在实现定制逻辑以不同方式处理错误时非常有用。注意,这个查询需要设置guruscope。稍后我们将介绍scope是什么,以及如何动态地更改它。

让我们继续使用相同的main.go文件。Go以其并发原语(如channels)而闻名。跟踪值如何在通道之间发送有时会很困难。为了更好地理解它,我们有peers模式guru。此查询显示通道操作数上可能的发送/接收集(发送或接收操作)。

将光标移到以下表达式并选择整行:

1
2
ch <- n

打电话给:GoChannelPeers。您将看到一个包含以下内容的位置列表窗口:

1
2
3
4
5
main.go|19 col 6| This channel of type chan<- int may be:
main.go|10 col 11| allocated here
main.go|19 col 6| sent to, here
main.go|27 col 53| received from, here

如您所见,您可以看到通道的分配,它从何处发送和接收。因为这使用指针分析,所以必须定义一个范围。

让我们看看函数调用和目标是如何相关的。这次创建以下文件。main.go的内容应为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"

"github.com/fatih/vim-go-tutorial/example"
)

func main() {
Hello(example.GopherCon)
Hello(example.Kenya)
}

func Hello(fn func() string) {
fmt.Println("Hello " + fn())
}

文件应该在example/example.go下:

1
2
3
4
5
6
7
8
9
10
package example

func GopherCon() string {
return "GopherCon"
}

func Kenya() string {
return "Kenya"
}

所以跳转到main.go中的Hello函数,并将光标放在名为fn()的函数调用的顶部。执行:GoCallees。此命令显示所选函数调用的可能调用目标。如您所见,它将向我们展示example函数中的函数声明。这些函数是被调用者,因为它们是由名为fn()的函数调用调用的。

再次跳回main.go,这次将光标放在函数声明Hello()上。如果我们想看到这个函数的调用者呢?执行:GoCallers

您应该看到输出:

1
2
3
main.go| 10 col 7 static function call from github.com/fatih/vim-go-tutorial.Main
main.go| 11 col 7 static function call from github.com/fatih/vim-go-tutorial.Main

最后还有callstack模式,它显示从调用图根到包含选择的函数的任意路径。

将光标放回Hello()函数内的fn()函数调用。选择函数并调用:GoCallstack。输出应如下(简化形式):

1
2
3
4
main.go| 15 col 26 Found a call path from root to (...)Hello
main.go| 14 col 5 (...)Hello
main.go| 10 col 7 (...)main

它从15行开始,然后到14行,然后在10行结束。这是从根(从main()开始)到我们选择的函数(在我们的例子中是fn())的图

对于大多数guru命令,您不需要定义任何范围。什么是scope?以下摘录直接摘自guru手册:

指针分析范围:有些查询涉及指针分析,这是一种回答“这个指针可能指向什么”形式的问题的技术?”. 对工作区中的所有包运行指针分析通常开销太大,因此这些查询需要一个称为scope的附加配置参数,它决定要分析的包集。将作用域设置为当前正在使用的应用程序(或applications—a客户机和服务器的集合)。指针分析是whole-program分析,因此范围内唯一重要的包是主包和测试包。

作用域通常被指定为comma-separated包集,或者像github.com/my/dir/…这样的通配符子树;请参阅编辑器的特定文档,了解如何设置和更改范围。

vim-go自动尝试智能化,并为您将当前包导入路径设置为scope。如果命令需要一个作用域,那么大部分都可以覆盖。大多数情况下,这已经足够了,但是对于某些查询,您可能需要更改范围设置。为了便于动态更改scope,请使用一个名为:GoGuruScope的特定设置

如果您调用它,它将返回一个错误:guru scope is not set。让我们显式地将其更改为`github.com/fatih/vim-go-tutorial“范围:

1
2
:GoGuruScope github.com/fatih/vim-go-tutorial

您应该看到以下消息:

1
2
guru scope changed to: github.com/fatih/vim-go-tutorial

如果不带任何参数运行:GoGuruScope,它将输出以下内容

1
2
current guru scope: github.com/fatih/vim-go-tutorial

要选择整个GOPATH,可以使用...参数:

1
2
:GoGuruScope ...

您还可以定义多个包和子目录。以下示例选择github.comgolang.org/x/tools包下的所有包:

1
2
:GoGuruScope github.com/... golang.org/x/tools

您可以通过在包前面加上-(负号)来排除包。以下示例选择encoding下而不是encoding/xml下的所有包:

1
2
:GoGuruScope encoding/... -encoding/xml

要清除范围,只需传递一个空字符串:

1
2
:GoGuruScope ""

如果您正在一个项目中工作,您必须将范围始终设置为相同的值,并且您不希望每次启动Vim时都调用:GoGuruScope,那么您还可以通过向vimrc添加一个设置来定义一个永久作用域。该值必须是字符串类型的列表。以下是上述命令中的一些示例:

1
2
3
4
5
let g:go_guru_scope = ["github.com/fatih/vim-go-tutorial"]
let g:go_guru_scope = ["..."]
let g:go_guru_scope = ["github.com/...", "golang.org/x/tools"]
let g:go_guru_scope = ["encoding/...", "-encoding/xml"]

最后,vim-go会在使用:GoGuruScope时自动完成软件包。所以当你试图写github.com/fatih/vim-go-tutorial只需输入gi并点击tab,你会发现它会扩展到github.com

另一个需要注意的设置是构建标记(也称为构建约束)。例如,下面是您在Go源代码中放置的构建标记:

1
2
// +build linux darwin

有时源代码中可能有自定义标记,例如:

1
2
// +build mycustomtag

在这种情况下,guru将失败,因为底层的go/build包将无法构建该包。因此,所有guru相关的命令都将失败(即使:GoDef在使用guru时也是如此)。幸运的是,guru有一个-tags标志,允许我们传递自定义标记。为了方便vim-go用户,我们有一个:GoBuildTags

对于示例,只需调用以下命令:

1
2
:GoBuildTags mycustomtag

这将把这个标记传递给guru,从现在起它将按预期工作。就像:GoGuruScope,你可以用以下方法清除它:

1
2
:GoBuildTags ""

最后,如果您愿意,可以使用以下设置使其永久化:

1
2
let g:go_build_tags = "mycustomtag"

Refactor it

Rename identifiers

重命名标识符是最常见的任务之一。但这也是一件需要小心处理的事情,以免破坏其他包裹。同样,仅仅使用sed这样的工具有时是没有用的,因为您希望能够感知AST的重命名,所以它只应该重命名属于AST的标识符(它不应该重命名其他非Go文件中的标识符,比如构建脚本)

有一个为您重命名的工具,名为gorenamevim-go使用:GoRename命令在引擎盖下使用gorename。让我们将main.go更改为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

type Server struct {
name string
}

func main() {
s := Server{name: "Alper"}
fmt.Println(s.name) // print the server name
}

func name() string {
return "Zeynep"
}

将光标放在Server结构中name字段的顶部,然后调用:GoRename bar。您将看到所有name引用都被重命名为bar。最终内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

type Server struct {
bar string
}

func main() {
s := Server{bar: "Alper"}
fmt.Println(s.bar) // print the server name
}

func name() string {
return "Zeynep"
}

如您所见,只有必要的标识符被重命名,但是函数name或注释中的字符串没有被重命名。更好的是:GoRename搜索GOPATH下的所有包,并重命名依赖于该标识符的所有标识符。这是一个非常强大的工具。

Extract function

让我们来看另一个例子。将main.go文件更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
msg := "Greetings\nfrom\nTurkey\n"

var count int
for i := 0; i < len(msg); i++ {
if msg[i] == '\n' {
count++
}
}

fmt.Println(count)
}

这是一个基本示例,它只计算msg变量中的换行数。如果您运行它,您将看到它输出3

假设我们想在其他地方重用换行计数逻辑。让我们重构它。在这些情况下,大师可以用freevars模式帮助我们。freevars模式显示在给定选择中被引用但未定义的变量。

让我们选择visual模式下的片段:

1
2
3
4
5
6
7
var count int
for i := 0; i < len(msg); i++ {
if msg[i] == '\n' {
count++
}
}

选择后,请致电:GoFreevars。它应该是:'<,'>GoFreevars的形式。结果又是一个快速修复列表,它包含了所有自由变量的变量。在我们的例子中,它是一个单一变量,结果是:

1
2
var msg string

那么这有多有用呢?这一小块信息足以将其重构为一个独立的函数。创建包含以下内容的新函数:

1
2
3
4
5
6
7
8
9
10
func countLines(msg string) int {
var count int
for i := 0; i < len(msg); i++ {
if msg[i] == '\n' {
count++
}
}
return count
}

您将看到内容是我们先前选择的代码。函数的输入是:GoFreevars,自由变量的结果。我们只决定归还什么(如果有的话)。在我们的情况下,我们返回计数。我们的main.go将采用以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {
msg := "Greetings\nfrom\nTurkey\n"

count := countLines(msg)
fmt.Println(count)
}

func countLines(msg string) int {
var count int
for i := 0; i < len(msg); i++ {
if msg[i] == '\n' {
count++
}
}
return count
}

这就是重构一段代码的方法。:GoFreevars也可以用来理解代码的复杂性。只需运行它,看看有多少变量与之相关。

Generate it

代码生成是一个热门话题。因为有很棒的std libs,比如go/ast、go/parser、go/printer等。。围棋的优势在于能够轻松地创造出伟大的发电机。

首先我们有一个:GoGenerate命令,它在引擎盖下调用go generate。它就像:GoBuild:GoTest,等等。。如果有任何错误,它也会显示它们,以便您可以轻松地修复它。

实现接口的方法存根

接口对组合非常有用。它们使代码更容易处理。创建测试也更容易,因为您可以模拟接受接口类型的函数,该接口类型具有实现测试方法的类型。

vim-go支持工具impl。impl生成实现给定接口的方法存根。让我们将main.go的内容更改为以下内容:

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

type T struct{}

func main() {
fmt.Println("vim-go")
}

将光标放在T的顶部,然后键入:GoImpl。系统将提示您编写接口。输入io.ReadWriteCloser,然后按enter键。您将看到内容更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type T struct{}

func (t *T) Read(p []byte) (n int, err error) {
panic("not implemented")
}

func (t *T) Write(p []byte) (n int, err error) {
panic("not implemented")
}

func (t *T) Close() error {
panic("not implemented")
}

func main() {
fmt.Println("vim-go")
}

你看那真是太好了。当你在一个类型上面时,你也可以只输入:GoImpl io.ReadWriteCloser,它也会这样做。

但不需要将光标放在类型的顶部。你可以从任何地方调用它。例如,执行以下操作:

1
2
:GoImpl b *B fmt.Stringer

您将看到将创建以下内容:

1
2
3
4
func (b *B) String() string {
panic("not implemented")
}

如您所见,这是非常有帮助的,特别是当您有一个带有大型方法集的大型接口时。您可以很容易地生成它,因为它使用panic(),所以编译时没有任何问题。只要把必要的部分填好就行了。

Share it

vim-go还具有通过https://play.golang.org/与他人轻松共享代码的功能。正如你所知,围棋场是一个分享小片段、练习和/或提示和技巧的完美场所。有时候你在玩弄一个想法,想和别人分享。复制代码并访问play.golang.org,然后粘贴它。`vim-go`使用`:GoPlay`命令可以使所有这些都变得更好。

首先,让我们用以下简单代码更改main.go文件:

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
fmt.Println("vim-go")
}

现在调用:GoPlay,然后按enter键。您将看到vim-go自动上载了源代码:GoPlay,并且还打开了一个显示它的浏览器选项卡。但还有更多。代码段链接也会自动复制到剪贴板。只需将链接粘贴到某个地方。你会看到链接与正在播放的内容相同play.golang.org.

:GoPlay也接受一个范围。您可以选择一段代码并调用:GoPlay。它只会上传所选的部分。

有两个设置可以调整:GoPlay的行为。如果您不喜欢vim-go为您打开一个浏览器选项卡,您可以使用以下命令禁用它:

1
2
let g:go_play_open_browser = 0

其次,如果您的浏览器被错误检测到(我们使用的是openxdg-open),您可以通过以下方式手动设置浏览器:

1
2
let g:go_play_browser_command = "chrome"

HTML template

默认情况下,.tmpl文件启用了gohtml模板的语法高亮显示。如果要为另一个文件类型启用它,请将以下设置添加到.vimrc

1
2
au BufRead,BufNewFile *.gohtml set filetype=gohtmltmpl

Donation

本教程是我在业余时间创作的。如果你喜欢并愿意捐款,你现在可以成为一个完全的支持者,成为一个赞助人!

作为一个用户,你使vim-go成长和成熟,帮助我投资于错误修复、新文档,并改进当前和未来的功能。它是完全可选的,只是支持vim-go’s正在进行的开发的一种直接方法。谢谢!

https://www.patreon.com/fatih

TODO Commands

  • :GoPath
  • :AsmFmt

VimGo 安装

由于众所周知的原因, “go get” & “:GoInstallBinaries” 无法正常使用,经过多方查询.找到当下能用的方法

  1. set proxy
  • 通过 export GO111MODULE=on 开启 MODULE
  • export GOPROXY=https://goproxy.io
    七牛也出了个国内代理 goproxy.cn 方便国内用户更快的访问不能访问的包
  1. 在VIM下运行 GoInstallBinaries

  2. 弹出来的错误列表中挨个复制,并使用 go install 安装.

参考资料:
https://shockerli.net/post/go-get-golang-org-x-solution/