Skip to content

Tutorial

Billie Cleek edited this page May 3, 2020 · 5 revisions

vim-go-tutorial

Tutorial for vim-go. A simple tutorial on how to install and use vim-go.

Both Go and vim-go have changed significantly since this was originally written. The tutorial is still mostly accurate, but some details have changed. If you find something that is inaccurate, missing, or otherwise out of date please open an issue so it can be addressed.

One of the most noticeable changes in the ecosystem since this tutorial was written is the introduction of Go modules. The tutorial does not yet explain some of the differences in behavior of the go tool and other community provided tools in GOPATH mode vs module aware mode. The vast majority of vim-go features work as described here with the exception of commands that rely on guru. guru only works as expected in GOPATH mode. Other commands may require the use of gopls, and many options have been added to vim-go to control whether gopls or the traditional tool is used under the hood. Please reference vim-go's help (:help vim-go) when in doubt.

Table of Contents

  1. Quick Setup
  2. Hello World
  3. Run it
  4. Build it
  5. Fix it
  6. Test it
  7. Cover it
  8. Edit it
  1. Beautify it
  2. Check it
  3. Navigate it
  1. Understand it
  1. Refactor it
  1. Generate it
  1. Share it
  2. HTML template

Quick Setup

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', { 'do': ':GoInstallBinaries' }
call plug#end()

Or open Vim and execute :GoInstallBinaries. This is a vim-go command that installs all vim-go dependencies for you. It doesn't download pre compiled binaries, instead it calls go get under the hood, so the binaries are all compiled in your host machine (which is both safe and simplifies the installation process as we don't need to provide binaries for multiple platforms). If you already have some of the dependencies (such as guru, goimports) call :GoUpdateBinaries to update the binaries.

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. 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

Or create the folder, if necessary.

Hello World!

Open the main.go file from your terminal:

vim main.go

It's a very basic file that prints vim-go to stdout.

Run it

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.

For whole package run with :GoRun.

Build it

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:

  • No binaries are created; you can call :GoBuild multiple times without polluting your workspace.
  • It automatically cds into the source package's directory
  • It parses any errors and shows them inside a quickfix list
  • It automatically detects the GOPATH and modifies it if needed (detects projects such as gb, Godeps, etc..)
  • Runs async if used within Vim 8.0.xxx or NeoVim

Fix it

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.

vimrc improvements

  • You can add some shortcuts to make it easier to jump between errors in quickfix list:
map <C-n> :cnext<CR>
map <C-m> :cprevious<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)
  • There are two types of error lists in Vim. One is called location list the other quickfix. Unfortunately the commands for each lists are different. So :cnext only works for quickfix list, for location lists you have to use :lnext. Some of the commands in vim-go open a location list, because location lists are associated with a window and each window can have a separate list. This means you can have multiple windows, and multiple location lists, one for Build, one for Check, one for Tests, etc..

Some people prefer to use only quickfix though. If you add the following to your vimrc all lists will be of type quickfix:

let g:go_list_type = "quickfix"

Test it

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. 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). If there are no files, vim-go automatically populates the content with a simple main package.

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 make 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] SUCCESS 

vimrc improvements

  • As with :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

  • Let's make building Go files simpler. First, remove the following mapping we added previously:
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 executes :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#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>

Now whenever you hit <leader>b it'll build either your Go file or it'll compile your test files seamlessly.

  • By default the leader shortcut is defined as: \ 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.

Cover it

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:

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 does not create any kind of temporary files and doesn't pollute your workflow. So you don't have to deal with removing unwanted files every time.

vimrc improvements

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

Edit it

Imports

Let us start with a sample main.go file:

package main

     import "fmt"

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

Let's start with something we know already. If we 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 "gopher" string in all uppercase. For it we're going to use the strings package. Change the definition to:

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

When you build it you'll get an error of course:

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

You'll see we get an error because the strings package is not imported. vim-go has a 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

Text objects

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, which leads 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 = 0

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?)

Struct split and join

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, then do a :source ~/.vimrc in your vim editor 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 fields are joined.

