Neovim lsp setup#
Basic workflow#
LSP setup is primarily done by 2 methods, as mentioned in the lsp-config docs.
vim.lsp.config("name", "config")vim.lsp.enable("name")
where name is the LSP server name.
When an LSP client starts, it resolves its configuration by merging the following sources (merge semantics) defined by vim.tbl.deep_extend, with force behaviour, in order of increasing priority.
-- Defined in init.lua
vim.lsp.config('*', {
capabilities = {
textDocument = {
semanticTokens = {
multilineTokenSupport = true,
}
}
},
root_markers = { '.git' },
})
-- Defined in <rtp>/lsp/clangd.lua
return {
cmd = { 'clangd' },
root_markers = { '.clangd', 'compile_commands.json' },
filetypes = { 'c', 'cpp' },
}
-- Defined in init.lua
vim.lsp.config('clangd', {
filetypes = { 'c' },
})
The merged result is
{
-- From the clangd configuration in <rtp>/lsp/clangd.lua
cmd = { 'clangd' },
-- From the clangd configuration in <rtp>/lsp/clangd.lua
-- Overrides the "*" configuration in init.lua
root_markers = { '.clangd', 'compile_commands.json' },
-- From the clangd configuration in init.lua
-- Overrides the clangd configuration in <rtp>/lsp/clangd.lua
filetypes = { 'c' },
-- From the "*" configuration in init.lua
capabilities = {
textDocument = {
semanticTokens = {
multilineTokenSupport = true,
}
}
}
}
Runtime flow in this config#
This repository wires LSP setup through lua/plugins/pde/lsp.lua and applies
buffer-local behavior in a single LspAttach callback.
lua/plugins/pde/lsp.lua
|-- lsp_setup()
| |-- enabled_servers = profile_config.get_enabled_lsp_servers()
| |-- get_server_configs()
| |-- restrict_lsp_configs_to_allowlist(enabled_servers)
| |-- update_server_configs(server_configs)
| |-- enable_servers(enabled_servers, server_configs)
| |-- if profile_config.enable_mason_installs()
| | `-- install_mason_packages(profile_config.get_mason_packages())
| |-- setup_lsp_keymaps()
| `-- setup_diagnostic_config()
`-- LspAttach callback
|-- setup_native_buffer_mappings(bufnr)
|-- setup_plugin_buffer_mappings(bufnr)
|-- setup_autocmds(client, bufnr)
`-- setup_clangd_extensions(bufnr) for clangd
Before profile enablement, this config applies a strict allowlist to runtime LSP configs.
138---@param allowed_server_names string[]
139local restrict_lsp_configs_to_allowlist = function(allowed_server_names)
140 ---@type table<string, boolean>
141 local allowed = {}
142
143 for _, name in ipairs(allowed_server_names) do
144 allowed[name] = true
145 end
146
147 for _, name in ipairs(list_runtime_lsp_config_names()) do
148 if not allowed[name] then
149 vim.lsp.config(name, {
150 filetypes = {},
151 })
152 end
153 end
154end
This helper clears filetypes for every runtime config that is not in the
current profile allowlist, so broad :lsp enable and :LspStart flows can
only match servers that this config intentionally enables.
LspAttach autocommand#
31local setup_lsp_keymaps = function()
32 vim.api.nvim_create_autocmd("LspAttach", {
33 group = vim.api.nvim_create_augroup("vvnraman.lsp.config", { clear = true }),
34 callback = function(event)
This registers the LspAttach callback entry point.
50 local client_is = function(name)
51 local client_exists = false
52 if client.server_info and name == client.server_info.name then
53 client_exists = true
54 end
55 if client.name and name == client.name then
56 client_exists = true
57 end
58
59 return client_exists
60 end
61
62 if client_is("clangd") then
63 require("plugins.pde.attach").setup_clangd_extensions(bufnr)
64 end
Inside the callback, the clangd branch applies clangd-specific extensions.
Profile-driven server and Mason policy#
195local lsp_setup = function()
196 local profile_config = require("vvn.profile_config")
197 local enabled_servers = profile_config.get_enabled_lsp_servers()
198
199 local server_configs = get_server_configs()
200 restrict_lsp_configs_to_allowlist(enabled_servers)
201 update_server_configs(server_configs)
202 enable_servers(enabled_servers, server_configs)
203
204 if profile_config.enable_mason_installs() then
205 install_mason_packages(profile_config.get_mason_packages())
The Mason plugin reads its install root from vvn.profile_config through
get_mason_install_root_dir().