-- nvim/.config/nvim/lua/lsp.lua
-- LSP setup for the languages I actually work in: rust, go, python, lua.
-- Anything else uses the defaults from nvim-lspconfig without my hands on it.
-- I swapped null-ls for conform.nvim + nvim-lint in commit ae44b17.
local lspconfig = require("lspconfig")
local ok_cmp, cmp_lsp = pcall(require, "cmp_nvim_lsp")
local capabilities = vim.lsp.protocol.make_client_capabilities()
if ok_cmp then
capabilities = cmp_lsp.default_capabilities(capabilities)
end
capabilities.textDocument.completion.completionItem.snippetSupport = true
-- Shared on_attach: per-buffer keymaps + a gentle format-on-save hook.
local augroup_fmt = vim.api.nvim_create_augroup("user_lsp_fmt", { clear = true })
local function on_attach(client, bufnr)
local bmap = function(mode, lhs, rhs, desc)
vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, silent = true, desc = desc })
end
bmap("n", "gd", vim.lsp.buf.definition, "goto definition")
bmap("n", "gD", vim.lsp.buf.declaration, "goto declaration")
bmap("n", "gi", vim.lsp.buf.implementation, "goto implementation")
bmap("n", "gr", vim.lsp.buf.references, "references")
bmap("n", "gt", vim.lsp.buf.type_definition, "type definition")
bmap("n", "K", vim.lsp.buf.hover, "hover")
bmap("n", "<leader>rn", vim.lsp.buf.rename, "rename symbol")
bmap({ "n", "v" }, "<leader>ca", vim.lsp.buf.code_action, "code action")
bmap("n", "<leader>f", function() vim.lsp.buf.format({ async = true }) end, "format buffer")
bmap("i", "<C-s>", vim.lsp.buf.signature_help, "signature help")
if client.supports_method("textDocument/formatting") then
vim.api.nvim_clear_autocmds({ group = augroup_fmt, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup_fmt,
buffer = bufnr,
callback = function()
vim.lsp.buf.format({
bufnr = bufnr,
timeout_ms = 2000,
filter = function(c) return c.name == client.name end,
})
end,
})
end
-- Inlay hints are noisy on small screens; off by default, toggle with ,h.
if client.supports_method("textDocument/inlayHint") then
vim.keymap.set("n", "<localleader>h",
function()
vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), { bufnr = bufnr })
end,
{ buffer = bufnr, desc = "toggle inlay hints" })
end
end
-- Diagnostics
vim.diagnostic.config({
virtual_text = { prefix = "ยท", spacing = 2 },
severity_sort = true,
update_in_insert = false,
float = { border = "rounded", source = "if_many" },
signs = {
text = {
[vim.diagnostic.severity.ERROR] = "E",
[vim.diagnostic.severity.WARN] = "W",
[vim.diagnostic.severity.INFO] = "I",
[vim.diagnostic.severity.HINT] = "H",
},
},
})
-- Rust: use rustaceanvim if present, otherwise fall back to rust_analyzer.
if vim.fn.executable("rust-analyzer") == 1 then
lspconfig.rust_analyzer.setup({
capabilities = capabilities,
on_attach = on_attach,
settings = {
["rust-analyzer"] = {
cargo = { allFeatures = true, buildScripts = { enable = true } },
check = { command = "clippy", extraArgs = { "--no-deps" } },
diagnostics = { disabled = { "inactive-code" } },
inlayHints = {
parameterHints = { enable = true },
typeHints = { enable = true },
chainingHints = { enable = true },
},
procMacro = { enable = true },
},
},
})
end
-- Go: gopls. I keep build tags off by default, override per-project in
-- .nvim.lua with vim.lsp.buf.request('workspace/configuration').
if vim.fn.executable("gopls") == 1 then
lspconfig.gopls.setup({
capabilities = capabilities,
on_attach = on_attach,
cmd = { "gopls", "-remote=auto" },
settings = {
gopls = {
gofumpt = true,
usePlaceholders = false,
staticcheck = true,
analyses = {
unusedparams = true,
shadow = true,
unusedvariable = true,
},
hints = {
assignVariableTypes = true,
compositeLiteralFields = true,
parameterNames = false,
rangeVariableTypes = true,
},
},
},
})
end
-- Python: pyright for types, ruff for lint/format (via ruff-lsp).
if vim.fn.executable("pyright-langserver") == 1 then
lspconfig.pyright.setup({
capabilities = capabilities,
on_attach = function(client, bufnr)
-- Defer formatting to ruff; pyright doesn't format.
client.server_capabilities.documentFormattingProvider = false
on_attach(client, bufnr)
end,
settings = {
python = {
analysis = {
typeCheckingMode = "basic",
diagnosticMode = "openFilesOnly",
useLibraryCodeForTypes = true,
},
},
},
})
end
if vim.fn.executable("ruff-lsp") == 1 or vim.fn.executable("ruff") == 1 then
lspconfig.ruff.setup({
capabilities = capabilities,
on_attach = on_attach,
init_options = {
settings = {
args = { "--line-length=100" },
},
},
})
end
-- Lua: lua_ls, tuned to know about the Neovim runtime only in ~/.config/nvim.
if vim.fn.executable("lua-language-server") == 1 then
lspconfig.lua_ls.setup({
capabilities = capabilities,
on_attach = on_attach,
on_init = function(client)
local root = client.workspace_folders and client.workspace_folders[1] and client.workspace_folders[1].name or ""
if root:find(vim.fn.stdpath("config"), 1, true) then
client.config.settings.Lua = vim.tbl_deep_extend("force", client.config.settings.Lua, {
runtime = { version = "LuaJIT" },
workspace = {
checkThirdParty = false,
library = { vim.env.VIMRUNTIME },
},
diagnostics = { globals = { "vim" } },
})
end
end,
settings = {
Lua = {
telemetry = { enable = false },
format = {
enable = true,
defaultConfig = { indent_style = "space", indent_size = "2" },
},
},
},
})
end
-- Handlers with rounded borders so hover/signatures look consistent.
vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { border = "rounded" })
vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, { border = "rounded" })
-- Escape hatch: :LspLog to inspect; :LspRestart to bounce all clients.
vim.api.nvim_create_user_command("LspRestart",
function()
for _, c in ipairs(vim.lsp.get_clients()) do
vim.lsp.stop_client(c.id, true)
end
vim.defer_fn(function() vim.cmd.edit() end, 250)
end,
{})