fatih / vim-go-tutorial
- пятница, 15 июля 2016 г. в 03:14:01
141 stars today
Tutorial for vim-go
Tutorial for vim-go. A simple tutorial on how to install and use vim-go.
We're going to use vim-plug
to install vim-go. Feel free to use other plugin
managers instead. We will create a minimal ~/.vimrc
, and add to it as we go along.
First fetch and install vim-plug
along with vim-go
:
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
Create ~/.vimrc
with following content:
call plug#begin()
Plug 'fatih/vim-go'
call plug#end()
And finally be sure to install all necessary Go tools (such as guru, goimports, gocode, etc...). If you already have them in your PATH, you're good to go.
vim -c "GoInstallBinaries" -c "qa"
Or open Vim and execute :GoInstallBinaries
For the tutorial, all our examples will be under
GOPATH/src/github.com/fatih/vim-go-tutorial/
. Please be sure you're inside this
folder. Create if necessary. This will make it easy to follow the tutorial.
If you already have a GOPATH
set up just execute:
go get github.com/fatih/vim-go-tutorial
Open a new file from your terminal:
vim main.go
As you see, vim-go automatically populated the content with a simple main
package. Save the file with :w
.
You can easily run the file with :GoRun
. Under the hood it calls go run
for
the current file. You should see that it prints vim-go
.
Replace vim-go
with Hello Gophercon
. Let us compile the file instead of running it.
For this we have :GoBuild
. If you call it, you should see this message:
vim-go: [build] SUCCESS
Under the hood it calls go build
, but it's a bit smarter. It does a couple of
things differently:
:GoBuild
multiple times without
polluting your workspace.cd
s into the source package's directorygb
, Godeps
, etc..)Let's introduce two errors by adding two compile errors:
var b = foo()
func main() {
fmt.Println("Hello GopherCon")
a
}
Save the file and call :GoBuild
again.
This time the quickfix view will be opened. To jump between the errors you can
use :cnext
and :cprevious
. Let us fix the first error, save the
file and call :GoBuild
again. You'll see the quickfix list is updated with a
single error. Remove the second error as well, save the file and call
:GoBuild
again. Now because there are no more errors, vim-go automatically
closes the quickfix window for you.
Let us improve it a little bit. Vim has a setting called autowrite
that
writes the content of the file automatically if you call :make
. vim-go also
makes use of this setting. Open your .vimrc
and add the following:
set autowrite
Now you don't have to save your file anymore when you call :GoBuild
. If we
reintroduce the two errors and call :GoBuild
, we can now iterate much more
quickly by only calling :GoBuild
.
:GoBuild
jumps to the first error encountered. If you don't want to jump
append the !
(bang) sign: :GoBuild!
.
In all the go
commands, such as :GoRun
, :GoInstall
, :GoTest
, etc..,
whenever there is an error the quickfix window always will pop up.
You can add some shortcuts to make it easier to jump between errors in quickfix list:
map <C-n> :cn<CR>
map <C-m> :cp<CR>
nnoremap <leader>a :cclose<CR>
I also use these shortcuts to build and run a Go program with <leader>b
and
<leader>r
:
autocmd FileType go nmap <leader>b <Plug>(go-build)
autocmd FileType go nmap <leader>r <Plug>(go-run)
Let's write a simple function and a test for the function. Add the following:
func Bar() string {
return "bar"
}
Open a new file called main_test.go
(it doesn't matter how you open it, from
inside Vim, a separate Vim session, etc.. it's up to you). Let us use the
current buffer and open it from Vim via :edit main_test.go
.
When you open the new file you notice something. The file automatically has the package declaration added:
package main
This is done by vim-go automatically. This time it didn't create a sample
application, instead it detected that the file is inside a valid package and
therefore created a file based on the package name (in our case the package
name was main
)
Update the test file with the following code:
package main
import (
"testing"
)
func TestBar(t *testing.T) {
result := Bar()
if result != "bar" {
t.Errorf("expecting bar, got %s", result)
}
}
Call :GoTest
. You'll see the following message:
vim-go: [test] PASS
:GoTest
calls go test
under the hood. It has the same improvements
we have for :GoBuild
. If there is any test error, a quickfix list is
opened again and you can jump to it easily.
Another small improvement is that you don't have to open the test file itself.
Try it yourself: open main.go
and call :GoTest
. You'll see the tests will
be run for you as well.
:GoTest
times out after 10 seconds by default. This is useful because Vim is
not async by default. You can change the timeout value with let g:go_test_timeout = 10s
We have two more commands that makes it easy to deal with test files. The first
one is :GoTestFunc
. This only tests the function under your cursor.
Let us change the content of the test file (main_test.go
) to:
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")
}
Now when we call :GoTest
a quickfix window will open with two errors.
However if go inside the TestBar
function and call :GoTestFunc
, you'll see
that our test passes! This is really useful if you have a lot of tests that
take time and you only want to run certain tests.
The other test-related command is :GoTestCompile
. Tests not only need to
pass with success, they must compile without any problems.
:GoTestCompile
compiles your test file, just like :GoBuild
and opens a
quickfix if there are any errors. This however doesn't run the tests. This
is very useful if you have a large test which you're editing a lot. Call
:GoTestCompile
in the current test file, you should see the following:
vim-go: [test] PASS
:GoBuild
we can add a mapping to easily call :GoTest
with a key
combination. Add the following to your .vimrc
:autocmd FileType go nmap <leader>t <Plug>(go-test)
Now you can easily test your files via <leader>t
autocmd FileType go nmap <leader>b <Plug>(go-build)
We're going to add an improved mapping. To make it seamless for
any Go file we can create a simple Vim function that checks the type of the Go
file, and execute :GoBuild
or :GoTestCompile
. Below is the helper function
you can add to your .vimrc
:
" 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#cmd#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>
Now whenever you hit <leader>b
it'll build either your Go file or it'll
compile your test files seamlessly.
\
I've mapped my leader to
,
as I find it more useful with the following setting (put this in the
beginning of .vimrc):let mapleader = ","
So with this setting, we can easily build any test and non test files with ,b
.
Let's dive further into the world of tests. Tests are really important. Go has a really great way of showing the coverage of your source code. vim-go makes it easy to see the code coverage without leaving Vim in a very elegant way.
Let's first change our main_test.go
file back to:
package main
import (
"testing"
)
func TestBar(t *testing.T) {
result := Bar()
if result != "bar" {
t.Errorf("expecting bar, got %s", result)
}
}
And main.go
to
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"
}
Now let us call :GoCoverage
. Under the hood this calls go test -coverprofile
tempfile
. It parses the lines from the profile and then dynamically changes
the syntax of your source code to reflect the coverage. As you see, because we
only have a test for the Bar()
function, that is the only function that is
green.
To clear the syntax highlighting you can call :GoCoverageClear
. Let us add a
test case and see how the coverage changes. Add the following to main_test.go
to:
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)
}
}
If we call :GoCoverage
again, you'll see that the Quz
function is now
tested as well and that it has a larger coverage. Call :GoCoverageClear
again
to clear the syntax highlighting.
Because calling :GoCoverage
and :GoCoverageClear
are used a lot together,
there is another command that makes it easy to call and clear the result. You
can also use :GoCoverageToggle
. This acts as a toggle and shows the coverage,
and when called again it clears the coverage. It's up to your workflow how you
want to use them.
Finally, if you don't like vim-go's internal view, you can also call
:GoCoverageBrowser
. Under the hood it uses go tool cover
to create a HTML
page and then opens it in your default browser. Some people like this more.
Using the :GoCoverageXXX
commands do not create any kind of temporary files.
It doesn't pollute your workflow. So you don't have to deal with removing
unwanted files every time.
Add the following to your .vimrc
:
autocmd FileType go nmap <Leader>c <Plug>(go-coverage-toggle)
With this you can easily call :GoCoverageToggle
with <leader>c
Let us start with a sample main.go
file:
package main
import "fmt"
func main() {
fmt.Println(" gophercon" )
}
Let's start with something we know already. If save the file, you'll see that
it'll be formatted automatically. It's enabled by default but can be disabled
if desired (not sure why you would though :)) with let g:go_fmt_autosave = 0
.
Optionally we also provide :GoFmt
command, which runs :GoFmt
under the hood.
Let's print the "gophercon"
string in all uppercase. For it we're going to use
the strings
package. Change the definition to:
fmt.Println(strings.ToLower("Gopher"))
When you build it you'll get an error of course:
main.go|8| undefined: strings in strings.ToLower
You'll see we get an error because the strings
package is not imported. vim-go
has couple of commands to make it easy to manipulate the import declarations.
We can easily go and edit the file, but instead we're going to use the Vim
command :GoImport
. This command adds the given package to the import path.
Run it via: :GoImport strings
. You'll see the strings
package is being
added. The great thing about this command is that it also supports
completion. So you can just type :GoImport s
and hit tab.
We also have :GoImportAs
and :GoDrop
to edit the import paths.
:GoImportAs
is the same as :GoImport
, but it allows changing the package
name. For example :GoImportAs str strings
, will import strings
with the
package name str.
Finally :GoDrop
makes it easy to remove any import paths from the import
declarations. :GoDrop strings
will remove it from the import declarations.
Of course manipulating import paths is so 2010. We have better tools to handle
this case for us. If you haven't heard yet, it's called goimports
.
goimports
is a replacement for gofmt
. You have two ways of using it. The
first (and recommended) way is telling vim-go to use it when saving the
file:
let g:go_fmt_command = "goimports"
Now whenever you save your file, goimports
will automatically format and also
rewrite your import declarations. Some people do not prefer goimports
as it
might be slow on very large codebases. In this case we also have the
:GoImports
command (note the s
at the end). With this, you can explicitly
call goimports
Let us show more editing tips/tricks. There are two text objects that we can
use to change functions. Those are if
and af
. if
means inner function and
it allows you to select the content of a function enclosure. Change your main.go
file to:
package main
import "fmt"
func main() {
fmt.Println(1)
fmt.Println(2)
fmt.Println(3)
fmt.Println(4)
fmt.Println(5)
}
Put your cursor on the func
keyword Now execute the following in normal
mode and see what happens:
dif
You'll see that the function body is removed. Because we used the d
operator.
Undo your changes with u
. The great thing is that your cursor can be anywhere
starting from the func
keyword until the closing right brace }
. It uses the tool
motion under the hood. I wrote motion
explicitly for vim-go to support features like this. It's Go AST aware and thus
its capabilities are really good. Like what you might ask? Change main.go
to:
package main
import "fmt"
func Bar() string {
fmt.Println("calling bar")
foo := func() string {
return "foo"
}
return foo()
}
Previously we were using regexp-based text objects. And it lead to problems.
For example in this example, put your cursor to the anonymous functions' func
keyword and execute dif
in normal
mode. You'll see that only the body of
the anonymous function is deleted.
We have only used the d
operator (delete) so far. However it's up to you. For
example you can select it via vif
or yank(copy) with yif
.
We also have af
, which means a function
. This text object includes the
whole function declaration. Change your main.go
to:
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()
}
So here is the great thing. Because of motion
we have full knowledge about
every single syntax node. Put your cursor on top of the func
keyword or
anywhere below or above (doesn't matter). If you now execute vaf
, you'll see
that the function declaration is being selected, along with the doc comment as
well! You can for example delete the whole function with daf
, and you'll see
that the comment is gone as well. Go ahead and put your cursor on top of the
comment and execute vif
and then vaf
. You'll see that it selects the
function body, even though your cursor is outside the function, or it selects
the function comments as well.
This is really powerful and this all is thanks to the knowledge we have from
let g:go_textobj_include_function_doc = 1 motion
. If you don't like comments
being a part of the function declaration, you can easily disable it with:
let g:go_textobj_include_function_doc = 1
If you are interested in learning more about motion
, check out the blog post I wrote for
more details: Treating Go types as objects in vim
(Optional question: without looking at the go/ast
package, is the doc comment
a part of the function declaration or not?)
There is a great plugin that allows you to split or join Go structs. It's
actually not a Go plugin, but it has support for Go structs. To enable it add
plugin directive between the plug
definition into your vimrc
and run
:PlugInstall
. Example:
call plug#begin()
Plug 'fatih/vim-go'
Plug 'AndrewRadev/splitjoin.vim'
call plug#end()
Once you have installed the plugin, change the main.go
file to:
package main
type Foo struct {
Name string
Ports []int
Enabled bool
}
func main() {
foo := Foo{Name: "gopher", Ports: []int{80, 443}, Enabled: true}
}
Put your cursor on the same line as the struct expression. Now type gS
. This
will split
the struct expression into multiple lines. And you can even
reverse it. If your cursor is still on the foo
variable, execute gJ
in
normal
mode. You'll see that the field definitions are all joined.
This doesn't use any AST-aware tools, so for example if you type gJ
on top of
the fields, you'll see that only two files are joined.
Vim-go supports two popular snippet plugins.
Ultisnips and
neosnippet. By default,
if you have Ultisnips
installed it'll work. Let us install ultisnips
first. Add it between the plug
directives in your vimrc
and then run
:PlugInstall
. Example:
call plug#begin()
Plug 'fatih/vim-go'
Plug 'SirVer/ultisnips'
call plug#end()
There are many helpful snippets. To see the full list check our current snippets: https://github.com/fatih/vim-go/blob/master/gosnippets/UltiSnips/go.snippets
Let me show some of the snippets that I'm using the most. Change your main.go
content to:
package main
import "encoding/json"
type foo struct {
Message string
Ports []int
ServerName string
}
func newFoo() (*foo, error) {
return &foo{
Message: "foo",
Ports: []int{80},
ServerName: "Foo",
}, nil
}
func main() {
res, err := newFoo()
out, err := json.Marshal(res)
}
Let's put our cursor just after the newFoo()
expression. Let's panic here if
the err is non-nil. Type errp
in insert mode and just hit tab
. You'll see
that it'll be expanded and put your cursor inside the panic()
` function:
if err != nil {
panic( )
^
cursor position
}
Fill the panic with err
and move on to the json.Marshal
statement. Do the
same for it.
Now let us print the variable out
. Because variable printing is so popular,
we have several snippets for it:
fn -> fmt.Println()
ff -> fmt.Printf()
ln -> log.Println()
lf -> log.Printf()
Here ff
and lf
are special. They dynamically copy the variable name into
the format string as well. Try it youself. Move your cursor to the end of the
main function and type ff
and hit tab. After expanding the snippet you can
start typing. Type string(out)
and you'll see that both the format string and
the variadic arguments will be filled with the same string you have typed.
This comes very handy to quickly print variables for debugging.
Run your file with :GoRun
and you should see the following output:
string(out) = {"Message":"foo loves bar","Ports":[80],"ServerName":"Foo"}
Great. Now let me show one last snippet that I think is very useful. As you see
from the output the fields Message
and Ports
begin with uppercase
characters. To fix it we can add a json tag to the struct field. vim-go makes it
very easy to add field tags. Move your cursor to the end of the Message
string line in the field:
type foo struct {
Message string .
^ put your cursor here
}
In insert
mode, type json
and hit enter. You'll see that it'll be
automatically expanded to valid field tag. The field name is converted
automatically to a lowercase and put there for you. You should now see the
following:
type foo struct {
Message string `json:"message"`
}
It's really amazing. But we can do even better! Go ahead and create a
snippet expansion for the ServerName
field. You'll see that it's converted to
server_name
. Amazing right?
type foo struct {
Message string `json:"message"`
Ports []int
ServerName string `json:"server_name"`
}
gofmt
to goimports
let g:go_fmt_command = "goimports"
gofmt
shows any errors during parsing the file. If
there are any parse errors it'll show them inside a quickfix list. This is
enabled by default. Some people don't like it. To disable it add:let g:go_fmt_fail_silently = 1
snake_case
. But you can also use camelCase
if you wish. For example
if you wish to change the default value to camel case use the following
setting:let g:go_snippet_case_type = "camelcase"
By default we only have a limited syntax highlighting enabled. There are two
main reasons. First is that people don't like too much color because it causes
too much distraction. The second reason is that it impacts
the performance of Vim a lot. We need to enable it explicitly. First add the
following settings to your .vimrc
:
let g:go_highlight_types = 1
This highlights the bar
and foo
below:
type foo struct{
quz string
}
type bar interface{}
Adding the following:
let g:go_highlight_fields = 1
Will highlight the quz
below:
type foo struct{
quz string
}
f := foo{quz:"QUZ"}
f.quz # quz here will be highlighted
If we add the following:
let g:go_highlight_functions = 1
let g:go_highlight_methods = 1
We are now also highlighting function names. Foo
and main
will now
be highlighted:
func (t *T) Foo() {}
func main() {
}
If you add let g:go_highlight_operators = 1
it will highlight the following
operators such as:
- + % < > ! & | ^ * =
-= += %= <= >= != &= |= ^= *= ==
<< >> &^
<<= >>= &^=
:= && || <- ++ --
If you add let g:go_highlight_extra_types = 1
the following extra types
will be highlighted as well:
bytes.(Buffer)
io.(Reader|ReadSeeker|ReadWriter|ReadCloser|ReadWriteCloser|Writer|WriteCloser|Seeker)
reflect.(Kind|Type|Value)
unsafe.Pointer
Let's move on to more useful highlights. What about build tags? It's not easy
to implement it without looking into the go/build
document. Let us first add
the following: let g:go_highlight_build_constraints = 1
and change your
main.go
file to:
// build linux
package main
You'll see that it's gray, thus it's not valid. Prepend +
to the build
word and save it again:
// +build linux
package main
Do you know why? If you read the go/build
package you'll see that the
following is buried in the document:
... preceded only by blank lines and other line comments.
Let us change our content again and save it to:
// +build linux
package main
You'll see that it automatically highlighted it in a valid way. It's really
great. If you go and change linux
to something you'll see that it also checks
for valid official tags (such as darwin
,race
, ignore
, etc... )
Another similar feature is to highlight the Go directive //go:generate
. If
you put let g:go_highlight_generate_tags = 1
into your vimrc, it'll highlight
a valid directive that is processed with the go generate
command.
We have a lot more highlight settings, these are just a sneak peek of it. For
more check out the settings via :help go-settings
8
spaces for a single tab. However it's up to us how to represent in Vim. The
following will change it to show a single tab as 4 spaces:autocmd BufNewFile,BufRead *.go setlocal noexpandtab tabstop=4 shiftwidth=4
This setting will not expand a tab into spaces. It'll show a single tab as 4
spaces. It will use 4
spaces to represent a single indent.
molokai
. To enable it add the Plug directive just between the plug
definitions:call plug#begin()
Plug 'fatih/vim-go'
Plug 'fatih/molokai'
call plug#end()
Also add the following to enable molokai with original color scheme and 256 color version:
let g:rehash256 = 1
let g:molokai_original = 1
colorscheme molokai
After that restart Vim and call :PlugInstall
. This will pull the plugin
and install it for you. After the plugin is installed, you need to restart Vim
again.
From the previous examples you saw that we had many commands that would show
the quickfix window when there was an issue. For example :GoBuild
shows
errors from the compile output (if any). Or for example :GoFmt
shows the
parse errors of the current file while formatting it.
We have many other commands that allows us to call and then collect errors, warnings or suggestions.
For example :GoLint
. Under the hood it calls golint
, which is a command
that suggests changes to make Go code more idiomatic. There
is also :GoVet
, which calls go vet
under the hood. There are many other
tools that check certain things. To make it easier, someone decided to
create a tool that calls all these checkers. This tool is called
gometalinter
. And vim-go supports it via the command :GoMetaLinter
. So what
does it do?
If you just call :GoMetaLinter
for a given Go source code. By default it'll run
go vet
, golint
and errcheck
concurrently. gometalinter
collects
all the outputs and normalizes it to a common format. Thus if you call
:GoMetaLinter
, vim-go shows the result of all these checkers inside a
quickfix list. You can then jump easily between the lint, vet and errcheck
results. The setting for this default is as following:
let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']
There are many other tools and you can easily customize this list yourself. If
you call :GoMetaLinter
it'll automatically uses the list above.
Because :GoMetaLinter
is usually fast, vim-go also can call it whenever you
save a file (just like :GoFmt
). To enable it you need to add the following to
your .vimrc:
let g:go_metalinter_autosave = 1
What's great is that the checkers for the autosave is different than what you
would use for :GoMetaLinter
. This is great because you can customize it so only
fast checkers are called when you save your file, but others if you call
:GoMetaLinter
. The following setting let you customize the checkers for the
autosave
feature.
let g:go_metalinter_autosave_enabled = ['vet', 'golint']
As you see by default vet
and golint
are enabled. Lastly, to prevent
:GoMetaLinter
running for too long, we have a setting to cancel it after a
given timeout. By default it is 5 seconds
but can be changed by the following
setting:
let g:go_metalinter_deadline = "5s"
So far we have only jumped between two files, main.go
and main_test.go
. It's
really easy to switch if you have just two files in the same directory. But
what if the project gets larger and larger with time? Or what if the file
itself is so large that you have hard time navigating it?
vim-go has several ways of improving navigation. First let me show how we can quickly jump between a Go source code and its test file.
Suppose you have both a foo.go
and its equivalent test file foo_test.go
.
If you have main.go
from the previous examples with its test file you can
also open it. Once you open it just execute the following Vim command:
:GoAlternate
You'll see that you switched immediately to main_test.go
. If you execute it
again, it'll switch to main.go
. :GoAlternate
works as a toggle and is
really useful if you have a package with many test files. The idea is very
similar to the plugin a.vim command
names. This plugin jumps between a .c
and .h
file. In our case
:GoAlternate
is used to switch between a test and non-test file.
One of the most used features is go to definition
. vim-go had from the
beginning the command :GoDef
that jumps to any identifier's declaration. Let
us first create a main.go
file to show it in action. Create it with the
following content:
package main
import "fmt"
type T struct {
Foo string
}
func main() {
t := T{
Foo: "foo",
}
fmt.Printf("t = %+v\n", t)
}
Now we have here several ways of jumping to declarations. For example if you put
your cursor on top of T
expression just after the main function and call
:GoDef
it'll jump to the type declaration.
If you put your cursor on top of the t
variable declaration just after the
main function and call :GoDef
, you'll see that nothing will happen. Because
there is no place to go, but if you scroll down a few lines and put your cursor
to the t
variable used in fmt.Printf()
and call :GoDef
, you'll see that
it jumped to the variable declaration.
:GoDef
not only works for local scope, it works also globally
(acrossGOPATH
). If, for example, you put your cursor on top of the Printf()
function and call :GoDef
, it'll jump directly to the fmt
package. Because
this is used so frequently, vim-go overrides the built in Vim shortcuts gd
and ctrl-]
as well. So instead of :GoDef
you can easily use gd
or
ctrl-]
Once we jump to a declaration, we also might want to get back into our previous
location. By default there is the Vim shortcut ctrl-o
that jumps to the
previous cursor location. It works great when it does, but not good enough if
you're navigating between Go declarations. If, for example, you jump to a file
with :GoDef
and then scroll down to the bottom, and then maybe to the top,
ctrl-o
will remember these locations as well. So if you want to jump back to
the previous location when invoking :GoDef
, you have to hit ctrl-o
multiple
times. And this is really annoying.
We don't need to use this shortcut though, as vim-go has a better implementation
for you. There is a command :GoDefPop
which does exactly this. vim-go
keeps an internal stack list for all the locations you visit with :GoDef
.
This means you can jump back easily again via :GoDefPop
to your older
locations, and it works even if you scroll down/up in a file. And because this
is also used so many times we have the shortcut ctrl-t
which calls under the
hood :GoDefPop
. So to recap:
ctrl-]
or gd
to jump to a definition, locally or globallyctrl-t
to jump back to the previous location Let us move on with another question, suppose you jump so far that you just
want to back to where you started? As mentioned earlier,
vim-go keeps an history of all your locations invoked via :GoDef
. There is a
command that shows all these and it's called :GoDefStack
. If you call it,
you'll see that a custom window with a list of your old locations will be
shown. Just navigate to your desired location and hit enter. And finally to
clear the stack list anytime call :GoDefStackClear
.
From the previous example that :GoDef
is nice if you know where you want to
jump. But what if you don't know what your next destination is? Or you just
partially partially the name of a function?
In our Edit it
section I mentioned a tool called motion
, which is a
custom built tool just for vim-go. motion
has other capabilities as well.
motion
parses your Go package and thus has a great understanding of all
declarations. We can take advantage of this feature for jumping between
declarations. There are two commands, which are not available until you install
a certain plugin. The commands are:
:GoDecls
:GoDeclsDir
First let us enable these two commands by installing the necessary plugin. The
plugin is called ctrlp. Long-time Vim
users have it installed already. To install it add the following line between
your plug
directives and call :PlugInstall
to install it:
Plug 'ctrlpvim/ctrlp.vim'
Once you have it installed, use the following main.go
content:
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"
}
And a main_test.go
file with the following content:
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)
}
}
Open main.go
and call :GoDecls
. You'll see that :GoDecls
shows all type
and function declarations for you. If you type ma
you'll see that ctrlp
filters the list for you. If you hit enter
it will automatically jump to it.
The fuzzy search capabilities combined with motion
's AST capabilities brings
us a very simple to use but powerful feature.
For example, call :GoDecls
and write foo
. You'll see that it'll filter
BarFoo
for you. The Go parser is very fast and works very well with large files
with hundreds of declarations.
Sometimes just searching within the current file is not enough. A Go package can
have multiple files (such as tests). A type declaration can be in one file,
whereas a some functions specific to a certain set of features can be in
another file. This is where :GoDeclsDir
is useful. It parses the whole
directory for the given file and lists all the declarations for the given
directory.
Call :GoDeclsDir
. You'll see this time it also included the declarations from
the main_test.go
file as well. If you type Bar
, you'll see both the Bar
and TestBar
functions. This is really great if you just want to get an
overview of all type and function declarations, and also jump to them.
Let's continue with a question. What if you just want to move to the next or previous function? If you current function body is long, you'll probably will not see the function names. Or maybe there are other declarations between the current and other functions.
Vim already has motion operators like w
for words or b
for backwards words.
But what if we could add motions for Go ast? For example for function declarations?
vim-go provides(overrides) two motion objects to move between functions. These are:
]] -> jump to next function
[[ -> jump to previous function
Vim has these shortcuts by default. But those are suited for C source code and
jumps between braces. We can do it better. Just like our previous example,
motion
is used under the hood for this operation
Open main.go
and move to the top of the file. In normal
mode, type ]]
and
see what happens. You'll see that you jumped to the main()
function. Another
]]
will jump to Bar()
If you hit [[
it'll jump back to the main()
function.
]]
and [[
also accepts counts
. For example if you move to the top again
and hit 3]]
you'll see that it'll jump the third function in the source file.
And going forward, because these are valid motions, you can apply operators to
it as well!
If you move your file to the top and hit d]]
you'll see that it deleted
anything before the next function. For example one useful usage would be typing
v]]
and then hit ]]
again to select the next function, until you've done
with your selection.
.vimrc
: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')
This will add new commands, called :A
, :AV
, :AS
and :AT
. Here :A
works just like :GoAlternate
, it replaces the current buffer with the
alternate file. :AV
will open a new vertical split with the alternate file.
:AS
will open the alternate file in a new split view and :AT
in a new tab.
These commands are very productive depending on how you use them, so I think
it's useful to have them.
guru
(formerly oracle
). guru
has
an excellent track of being very predictable. It works for dot imports,
vendorized imports and many other non-obvious identifiers. But sometimes it's
very slow for certain queries. Previously vim-go was using godef
which is
very fast on resolving queries. With the latest release one can easily use or
switch the underlying tool for :GoDef
. To change it back to godef
use the
following setting:let g:go_def_mode = 'godef'
:GoDecls
and :GoDeclsDir
shows type and function
declarations. This is customizable with the g:go_decls_includes
setting. By
default it's in the form of:let g:go_decls_includes = "func,type"
If you just want to show function declarations, change it to:
let g:go_decls_includes = "func"
Writing/editing/changing code is usually something we can do only if we first understand what the code is doing. vim-go has several ways to make it easy to understand what your code is all about.
Let's start with the basics. Go documentation is very well-written and is highly integrated into the Go AST as well. If you just write some comments, the parser can easily parse it and associate with any node in the AST. So what it means is that, we can easily find the documentation in the reverse order. If you have the node from an AST, you can easily read the documentation (if you have it)!
We have a command called :GoDoc
that shows any documentation associated with
the identifier under your cursor. Let us change the content of main.go
to:
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"
}
Put your cursor on top of the Println
function just after the main
function
and call :GoDoc
. You'll see that it vim-go automatically opens a scratch
window that shows the documentation for you:
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.
It shows the import path, the function signature and then finally the doc
comment of the identifier. Initially vim-go was using plain go doc
, but it
has some shortcomings, such as not resolving based on a byte identifier. go
doc
is great for terminal usages, but it's hard to integrate into editors.
Fortunately we have a very useful tool called gogetdoc
, which resolves and
retrieves the AST node for the underlying node and outputs the associated doc
comment.
That's why :GoDoc
works for any kind of identifier. If your put your cursor under
sayHi()
and call :GoDoc
you'll see that it shows it as well. And if you put
it under sayYoo()
you'll see that it just outputs no documentation
for AST
nodes without doc comments.
As usual with other features, we override the default normal shortcut K
so
that it invokes :GoDoc
instead of man
(or something else). It's really easy
to find the documentation, just hit K
in normal mode!
:GoDoc
just shows the documentation for a given identifier. But it's not a
documentation explorer, if you want to explore the documentation there is
third-party plugin that does it:
go-explorer. There is a open bug to
include it into vim-go.
Sometimes you want to know what a function is accepting or returning. Or what the identifier under your cursor is. Questions like this are common and we have a command to answer it.
Using the same main.go
file, go over the Println
function and call
:GoInfo
. You'll see that the function signature is being printed in the
statusline. This is really great to see what it's doing, as you don't have to
jump to the definition and check out what the signature is.
But calling :GoInfo
every time is tedious. We can make some improvements to
call it faster. As always a way of making it faster is to add a shortcut:
autocmd FileType go nmap <Leader>i <Plug>(go-info)
Now you easily call :GoInfo
by just hitting <leader>i
. But there is still
room to improve it. vim-go has a support to automatically show the information
whenever you move your cursor. To enable it add the following to your .vimrc
:
let g:go_auto_type_info = 1
Now whenever you move your cursor onto a valid identifier, you'll see that your
status line is updated automatically. By default it updates every 800ms
. This
is a vim setting and can be changed with the updatetime
setting. To change it
to 100ms
add the following to your .vimrc
set updatetime=100
Sometimes we just want to quickly see all matching identifiers. Such as variables, functions, etc.. Suppose you have the following Go code:
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
}
If you put your cursor on top of err
and call :GoSameIds
you'll see that
all the err
variables get highlighted. Put your cursor on the sayHi()
function call, and you'll see that the sayHi()
function identifiers all are
highlighted. To clear them just call :GoSameIdsClear
This is more useful if we don't have to call it manually every time. vim-go
can automatically highlight matching identifiers. Add the following to your
vimrc
:
let g:go_auto_sameids = 1
After restarting your virmc, you'll see that you don't need to call
:GoSameIds
manually anymore. Matching identifier variables are now highlighted
automatically for you.
The previous feature was using the tool guru
under the hood. So let's talk a
little bit about guru. So what is guru? Guru is an editor integrated tool for
navigating and understanding Go code. There is a user manual that shows all the
features: https://golang.org/s/using-guru
Let us use the same example from that manual to show some of the features we've integrated into vim-go:
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)
}
Put your cursor on top of the handler
and call :GoReferrers". This calls the
referrersmode of
vim-go`, which finds finds references to the selected
identifier, scanning all necessary packages within the workspace. The result
will be a quickfix list, so you should be able to jump to the results easily.
Let's move to another example. Change your main.go
file to:
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)
}
This is a basic example that just counts the newlines in our msg
variable. If
you run it, you'll see that it outputs 3
.
Assume we want to reuse the newline counting logic somewhere else. Let us
refactor it. Guru can help us in these situations with the freevars
mode. The
freevars
mode shows variables that are referenced but not defined within a
given selection.
Let us select the piece in visual
mode:
var count int
for i := 0; i < len(msg); i++ {
if msg[i] == '\n' {
count++
}
}
After selecting it, call :GoFreevars
. It should be in form of
:'<,'>GoFreevars
. The result is again a quickfix list and it contains all the
variables that free variables. In our case it's a single variable and the
result is:
var msg string
So how useful is this? This little piece of information is enough to refactor it into a standalone function. Create a new function with the following content:
func countLines(msg string) int {
var count int
for i := 0; i < len(msg); i++ {
if msg[i] == '\n' {
count++
}
}
return count
}
You'll see that the content is our previously selected code. And the input to
the function is the result of :GoFreevars
, the free variables. We only
decided what to return (if any). In our case we return the count. Our main.go
will be in the form of:
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
}
That's how you refactor a piece of code. :GoFreevars
can be used also to
understand the complexity of a code. Just run it and see how many variables are
dependent to it.
Let us see how function calls and targets are related. This time create the
following files. The content of main.go
should be:
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())
}
And the file should be under example/example.go
:
package example
func GopherCon() string {
return "GopherCon"
}
func Kenya() string {
return "Kenya"
}
So jump to the Hello
function inside main.go
and put your cursor on top of
the function call named fn()
. Execute :GoCallees
. This command shows the
possible call targets of the selected function call. As you see it'll show us
the function declarations inside the example
function. Those functions are
the callees, because they were called by the function call named fn()
.
Jump back to main.go
again and this time put your cursor on the function
declaration Hello()
. What if we want to see the callers of this function?
Execute :GoCallers
.
You should see the output:
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
As with the other usages you can easily navigate the callers inside the quickfix window.
Finally there is also the callstack
mode, which shows an arbitrary path from
the root of the call graph to the function containing the selection.
Put your cursor back to the fn()
function call inside the Hello()
function.
Select the function and call :GoCallStack
. The output should be like
(simplified form):
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
It starts from line 15
, and then to line 14
and then ends at line 10
.
This is the graph from the root (which starts from main()
) to the function we
selected (in our case fn()
)
:GoPlay (accepts range)
:GoInstallBinaries