LSP Configuration in Neovim 0.11
Neovim 0.11 adds a new function to configure language servers:
vim.lsp.config()
and allows splitting configurations in specialised
lua/<name>.lua files. This is nice because I can now move a bunch of code from
ftplugins to a different place. It doesn’t differ a lot from the old approach
with vim.lsp.start()
,
(sidenote: In fact, vim.lsp.config()
is a
wrapper for vim.lsp.start()
which allows us to share, merge and selectively
enable configs)
but it feels right. Yay!
vim.lsp.config("name", {...})
lets us to define LSP configuration. Name can be
anything: name of language server, file type, your favourite fruit… It’s just
a key under which we’ll store a bunch of configuration options, which we’ll
later enable with vim.lsp.enable("name")
.
Neovim first
(sidenote: :h lsp-config
)
reads a global
configuration for all language servers which we configure for a special name:
'*'
. I like to put there on_attach
function which changes a configuration of
buffers for which LSP server runs.
-- init.lua
vim.lsp.config('*', {
on_attach = function(client, bufnr)
-- overwrites omnifunc/tagfunc set by some Python plugins to the
-- default values for LSP
vim.api.nvim_set_option_value('omnifunc', 'v:lua.vim.lsp.omnifunc', {buf = bufnr})
vim.api.nvim_set_option_value('tagfunc', 'v:lua.vim.lsp.tagfunc', {buf = bufnr})
vim.keymap.set({'n', 'v'}, 'K', vim.lsp.buf.hover, { buffer = bufnr })
vim.keymap.set({'n', 'v'}, '<F4>', vim.lsp.buf.format, { buffer = bufnr })
vim.keymap.set('n', 'gu', vim.lsp.buf.references, { buffer = bufnr })
vim.keymap.set('n', 'gr', vim.lsp.buf.rename, { buffer = bufnr })
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
vim.lsp.diagnostic.on_publish_diagnostics, {
signs = true,
underline = true,
virtual_text = true
}
)
end
})
Then Neovim takes configuration tables returned by <runtimepath>/lsp/<name>.lua and merges them all together and then merges them on top of a global configuration from above. It means that options in <name>.lua overwrite global options.
Sometimes it makes sense to extend defaults instead:
-- lua/pylsp.lua
return {
name = "pylsp",
on_attach = function(client, bufnr)
-- extend global configuration
vim.lsp.config['*'].on_attach(client, bufnr)
-- "fix" gq
vim.api.nvim_buf_set_option(bufnr, "formatexpr", "");
end
}
Oh, and if you don’t know what’s “runtimepath”, assume it’s ~/.config/nvim.
(sidenote: :set runtimepath?
shows the full path)
Then Neovim takes configurations set elsewhere and merges them on top of
whatever it has so far. Basically, this means that any explicit uses of
vim.lsp.config()
in init.lua will take precedence.
We may now enable LSP configuration. I like to maintain a mapping of configuration names to executable names an enable them conditionally for servers which exist on the system. This makes my Neovim configuration portable to the systems without installed language servers.
-- init.lua
local lsp_servers = {
pylsp = "pylsp",
godot = "godot",
lua = "lua-language-server",
cpp = "clangd"
}
for server_name, lsp_executable in pairs(lsp_servers) do
if vim.fn.executable(lsp_executable) == 1 then
vim.lsp.enable(server_name)
end
end