Snippets

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, then do a :source ~/.vimrc in your vim editor 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

UltiSnips and YouCompleteMe may conflict on [tab] button

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 loves bar",
		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 yourself. 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 tab. 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"`
}

vimrc improvements

  • Don't forget to change gofmt to goimports
let g:go_fmt_command = "goimports"
  • When you save your file, 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
  • You can change which case it should apply while converting. By default vim-go uses 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_addtags_transform = "camelcase"

Beautify it

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

We are now also highlighting function and method names in declarations. Foo and main will now be highlighted, but Println is not as that is an invocation:

func (t *T) Foo() {}

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

If you also want to highlight function and method invocations, add the following:

let g:go_highlight_function_calls = 1

Now, Println will also be highlighted:

func (t *T) Foo() {}

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

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

vimrc improvements

  • Some people don't like how the tabs are shown. By default Vim shows 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.

  • A lot of people ask for my colorscheme. I'm using a slightly modified 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 :source ~/.vimrc, then :PlugInstall. This will pull the plugin and install it for you. After the plugin is installed, you need to restart Vim again.

Check it

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"

Navigate it

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?

Alternate files

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.

Go to definition

One of the most used features is go to definition. From the beginning, vim-go had the :GoDef command 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:

  • Use ctrl-] or gd to jump to a definition, locally or globally
  • Use ctrl-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.

Move between functions

From the previous example we see 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 know 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, then do a :source ~/.vimrc in your vim editor 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 from the files in the given directory (but not subdirectories).

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 your 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 to 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 improvements

  • We can improve it to control how it opens the alternate file. Add the following to your .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.

  • The "go to definition" command families are very powerful but yet easy to use. Under the hood it uses by default the tool guru (formerly oracle). guru has an excellent track record 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'
  • Currently by default :GoDecls and :GoDeclsDir show 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"

Understand it

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.

Documentation lookup

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 you 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 an open bug to include it into vim-go.

Identifier resolution

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 status line. 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

Identifier highlighting

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 vim, you'll see that you don't need to call :GoSameIds manually anymore. Matching identifier variables are now highlighted automatically for you.

Dependencies and files

As you know a package can consist of multiple dependencies and files. Even if you have many files inside the directory, only the files that have the package clause correctly are part of a package.

To see the files that make a package you can call the following:

:GoFiles

which will output (my $GOPATH is set to ~/Code/Go):

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

If you have other files those will be listed as well. Note that this command is only for listing Go files that are part of the build. Test files will be not listed.

For showing the dependencies of a file you can call :GoDeps. If you call it you'll see:

['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

The previous feature was using the guru tool 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 examples 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 referrers mode of guru, which finds references to the selected identifier, scanning all necessary packages within the workspace. The result will be a location list.


One of the modes of guru is also the describe mode. It's just like :GoInfo, but it's a little bit more advanced (it gives us more information). It shows for example the method set of a type if there is any. It shows the declarations of a package if selected.

Let's continue with same main.go file. Put the cursor on top of the URL field or req.URL (inside the ServeHTTP function). Call :GoDescribe. You'll see a location list populated with the following content:

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

You'll see that we can see the definition of the field, the method set and the URL struct's fields. This is a very useful command and it's there if you need it and want to understand the surrounding code. Try and experiment by calling :GoDescribe on various other identifiers to see what the output is.


One of the most asked questions is how to know the interfaces a type is implementing. Suppose you have a type and with a method set of several methods. You want to know which interface it might implement. The mode implement of guru just does it and it helps to find the interface a type implements.

Just continue with the same previous main.go file. Put your cursor on the handler identifier just after the main() function. Call :GoImplements You'll see a location list populated with the following content:

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

The first line is our selected type and the second line will be the interface it implements. Because a type can implement many interfaces it's a location list.


One of the guru modes that might be helpful is whicherrs. As you know errors are just values. So they can be programmed and thus can represent any type. See what the guru manual says:

The whicherrs mode reports the set of possible constants, global variables, and concrete types that may appear in a value of type error. This information may be useful when handling errors to ensure all the important cases have been dealt with.

So how do we use it? It's easy. We still use the same main.go file. Put your cursor on top of the err identifier which is returned from http.ListenAndServe. Call :GoWhicherrs and you'll see the following output:

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

You'll see that the err value may be the syscall.EINVAL constant or it also might be the dynamic types syscall.Errno or *net.OpError. As you see this is really helpful when implementing custom logic to handle the error differently if needed. Note that this query needs a guru scope to be set. We'll going to cover in a moment what a scope is and how you can change it dynamically.


Let's continue with the same main.go file. Go is famous for its concurrency primitives, such as channels. Tracking how values are sent between channels can sometimes be hard. To understand it better we have the peers mode of guru. This query shows the set of possible send/receives on the channel operand (send or receive operation).

Move your cursor to the following expression and select the whole line:

ch <- n

Call :GoChannelPeers. You'll see a location list window with the following content:

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

As you see you can see the allocation of the channel, where it's sending and receiving from. Because this uses pointer analysis, you have to define a scope.


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

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())


