local augroup = vim.api.nvim_create_augroup("UserConfig", { clear = false }) -- Diagnostic icons shown in the sign column and virtual text local diagnostic_signs = { Error = " ", Warn = " ", Hint = "", Info = "", } -- Global diagnostic display options vim.diagnostic.config({ virtual_text = { prefix = "●", spacing = 4 }, -- inline diagnostic text to the right signs = { text = { [vim.diagnostic.severity.ERROR] = diagnostic_signs.Error, [vim.diagnostic.severity.WARN] = diagnostic_signs.Warn, [vim.diagnostic.severity.INFO] = diagnostic_signs.Info, [vim.diagnostic.severity.HINT] = diagnostic_signs.Hint, }, }, underline = true, update_in_insert = false, -- do not update diagnostics while typing (reduces noise) severity_sort = true, -- show errors before warnings in lists float = { border = "rounded", source = "always", -- always show which LSP server produced the diagnostic header = "", prefix = "", focusable = false, style = "minimal", }, }) -- Patch vim.lsp.util.open_floating_preview so every LSP floating window -- (hover, signature help, etc.) gets a rounded border without having to -- configure it per-server. do local orig = vim.lsp.util.open_floating_preview function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...) opts = opts or {} opts.border = opts.border or "rounded" return orig(contents, syntax, opts, ...) end end -- lsp_on_attach is called whenever an LSP server attaches to a buffer. -- It sets up buffer-local keymaps that are only active when an LSP is present. local function lsp_on_attach(ev) local client = vim.lsp.get_client_by_id(ev.data.client_id) if not client then return end local bufnr = ev.buf local function map(key, fn, desc) vim.keymap.set("n", key, fn, { noremap = true, silent = true, buffer = bufnr, desc = desc }) end -- Navigation map("gd", vim.lsp.buf.definition, "Go to definition") map("gS", function() vim.cmd("vsplit") vim.lsp.buf.definition() end, "Go to definition (vsplit)") -- Code actions and refactoring map("ca", vim.lsp.buf.code_action, "Code action") map("rn", vim.lsp.buf.rename, "Rename symbol") -- Diagnostics map("D", function() vim.diagnostic.open_float({ scope = "line" }) end, "Line diagnostics") map("d", function() vim.diagnostic.open_float({ scope = "cursor" }) end, "Cursor diagnostic") map("nd", function() vim.diagnostic.jump({ count = 1 }) end, "Next diagnostic") map("pd", function() vim.diagnostic.jump({ count = -1 }) end, "Prev diagnostic") -- Hover documentation map("K", vim.lsp.buf.hover, "Hover docs") -- fzf-lua LSP pickers map("fd", function() require("fzf-lua").lsp_definitions({ jump_to_single_result = true }) end, "Definitions") map("fr", function() require("fzf-lua").lsp_references() end, "References") map("ft", function() require("fzf-lua").lsp_typedefs() end, "Type definitions") map("fs", function() require("fzf-lua").lsp_document_symbols() end, "Document symbols") map("fw", function() require("fzf-lua").lsp_workspace_symbols() end, "Workspace symbols") map("fi", function() require("fzf-lua").lsp_implementations() end, "Implementations") -- Organise imports then format (only registered when the server supports codeAction) if client:supports_method("textDocument/codeAction", bufnr) then map("oi", function() vim.lsp.buf.code_action({ context = { only = { "source.organizeImports" }, diagnostics = {} }, apply = true, bufnr = bufnr, }) -- Small delay lets the import action settle before formatting vim.defer_fn(function() vim.lsp.buf.format({ bufnr = bufnr }) end, 50) end, "Organize imports") end end vim.api.nvim_create_autocmd("LspAttach", { group = augroup, callback = lsp_on_attach }) -- Global diagnostic keymaps (available without an LSP attached) vim.keymap.set("n", "q", function() vim.diagnostic.setloclist({ open = true }) end, { desc = "Open diagnostic list" }) -- blink.cmp: completion engine wired to LuaSnip for snippet expansion require("blink.cmp").setup({ enabled = function() return not vim.tbl_contains({ "markdown", "text" }, vim.bo.filetype) end, keymap = { preset = "none", -- start from a blank slate; all bindings are explicit [""] = { "show", "hide" }, -- toggle the completion menu [""] = { "accept", "fallback" }, -- confirm selection [""] = { "select_next", "fallback" }, [""] = { "select_prev", "fallback" }, [""] = { "snippet_forward", "fallback" }, -- jump to next snippet placeholder [""] = { "snippet_backward", "fallback" }, -- jump to previous placeholder }, appearance = { nerd_font_variant = "mono" }, completion = { menu = { auto_show = true } }, -- show the menu automatically (no manual trigger needed) sources = { default = { "lsp", "path", "buffer", "snippets" } }, snippets = { -- Delegate snippet expansion to LuaSnip expand = function(snippet) require("luasnip").lsp_expand(snippet) end, }, fuzzy = { implementation = "prefer_rust", -- use the faster Rust fuzzy-matching backend when available prebuilt_binaries = { download = true }, -- automatically download the pre-built binary }, }) -- Inject blink.cmp capabilities into every LSP server so they can offer -- completions that respect snippet and LSP-specific completion item fields vim.lsp.config["*"] = { capabilities = require("blink.cmp").get_lsp_capabilities(), } -- Server configurations vim.lsp.config("lua_ls", { settings = { Lua = { diagnostics = { globals = { "vim" } }, -- teach lua_ls that `vim` is a known global telemetry = { enable = false }, }, }, }) vim.lsp.config("pyright", {}) -- Python type checker and language server vim.lsp.config("bashls", {}) -- Bash language server vim.lsp.config("vtsls", {}) -- TypeScript / JavaScript language server (VS Code's TS server) vim.lsp.config("gopls", {}) -- Go language server vim.lsp.config("omnisharp", {}) -- C# language server -- EFM (efm-langserver): a generic LSP adapter that runs command-line linters -- and formatters and exposes them as LSP capabilities. do local luacheck = require("efmls-configs.linters.luacheck") local stylua = require("efmls-configs.formatters.stylua") local flake8 = require("efmls-configs.linters.flake8") local black = require("efmls-configs.formatters.black") local prettier_d = require("efmls-configs.formatters.prettier_d") local eslint_d = require("efmls-configs.linters.eslint_d") local fixjson = require("efmls-configs.formatters.fixjson") local shellcheck = require("efmls-configs.linters.shellcheck") local shfmt = require("efmls-configs.formatters.shfmt") local go_revive = require("efmls-configs.linters.go_revive") local gofumpt = require("efmls-configs.formatters.gofumpt") local csharpier = { formatCommand = "dotnet csharpier --write-stdout", formatStdin = true } vim.lsp.config("efm", { filetypes = { "cs", "css", "go", "html", "javascript", "javascriptreact", "json", "jsonc", "lua", "markdown", "python", "sh", "typescript", "typescriptreact", }, init_options = { documentFormatting = true }, settings = { languages = { cs = { csharpier }, go = { gofumpt, go_revive }, css = { prettier_d }, html = { prettier_d }, javascript = { eslint_d, prettier_d }, javascriptreact = { eslint_d, prettier_d }, json = { eslint_d, fixjson }, jsonc = { eslint_d, fixjson }, lua = { luacheck, stylua }, markdown = { prettier_d }, python = { flake8, black }, sh = { shellcheck, shfmt }, typescript = { eslint_d, prettier_d }, typescriptreact = { eslint_d, prettier_d }, }, }, }) end -- Start all configured LSP servers vim.lsp.enable({ "lua_ls", "pyright", "bashls", "vtsls", "gopls", "omnisharp", "efm", })