Dalius's blog

Friday, October 23, 2020

NeoVim native LSP support (attempt No.1)

UPDATE 2022-08-02: Check “Typescript and Neovim LSP” for my second attempt with Neovim native LSP.

UPDATE 2020-10-24: I have managed to configure LSP and other things for me properly so I have rewritten this article.

Today I have tried to use Neovim native LSP functionality. I will give some time for this configuration so I could see if it is working good for me.

You can read about neovim’s LSP here: https://neovim.io/doc/user/lsp.html

Here is what I have managed to achieve. First you might want to install these plugins:

Plug 'neovim/nvim-lspconfig'
Plug 'nvim-lua/completion-nvim'
Plug 'nvim-lua/diagnostic-nvim'

You can use nvim LSP without these plugins but most probably you want to use them.

Not let’s assume we want to use tsserver. You need to add following lines to your nvim config file:

lua <<EOF

local on_attach = function(_, bufnr)


Read completion-nvim documentation and configure as recommended. Next you can use some mappings, e.g.:

nnoremap <silent> <c-]> <cmd>lua vim.lsp.buf.definition()<CR>
nnoremap <silent> K     <cmd>lua vim.lsp.buf.hover()<CR>
nnoremap <silent> gr    <cmd>lua vim.lsp.buf.references()<CR>
nnoremap <leader>qf     <cmd>lua vim.lsp.buf.code_action()<CR>
nnoremap <leader>rn     <cmd>lua vim.lsp.buf.rename()<CR>
nnoremap <leader>ld     <cmd>lua vim.lsp.util.show_line_diagnostics()<CR>

Follow plugins documentation and configure other things to match your needs. I’m using following config:

inoremap <expr> <Tab>   pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <expr> <C-j>   pumvisible() ? "\<C-n>" : "\<C-j>"
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
inoremap <expr> <C-k>   pumvisible() ? "\<C-p>" : "\<C-k>"

nnoremap <silent> <leader>aj :PrevDiagnosticCycle<CR>
nnoremap <silent> <leader>ak :NextDiagnosticCycle<CR>

" Set completeopt to have a better completion experience
set completeopt=menuone,noinsert,noselect

" Avoid showing message extra message when using completion
set shortmess+=c

let g:diagnostic_enable_virtual_text = 1
let g:diagnostic_virtual_text_prefix = ' '
let g:diagnostic_insert_delay = 1

call sign_define("LspDiagnosticsErrorSign", {"text" : "➤", "texthl" : "LspDiagnosticsError"})
call sign_define("LspDiagnosticsWarningSign", {"text" : "➤", "texthl" : "LspDiagnosticsWarning"})
call sign_define("LspDiagnosticsInformationSign", {"text" : "↬", "texthl" : "LspDiagnosticsInformation"})
call sign_define("LspDiagnosticsHintSign", {"text" : "↬", "texthl" : "LspDiagnosticsHint"})

This is already much more lines than CoC.

For linting instead of coc-eslint I have returned Ale to my configuration. However I have disabled tsserver in Ale config by stating explicitly that only eslint would be used:

let g:ale_linters = {
\   'javascript': ['eslint'],
\   'javascript.jsx': ['eslint'],
\   'typescript': ['eslint'],
\   'typescriptreact': ['eslint'],

I have done this to avoid tsserver being started both by LSP and ALE and running diagnostics twice. The only problem here is that eslint and LSP diagnostics are not unified into single source of true.

coc-explorer was replaced with fern.vim.

The only remaining coc plugins that do no have replacement are coc-emoji and coc-word. So overall it feels like a slight downgrade but let’s see how usage experience will feel.