My neovim setup for Go

I’m getting questions quite often about how my neovim is configured for Go development. So in this article I intend to describe just that. In the interest of brevity I’ll not cover the setup in general, but rather the Go parts in particular.

My setup is based on neovim. Most of the things below should work well on vim too, except the debugger and code completion (which can be solved otherwise in vim — but I’m not covering that here).

All of my dotfiles are here on GitHub.

Basic vim-go setup configuration

vim-go is the great product of Fatih Arslan and it’s the de-facto standard for Go developers in (neo)vim. I’m gonna run you through the basic vim-go configuration I’ve made to have a great development environment.

Configure indentation

I’m using tabs and a tab width of 4 for my source files. Here’s the configuration for setting that for all your Go files.

au FileType go set noexpandtab
au FileType go set shiftwidth=4
au FileType go set softtabstop=4
au FileType go set tabstop=4

Code highlighting

I like colors and I believe they are generally very helpful when developing to distinguish between various entities in the code. So I have enabled a lot of the available highlighting options in vim-go.

let g:go_highlight_build_constraints = 1
let g:go_highlight_extra_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_structs = 1
let g:go_highlight_types = 1

This will highlight fields, functions, methods, operators, structs, types and such.

I have also enabled highlighting of variables that are the same. So if I have the cursor over the variable actualData, other uses of that variable within the viewport will be highlighted. See the example below:

To enable it, add:

let g:go_auto_sameids = 1

Auto import dependencies

One of the things I liked about Go when I came from a Python background was the auto import of dependencies. It’s sometimes a little frustrating when the wrong package is imported. But mostly it boosts my productivity. Auto import is a feature of gofmt and you can enable it in vim like this:

let g:go_fmt_command = "goimports"

Now you’ll get the dependencies imported when you save the file.

Linting code

I’m using ale to lint code for all languages I code in. It works pretty well right out of the box, I have made only a few small adjustments to make it look nicer and integrate with airline. ale is asynchronous, so it’s not interfering too much with your coding.

" Error and warning signs.
let g:ale_sign_error = '⤫'
let g:ale_sign_warning = '⚠'
" Enable integration with airline.
let g:airline#extensions#ale#enabled = 1
Warning from ALE for an optimization that can be made in the code.

Navigation