For most of the guru commands you don't need to define any scope. What is a scope? The following excerpt is straight from the guru manual:

Pointer analysis scope: some queries involve pointer analysis, a technique for answering questions of the form “what might this pointer point to?”. It is usually too expensive to run pointer analysis over all the packages in the workspace, so these queries require an additional configuration parameter called the scope, which determines the set of packages to analyze. Set the scope to the application (or set of applications---a client and server, perhaps) on which you are currently working. Pointer analysis is a whole-program analysis, so the only packages in the scope that matter are the main and test packages.

The scope is typically specified as a comma-separated set of packages, or wildcarded subtrees like github.com/my/dir/...; consult the specific documentation for your editor to find out how to set and vary the scope.

vim-go automatically tries to be smart and sets the current packages import path as the scope for you. If the command needs a scope, you're mostly covered. Most of the times this is enough, but for some queries you might to change the scope setting. To make it easy to change the scope on the fly with have a specific setting called :GoGuruScope

If you call it, it'll return an error: guru scope is not set. Let us change it explicitly to the `github.com/fatih/vim-go-tutorial" scope:

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

You should see the message:

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

If you run :GoGuruScope without any arguments, it'll output the following

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

To select the whole GOPATH you can use the ... argument:

:GoGuruScope ...

You can also define multiple packages and also subdirectories. The following example selects all packages under github.com and the golang.org/x/tools package:

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

You can exclude packages by prepending the - (negative) sign to a package. The following example selects all packages under encoding but not encoding/xml:

:GoGuruScope encoding/... -encoding/xml

To clear the scope just pass an empty string:

:GoGuruScope ""

If you're working on a project where you have to set the scope always to the same value and you don't want to call :GoGuruScope everytime you start Vim, you can also define a permanent scope by adding a setting to your vimrc. The value needs to be a list of string types. Here are some examples from the commands above:

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"]

Finally, vim-go tries to auto complete packages for you while using :GoGuruScope as well. So when you try to write github.com/fatih/vim-go-tutorial just type gi and hit tab, you'll see it'll expand to github.com


Another setting that you should be aware are build tags (also called build constraints). For example the following is a build tag you put in your Go source code:

// +build linux darwin

Sometimes there might be custom tags in your source code, such as:

// +build mycustomtag

In this case, guru will fail as the underlying go/build package will be not able to build the package. So all guru related commands will fail (even :GoDef when it uses guru). Fortunately guru has a -tags flag that allows us to pass custom tags. To make it easy for vim-go users we have a :GoBuildTags

For the example just call the following:

:GoBuildTags mycustomtag

This will pass this tag to guru and from now on it'll work as expected. And just like :GoGuruScope, you can clear it with:

:GoBuildTags ""

And finally if you wish you can make it permanent with the following setting:

let g:go_build_tags = "mycustomtag"

Refactor it

Rename identifiers

