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.
[vim-go](https://github.com/fatih/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.
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 noexpandtabau FileType go set shiftwidth=4au FileType go set softtabstop=4au FileType go set tabstop=4
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 = 1let g:go_highlight_extra_types = 1let g:go_highlight_fields = 1let g:go_highlight_functions = 1let g:go_highlight_methods = 1let g:go_highlight_operators = 1let g:go_highlight_structs = 1let 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
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.
I’m using [ale](https://github.com/w0rp/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](https://github.com/vim-airline/vim-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.
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 methodThe 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](https://github.com/ctrlpvim/ctrlp.vim)
installed along with vim-go
.
I have added a key mapping to ,gt
for this:
au FileType go nmap <leader>gt :GoDeclsDir<cr>
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.
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
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 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
.
I’m — just like the neovim community in general it seems — using [deoplete](https://github.com/Shougo/deoplete.nvim)
to power all my completion needs for all languages. To make it run with Go you also need to install [deoplete-go](https://github.com/zchee/deoplete-go)
.
To enable deoplete
by default, add this to your configuration:
if has('nvim')" Enable deoplete on startuplet g:deoplete#enable_at_startup = 1endif
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](https://github.com/terryma/vim-multiple-cursors)
). To prevent that, disable the completion when in multi cursor mode:
" Disable deoplete when in multi cursor modefunction! Multiple_cursors_before()let b:deoplete_disable_auto_complete = 1endfunction
function! Multiple_cursors_after()let b:deoplete_disable_auto_complete = 0endfunction
For those who are working on JSON API’s it’s often cumbersome to define struct
s 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 jInvoicePaymentMethods []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
:
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](https://github.com/Shougo/neosnippet.vim)
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](https://github.com/Shougo/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](https://github.com/sebdah/dotfiles/blob/master/config/nvim/snippets/go.snip#L22)
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 patternI’d really advice you to take some time and learn to use snippets, it’s truly a time saver.
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](https://github.com/derekparker/delve/)
on your machine. Refer to their installation documentation for details.
The other thing you’d need is my package [vim-delve](https://github.com/sebdah/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.
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](https://github.com/sebdah/dotfiles)
(and my [init.vim](https://github.com/sebdah/dotfiles/blob/master/config/nvim/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
.