return { { "williamboman/mason.nvim", lazy = false, config = function() require("mason").setup({ registries = { "github:mason-org/mason-registry", "github:Crashdummyy/mason-registry", }, }) end, }, { "williamboman/mason-lspconfig.nvim", lazy = false, opts = { automatic_enable = false, auto_install = true, }, }, { "linux-cultist/venv-selector.nvim", dependencies = { "neovim/nvim-lspconfig", "mfussenegger/nvim-dap", "mfussenegger/nvim-dap-python", { "nvim-telescope/telescope.nvim", branch = "0.1.x", dependencies = { "nvim-lua/plenary.nvim" } }, }, ft = "python", keys = { { "v", "VenvSelect" }, }, opts = { search = {}, options = {}, }, }, { "b0o/SchemaStore.nvim", }, { "neovim/nvim-lspconfig", lazy = false, config = function() local capabilities = require("cmp_nvim_lsp").default_capabilities() vim.lsp.handlers["window/showMessage"] = function(_, result, ctx) local client = vim.lsp.get_client_by_id(ctx.client_id) local lvl = ({ ERROR = vim.log.levels.ERROR, WARNING = vim.log.levels.WARN, INFO = vim.log.levels.INFO, LOG = vim.log.levels.DEBUG, })[result.type] vim.notify(result.message, lvl, { title = string.format("LSP[%s]", client and client.name or "Unknown"), }) end vim.lsp.handlers["$/progress"] = function(_, result, ctx) local client = vim.lsp.get_client_by_id(ctx.client_id) if result.value.kind == "begin" then vim.notify( string.format("LSP[%s] %s", client and client.name or "Unknown", result.value.title), vim.log.levels.INFO ) end end local base_on_attach = vim.lsp.config.eslint.on_attach vim.lsp.config("eslint", { on_attach = function(client, bufnr) if not base_on_attach then return end base_on_attach(client, bufnr) vim.api.nvim_create_autocmd("BufWritePre", { buffer = bufnr, command = "LspEslintFixAll", }) end, }) vim.lsp.enable("eslint") vim.lsp.config("roslyn_ls", { settings = { ["csharp|projects"] = { dotnet_enable_file_based_programs = true, }, }, root_dir = function(bufnr, cb) local bufname = vim.api.nvim_buf_get_name(bufnr) -- decompiled files: reuse previous buffer's client root if bufname:find('[/\\]MetadataAsSource[/\\]') then local _, endpos = bufname:find('[/\\]MetadataAsSource[/\\]') if vim.fn.finddir(bufname:sub(1, endpos), vim.uv.os_tmpdir()) ~= '' then local prev = vim.fn.bufnr('#') local client = vim.lsp.get_clients({ name = 'roslyn_ls', bufnr = (prev ~= -1 and prev) or nil })[1] if client then cb(client.config.root_dir) return end end end -- 1) Look for .sln / .slnx local root = vim.fs.root(bufnr, function(fname) return fname:match('%.sln[x]?$') ~= nil end) -- 2) Look for .csproj if not root then root = vim.fs.root(bufnr, function(fname) return fname:match('%.csproj$') ~= nil end) end -- 3) Fallback: file-based app — use file's parent directory if not root then root = vim.uv.fs_realpath(vim.fs.dirname(bufname)) end if root then cb(root) end end, }) vim.lsp.enable("roslyn_ls") vim.lsp.config("fsautocomplete", { capabilities = capabilities, }) vim.lsp.enable("fsautocomplete") vim.lsp.config("lua_ls", { capabilities = capabilities, }) vim.lsp.enable("lua_ls") local vue_language_server_path = vim.fn.stdpath("data") .. "/mason/packages/vue-language-server/node_modules/@vue/language-server" local vue_plugin = { name = "@vue/typescript-plugin", location = vue_language_server_path, languages = { "vue" }, configNamespace = "typescript", } -- NOTE: Vue projects always use ts_ls + vue_ls because the Vue -- language tooling depends on the TS6 compiler API. TS7 does not -- yet expose a stable API (expected in 7.1). Once Vue supports -- TS7, this branching can be simplified. -- See: https://github.com/vuejs/language-tools/issues/5381 -- Define tsgo config unconditionally (simple, no detection needed) vim.lsp.config("tsgo", { cmd = function(dispatchers, config) local cmd = "tsc" if config and config.root_dir then local local_cmd = vim.fs.joinpath(config.root_dir, "node_modules/.bin", cmd) if vim.fn.executable(local_cmd) == 1 then cmd = local_cmd end end return vim.lsp.rpc.start({ cmd, "--lsp", "--stdio" }, dispatchers) end, filetypes = { "typescript", "javascript", "javascriptreact", "typescriptreact" }, }) -- Detect project type from the first loaded buffer (so vim.fs.root works) local function detect_and_enable_lsp() local root = vim.fs.root(0, "package.json") or vim.fn.getcwd() -- Check if this is a Vue project local ok, content = pcall(vim.fn.readfile, root .. "/package.json") local is_vue = false if ok and content and #content > 0 then local decoded = vim.json.decode(table.concat(content, "\n")) if decoded then local deps = vim.tbl_extend("force", decoded.dependencies or {}, decoded.devDependencies or {}) if deps["vue"] or deps["nuxt"] or deps["@vue/compiler-sfc"] then is_vue = true end end end if not is_vue and vim.fn.glob(root .. "/vue.config.*") ~= "" then is_vue = true end -- Check TypeScript version (for tsgo vs ts_ls decision) local ts_version local ts_ok, ts_content = pcall(vim.fn.readfile, root .. "/node_modules/typescript/package.json") if ts_ok and ts_content and #ts_content > 0 then local decoded = vim.json.decode(table.concat(ts_content, "\n")) if decoded and decoded.version then ts_version = tonumber(decoded.version:match("^(%d+)")) end end if is_vue then vim.lsp.config("vue_ls", {}) vim.lsp.config("ts_ls", { init_options = { plugins = { vue_plugin, }, }, filetypes = { "typescript", "javascript", "javascriptreact", "typescriptreact", "vue" }, }) vim.lsp.enable("vue_ls") vim.lsp.enable("ts_ls") elseif ts_version and ts_version >= 7 then vim.lsp.enable("tsgo") else vim.lsp.config("ts_ls", {}) vim.lsp.enable("ts_ls") end end -- Defer detection until a real buffer is loaded (so buffer 0 has a path) vim.api.nvim_create_autocmd("BufReadPost", { group = vim.api.nvim_create_augroup("TsVueLspSetup", { clear = true }), once = true, callback = function() pcall(detect_and_enable_lsp) end, }) vim.lsp.config("jsonls", { capabilities = capabilities, settings = { json = { schemas = require("schemastore").json.schemas(), validate = { enable = true }, format = { enable = true }, }, }, }) vim.lsp.enable("jsonls") vim.lsp.config("html", { filetypes = { "html", "ejs", "gohtml" }, capabilities = capabilities, }) vim.lsp.enable("html") vim.lsp.config("emmet_language_server", { filetypes = { "html", "css", "javascriptreact", "typescriptreact", "vue", "gohtml" }, capabilities = capabilities, }) vim.lsp.enable("emmet_language_server") vim.lsp.config("cssls", { filetypes = { "css", "scss", "less" }, capabilities = capabilities, }) vim.lsp.enable("cssls") vim.lsp.config("tailwindcss", { capabilities = capabilities, }) vim.lsp.enable("tailwindcss") vim.lsp.config("pyright", { capabilities = capabilities, }) vim.lsp.enable("pyright") vim.lsp.config("lemminx", { capabilities = capabilities, }) vim.lsp.enable("lemminx") vim.lsp.config("gopls", { capabilities = capabilities, filetypes = { "go", "gomod", "gowork", "gotmpl", "gohtml" }, settings = { gopls = { buildFlags = { "-tags=integration" }, }, }, }) vim.lsp.enable("gopls") vim.lsp.config("rust_analyzer", { settings = { ["rust-analyzer"] = { files = { watcher = "server", }, check = { command = "clippy", }, diagnostics = { enable = false, }, }, }, capabilities = capabilities, }) vim.lsp.enable("rust_analyzer") vim.lsp.config("clangd", { capabilities = capabilities, }) vim.lsp.enable("clangd") vim.lsp.config("gh_actions_ls", { capabilities = capabilities, filetypes = { "yaml" }, settings = { yaml = { schemas = { ["https://json.schemastore.org/github-workflow.json"] = "/*/.github/workflows/*", }, }, }, }) vim.lsp.enable("gh_actions_ls") vim.lsp.config("sqlls", { capabilities = capabilities, filetypes = { "sql" }, }) vim.lsp.enable("sqlls") vim.api.nvim_create_autocmd("LspAttach", { group = vim.api.nvim_create_augroup("UserLspConfig", {}), callback = function(ev) local client = vim.lsp.get_client_by_id(ev.data.client_id) if not client then vim.notify("LSP client not found", vim.log.levels.ERROR) return end if not client.server_capabilities then vim.notify("LSP server not ready yet", vim.log.levels.WARN) return end client.on_error = function(err) vim.notify(string.format("LSP server error: %s", err.message), vim.log.levels.ERROR) end client.on_status = function(status) if status == "stopped" then vim.notify("LSP server stopped", vim.log.levels.WARN) elseif status == "running" then vim.notify("LSP server running", vim.log.levels.INFO) end end vim.keymap.set("n", "gd", vim.lsp.buf.definition, { desc = "Go to definition", buffer = ev.buf }) vim.keymap.set("n", "gD", vim.lsp.buf.declaration, { desc = "Go to declaration", buffer = ev.buf }) vim.keymap.set( "n", "gi", vim.lsp.buf.implementation, { desc = "Go to implementation", buffer = ev.buf } ) vim.keymap.set("n", "gr", vim.lsp.buf.references, { desc = "Go to references", buffer = ev.buf }) vim.api.nvim_create_autocmd({ "FileType" }, { callback = function() local parsers = require("nvim-treesitter.parsers") if parsers then vim.opt.foldmethod = "expr" vim.opt.foldexpr = "nvim_treesitter#foldexpr()" else vim.opt.foldmethod = "syntax" end end, }) vim.api.nvim_create_autocmd("BufWritePre", { pattern = "*.go", callback = function() local params = vim.lsp.util.make_range_params() params.context = { only = { "source.organizeImports" } } local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params) for cid, res in pairs(result or {}) do for _, r in pairs(res.result or {}) do if r.edit then local enc = (vim.lsp.get_client_by_id(cid) or {}).offset_encoding or "utf-16" vim.lsp.util.apply_workspace_edit(r.edit, enc) end end end vim.lsp.buf.format({ async = false }) end, }) vim.keymap.set( "n", "ca", vim.lsp.buf.code_action, { desc = "Code actions", buffer = ev.buf } ) vim.keymap.set("n", "rn", vim.lsp.buf.rename, { desc = "Rename", buffer = ev.buf }) vim.keymap.set( "n", "e", vim.diagnostic.open_float, { desc = "Display error for current line", buffer = ev.buf } ) vim.keymap.set( "n", "ea", ":Telescope diagnostics bufnr=0", { desc = "Display errors for current buffer", buffer = ev.buf } ) vim.keymap.set("n", "eca", function() local diagnostics = vim.diagnostic.get(0) if #diagnostics == 0 then vim.notify("No diagnostics in buffer", vim.log.levels.INFO) return end local lines = vim.tbl_map(function(d) return string.format( "%s:%d %s", vim.fs.basename(vim.api.nvim_buf_get_name(d.bufnr)), d.lnum + 1, d.message ) end, diagnostics) vim.fn.setreg("+", table.concat(lines, "\n")) vim.notify(string.format("Copied %d diagnostics to clipboard", #diagnostics)) end, { desc = "Copy buffer diagnostics to clipboard", buffer = ev.buf }) vim.keymap.set("n", "K", vim.lsp.buf.hover, { desc = "Display symbol info", buffer = ev.buf }) end, }) end, }, }