Renaming identifiers is one of the most common tasks. But it's also something that needs to be done carefully in order not to break other packages as well. Also just using a tool like sed is sometimes not useful, as you want AST aware renaming, so it only should rename identifiers that are part of the AST (it should not rename for example identifiers in other non Go files, say build scripts)

Vim-go supports two tools to rename identifiers: gorename and gopls. It currently uses gorename by default.

You can set which tool is used by configuring g:go_rename_command (see :help g:go_rename_command). gorename only works in GOPATH mode. gopls can be used to rename in both GOPATH mode and module aware mode and will be the default tool used to rename soon.

Let us change main.go to the following content:

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"
}

Put your cursor on top of the name field inside the Server struct and call :GoRename bar. You'll see all name references are renamed to bar. The final content would look like:

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"
}

As you see, only the necessary identifiers are renamed, but the function name or the string inside the comment is not renamed. What's even better is that :GoRename searches all packages under GOPATH and renames all identifiers that depend on the identifier. It's a very powerful tool.

Extract function

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 are 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.

Generate it

Code generation is a hot topic. Because of the great std libs such as go/ast, go/parser, go/printer, etc.. Go has the advantage of being able to create great generators easily.

First we have the :GoGenerate command that calls go generate under the hood. It just works like :GoBuild, :GoTest, etc.. If there are any errors it also shows them so you can easily fix it.

Method stubs implementing an interface

Interfaces are really great for composition. They make your code easier to deal with. It's also easier for you to create tests as you can mock functions that accept an interface type with a type that implements methods for testing.

vim-go has support for the tool impl. impl generates method stubs that implement a given interface. Let us change main.go's content to the following:

package main

import "fmt"

type T struct{}

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

Put your cursor on top of T and type :GoImpl. You'll be prompted to write an interface. Type io.ReadWriteCloser and hit enter. You'll see the content changed to:

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")
}

That's really neat as you see. You can also just type :GoImpl io.ReadWriteCloser when you're on top of a type and it'll do the same.

But you don't need to put your cursor on top of a type. You can invoke it from everywhere. For example execute this:

:GoImpl b *B fmt.Stringer

You'll see the following will be created:

func (b *B) String() string {
	panic("not implemented")
}

As you see this is very helpful, especially if you have a large interface with large method set. You can easily generate it, and because it uses panic() this compiles without any problem. Just fill the necessary parts and you're done.

Share it

vim-go has also features to easily share your code with other via https://play.golang.org/. As you know the Go playground is a perfect place to share small snippets, exercises and/or tips & tricks. There are times you are playing with an idea and want to share with others. You copy the code and visit play.golang.org and then paste it. vim-go makes all these better with the :GoPlay command.

First let us change our main.go file with the following simple code:

package main

import "fmt"

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

Now call :GoPlay and hit enter. You'll see that vim-go automatically uploaded your source code :GoPlay and also opened a browser tab that shows it. But there is more. The snippet link is automatically copied to your clipboard as well. Just paste the link to somewhere. You'll see the link is the same as what's on play.golang.org.

:GoPlay also accepts a range. You can select a piece of code and call :GoPlay. It'll only upload the selected part.

There are two settings to tweak the behavior of :GoPlay. If you don't like that vim-go opens a browser tab for you, you can disable it with:

let g:go_play_open_browser = 0

Secondly, if your browser is misdetected (we're using open or xdg-open) you can manually set the browser via:

let g:go_play_browser_command = "chrome"

HTML template

By default syntax highlighting for Go HTML template is enabled for .tmpl files. If you want to enable it for another filetype add the following setting to your .vimrc:

au BufRead,BufNewFile *.gohtml set filetype=gohtmltmpl

Donation

This tutorial was created by me in my spare time. If you like it and would like to donate, you now you can be a fully supporter by being a patron!

By being a patron, you are enabling vim-go to grow and mature, helping me to invest in bug fixes, new documentation, and improving both current and future features. It's completely optional and is just a direct way to support vim-go's ongoing development. Thanks!

https://www.patreon.com/fatih

TODO Commands

  • :GoPath
  • :AsmFmt