Advance usage
Configuring language servers
Here's an example configuration showing the functions you have available to configure and install LSP servers.
-- reserve space for diagnostic icons
vim.opt.signcolumn = 'yes'
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
-- make sure this servers are installed
-- see :help lsp-zero.ensure_installed()
lsp.ensure_installed({
'rust_analyzer',
'tsserver',
'eslint',
})
-- don't initialize this language server
-- we will use rust-tools to setup rust_analyzer
lsp.skip_server_setup({'rust_analyzer'})
-- the function below will be executed whenever
-- a language server is attached to a buffer
lsp.on_attach(function(client, bufnr)
print('Greetings from on_attach')
end)
-- pass arguments to a language server
-- see :help lsp-zero.configure()
lsp.configure('tsserver', {
on_attach = function(client, bufnr)
print('hello tsserver')
end,
settings = {
completions = {
completeFunctionCalls = true
}
}
})
-- share configuration between multiple servers
-- see :help lsp-zero.setup_servers()
lsp.setup_servers({
'eslint',
'angularls',
opts = {
single_file_support = false,
on_attach = function(client, bufnr)
print("I'm doing web dev")
end
}
})
-- configure lua language server for neovim
-- see :help lsp-zero.nvim_workspace()
lsp.nvim_workspace()
lsp.setup()
-- initialize rust_analyzer with rust-tools
-- see :help lsp-zero.build_options()
local rust_lsp = lsp.build_options('rust_analyzer', {
single_file_support = false,
on_attach = function(client, bufnr)
print('hello rust-tools')
end
})
require('rust-tools').setup({server = rust_lsp})
Setup LSP keybindings in vimscript
The easiest way I can think of is using a global function. Somewhere in your config you declare a function with your keybindings.
function! LspAttached() abort
nnoremap <buffer> <leader>r <cmd>lua vim.lsp.buf.rename()<cr>
nnoremap <buffer> <leader>k <cmd>lua vim.lsp.buf.signature_help()<CR>
" and many more ...
endfunction
Next you call that function when the LSP server is attached to a buffer.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = false, -- (optional) Disable default keybindings
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.on_attach(function(client, bufnr)
vim.call('LspAttached')
end)
lsp.setup()
Can I use that one language server I have installed globally?
Yes, call the function .configure() and set the option force_setup
to true
.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.configure('dartls', {
force_setup = true,
on_attach = function()
print('hello dartls')
end,
})
lsp.setup()
Configure errors messages
You are looking for "diagnostics". If you want to configure them use the function vim.diagnostic.config, and make sure to call it after lsp-zero's setup function.
Here is an example that enables virtual text.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup()
vim.diagnostic.config({
virtual_text = true,
})
Buffer formats twice
This can happen because the built-in function for formatting (vim.lsp.buf.format()) uses every server with "formatting capabilities" enabled.
You can disable an LSP server formatting capabilities like this:
lsp.configure("tsserver", {
on_init = function(client)
client.server_capabilities.documentFormattingProvider = false
client.server_capabilities.documentFormattingRangeProvider = false
end
})
Or if you have a custom lsp.on_attach
:
lsp.on_attach(function(client, bufnr)
-- Disable LSP server formatting, to prevent formatting twice.
-- Once by the LSP server, second time by NULL-ls.
if client.name == "volar" or client.name == "tsserver" then
client.server_capabilities.documentFormattingProvider = false
client.server_capabilities.documentFormattingRangeProvider = false
end
end)
Customizing nvim-cmp
Using .setup_nvim_cmp() will allow you to override some options of nvim-cmp. Here's a few useful things you can do.
Don't preselect first match
You want to modify completion.completeopt
. For this to work write all the defaults and then add noselect
. Then make sure "preselect mode" is set to none
. Like this.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup_nvim_cmp({
preselect = 'none',
completion = {
completeopt = 'menu,menuone,noinsert,noselect'
},
})
lsp.setup()
In theory, you should use preselect = require('cmp').PreselectMode.None
. But for now is the same as 'none'
.
Setting up sources
Using the sources
option you can specify the priority of each source by changing the order. You could also include new ones. Check out nvim-cmp's documentation to know what are the possibilities.
Here is an example that recreates the default configuration for sources.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup_nvim_cmp({
sources = {
{name = 'path'},
{name = 'nvim_lsp'},
{name = 'buffer', keyword_length = 3},
{name = 'luasnip', keyword_length = 2},
}
})
-- still, setup must be the last function
lsp.setup()
Change the look
This you do with the formatting
option. It is kind of a complex topic because it requires some knowledge about nvim-cmp
and lua. Again, you should check out nvim-cmp
docs.
Anyway, here is an example changing the names of the sources with some icons.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup_nvim_cmp({
formatting = {
-- changing the order of fields so the icon is the first
fields = {'menu', 'abbr', 'kind'},
-- here is where the change happens
format = function(entry, item)
local menu_icon = {
nvim_lsp = 'λ',
luasnip = '⋗',
buffer = 'Ω',
path = '🖫',
nvim_lua = 'Π',
}
item.menu = menu_icon[entry.source.name]
return item
end,
},
})
lsp.setup()
Documentation window
We can change that too. There's the documentation
option. Is the same as nvim-cmp's window.documentation
option. And these are the defaults.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup_nvim_cmp({
documentation = {
max_height = 15,
max_width = 60,
border = 'rounded',
col_offset = 0,
side_padding = 1,
winhighlight = 'Normal:Normal,FloatBorder:Normal,CursorLine:Visual,Search:None',
zindex = 1001
}
})
lsp.setup()
You could also disable it if you set it to false
.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup_nvim_cmp({
documentation = false
})
lsp.setup()
Changing the keybindings
The option you want is mapping
. The trickiest. Here you are going to find yourself in an all or nothing situation, if you choose to use it then you are in charge of all mappings, all the defaults will disappear. But don't worry, you can access those defaults with the function lsp.defaults.cmp_mappings().
Here is an example that adds <C-Space>
to trigger completion and makes <C-e>
cancel the completion instead of toggling.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
local cmp = require('cmp')
lsp.setup_nvim_cmp({
mapping = lsp.defaults.cmp_mappings({
['<C-Space>'] = cmp.mapping.complete(),
['<C-e>'] = cmp.mapping.abort(),
})
})
lsp.setup()
I just want to use vim default keybindings for autocomplete
You can use the preset that comes with nvim-cmp.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
local cmp = require('cmp')
lsp.setup_nvim_cmp({
mapping = cmp.mapping.preset.insert({
['<C-Space>'] = cmp.mapping.complete(),
['<C-b>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
})
})
lsp.setup()
What about the navigating through snippets placeholder? That's not a part vim's default, I don't know what those should be. But here, I suggest these:
-- go to next placeholder in the snippet
['<C-g>'] = cmp.mapping(function(fallback)
if luasnip.jumpable(1) then
luasnip.jump(1)
else
fallback()
end
end, {'i', 's'}),
-- go to previous placeholder in the snippet
['<C-d>'] = cmp.mapping(function(fallback)
if luasnip.jumpable(-1) then
luasnip.jump(-1)
else
fallback()
end
end, {'i', 's'}),
"Unmap" a default keybinding
You can disable any default keymap by overriding the mapping
property in nvim-cmp
. Use lsp.defaults.cmp_mappings() to expose the default keybindings then "delete" the one you want. Let's make an example with Tab
.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
local cmp = require('cmp')
lsp.setup_nvim_cmp({
mapping = lsp.defaults.cmp_mappings({
['<C-Space>'] = cmp.mapping.complete(),
['<Tab>'] = vim.NIL,
['<S-Tab>'] = vim.NIL,
})
})
lsp.setup()
Adding a source
You can extend the sources by overriding the sources
property. Use lsp.defaults.cmp_sources() to expose the default sources and then insert the new source.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
local cmp_sources = lsp.defaults.cmp_sources()
table.insert(cmp_sources, {name = 'name-of-new-source'})
lsp.setup_nvim_cmp({
sources = cmp_sources
})
lsp.setup()
Invoke completion menu manually
Not a fan of constant completion suggestions? Don't worry there is a way to invoke the completion only demand. If you set completion.autocomplete
to false
, the menu will only show up when you press tab
or ctrl + e
.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup_nvim_cmp({
completion = {autocomplete = false}
})
lsp.setup()
The current api is not enough?
Welp, that's interesting. Maybe this is a good time to setup nvim-cmp
yourself. Disable the setting manage_nvim_cmp
and then use the function lsp.defaults.cmp_config() to extend or change the default configuration table.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = false,
suggest_lsp_servers = false,
})
lsp.setup()
vim.opt.completeopt = {'menu', 'menuone', 'noselect'}
local cmp = require('cmp')
local cmp_config = lsp.defaults.cmp_config({
window = {
completion = cmp.config.window.bordered()
}
})
cmp.setup(cmp_config)
Finally, in case no one has told you this today... you should read nvim-cmp's documentation. You are awesome.
Intergrate with null-ls
Standalone null-ls instance
null-ls isn't a real language server, if you want "integrate it" with lsp-zero all you need to do is call their setup function after lsp-zero's config.
The only option that makes sense to share with null-ls is the on_attach
function. Here is an example on how to do it.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup()
local null_ls = require('null-ls')
local null_opts = lsp.build_options('null-ls', {})
null_ls.setup({
on_attach = function(client, bufnr)
null_opts.on_attach(client, bufnr)
---
-- you can add other stuff here....
---
end,
sources = {
-- Replace these with the tools you have installed
null_ls.builtins.formatting.prettier,
null_ls.builtins.diagnostics.eslint,
null_ls.builtins.formatting.stylua,
}
})
Make sure the
build_options
is afterlsp.setup()
. see #60
Format buffer using only null-ls
You can assign a keyboard shortcut using the .format_mapping() function. This will allow you to specify a list of filetypes where you want to format using null-ls.
Here is an example showing a setup focused on lua and javascript. We assign the keymap gq
to format.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.format_mapping('gq', {
format_opts = {
async = false,
timeout_ms = 10000,
},
servers = {
['null-ls'] = {'javascript', 'typescript', 'lua'},
}
})
lsp.setup()
local null_ls = require('null-ls')
local null_opts = lsp.build_options('null-ls', {})
null_ls.setup({
on_attach = function(client, bufnr)
null_opts.on_attach(client, bufnr)
end,
sources = {
--- Replace these with the tools you have installed
null_ls.builtins.formatting.stylua,
null_ls.builtins.formatting.prettier,
}
})
Format on save
This can be almost the same as the previous example, except here we replace the function .format_mapping() with .format_on_save().
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.format_on_save({
format_opts = {
async = false,
timeout_ms = 10000,
},
servers = {
['null-ls'] = {'javascript', 'typescript', 'lua'},
}
})
lsp.setup()
local null_ls = require('null-ls')
local null_opts = lsp.build_options('null-ls', {})
null_ls.setup({
on_attach = function(client, bufnr)
null_opts.on_attach(client, bufnr)
end,
sources = {
--- Replace these with the tools you have installed
null_ls.builtins.formatting.stylua,
null_ls.builtins.formatting.prettier,
}
})
Adding mason-null-ls.nvim
mason-null-ls.nvim can help you install tools compatible with null-ls.
Automatic Install
Ensure the tools you have listed in the sources
option are installed automatically.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup()
local null_ls = require('null-ls')
local null_opts = lsp.build_options('null-ls', {})
null_ls.setup({
on_attach = function(client, bufnr)
null_opts.on_attach(client, bufnr)
---
-- you can add other stuff here....
---
end,
sources = {
-- Replace these with the tools you want to install
null_ls.builtins.formatting.prettier,
null_ls.builtins.diagnostics.eslint,
null_ls.builtins.formatting.stylua,
}
})
-- See mason-null-ls.nvim's documentation for more details:
-- https://github.com/jay-babu/mason-null-ls.nvim#setup
require('mason-null-ls').setup({
ensure_installed = nil,
automatic_installation = true,
automatic_setup = false,
})
Automatic setup
Make null-ls aware of the tools you installed using mason.nvim, and configure them automatically.
local lsp = require('lsp-zero').preset({
name = 'minimal',
set_lsp_keymaps = true,
manage_nvim_cmp = true,
suggest_lsp_servers = false,
})
lsp.setup()
local null_ls = require('null-ls')
local null_opts = lsp.build_options('null-ls', {})
null_ls.setup({
on_attach = function(client, bufnr)
null_opts.on_attach(client, bufnr)
end,
sources = {
-- You can add tools not supported by mason.nvim
}
})
-- See mason-null-ls.nvim's documentation for more details:
-- https://github.com/jay-babu/mason-null-ls.nvim#setup
require('mason-null-ls').setup({
ensure_installed = nil,
automatic_installation = false, -- You can still set this to `true`
automatic_setup = true,
})
-- Required when `automatic_setup` is true
require('mason-null-ls').setup_handlers()