I haven’t customized much when it comes to navigation. But I wanted to highlight a few things that are useful:

  • ]] takes you to the next function or method
  • [[ takes you to the previous function or method

The above two things are handy and comes out of the box. But they are limited to the functions or methods defined in the file you have open. Personally I try to have a minimal number of functions in each file and rather have many files. Then I want to be able to search and easily navigate between the function definitions within the package.

That can be achieved using :GoDeclsDir. For that you have to have ctrlp installed along with vim-go.

I have added a key mapping to ,gt for this:

au FileType go nmap <leader>gt :GoDeclsDir<cr>

Running tests

I’m writing a lot of tests cases and thus working a lot with switching back and forth between the implementation and the test. vim-go provides a command called :GoAlternate, which switches to the test case if you’re in the implementation and vice versa. This is really handy and I use it a lot, so I have added a keybinding for it:

au Filetype go nmap <leader>ga <Plug>(go-alternate-edit)
au Filetype go nmap <leader>gah <Plug>(go-alternate-split)
au Filetype go nmap <leader>gav <Plug>(go-alternate-vertical)

So now I just do ,ga to switch to the test (, is my leader key) or ,gav,gah to open in a vertical / horizontal split.

I have also mapped the :GoTest command to <F10>. Notice though that I’m adding the -short flag in order to only run tests in short mode in my editor. This would typically by-pass tests that has a third party dependency like a database or so.

au FileType go nmap <F10> :GoTest -short<cr>

Finally what I use quite much is code coverage reports, both for my own development and for code reviews, to identify what parts of the code has not been properly covered by tests. I have that mapped to <F9>, right next to the regular tests.

au FileType go nmap <F9> :GoCoverageToggle -short<cr>

The result of a coverage report could look something like this.

The implementation is on the left hand side and the tests on the right hand side

Pressing <F9> again will remove the green and red coloring.

Working with types and definitions

Show type information in status line

One of the neat little things I use all the time is to move the cursor over a variable or such to see what type it is. Or move it to a function call and see it’s input parameters and return values.

actualData is a byte slice in this case

It’s really one of these basic sanitary features that should be enabled. Enable it with:

let g:go_auto_type_info = 1

Get the documentation

I often find my self needing more details about certain functions or so. There is a built-in support which works great out of the box in vim-go. Simply press K when over a type or function to get more details.

Go to definition

Go to definition is something I do a whole lot, so therefor I’ve added a mapping for that on <F12>.

au FileType go nmap <F12> <Plug>(go-def)

It takes me to the definition of whatever I have under my cursor. When I need to get back I press C-t.

Code completion

I’m — just like the neovim community in general it seems — using deoplete to power all my completion needs for all languages. To make it run with Go you also need to install deoplete-go.

To enable deoplete by default, add this to your configuration:

if has('nvim')
" Enable deoplete on startup
let g:deoplete#enable_at_startup = 1
endif

It will look something like this in action:

Also, you will find that deoplete crashes a whole lot if you are also using multiple cursors (terryma/vim-multiple-cursors). To prevent that, disable the completion when in multi cursor mode:

" Disable deoplete when in multi cursor mode
function! Multiple_cursors_before()
let b:deoplete_disable_auto_complete = 1
endfunction
function! Multiple_cursors_after()
let b:deoplete_disable_auto_complete = 0
endfunction

Adding JSON tags to structs

For those who are working on JSON API’s it’s often cumbersome to define structs and manually type all the JSON tags as well. vim-go is here to the rescue! If you have a struct like this:

type jCreateInvoiceResponse struct {
Invoice jInvoice
PaymentMethods []jPaymentMethod
}

You’d want to convert it to

type jCreateInvoiceResponse struct {
Invoice jInvoice `json:"invoice"`
PaymentMethods []jPaymentMethod `json:"payment_methods"`
}

To accomplish that, run :GoAddTags. My APIs usually want snakecase properties, but it also supports camelcase. Set your preference using

let g:go_addtags_transform = "snakecase"

Here’s the feature in action for the above struct:

Snippet driven development

For a long time I did not use snippets when coding. Largely because I didn’t understand how useful they can be. But at the end of the day you’re repeating patterns in your code over and over. For these patterns, it’s really efficient to have snippets that fills out the generic parts.

I’m using neosnippet for my development. It’s basic and foremost, it’s easy to write your own snippets. The gist of it is that you write a keyword and then press C-k, to “expand” the snippet. There might be multiple places for inserting your code. You can move to the next by hitting C-k again.

To enable neosnippet for vim-go, add this line:

let g:go_snippet_engine = "neosnippet"

You need to install neosnippet separately from vim-go. And you should also make sure to install neosnippet-snippets, which provides you with a set of default snippets for a long list of languages.

Here are a couple of the most powerful snippets I use:

  • ts expands to type | struct
  • ti expands to type | interface
  • funcTest adds boilerplate code for a table based unit test. This is my own custom snippet, it’s not part of neosnippet-snippets. You can find it in my dotfiles.
  • ife expands to a if err != nil { | } type pattern

I’d really advice you to take some time and learn to use snippets, it’s truly a time saver.

Debugging Go code

I’m hoping to cover more about how to debug Go applications in a later article. But in this one we’re only gonna touch upon how to dress neovim for success in the field.

Firstly, you’d need to install delve on your machine. Refer to their installation documentation for details.

The other thing you’d need is my package vim-delve which is utilizing some neovim features to integrate with delve. So unfortunately this package does not work on regular vim.

With the dependencies installed you can now start debugging your Go code. You can add breakpoints with :DlvToggleBreakpoint. Then you’d start the debugger with either :DlvDebug or :DlvTest (if you are debugging a non-main package use :DlvTest).

Tracepoints are also supported. They can be toggled in code using :DlvToggleTracepoint. A tracepoint is not stopping your execution, it just prints a note that the tracepoint was hit.

An example of delve in action:

Code to the left with a breakpoint (●) and a tracepoint (◆). Delve output to the right.

The End

There is so much more about vim that I’d love to write about. But at some point I’d probably diverge too much away from to Go subject in this article.

Anyhow, my dotfiles (and my init.vim)holds all details so you could always dig deeper there for more details on my setup.

Update 9/7–2017: Changed the linting section to replace direct use of gometalinter with ale.

Topics of interest

More Related Stories