-- ── plugins/lsp.lua ─────────────────────────────────────────────────────────── -- nvim 0.11+ native LSP API: vim.lsp.config / vim.lsp.enable -- lspconfig still used as a config data source (server cmd/root detection) -- but we drive it through vim.lsp.config, not lspconfig.setup() -- No vim.lsp.with() — border config goes through vim.lsp.config globals return { -- ── LSP ─────────────────────────────────────────────────────────────────── { "neovim/nvim-lspconfig", dependencies = { "hrsh7th/nvim-cmp", "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer", "hrsh7th/cmp-path", "hrsh7th/cmp-cmdline", "L3MON4D3/LuaSnip", "saadparwaiz1/cmp_luasnip", "rafamadriz/friendly-snippets", "onsails/lspkind.nvim", }, config = function() -- ── Diagnostics ─────────────────────────────────────────────────── vim.diagnostic.config({ virtual_text = { prefix = "●", spacing = 4 }, signs = true, underline = true, update_in_insert = false, severity_sort = true, float = { border = "rounded", source = true }, }) -- Diagnostic gutter icons 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 = hl }) end -- ── Borders on hover/signature (0.11 way, no vim.lsp.with) ─────── vim.lsp.config("*", { handlers = { ["textDocument/hover"] = function(err, result, ctx, config) config = config or {} config.border = "rounded" vim.lsp.handlers.hover(err, result, ctx, config) end, ["textDocument/signatureHelp"] = function(err, result, ctx, config) config = config or {} config.border = "rounded" vim.lsp.handlers.signature_help(err, result, ctx, config) end, }, }) -- ── Capabilities (nvim-cmp) ─────────────────────────────────────── local caps = vim.tbl_deep_extend( "force", vim.lsp.protocol.make_client_capabilities(), require("cmp_nvim_lsp").default_capabilities() ) -- ── on_attach via LspAttach autocmd (0.11 pattern) ─────────────── vim.api.nvim_create_autocmd("LspAttach", { group = vim.api.nvim_create_augroup("arch-dev-lsp", { clear = true }), callback = function(event) local buf = event.buf local m = function(k, fn, d) vim.keymap.set("n", k, fn, { buffer = buf, desc = d, silent = true }) end m("gd", vim.lsp.buf.definition, "Definition") m("gD", vim.lsp.buf.declaration, "Declaration") m("gi", vim.lsp.buf.implementation, "Implementation") m("gt", vim.lsp.buf.type_definition, "Type definition") m("K", vim.lsp.buf.hover, "Hover docs") m("", vim.lsp.buf.signature_help, "Signature") m("rn", vim.lsp.buf.rename, "Rename") m("ca", vim.lsp.buf.code_action, "Code action") m("f", function() vim.lsp.buf.format({ async = true }) end, "Format") m("gr", "Telescope lsp_references", "References") m("ds", "Telescope lsp_document_symbols", "Doc symbols") end, }) -- ── Servers ─────────────────────────────────────────────────────── -- pyright — from pacman vim.lsp.config("pyright", { capabilities = caps, settings = { python = { analysis = { typeCheckingMode = "basic", autoSearchPaths = true, useLibraryCodeForTypes = true, diagnosticMode = "openFilesOnly", }, }, }, }) vim.lsp.enable("pyright") -- bash-language-server — from pacman vim.lsp.config("bashls", { capabilities = caps, filetypes = { "sh", "bash", "zsh" }, }) vim.lsp.enable("bashls") -- lua_ls — for editing neovim config inside arch-dev vim.lsp.config("lua_ls", { capabilities = caps, settings = { Lua = { runtime = { version = "LuaJIT" }, diagnostics = { globals = { "vim" } }, workspace = { library = vim.api.nvim_get_runtime_file("", true), checkThirdParty = false, }, telemetry = { enable = false }, }, }, }) vim.lsp.enable("lua_ls") end, }, -- ── Completion ──────────────────────────────────────────────────────────── { "hrsh7th/nvim-cmp", event = { "InsertEnter", "CmdlineEnter" }, dependencies = { "L3MON4D3/LuaSnip", "saadparwaiz1/cmp_luasnip", "rafamadriz/friendly-snippets", "onsails/lspkind.nvim", "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer", "hrsh7th/cmp-path", "hrsh7th/cmp-cmdline", }, config = function() local cmp = require("cmp") local luasnip = require("luasnip") local lspkind = require("lspkind") require("luasnip.loaders.from_vscode").lazy_load() cmp.setup({ snippet = { expand = function(args) luasnip.lsp_expand(args.body) end, }, window = { completion = cmp.config.window.bordered(), documentation = cmp.config.window.bordered(), }, mapping = cmp.mapping.preset.insert({ [""] = cmp.mapping.scroll_docs(-4), [""] = cmp.mapping.scroll_docs(4), [""] = cmp.mapping.complete(), [""] = cmp.mapping.abort(), [""] = cmp.mapping.confirm({ select = false }), [""] = cmp.mapping(function(fallback) if cmp.visible() then cmp.select_next_item() elseif luasnip.expand_or_jumpable() then luasnip.expand_or_jump() else fallback() end end, { "i", "s" }), [""] = cmp.mapping(function(fallback) if cmp.visible() then cmp.select_prev_item() elseif luasnip.jumpable(-1) then luasnip.jump(-1) else fallback() end end, { "i", "s" }), }), sources = cmp.config.sources({ { name = "nvim_lsp", priority = 1000 }, { name = "luasnip", priority = 750 }, { name = "buffer", priority = 500 }, { name = "path", priority = 250 }, }), formatting = { format = lspkind.cmp_format({ mode = "symbol_text", maxwidth = 50, ellipsis_char = "…", menu = { nvim_lsp = "[LSP]", luasnip = "[Snip]", buffer = "[Buf]", path = "[Path]", }, }), }, }) cmp.setup.cmdline({ "/", "?" }, { mapping = cmp.mapping.preset.cmdline(), sources = { { name = "buffer" } }, }) cmp.setup.cmdline(":", { mapping = cmp.mapping.preset.cmdline(), sources = cmp.config.sources( { { name = "path" } }, { { name = "cmdline" } } ), }) end, }, -- ── Linting ─────────────────────────────────────────────────────────────── { "mfussenegger/nvim-lint", event = { "BufReadPre", "BufNewFile" }, config = function() local lint = require("lint") lint.linters_by_ft = { python = { "ruff" }, sh = { "shellcheck" }, bash = { "shellcheck" }, zsh = { "shellcheck" }, } vim.api.nvim_create_autocmd( { "BufWritePost", "BufReadPost", "InsertLeave" }, { callback = function() lint.try_lint() end } ) end, }, -- ── Formatting ──────────────────────────────────────────────────────────── { "stevearc/conform.nvim", event = { "BufWritePre" }, config = function() require("conform").setup({ formatters_by_ft = { python = { "black" }, sh = { "shfmt" }, bash = { "shfmt" }, lua = { "stylua" }, }, format_on_save = { timeout_ms = 1000, lsp_fallback = true }, notify_on_error = false, }) end, }, -- ── Trouble: diagnostics panel ──────────────────────────────────────────── { "folke/trouble.nvim", dependencies = { "nvim-tree/nvim-web-devicons" }, config = function() require("trouble").setup({ use_diagnostic_signs = true }) vim.keymap.set("n", "xx", "TroubleToggle", { desc = "Toggle trouble" }) vim.keymap.set("n", "xd", "TroubleToggle document_diagnostics", { desc = "Document diagnostics" }) end, }, }