Integrate with mason.nvim
We can use mason.nvim and mason-lspconfig.nvim to help us manage the installation of language servers. And then we can use lspconfig to setup the servers only when they are installed.
Here is a basic example.
local lsp_zero = require('lsp-zero')
lsp_zero.on_attach(function(client, bufnr)
-- see :help lsp-zero-keybindings
-- to learn the available actions
lsp_zero.default_keymaps({buffer = bufnr})
end)
require('mason').setup({})
require('mason-lspconfig').setup({
-- Replace the language servers listed here
-- with the ones you want to install
ensure_installed = {'tsserver', 'rust_analyzer'},
handlers = {
function(server_name)
require('lspconfig')[server_name].setup({})
end,
},
})
This config will tell mason-lspconfig
to install tsserver and rust_analyzer automatically if they are missing. And lspconfig will handle the configuration of those servers.
The servers listed in the ensure_installed
option must be on this list.
Note that after you install a language server you will need to restart Neovim so the language server can be configured properly.
Configure a language server
If we need to add a custom configuration for a server, you'll need to add a property to handlers
. This new property must have the same name as the language server you want to configure, and you need to assign a function to it.
Lets use an imaginary language server called example_server
as an example.
--- in your own config you should replace
--- `example_server` with the name of a language server
require('mason-lspconfig').setup({
handlers = {
-- this first function is the "default handler"
-- it applies to every language server without a "custom handler"
function(server_name)
require('lspconfig')[server_name].setup({})
end,
-- this is the "custom handler" for `example_server`
example_server = function()
require('lspconfig').example_server.setup({
---
-- in here you can add your own
-- custom configuration
---
})
end,
},
})
Here we use the module lspconfig
to setup the language server and we add our custom config in the first argument of .example_server.setup()
.
Exclude a language server from the automatic setup
If we want to ignore a language server we can use the function .noop() as a handler. This will make mason-lspconfig
ignore the setup for the language server.
--- in your own config you should replace
--- `example_server` with the name of a language server
require('mason-lspconfig').setup({
handlers = {
-- this first function is the "default handler"
-- it applies to every language server without a "custom handler"
function(server_name)
require('lspconfig')[server_name].setup({})
end,
-- this is the "custom handler" for `example_server`
-- noop is an empty function that doesn't do anything
example_server = lsp_zero.noop,
},
})
So example_server = lsp_zero.noop
is the same thing as this.
example_server = function() end
When the time comes for mason-lspconfig
to setup example_server
it will execute an empty function.
The default_setup shortcut
In lsp-zero there is a function called .default_setup(), the purpose of this function is to act as a default handler in mason-lspconfig
's options. Like this.
require('mason-lspconfig').setup({
handlers = {
lsp_zero.default_setup,
},
})
This way you can setup your language server without needing to call lspconfig
yourself.
Why is this not in the other documentation examples?
This used to be the recommended way of to configure lsp-zero. The problem with .default_setup() is not about the code behind it, is about people. When new users ask for help on discord or reddit they don't get support from the community. .default_setup() hides the fact that lspconfig is being called, so new users don't know how to ask the right questions, and other Neovim users often will advice against using lsp-zero. The documentation now shows an explicit setup with lspconfig, my hope is that increases the chances of new users getting the support they need.
You can still use .default_setup() if you want, but you have to know how to ask the right question when you ask for help online.
Behind the scenes
If you want to know, this is what happens behind the scenes when you call .default_setup().
-- just in case: there is no need to copy/paste this example in your own config
-- this snippet exists only for educational purpose.
require('mason-lspconfig').setup({
ensure_installed = {'tsserver', 'rust_analyzer'},
handlers = {
function(name)
local lsp = require('lspconfig')[name]
if lsp.manager then
-- if lsp.manager is defined it means the
-- language server was configured some place else
return
end
-- at this point lsp-zero has already applied
-- the "capabilities" options to lspconfig's defaults.
-- so there is no need to add them here manually.
lsp.setup({})
end,
},
})