Skip to main content
This configuration uses Neovim 0.11+ native LSP support with nvim-lspconfig. LSP server configurations can be overridden using files in the after/lsp/ directory.

How LSP configuration works

The LSP system has three layers:
  1. Base configuration - nvim-lspconfig defaults
  2. Global overrides - Settings in lua/plugins/lsp/init.lua
  3. Server-specific overrides - Files in after/lsp/*.lua
Server-specific overrides in after/lsp/ are automatically merged with base configurations. Create a file named after/lsp/servername.lua to customize any LSP server.

Enabled language servers

The following LSP servers are enabled by default:
lua/plugins/lsp/init.lua
vim.lsp.enable({
  "lua_ls",              -- Lua
  "gopls",               -- Go
  "tsgo",                -- TypeScript/JavaScript
  "angularls",           -- Angular
  "biome",               -- Biome (JS/TS/JSON)
  "bashls",              -- Bash/Shell
  "cssls",               -- CSS/SCSS/Less
  "html",                -- HTML
  "jsonls",              -- JSON
  "yamlls",              -- YAML
  "dockerls",            -- Docker
  "clangd",              -- C/C++
  "tailwindcss",         -- Tailwind CSS
  "emmet_language_server", -- Emmet
  "harper_ls",           -- Grammar/spell checking
})

Creating LSP overrides

1

Create override file

Create a file in after/lsp/ matching the server name:
touch after/lsp/lua_ls.lua
2

Add configuration

Return a table with your custom settings:
after/lsp/lua_ls.lua
return {
  settings = {
    Lua = {
      diagnostics = {
        globals = { "vim" },
      },
      workspace = {
        library = { vim.env.VIMRUNTIME },
      },
    },
  },
}
3

Restart Neovim

Restart Neovim for changes to take effect.

Example configurations

Lua language server

Complete configuration for Lua development with Neovim API support:
after/lsp/lua_ls.lua
return {
  settings = {
    Lua = {
      diagnostics = {
        globals = { "vim" },
        disable = { "inject-field", "undefined-field", "missing-fields" },
      },
      runtime = { version = "LuaJIT" },
      workspace = {
        library = { vim.env.VIMRUNTIME },
        checkThirdParty = false,
      },
      telemetry = { enable = false },
    },
  },
}

Go language server (gopls)

Optimized gopls configuration with performance tuning:
after/lsp/gopls.lua
return {
  cmd = { "gopls" },
  settings = {
    gopls = {
      codelenses = {
        generate = false,
        gc_details = false,      -- Very CPU intensive
        test = false,
        tidy = false,
        vendor = false,
        regenerate_cgo = false,
        upgrade_dependency = false,
        run_govulncheck = false, -- Very CPU intensive, run manually
      },
      hints = {
        assignVariableTypes = true,
        compositeLiteralFields = true,
        compositeLiteralTypes = true,
        constantValues = true,
        functionTypeParameters = true,
        parameterNames = true,
        rangeVariableTypes = true,
      },
      analyses = {
        nilness = true,
        unusedparams = true,
        unusedwrite = true,
        ST1003 = false,          -- Disable naming convention checks
        undeclaredname = true,
        fillreturns = true,
        nonewvars = true,
      },
      usePlaceholders = false,
      completeUnimported = true,
      staticcheck = false,       -- Save CPU/memory
      matcher = "Fuzzy",
      diagnosticsDelay = "1000ms",
      symbolMatcher = "fuzzy",
      directoryFilters = {
        "-.git",
        "-.vscode",
        "-.idea",
        "-node_modules",
      },
      semanticTokens = true,
      gofumpt = true,
    },
  },
}
Some gopls analyzers and codelenses are disabled for performance reasons. Enable them selectively based on your needs.

TypeScript language server (tsgo)

Comprehensive TypeScript configuration with inlay hints and code lens:
after/lsp/tsgo.lua
return {
  capabilities = {
    workspace = {
      didChangeWatchedFiles = {
        dynamicRegistration = true,
      },
    },
  },
  init_options = {
    maxTsServerMemory = 8192,
  },
  settings = {
    typescript = {
      suggest = {
        autoImports = true,
        includeAutomaticOptionalChainCompletions = true,
        includeCompletionsForImportStatements = true,
        classMemberSnippets = { enabled = true },
        objectLiteralMethodSnippets = { enabled = true },
      },
      preferences = {
        importModuleSpecifierPreference = "shortest",
        importModuleSpecifierEnding = "auto",
        includePackageJsonAutoImports = "auto",
        preferTypeOnlyAutoImports = false,
        quoteStyle = "auto",
        organizeImports = {
          typeOrder = "last",
          caseSensitivity = "auto",
        },
      },
      inlayHints = {
        parameterNames = {
          enabled = "all",
          suppressWhenArgumentMatchesName = false,
        },
        parameterTypes = { enabled = true },
        variableTypes = {
          enabled = true,
          suppressWhenTypeMatchesName = false,
        },
        propertyDeclarationTypes = { enabled = true },
        functionLikeReturnTypes = { enabled = true },
        enumMemberValues = { enabled = true },
      },
      implementationsCodeLens = {
        enabled = true,
        showOnInterfaceMethods = true,
        showOnAllClassMethods = true,
      },
      referencesCodeLens = {
        enabled = true,
        showOnAllFunctions = true,
      },
    },
  },
}

Configuration options

Available override fields

return {
  settings = {
    -- Server-specific settings object
  },
}

Global LSP settings

Global settings apply to all language servers:
lua/plugins/lsp/init.lua
local capabilities = require("blink.cmp").get_lsp_capabilities({
  textDocument = {
    foldingRange = {
      dynamicRegistration = false,
      lineFoldingOnly = true,
    },
  },
})

vim.lsp.config("*", {
  capabilities = capabilities,
  root_markers = { ".git" },
})

Diagnostic configuration

Customize diagnostic display globally:
lua/plugins/lsp/init.lua
vim.diagnostic.config({
  severity_sort = true,
  float = { border = "rounded", source = true },
  underline = true,
  update_in_insert = false,
  signs = {
    text = {
      [vim.diagnostic.severity.ERROR] = "󰅚 ",
      [vim.diagnostic.severity.WARN] = "󰀪 ",
      [vim.diagnostic.severity.INFO] = "󰋽 ",
      [vim.diagnostic.severity.HINT] = "󰌶 ",
    },
  },
  virtual_text = false,  -- Using tiny-diagnostic plugin
})

Inlay hints

Inlay hints are disabled by default but can be toggled with <leader>ih:
lua/plugins/lsp/init.lua
vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(args)
    local bufnr = args.buf
    local client = vim.lsp.get_client_by_id(args.data.client_id)
    
    if client.server_capabilities.inlayHintProvider then
      vim.lsp.inlay_hint.enable(false, { bufnr = bufnr })
    end
  end,
})
Inlay hints can impact performance in large files. They’re disabled by default and can be enabled per-buffer with <leader>ih.

Adding a new language server

1

Install the server

Install the language server globally:
npm install -g typescript-language-server
2

Enable in configuration

Add the server to the enabled list:
lua/plugins/lsp/init.lua
vim.lsp.enable({
  "lua_ls",
  "gopls",
  "typescript-language-server",  -- Add your server
  -- ... other servers
})
3

Create override file (optional)

Create after/lsp/typescript-language-server.lua if you need custom settings.

Troubleshooting

Check LSP status

:LspInfo

View logs

:lua vim.cmd.edit(vim.lsp.get_log_path())

Restart LSP server

:LspRestart

Check which servers are attached

:lua print(vim.inspect(vim.lsp.get_clients()))

Build docs developers (and LLMs) love