10 min read

On vim-go

vim-go is a wonderful tool that makes working with Go tooling a breeze. Though I’ve been a long time user, I only recently finished reading the entire vim-go-tutorial, and I’d thought I’d capture some of the functionality that I have found either most useful or most used (well, both, actually).



GoBuild

This does exactly what you think. Simply call :GoBuild. Under the covers, it does the following (from the docs):

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

The tutorial suggests to use a nice Vim function. I’ve copied it to my vim.functions file and have it mapped in my ftplugin/go.mod file (see below):

function! 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

Depending on the name of the file, this will either compile the test cases or build the program.

See Build it in the vim-go-tutorial.

GoCoverage

This calls go test -coverprofile tempfile under the hood. It will visually change the syntax of the source code to reflect which code paths have coverage. Very cool.

Call :GoCoverageClear to clear the syntax, or better yet, use :GoCoverageToggle.

Want to load the results in the browser? Of course, you do. Call :GoCoverageBrowser and vim-go will open a browser window the the results.

See Cover it in the vim-go-tutorial.

GoDef

Easily jump to declarations by putting the cursor over the symbol and calling :GoDef (and, use the shortcut gd or Ctrl-]). This will create a stack that is unwound by the shortcut Ctrl-t.

Note that the stack consists only of the declarations that you’ve jumped to, not the other cursor positions that you’ve navigated to in the course of exploring the codebase. So, when unwinding the stack with Ctrl-t, each item that’s popped off the stack is a declaration that you’ve visited, with the interleaving cursor navigation positions ignored (unlike Ctrl-o, which will visit each cursor location - this is often not what you want).

Incidentally, call :GoDefStack to view the stack.

See Go to definition in the vim-go-tutorial.

GoDoc

Simply place the cursor over any identifier and call :GoDoc. Any documentation for that particular symbol will open in a scratch window.

There’s also a handy shortcut, the letter K. This is overrides the normal shortcut which usually opens a man page (C programmers will be familiar with this).

See Documentation lookup in the vim-go-tutorial.

GoFiles

To understand all of the files that make up a particular package, call :GoFiles in any file, and it will list all of the files that make up the package named in the package clause at the top.

Note that it does not include test files.

The result will look something like this:

['/home/btoll/projects/validator/validators/configmap.go', '/home/btoll/projects/validator/validators/deployment.go', '/home/btoll/projects/validator/validators/document.go', '/home/btoll/projects/validator/validators/ingress.go', '/home/btoll/projects/validator/validators/kubeclient.go', '/home/btoll/projects/validator/validators/service.go']

See Dependencies and files in the vim-go-tutorial.

GoImpl

GoImpl uses the tool impl.

:GoImpl will dynamically add methods to a struct type that will satisfy a given interface.

For example:

package main

type Animal interface {
	Talk(string)
	Walk() error
}

type A struct{}

Put the cursor on the A of the struct type and enter the following in ed:

:GoImpl Animal

This will create the following methods:

func (a *A) Talk(_ string) {
	panic("not implemented") // TODO: Implement
}

func (a *A) Walk() error {
	panic("not implemented") // TODO: Implement
}

Or, put the cursor on the A and enter:

:GoImpl

Followed by:

vim-go: generating method stubs for interface: Animal

Lastly, you could put your cursor anywhere, it doesn’t have to on top of the A:

:GoImpl a *A Animal

The results are all the same.

Note that it’s possible to do this for any interface type, including ones from the standard library, third-party packages or your own code.

See Method stubs implementing an interface in the vim-go-tutorial.

GoImplements

This is one of my favorites, and I use it heavily. Want to know which interfaces type implements? Wonder no more!

Using the example from GoImpl, we can place the cursor over any A and type:

:GoImplements

The quickfix list will open with the following:

1   main.go|5 col 6| type Animal interface {

That was easy, and we already knew that.

Let’s add the following methods:

func (a *A) String() string {
	panic("string")
}

func (a *A) Write(b []byte) (int, error) {
	panic("write")
}

Now, if we place the cursor over any A and type :GoImplements we get:

1   /home/btoll/projects/go/interfaces/impl/main.go|5 col 6| type Animal interface {
  1 /usr/local/go/src/fmt/print.go|63 col 6| type Stringer interface {
  2 /usr/local/go/src/internal/bisect/bisect.go|473 col 6| type Writer interface {
  3 /usr/local/go/src/io/io.go|99 col 6| type Writer interface {
  4 /usr/local/go/src/runtime/error.go|210 col 6| type stringer interface {

That’s pretty darn cool.

Wondering about the odd numbering in my quickfix list? That’s the relativenumber setting (from .vimrc):

" Set the number of lines to jump forwards or backwards relative to the current cursor position.
set relativenumber

GoInfo

This is great to quickly see the function signature (prototype). Lots of times it is incredibly handy to get a quick snapshot of the inputs and outputs of a function or method.

For instance, if you’re writing code that calls the fmt.Fprintf method, you can quickly see its signature by calling :GoInfo with the cursor over Fprintf:

vim-go: func Fprintf(w io.Writer, format string, a ...any) (n int, err error)

See Identifier resolution in the vim-go-tutorial.

GoMetaLinter

The GoMetaLinter function calls the gometalinter program. It is a tool that calls go vet, golint and errcheck by default. In addition, it can be configured to run these checkers anytime a file is saved.

For example:

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

Moreover, the checkers that are automatically called when a file is saved can be different from those that are called when :GoMetaLinter is called explicitly. This can be nice when a full run of the checkers isn’t wanted anytime the file is saved.

Check this out:

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

This says to call vet and golint, but not errcheck, when the file is saved (but run all three when it’s called explicity, see the variable setting directly above).

See Check it in the vim-go-tutorial.

GoPlay

The GoPlay command is great when you want to quickly share code on the Internet. Open a file, and enter the following command anywhere in the file:

:GoPlay

This will open the Go Playground in a browser tab with your file as the contents. It will also copy the URL to your clipboard. This also works for ranges.

Weeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

See Share it in the vim-go-tutorial.

GoRename

Everyone knows that the compiler is your friend when refactoring. vim-go also assists in this, and GoRename will rename all of the symbols in one fell swoop.

This is AST-aware. That means that it will only rename symbols in the tree and not just same-named words. Observe:

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

Now, change name to bar in the Server struct. Put the cursor over either the name field in Server or over name in the line that defines the variable s:

:GoRename bar
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"
}

Note that it only changes the references for the struct and not the name of the name function nor the word name in the comment.

This is taken directly from the vim-go-tutorial in the Rename identifiers section.

GoRun

To only run the current file, call :GoRun % (it calls go run under the hood). To run the entire package, call :GoRun.

No more need to drop to the command line and call:

$ go run .

See Run it in the vim-go-tutorial.

GoSameIds

Another handy tool is GoSameIds which allows you to quickly see all of the same-named identifiers. I’ll use it to see at a glance all of the err variables in a function, for example. Just place the cursor over the identifier and call :GoSameIds.

Easy peasy.

See Identifier highlighting in the vim-go-tutorial.

Call :GoSameIdsClear or :GoSameIdsToggle to clear the highlighting.

GoTest

This calls go test under the hood and can be called either from the test file itself or its brother (i.e., either from main_test.go or main.go).

Any errors will be listed in the quickfix list.

To only test a single function, place the cursor above the function in question and call :GoTestFunc. This is useful for many different occasions, especially when running the entire test suite is time-consuming.

Finally, to make sure that the tests compile, simply call :GoTestCompile, and any errors will be in the quickfix list. Importantly, it doesn’t run the tests themselves.

See Test it in the vim-go-tutorial.

Snippets

I’m not going to cover snippets here, other than to say that vim-go supports them. I use Ultisnips (even though I tend not to use them), which is supported by vim-go, and it provides many builtin examples that are ready to use.

I have many abbreviations in my Vim config files that I use quite often, which are similar to snippets, so perhaps I’ll become a huge fan of them and then subsequently expand this section.

See Snippets in the vim-go-tutorial.

Configs

.vimrc

This is a snippet of my .vim.plugins configuration, just one of the files that is sourced from my .vimrc.

" vim: set ft=vim:

call plug#begin('~/.vim/plugged')

Plug 'fatih/vim-go', { 'do': ':GoInstallBinaries' }
"let g:go_auto_sameids = 1 " Highlight same identifiers in file.
let g:go_auto_type_info = 1 " Automatically show function signature of symbol under cursor.
"set updatetime=800 // Default time to show function signature in status line (800ms).
let g:go_decls_includes = 'func,type'
let g:go_def_mode = 'gopls'
let g:go_fmt_command = 'goimports'
let g:go_fmt_fail_silently = 1 " Don't show errors in quickfix window when file is saved (not working).
let g:go_highlight_types = 1
let g:go_highlight_fields = 1
let g:go_highlight_functions = 1
let g:go_highlight_methods = 1
let g:go_highlight_operators = 1
let g:go_highlight_extra_types = 1
let g:go_highlight_build_constraints = 1
let g:go_highlight_generate_tags = 1
let g:go_info_mode = 'gopls'
let g:go_list_type = 'quickfix' " ONLY have quickfix lists (no location lists).
let g:go_metalinter_autosave = 1 " Calls `GoMetaLinter` when the file is saved.
let g:go_metalinter_autosave_enabled = ['vet', 'golint'] " Don't call the whole list when saving to make it faster.
let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']
let g:go_metalinter_deadline = '5s'
"let g:go_play_browser_command = 'chrome' " When xdg-open misdetecting the browser.
let g:go_play_open_browser = 1
let g:go_test_timeout = '10s'
let g:go_textobj_include_function_doc = 1
let g:go_version_warning = 0

call plug#end()

If you are curious as to why some of my dotfiles are prefaced with dot-, see the stow package. I use it to install all of my dotfiles.

ftplugin/go.mod

Here is a snippet of my ftplugin/go.vim configuration. I’ve only included the bits relevant to vim-go:

nnoremap <leader>b :<C-u>call Build_go_files()<cr>
nnoremap <Leader>co <Plug>(go-coverage-toggle)
nnoremap <Leader>cob <Plug>(go-coverage-browser)
nnoremap <Leader>i <Plug>(go-info)
nnoremap <leader>r :!clear<cr><Plug>(go-run)
nnoremap <leader>t <Plug>(go-test)
nnoremap <leader>tc <Plug>(go-test-compile) " Compiles but does not test!
nnoremap <leader>tf <Plug>(go-test-func) " Only test the function under the cursor.

Summary

There are some commands that I haven’t covered, such as GoImports, GoInstallBinaries, GoLint, et al. The reason for this is because they are called automatically by variable settings that are defined in my configuration (see the .vimrc snippet above).

Just set it and forget it, my child.

References