I have been a vim user even since I installed the very first Linux distro in my laptop. Yes, I know how to exit it, that’s not the reason why I am still using it. I started from vim with literally no configuration or pluggins. I hated it, no doubt about it, but after checking the forums and subreddits, comments and replies from veteran vimmers I was was asked to try this one small change, remove all text editors and then use vim. Sure, why not, I went that path and never looked back. It took me 2-3 months to get comfortable with the keyboard and the verb of vim(more about this in the coming blogs). After around 3 months I came know about neovim. It’s been a beautiful journey and I have tried serveral other text editors like emacs, vscode, geany. But still sticking to neovim because it’s a no bullshit and get shit done kinda editor. But one feature that had troubled me for many years and reason why I had to look at other editors was autocompletion. I know vim has a very sane and useful autocompletion as well know as omnicomplete but you have to admit as one point you look at any other text editor that shows you suggestions as you type without breaking a sweat. I have tried ycm, which is honestly a hell to configure and install and too slow for all that effort. I have also tried deoplete.nvim which is well loved and doesn’t get in the way. But I personally like mucomplete which is like the very minimal wrapper on top of omnicomplete. Now after 3 years I came across coc.nvim which is well loved withing the neovim community as well. What is coc.nvim? Why is it different from other options?
Well it’s not different it just happen to work on the very popular principal as that of VSCode, the language server protocol.

What is Language Server Protocol (LSP)?

According to official Page :

The Language Server protocol is used between a tool (the client) and a language smartness provider (the server) to integrate features like auto complete, go to definition, find all references and alike into the tool

But what makes LSP special? I already have x,y,z pluggins.

The idea behind the Language Server Protocol (LSP) is to standardize the protocol for how such servers and development tools communicate. This way, a single Language Server can be re-used in multiple development tools, which in turn can support multiple languages with minimal effort.

LSP is a win for both language providers and tooling vendors!

How did I ended up here?

I have been trying to write useful go code for some time now and since I am not very regular with it, I need some autocompletion and support to get around. In 2018, when I started, vim-go has been the goto tool for most of the developers. It used to rely on gocode for autocompletion. Then when go matured to 1.10 it started breaking. User had to switch to different fork of gocode and then came the go modules, with was powered to break all the autocompletion tools and things so we had to move to another fork of gocode. Finally, go team decided to think about this problem that most of the developers are facing and made a language server for go gopls which define itself as:

gopls (pronounced: “go please”) is the official language server for the Go language.

You can check out the official video which explains this change much better here This made me learn about language servers and why it might be a good solution for my vim autocompletion problem.

How can I set up LSP?

We need a language server client, that can make use of the language servers. For the moment I am using coc.nvim, while there is another minimalist client like LanguageClient-neovim which is written in rust & pretty fast, but dependent on other completion frameworks to work efficiently. You can also have a look at ale which has matured as a check syntax and supports LSP.

But you want to stick to the same things that I have you need few things in place, Like:

  • Neovim ( You can use vim8 as well )
  • I use vim-plug ( You can use any other plug-in manager )
  • yarn installed and in path
  • ~/.config/nvim/init.vim ( this is ~/.vimrc of neovim )

If I forgot something, you can always refer this official wiki for installation.

My Config

This is how minimal init.vim will look like

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

Plug 'fatih/vim-go'
" < Other Plugins, if they exist >

Plug 'neoclide/coc.nvim', {'do': 'yarn install --frozen-lockfile'}

call plug#end()

You can copy this in your init.vim since this is a slightly modified configuration based on coc’s nvim recommendation

" -------------------------------------------------------------------------------------------------
" coc.nvim default settings
" -------------------------------------------------------------------------------------------------

" if hidden is not set, TextEdit might fail.
set hidden
" Better display for messages
set cmdheight=2
" Smaller updatetime for CursorHold & CursorHoldI
set updatetime=300
" don't give |ins-completion-menu| messages.
set shortmess+=c
" always show signcolumns
set signcolumn=yes

" Use tab for trigger completion with characters ahead and navigate.
" Use command ':verbose imap <tab>' to make sure tab is not mapped by other plugin.
inoremap <silent><expr> <TAB>
      \ pumvisible() ? "\<C-n>" :
      \ <SID>check_back_space() ? "\<TAB>" :
      \ coc#refresh()
inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"

function! s:check_back_space() abort
  let col = col('.') - 1
  return !col || getline('.')[col - 1]  =~# '\s'
endfunction

" Use <c-space> to trigger completion.
inoremap <silent><expr> <c-space> coc#refresh()

" Use `[c` and `]c` to navigate diagnostics
nmap <silent> [c <Plug>(coc-diagnostic-prev)
nmap <silent> ]c <Plug>(coc-diagnostic-next)

" Remap keys for gotos
nmap <silent> gd <Plug>(coc-definition)
nmap <silent> gy <Plug>(coc-type-definition)
nmap <silent> gi <Plug>(coc-implementation)
nmap <silent> gr <Plug>(coc-references)

" Use U to show documentation in preview window
nnoremap <silent> U :call <SID>show_documentation()<CR>

" Remap for rename current word
nmap <leader>rn <Plug>(coc-rename)

" Remap for format selected region
vmap <leader>f  <Plug>(coc-format-selected)
nmap <leader>f  <Plug>(coc-format-selected)
" Show all diagnostics
nnoremap <silent> <space>a  :<C-u>CocList diagnostics<cr>
" Manage extensions
nnoremap <silent> <space>e  :<C-u>CocList extensions<cr>
" Show commands
nnoremap <silent> <space>c  :<C-u>CocList commands<cr>
" Find symbol of current document
nnoremap <silent> <space>o  :<C-u>CocList outline<cr>
" Search workspace symbols
nnoremap <silent> <space>s  :<C-u>CocList -I symbols<cr>
" Do default action for next item.
nnoremap <silent> <space>j  :<C-u>CocNext<CR>
" Do default action for previous item.
nnoremap <silent> <space>k  :<C-u>CocPrev<CR>
" Resume latest coc list
nnoremap <silent> <space>p  :<C-u>CocListResume<CR>

You should save the init.vim and exit (Neo)vim. Now you need to do few things

$ vim +PlugInstall
$ vim +CocInfo

If both the above commands worked without any error, your LS client is installed and good to use. You may need to setup few language server configurations, to do that:

$ vim +CocConfig

I have the following config:

{
    "languageserver": {
        "golang": {
            "command": "gopls",
            "rootPatterns": ["go.mod", ".vim/", ".git/", ".hg/"],
            "filetypes": ["go"]
        },
        "clangd": {
            "command": "clangd",
            "args": ["--background-index"],
            "rootPatterns": ["compile_flags.txt", "compile_commands.json", ".vim/", ".git/", ".hg/"],
            "filetypes": ["c", "cpp", "objc", "objcpp"]
        }
    },
    "yaml.schemas": {
        "kubernetes": "/*.yaml"
    }

}

If you are also write golang like me, these small addition in init.vim might be useful for you as well

augroup vimgo_keybinding
    au FileType go nmap <leader>r <Plug>(go-run)
    au FileType go nmap <leader>b <Plug>(go-build)
    au FileType go nmap <C-]> <Plug>(go-def)
    au FileType go nmap <C-k> <Plug>(go-doc-browser)
augroup END
" -------------------------------------------------------------------------------------------------
" vim-go
" -------------------------------------------------------------------------------------------------
let g:go_fmt_command = "gopls"
let g:go_rename_command = "gopls"
let g:go_implements_mode = "gopls"
let g:go_metalinter_command = "gopls"
let g:go_gopls_staticcheck = "gopls"
let g:go_gopls_temp_modfile = 1
let g:go_gopls_complete_unimported = 1
let g:go_autodetect_gopath = 1
let g:go_decls_mode = "fzf"
let g:go_snippet_engine = "ultisnips"
let g:go_list_type = "quickfix"
let g:go_term_mode = "split"
let g:go_term_height = 10
" let g:go_auto_type_info = 1
let g:go_doc_url = 'https://pkg.go.dev'
" this is handled by LanguageClient [LC]
let g:go_def_mapping_enabled = 0

With all these steps you will have a working autocompletion in neovim without any <C-x><C-o> or any extra keys.
Peace.