feat: migrate omnisharp to roslyn

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