-- Neovim 0.11+ LSP configuration -- Uses nvim-lspconfig for server definitions + vim.lsp.enable() API -- Detect NixOS (Mason doesn't work on NixOS due to FHS issues) local is_nixos = vim.fn.filereadable("/etc/NIXOS") == 1 -- Servers to ensure are installed via Mason (non-NixOS only) -- On NixOS, install these via extraPackages or per-project devShells local mason_ensure_installed = { "lua_ls", -- Neovim config "nil_ls", -- Nix (nixd not available in Mason) "bashls", -- Shell scripts "jsonls", -- JSON configs "html", -- HTML "cssls", -- CSS "marksman", -- Markdown "taplo", -- TOML "yamlls", -- YAML } return { { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate", config = function() require('nvim-treesitter.configs').setup({ ensure_installed = { "lua", "c", "cpp", "python", "nix", "rust", "bash", "markdown", "html", "javascript", "css", "vim", "git_config", "git_rebase", "gitattributes", "gitcommit", "gitignore" }, auto_install = true, sync_install = true, highlight = { enable = true, } }) end }, { "m4xshen/autoclose.nvim", config = function() require("autoclose").setup() end }, { "hrsh7th/nvim-cmp", dependencies = { { "L3MON4D3/LuaSnip", version = "v2.*", build = "make install_jsregexp", }, "saadparwaiz1/cmp_luasnip", "hrsh7th/cmp-nvim-lsp" }, config = function() local cmp = require("cmp") cmp.setup({ enabled = function() local context = require("cmp.config.context") if vim.api.nvim_get_mode().mode == "c" then return true else return not context.in_treesitter_capture("comment") and not context.in_syntax_group("comment") end end, snippet = { expand = function(args) require('luasnip').lsp_expand(args.body) end }, mapping = cmp.mapping.preset.insert({ [""] = cmp.mapping.select_prev_item(), [""] = cmp.mapping.select_next_item(), [""] = cmp.mapping.scroll_docs(-4), [""] = cmp.mapping.scroll_docs(4), [""] = cmp.mapping.abort(), [""] = cmp.mapping.confirm(), [""] = cmp.mapping(function(fallback) fallback() end, { "i", "s" }), }), sources = cmp.config.sources({ { name = 'nvim_lsp' }, { name = 'luasnip' }, }, { { name = 'buffer' } }), }) end }, -- Mason: portable LSP installer (disabled on NixOS where it doesn't work) { "williamboman/mason.nvim", enabled = not is_nixos, config = function() require("mason").setup() end }, { "williamboman/mason-lspconfig.nvim", enabled = not is_nixos, dependencies = { "williamboman/mason.nvim" }, config = function() require("mason-lspconfig").setup({ ensure_installed = mason_ensure_installed, automatic_installation = false, -- Only install what's in ensure_installed }) end }, { "neovim/nvim-lspconfig", dependencies = { "hrsh7th/cmp-nvim-lsp", }, config = function() local lspconfig = require('lspconfig') -- Diagnostic display configuration vim.diagnostic.config({ virtual_text = { prefix = '●', spacing = 2, }, float = { border = 'rounded', source = true, }, signs = { text = { [vim.diagnostic.severity.ERROR] = '', [vim.diagnostic.severity.WARN] = '', [vim.diagnostic.severity.INFO] = '', [vim.diagnostic.severity.HINT] = '', }, }, underline = true, update_in_insert = false, severity_sort = true, }) -- Add border to hover and signature help windows 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' } ) -- Get all known server names by scanning lspconfig's lsp directory local function get_all_servers() local servers = {} local lsp_path = vim.fn.stdpath('data') .. '/lazy/nvim-lspconfig/lsp' local files = vim.fn.globpath(lsp_path, '*.lua', false, true) for _, file in ipairs(files) do local server = vim.fn.fnamemodify(file, ':t:r') table.insert(servers, server) end return servers end local all_servers = get_all_servers() -- local navic = require('nvim-navic') local capabilities = require('cmp_nvim_lsp').default_capabilities() -- Global config applied to all servers vim.lsp.config('*', { autostart = false, -- Don't auto-attach, use css to start manually capabilities = capabilities, -- on_attach = function(client, bufnr) -- if client.server_capabilities.documentSymbolProvider then -- navic.attach(client, bufnr) -- end -- end, }) -- Server-specific settings (merged with lspconfig defaults) vim.lsp.config.lua_ls = { settings = { Lua = { diagnostics = { globals = { 'vim' } } } } } -- Check if server binary is available local function is_server_installed(config) if config.default_config and config.default_config.cmd then local cmd = config.default_config.cmd[1] return vim.fn.executable(cmd) == 1 end return false end -- Find and start LSP server(s) for current filetype local function lsp_start_smart() local ft = vim.bo.filetype if ft == '' then vim.notify("No filetype detected", vim.log.levels.WARN) return end -- Find all matching servers (filetype match + binary installed) local matching = {} for _, server in ipairs(all_servers) do local ok, config = pcall(require, 'lspconfig.configs.' .. server) if ok and config.default_config and config.default_config.filetypes then if vim.tbl_contains(config.default_config.filetypes, ft) and is_server_installed(config) then table.insert(matching, server) end end end -- Sort for consistent ordering table.sort(matching) local function start_server(server) vim.lsp.enable(server) -- Register on-demand vim.cmd('LspStart ' .. server) end if #matching == 0 then vim.notify("No LSP server installed for filetype: " .. ft, vim.log.levels.WARN) elseif #matching == 1 then start_server(matching[1]) else vim.ui.select(matching, { prompt = "Select LSP server:", }, function(choice) if choice then start_server(choice) end end) end end -- LSP keybindings require("which-key").add({ { "cs", group = "LSP Commands" }, { "cf", function() vim.lsp.buf.format() end, desc = "Code Format" }, { "csi", ":checkhealth vim.lsp", desc = "LSP Info" }, { "csr", ":LspRestart", desc = "LSP Restart" }, { "css", lsp_start_smart, desc = "LSP Start" }, { "csx", ":LspStop", desc = "LSP Stop" }, }) end }, { "taproot-wizards/bitcoin-script-hints.nvim", dependencies = { "nvim-treesitter/nvim-treesitter" }, config = function() require("bitcoin-script-hints").setup() end } }