LSP Setup
Neovim has a built-in LSP client. Language servers (like pyright, lua_ls, tsserver) provide code intelligence: autocompletion, go-to-definition, diagnostics, and formatting.
Core Idea
The LSP client is built into Neovim. You need to install the server binaries and tell Neovim how to start them. mason.nvim automates server installation; nvim-lspconfig provides the startup configurations.
LSP Architecture
flowchart LR
NVIM[Neovim LSP Client] <-->|LSP Protocol| SERVER[Language Server\ne.g. pyright, lua_ls]
SERVER --> ANALYSIS[Code Analysis]
ANALYSIS --> DIAG[Diagnostics]
ANALYSIS --> COMP[Completions]
ANALYSIS --> DEF[Definitions]
ANALYSIS --> REF[References]
ANALYSIS --> FMT[Formatting]
Required Plugins
lua/plugins/lsp.lua
return {
-- LSP configurations
{
"neovim/nvim-lspconfig",
event = { "BufReadPre", "BufNewFile" },
dependencies = {
-- Mason: server installer
{ "williamboman/mason.nvim", config = true },
-- Mason + lspconfig bridge
"williamboman/mason-lspconfig.nvim",
-- neodev: better Lua/Neovim completions
{ "folke/neodev.nvim", opts = {} },
},
config = function()
require("plugins.lsp.config")
end,
},
}
The LSP Config Module
lua/plugins/lsp/config.lua
local lspconfig = require("lspconfig")
local mason_lspconfig = require("mason-lspconfig")
-- ─── Shared on_attach ─────────────────────────────────────────────────────
local on_attach = function(client, bufnr)
local map = function(lhs, rhs, desc)
vim.keymap.set("n", lhs, rhs, { buffer = bufnr, desc = desc })
end
-- Navigation
map("gd", vim.lsp.buf.definition, "Go to definition")
map("gD", vim.lsp.buf.declaration, "Go to declaration")
map("gi", vim.lsp.buf.implementation, "Go to implementation")
map("gt", vim.lsp.buf.type_definition, "Go to type definition")
map("gr", vim.lsp.buf.references, "List references")
-- Hover and docs
map("K", vim.lsp.buf.hover, "Hover documentation")
map("<C-k>", vim.lsp.buf.signature_help, "Signature help")
-- Diagnostics
map("[d", vim.diagnostic.goto_prev, "Prev diagnostic")
map("]d", vim.diagnostic.goto_next, "Next diagnostic")
map("<leader>dl", vim.diagnostic.open_float, "Line diagnostics")
map("<leader>dL", "<cmd>Telescope diagnostics<cr>", "All diagnostics")
-- Code actions
map("<leader>la", vim.lsp.buf.code_action, "Code action")
map("<leader>lR", vim.lsp.buf.rename, "Rename symbol")
map("<leader>lf", function()
vim.lsp.buf.format({ async = true })
end, "Format file")
-- Workspace
map("<leader>lwa", vim.lsp.buf.add_workspace_folder, "Add workspace folder")
map("<leader>lwl", function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end, "List workspace folders")
-- Inlay hints (Neovim 0.10+)
if client.supports_method("textDocument/inlayHint") then
vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
end
end
-- ─── Shared capabilities (for nvim-cmp integration) ───────────────────────
local capabilities = vim.lsp.protocol.make_client_capabilities()
-- Add nvim-cmp capabilities if available
local ok, cmp_lsp = pcall(require, "cmp_nvim_lsp")
if ok then
capabilities = cmp_lsp.default_capabilities(capabilities)
end
-- ─── Mason server installation ────────────────────────────────────────────
require("mason").setup({ ui = { border = "rounded" } })
mason_lspconfig.setup({
ensure_installed = {
"lua_ls", -- Lua
"pyright", -- Python
"ts_ls", -- TypeScript/JavaScript
"intelephense", -- PHP
"bashls", -- Bash
"jsonls", -- JSON
"yamlls", -- YAML
"marksman", -- Markdown
},
automatic_installation = true,
})
-- ─── Server Configs ───────────────────────────────────────────────────────
local default_config = { on_attach = on_attach, capabilities = capabilities }
-- Lua
lspconfig.lua_ls.setup(vim.tbl_deep_extend("force", default_config, {
settings = {
Lua = {
runtime = { version = "LuaJIT" },
diagnostics = { globals = { "vim" } },
workspace = {
library = vim.api.nvim_get_runtime_file("", true),
checkThirdParty = false,
},
telemetry = { enable = false },
},
},
}))
-- Python
lspconfig.pyright.setup(vim.tbl_deep_extend("force", default_config, {
settings = {
python = {
analysis = {
typeCheckingMode = "basic",
autoSearchPaths = true,
},
},
},
}))
-- TypeScript/JavaScript
lspconfig.ts_ls.setup(default_config)
-- PHP
lspconfig.intelephense.setup(default_config)
-- Bash
lspconfig.bashls.setup(default_config)
-- JSON
lspconfig.jsonls.setup(default_config)
-- YAML
lspconfig.yamlls.setup(default_config)
-- Markdown
lspconfig.marksman.setup(default_config)
-- ─── Diagnostics display ──────────────────────────────────────────────────
vim.diagnostic.config({
virtual_text = {
prefix = "●",
source = "if_many",
},
float = {
border = "rounded",
source = "always",
},
signs = true,
underline = true,
update_in_insert = false,
severity_sort = true,
})
-- Diagnostic signs
local signs = { Error = " ", Warn = " ", Hint = " ", Info = " " }
for type, icon in pairs(signs) do
local hl = "DiagnosticSign" .. type
vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" })
end
Installing Language Servers via Mason
:Mason → open Mason UI
:MasonInstall lua_ls pyright
:MasonUninstall pyright
:MasonUpdate → update all installed servers
Checking LSP Status
:LspInfo → show attached LSP clients
:LspStart → manually start LSP
:LspStop → stop LSP
:LspRestart → restart LSP
:checkhealth lsp → LSP health check