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()
|   |-- get_server_configs()
|   |-- update_server_configs(server_configs)
|   |-- enable_servers(profile_config.get_enabled_lsp_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

LspAttach autocommand#

lsp.lua#
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.

lsp.lua#
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#

lsp.lua#
195    opts = function()
196      return {
197        install_root_dir = require("vvn.profile_config").get_mason_install_root_dir(),
198        ui = {
199          icons = {
200            server_installed = "✓",
201            server_pending = "➜",
202            server_uninstalled = "✗",
203          },
204        },
205      }
206    end,

The Mason plugin reads its install root from vvn.profile_config through get_mason_install_root_dir().

Relevant changelogs#