Mastodon hachyterm.io

Language Server Support for Go in Vim

Last month I found a reddit thread about how to setup Vim for Go development.

The author uses coc.nvim, a heavy-weight plugin with external dependencies. coc.nvim (“Conqueror of Completion”) offers powerful features, but you’ll need to install Node.js.
If you prefer a more lightweight option that also work with plain Vim, this blog post might be for you.

Today I’m going to share my setup:

  • works with Vim and NeoVim
  • minimal external dependencies (gopls)
  • auto-completion support

Prerequisites

You should have a working installation of Go on your computer.

Install gopls, the official language server:

GO111MODULE=on go get golang.org/x/tools/gopls@latest

Vim Setup

Vim needs a plugin for the Language Server Protocol.

I use prabirshrestha/vim-lsp, an asynchronous implementation that works both in Vim 8 and NeoVim. The plugin uses VimL and thus has no external dependencies.

Install with native package support or a plugin manager of your choice. Example:

cd ~/vim/pack
git submodule init
git submodule add https://github.com/prabirshrestha/vim-lsp.git
git add .gitmodules vim/pack/prabirshrestha/vim-lsp
git commit

(I’m happy with vim-packager, but configuring it requires a bit more effort and time. The native package manager works well since Vim 8, but is a bit clunky.)

Let’s wire up the language server with Go.

Create a new file in your Vim folder (~/vim/plugin/lsp.vim) with the following content:

func! s:setup_ls(...) abort
    let l:servers = lsp#get_allowed_servers()

    # key mappings
    for l:server in l:servers
        let l:cap = lsp#get_server_capabilities(l:server)

        if has_key(l:cap, 'completionProvider')
            setlocal completefunc=lsp#complete
        endif

        if has_key(l:cap, 'hoverProvider')
            setlocal keywordprg=:LspHover
        endif

        if has_key(l:cap, 'codeActionProvider')
            nmap <silent><buffer>ga <plug>(lsp-code-action)
        endif

        if has_key(l:cap, 'definitionProvider')
            nmap <silent><buffer>gd <plug>(lsp-definition)
            nmap <silent><buffer>gk <plug>(lsp-peek-definition)
        endif
    endfor
endfunc

# register language server
augroup LSC
    autocmd!
    autocmd User lsp_setup call lsp#register_server({
                \ 'name': 'gopls',
                \ 'cmd': {_->['gopls']},
                \ 'allowlist': ['go']
                \})

    autocmd User lsp_server_init call <SID>setup_ls()
    autocmd BufEnter * call <SID>setup_ls()
augroup END

# disable diagnostics etc.
let g:lsp_diagnostics_enabled                = 0
let g:lsp_diagnostics_signs_enabled          = 0
let g:lsp_diagnostics_virtual_text_enabled   = 0
let g:lsp_diagnostics_highlights_enabled     = 0
let g:lsp_document_code_action_signs_enabled = 0

The first function creates key bindings if the language server runs. For example, hitting ga will give you “code actions”. The most useful code action is auto-importing the necessary definitions. Code Actions are a feature I use a lot.

You can find a list of supported commands on the vim-lsp GitHub page.

Auto-completion?

Vim already has auto-completion out of the box.

Here is an excerpt from some extra key mappings in my ~/.vimrc (“stolen” from bluz71:

"-----------------------------
" completion mappings
"-----------------------------
"   t     - user defined completion (via completefunc setting)
inoremap <C-t>     <C-x><C-u>

Now, when I want the language server to kick in to complete my Go code, I smash CTRL+T on my keyboard.

How to Format And Lint

The easiest way is to use the gofmt tool from the command-line. For example,

gofmt -w .

The same goes for linting.

Install golint:

go install golang.org/x/lint/golint@latest

Usage:

golint ./...

Alternatively, you can install ALE. Here is an example setup which you can adjust for Go.

Conclusion

I’ve showed you a lightweight setup for Go and Vim. The above plugins and settings have served me well.

There are languages where I’d recommend a dedicated IDE (Java), but for Go I haven’t felt the need yet.