From f4423f18abc94efb8a95a4bb06b8e2773d0404e9 Mon Sep 17 00:00:00 2001 From: CaueAnjos <141049846+CaueAnjos@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:32:47 -0300 Subject: [PATCH 1/8] lsp/presets/csharp_ls: int --- modules/plugins/lsp/presets/csharp_ls.nix | 51 +++++++++++++++++++++++ modules/plugins/lsp/presets/default.nix | 1 + 2 files changed, 52 insertions(+) create mode 100644 modules/plugins/lsp/presets/csharp_ls.nix diff --git a/modules/plugins/lsp/presets/csharp_ls.nix b/modules/plugins/lsp/presets/csharp_ls.nix new file mode 100644 index 00000000..ba0238f5 --- /dev/null +++ b/modules/plugins/lsp/presets/csharp_ls.nix @@ -0,0 +1,51 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib.meta) getExe; + inherit (lib.modules) mkIf; + inherit (lib.nvim.types) mkLspPresetEnableOption; + inherit (lib.generators) mkLuaInline; + + cfg = config.vim.lsp.presets.csharp_ls; +in { + # HACK: this server should be named `csharp-ls`, but the extension `csharpls-extended-lsp-nvim` only works if it is named `csharp_ls` + options.vim.lsp.presets.csharp_ls = { + enable = mkLspPresetEnableOption "csharp_ls" "C#" []; + }; + + config = mkIf cfg.enable { + vim.lsp.servers.csharp_ls = { + cmd = mkLuaInline '' + function(dispatchers, config) + return vim.lsp.rpc.start({ '${getExe pkgs.csharp-ls}', '--features', 'razor-support' , '--features', 'metadata-uris'}, dispatchers, { + -- csharp-ls attempt to locate sln, slnx or csproj files from cwd, so set cwd to root directory. + -- If cmd_cwd is provided, use it instead. + cwd = config.cmd_cwd or config.root_dir, + env = config.cmd_env, + detached = config.detached, + }) + end + ''; + root_dir = mkLuaInline '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + on_dir(util.root_pattern '*.sln'(fname) or util.root_pattern '*.slnx'(fname) or util.root_pattern '*.csproj'(fname)) + end + ''; + init_options = { + AutomaticWorkspaceInit = true; + }; + get_language_id = mkLuaInline '' + function(_, ft) + if ft == 'cs' then + return 'csharp' + end + return ft + end + ''; + }; + }; +} diff --git a/modules/plugins/lsp/presets/default.nix b/modules/plugins/lsp/presets/default.nix index d3b0bed7..abd8346d 100644 --- a/modules/plugins/lsp/presets/default.nix +++ b/modules/plugins/lsp/presets/default.nix @@ -9,6 +9,7 @@ ./ccls.nix ./clangd.nix ./clojure-lsp.nix + ./csharp_ls.nix ./cue.nix ./dart.nix ./deno.nix From f6812858c0aa213c84bb838f08a6d044c95c1217 Mon Sep 17 00:00:00 2001 From: CaueAnjos <141049846+CaueAnjos@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:19:10 -0300 Subject: [PATCH 2/8] lsp/presets/omnisharp: init --- modules/plugins/lsp/presets/default.nix | 1 + modules/plugins/lsp/presets/omnisharp.nix | 98 +++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 modules/plugins/lsp/presets/omnisharp.nix diff --git a/modules/plugins/lsp/presets/default.nix b/modules/plugins/lsp/presets/default.nix index abd8346d..bb3ce502 100644 --- a/modules/plugins/lsp/presets/default.nix +++ b/modules/plugins/lsp/presets/default.nix @@ -40,6 +40,7 @@ ./nushell.nix ./ocaml-lsp.nix ./ols.nix + ./omnisharp.nix ./openscad-lsp.nix ./phan.nix ./phpactor.nix diff --git a/modules/plugins/lsp/presets/omnisharp.nix b/modules/plugins/lsp/presets/omnisharp.nix new file mode 100644 index 00000000..7ddd4b48 --- /dev/null +++ b/modules/plugins/lsp/presets/omnisharp.nix @@ -0,0 +1,98 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib.meta) getExe; + inherit (lib.modules) mkIf; + inherit (lib.nvim.types) mkLspPresetEnableOption; + inherit (lib.generators) mkLuaInline; + + cfg = config.vim.lsp.presets.omnisharp; +in { + # HACK: this server should be named `omnisharp-roslyn`, but the extension `omnisharp-extended-lsp-nvim` only works if it is named `omnisharp` + options.vim.lsp.presets.omnisharp = { + enable = mkLspPresetEnableOption "omnisharp" "OmniSharp Roslyn" []; + }; + + config = mkIf cfg.enable { + vim.lsp.servers.omnisharp = { + cmd = mkLuaInline '' + { + '${getExe pkgs.omnisharp-roslyn}', + '-z', -- https://github.com/OmniSharp/omnisharp-vscode/pull/4300 + '--hostPID', + tostring(vim.fn.getpid()), + 'DotNet:enablePackageRestore=false', + '--encoding', + 'utf-8', + '--languageserver', + } + ''; + root_dir = mkLuaInline '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + on_dir( + util.root_pattern '*.slnx'(fname) + or util.root_pattern '*.sln'(fname) + or util.root_pattern '*.csproj'(fname) + or util.root_pattern 'omnisharp.json'(fname) + or util.root_pattern 'function.json'(fname) + ) + end + ''; + init_options = {}; + capabilities = { + workspace = { + workspaceFolders = false; # https://github.com/OmniSharp/omnisharp-roslyn/issues/909 + }; + }; + settings = { + FormattingOptions = { + # Enables support for reading code style, naming convention and analyzer + # settings from .editorconfig. + EnableEditorConfigSupport = true; + # Specifies whether 'using' directives should be grouped and sorted during + # document formatting. + OrganizeImports = null; + }; + MsBuild = { + # If true, MSBuild project system will only load projects for files that + # were opened in the editor. This setting is useful for big C# codebases + # and allows for faster initialization of code navigation features only + # for projects that are relevant to code that is being edited. With this + # setting enabled OmniSharp may load fewer projects and may thus display + # incomplete reference lists for symbols. + LoadProjectsOnDemand = null; + }; + RoslynExtensionsOptions = { + # Enables support for roslyn analyzers, code fixes and rulesets. + EnableAnalyzersSupport = null; + # Enables support for showing unimported types and unimported extension + # methods in completion lists. When committed, the appropriate using + # directive will be added at the top of the current file. This option can + # have a negative impact on initial completion responsiveness; + # particularly for the first few completion sessions after opening a + # solution. + EnableImportCompletion = null; + # Only run analyzers against open files when 'enableRoslynAnalyzers' is + # true + AnalyzeOpenDocumentsOnly = null; + # Enables the possibility to see the code in external nuget dependencies + EnableDecompilationSupport = null; + }; + RenameOptions = { + RenameInComments = null; + RenameOverloads = null; + RenameInStrings = null; + }; + Sdk = { + # Specifies whether to include preview versions of the .NET SDK when + # determining which version to use for project loading. + IncludePrereleases = true; + }; + }; + }; + }; +} From 6ef413e526d1141ad55fe4a12c87ed16d74c8044 Mon Sep 17 00:00:00 2001 From: CaueAnjos <141049846+CaueAnjos@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:19:34 -0300 Subject: [PATCH 3/8] lsp/presets/roslyn-ls: init --- docs/manual/release-notes/rl-0.9.md | 4 + modules/plugins/lsp/presets/default.nix | 1 + modules/plugins/lsp/presets/roslyn-ls.nix | 402 ++++++++++++++++++++++ 3 files changed, 407 insertions(+) create mode 100644 modules/plugins/lsp/presets/roslyn-ls.nix diff --git a/docs/manual/release-notes/rl-0.9.md b/docs/manual/release-notes/rl-0.9.md index 995d74f0..a479d6c6 100644 --- a/docs/manual/release-notes/rl-0.9.md +++ b/docs/manual/release-notes/rl-0.9.md @@ -108,6 +108,10 @@ `languages.go.treesitter.gotmplPackage` to {option}`vim.languages.go.treesitter.gotmpl.package` +[CaueAnjos](https://github.com/caueanjos) + +- Renamed `roslyn_ls` to `roslyn-ls` + ## Changelog {#sec-release-0-9-changelog} [SecBear](https://github.com/SecBear): diff --git a/modules/plugins/lsp/presets/default.nix b/modules/plugins/lsp/presets/default.nix index bb3ce502..8118f4c5 100644 --- a/modules/plugins/lsp/presets/default.nix +++ b/modules/plugins/lsp/presets/default.nix @@ -49,6 +49,7 @@ ./python-lsp-server.nix ./qmlls.nix ./r-languageserver.nix + ./roslyn-ls.nix ./ruby-lsp.nix ./ruff.nix ./rumdl.nix diff --git a/modules/plugins/lsp/presets/roslyn-ls.nix b/modules/plugins/lsp/presets/roslyn-ls.nix new file mode 100644 index 00000000..f24f5a73 --- /dev/null +++ b/modules/plugins/lsp/presets/roslyn-ls.nix @@ -0,0 +1,402 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib.meta) getExe; + inherit (lib.modules) mkIf; + inherit (lib.nvim.types) mkLspPresetEnableOption; + inherit (lib.generators) mkLuaInline; + + cfg = config.vim.lsp.presets.roslyn-ls; +in { + options.vim.lsp.presets.roslyn-ls = { + enable = mkLspPresetEnableOption "roslyn-ls" "Roslyn" []; + }; + + config = mkIf cfg.enable { + vim.lsp.servers.roslyn-ls = { + cmd = mkLuaInline '' + { + '${getExe pkgs.roslyn-ls}', + '--logLevel', + 'Information', + '--extensionLogDirectory', + vim.fs.joinpath(vim.uv.os_tmpdir(), 'roslyn_ls/logs'), + '--stdio', + } + ''; + cmd_env = mkLuaInline '' + { + -- Fixes LSP navigation in decompiled files for systems with symlinked TMPDIR (macOS) + TMPDIR = vim.env.TMPDIR and vim.env.TMPDIR ~= "" and vim.fn.resolve(vim.env.TMPDIR) or nil, + } + ''; + handlers = { + "workspace/projectInitializationComplete" = mkLuaInline '' + function(_, _, ctx) + vim.notify('Roslyn project initialization complete', vim.log.levels.INFO, { title = 'roslyn_ls' }) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + + local function refresh_diagnostics(client) + for buf, _ in pairs(vim.lsp.get_client_by_id(client.id).attached_buffers) do + if vim.api.nvim_buf_is_loaded(buf) then + client:request( + vim.lsp.protocol.Methods.textDocument_diagnostic, + { textDocument = vim.lsp.util.make_text_document_params(buf) }, + nil, + buf + ) + end + end + end + + refresh_diagnostics(client) + return vim.NIL + end + ''; + "workspace/_roslyn_projectNeedsRestore" = mkLuaInline '' + function(_, result, ctx) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + + ---@diagnostic disable-next-line: param-type-mismatch + client:request('workspace/_roslyn_restore', result, function(err, response) + if err then + vim.notify(err.message, vim.log.levels.ERROR, { title = 'roslyn_ls' }) + end + if response then + for _, v in ipairs(response) do + vim.notify(v.message, vim.log.levels.INFO, { title = 'roslyn_ls' }) + end + end + end) + + return vim.NIL + end + ''; + "razor/provideDynamicFileInfo" = mkLuaInline '' + function(_, _, _) + vim.notify( + 'Razor is not supported.\nPlease use https://github.com/tris203/rzls.nvim', + vim.log.levels.WARN, + { title = 'roslyn_ls' } + ) + return vim.NIL + end + ''; + }; + commands = { + "roslyn.client.completionComplexEdit" = mkLuaInline '' + function(command, ctx) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + local args = command.arguments or {} + local uri, edit = args[1], args[2] + + ---@diagnostic disable: undefined-field + if uri and edit and edit.newText and edit.range then + local workspace_edit = { + changes = { + [uri.uri] = { + { + range = edit.range, + newText = edit.newText, + }, + }, + }, + } + vim.lsp.util.apply_workspace_edit(workspace_edit, client.offset_encoding) + ---@diagnostic enable: undefined-field + else + vim.notify('roslyn_ls: completionComplexEdit args not understood: ' .. vim.inspect(args), vim.log.levels.WARN) + end + end + ''; + + "roslyn.client.nestedCodeAction" = mkLuaInline '' + function(command, ctx) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + local arg = command.arguments and command.arguments[1] + + if type(arg) ~= 'table' then + vim.notify('roslyn_ls: invalid nestedCodeAction arguments', vim.log.levels.ERROR) + return + end + + local function handle(action) + if not action then + return + end + + if action.data and not action.edit and not action.command then + client:request('codeAction/resolve', action, function(err, resolved) + if err then + vim.notify(err.message or tostring(err), vim.log.levels.ERROR) + return + end + if resolved then + handle(resolved) + end + end, ctx.bufnr) + return + end + + local nested = vim.islist(action) and action or action.NestedCodeActions + if type(nested) ~= 'table' or vim.tbl_isempty(nested) then + local function apply_action(client, action) + if action.edit then + vim.lsp.util.apply_workspace_edit(action.edit, client.offset_encoding) + end + if action.command then + client:exec_cmd(action.command) + end + end + apply_action(client, action) + return + end + + if #nested == 1 then + handle(nested[1]) + return + end + + vim.ui.select(nested, { + prompt = action.title or 'Select code action', + format_item = function(item) + return item.title or (item.command and item.command.title) or 'Unnamed action' + end, + }, function(choice) + if choice then + handle(choice) + end + end) + end + + handle(arg) + end + ''; + + "roslyn.client.fixAllCodeAction" = mkLuaInline '' + function(command, ctx) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + + local function handle_fix_all_action(client, command, bufnr) + local arg = command.arguments and command.arguments[1] + if type(arg) ~= 'table' then + vim.notify('roslyn_ls: invalid fixAllCodeAction arguments', vim.log.levels.ERROR) + return + end + + local flavors = arg.FixAllFlavors + if type(flavors) ~= 'table' or vim.tbl_isempty(flavors) then + vim.notify('roslyn_ls: fixAllCodeAction has no FixAllFlavors', vim.log.levels.WARN) + return + end + + vim.ui.select(flavors, { + prompt = 'Fix All Scope:', + }, function(chosen_scope) + if not chosen_scope then + return + end + + client:request('codeAction/resolveFixAll', { + title = command.title, + data = arg, + scope = chosen_scope, + }, function(err, resolved) + if err then + vim.notify( + 'roslyn_ls: fixAllCodeAction resolve error: ' .. (err.message or tostring(err)), + vim.log.levels.ERROR + ) + return + end + if resolved then + local function apply_action(client, action) + if action.edit then + vim.lsp.util.apply_workspace_edit(action.edit, client.offset_encoding) + end + if action.command then + client:exec_cmd(action.command) + end + end + apply_action(client, resolved) + end + end, bufnr) + end) + end + + handle_fix_all_action(client, command, ctx.bufnr) + end + ''; + }; + root_dir = mkLuaInline '' + function(bufnr, cb) + local bufname = vim.api.nvim_buf_get_name(bufnr) + + local function is_decompiled(bufname) + local _, endpos = bufname:find('[/\\]MetadataAsSource[/\\]') + if endpos == nil then + return false + end + return vim.fn.finddir(bufname:sub(1, endpos), vim.uv.os_tmpdir()) ~= "" + end + + -- don't try to find sln or csproj for files from libraries + -- outside of the project + if not is_decompiled(bufname) then + -- try find solutions root first + local root_dir = vim.fs.root(bufnr, function(fname, _) + return fname:match('%.sln[x]?$') ~= nil + end) + + if not root_dir then + -- try find projects root + root_dir = vim.fs.root(bufnr, function(fname, _) + return fname:match('%.csproj$') ~= nil + end) + end + + if root_dir then + cb(root_dir) + end + else + -- Decompiled code (example: "/tmp/MetadataAsSource/f2bfba/DecompilationMetadataAsSourceFileProvider/d5782a/Console.cs") + local prev_buf = vim.fn.bufnr('#') + local client = vim.lsp.get_clients({ + name = 'roslyn_ls', + bufnr = prev_buf ~= 1 and prev_buf or nil, + })[1] + if client then + cb(client.config.root_dir) + end + end + end + ''; + on_init = [ + (mkLuaInline + '' + function(client) + local root_dir = client.config.root_dir + + local function on_init_sln(client, target) + vim.notify('Initializing: ' .. target, vim.log.levels.TRACE, { title = 'roslyn_ls' }) + ---@diagnostic disable-next-line: param-type-mismatch + client:notify('solution/open', { + solution = vim.uri_from_fname(target), + }) + end + + + local function on_init_project(client, project_files) + vim.notify('Initializing: projects', vim.log.levels.TRACE, { title = 'roslyn_ls' }) + ---@diagnostic disable-next-line: param-type-mismatch + client:notify('project/open', { + projects = vim.tbl_map(function(file) + return vim.uri_from_fname(file) + end, project_files), + }) + end + + -- try load first solution we find + for entry, type in vim.fs.dir(root_dir) do + if type == 'file' and (vim.endswith(entry, '.sln') or vim.endswith(entry, '.slnx')) then + on_init_sln(client, vim.fs.joinpath(root_dir, entry)) + return + end + end + + -- if no solution is found load project + for entry, type in vim.fs.dir(root_dir) do + if type == 'file' and vim.endswith(entry, '.csproj') then + on_init_project(client, { vim.fs.joinpath(root_dir, entry) }) + end + end + end + '') + ]; + on_attach = mkLuaInline '' + function(client, bufnr) + -- avoid duplicate autocmds for same buffer + if vim.api.nvim_get_autocmds({ buffer = bufnr, group = group })[1] then + return + end + + local function refresh_diagnostics(client) + for buf, _ in pairs(vim.lsp.get_client_by_id(client.id).attached_buffers) do + if vim.api.nvim_buf_is_loaded(buf) then + client:request( + vim.lsp.protocol.Methods.textDocument_diagnostic, + { textDocument = vim.lsp.util.make_text_document_params(buf) }, + nil, + buf + ) + end + end + end + + vim.api.nvim_create_autocmd({ 'BufWritePost', 'InsertLeave' }, { + group = group, + buffer = bufnr, + callback = function() + local function refresh_diagnostics(client) + for buf, _ in pairs(vim.lsp.get_client_by_id(client.id).attached_buffers) do + if vim.api.nvim_buf_is_loaded(buf) then + client:request( + vim.lsp.protocol.Methods.textDocument_diagnostic, + { textDocument = vim.lsp.util.make_text_document_params(buf) }, + nil, + buf + ) + end + end + end + refresh_diagnostics(client) + end, + desc = 'roslyn_ls: refresh diagnostics', + }) + end + ''; + capabilities = { + # HACK: Doesn't show any diagnostics if we do not set this to true + textDocument = { + diagnostic = { + dynamicRegistration = true; + }; + }; + }; + settings = { + "csharp|background_analysis" = { + dotnet_analyzer_diagnostics_scope = "fullSolution"; + dotnet_compiler_diagnostics_scope = "fullSolution"; + }; + "csharp|inlay_hints" = { + csharp_enable_inlay_hints_for_implicit_object_creation = true; + csharp_enable_inlay_hints_for_implicit_variable_types = true; + csharp_enable_inlay_hints_for_lambda_parameter_types = true; + csharp_enable_inlay_hints_for_types = true; + dotnet_enable_inlay_hints_for_indexer_parameters = true; + dotnet_enable_inlay_hints_for_literal_parameters = true; + dotnet_enable_inlay_hints_for_object_creation_parameters = true; + dotnet_enable_inlay_hints_for_other_parameters = true; + dotnet_enable_inlay_hints_for_parameters = true; + dotnet_suppress_inlay_hints_for_parameters_that_differ_only_by_suffix = true; + dotnet_suppress_inlay_hints_for_parameters_that_match_argument_name = true; + dotnet_suppress_inlay_hints_for_parameters_that_match_method_intent = true; + }; + "csharp|symbol_search" = { + dotnet_search_reference_assemblies = true; + }; + "csharp|completion" = { + dotnet_show_name_completion_suggestions = true; + dotnet_show_completion_items_from_unimported_namespaces = true; + dotnet_provide_regex_completions = true; + }; + "csharp|code_lens" = { + dotnet_enable_references_code_lens = true; + }; + }; + }; + }; +} From 99c2594edde40c1c818e27b386dae990541ef005 Mon Sep 17 00:00:00 2001 From: CaueAnjos <141049846+CaueAnjos@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:48:05 -0300 Subject: [PATCH 4/8] languages/csharp: update lsp servers --- docs/manual/release-notes/rl-0.9.md | 2 +- modules/plugins/languages/csharp.nix | 219 +++------------------------ 2 files changed, 18 insertions(+), 203 deletions(-) diff --git a/docs/manual/release-notes/rl-0.9.md b/docs/manual/release-notes/rl-0.9.md index a479d6c6..82c5b601 100644 --- a/docs/manual/release-notes/rl-0.9.md +++ b/docs/manual/release-notes/rl-0.9.md @@ -491,7 +491,7 @@ https://github.com/gorbit99/codewindow.nvim [CaueAnjos](https://github.com/caueanjos) -- Add razor support for `roslyn_ls` and `csharp_ls` +- Added razor support for `roslyn_ls` and `csharp_ls` [mputz86](https://github.com/mputz86) diff --git a/modules/plugins/languages/csharp.nix b/modules/plugins/languages/csharp.nix index f606c603..e7ca2d39 100644 --- a/modules/plugins/languages/csharp.nix +++ b/modules/plugins/languages/csharp.nix @@ -2,201 +2,14 @@ lib, pkgs, config, - options, ... }: let - inherit (builtins) attrNames concatMap; + inherit (builtins) concatMap; + inherit (lib) genAttrs; inherit (lib.options) mkEnableOption mkOption literalExpression; - inherit (lib.types) enum; + inherit (lib.types) enum listOf; inherit (lib.modules) mkIf mkMerge; - inherit (lib.meta) getExe; - inherit (lib.generators) mkLuaInline; - inherit (lib.strings) optionalString; - inherit (lib.nvim.types) mkGrammarOption mkPluginSetupOption deprecatedSingleOrListOf; - inherit (lib.nvim.lua) toLuaObject; - inherit (lib.nvim.attrsets) mapListToAttrs; - - lspKeyConfig = config.vim.lsp.mappings; - lspKeyOptions = options.vim.lsp.mappings; - mkLspBinding = optionName: action: let - key = lspKeyConfig.${optionName}; - desc = lspKeyOptions.${optionName}.description; - in - optionalString (key != null) "vim.keymap.set('n', '${key}', ${action}, {buffer=bufnr, noremap=true, silent=true, desc='${desc}'})"; - - defaultServers = ["csharp_ls"]; - servers = { - omnisharp = { - cmd = mkLuaInline '' - { - ${toLuaObject (getExe pkgs.omnisharp-roslyn)}, - '-z', -- https://github.com/OmniSharp/omnisharp-vscode/pull/4300 - '--hostPID', - tostring(vim.fn.getpid()), - 'DotNet:enablePackageRestore=false', - '--encoding', - 'utf-8', - '--languageserver', - } - ''; - filetypes = ["cs" "vb"]; - root_dir = mkLuaInline '' - function(bufnr, on_dir) - local function find_root_pattern(fname, lua_pattern) - return vim.fs.root(0, function(name, path) - return name:match(lua_pattern) - end) - end - - local fname = vim.api.nvim_buf_get_name(bufnr) - on_dir(find_root_pattern(fname, "%.sln$") or find_root_pattern(fname, "%.csproj$")) - end - ''; - init_options = {}; - capabilities = { - workspace = { - workspaceFolders = false; # https://github.com/OmniSharp/omnisharp-roslyn/issues/909 - }; - }; - on_attach = mkLuaInline '' - function(client, bufnr) - local oe = require("omnisharp_extended") - ${mkLspBinding "goToDefinition" "oe.lsp_definition"} - ${mkLspBinding "goToType" "oe.lsp_type_definition"} - ${mkLspBinding "listReferences" "oe.lsp_references"} - ${mkLspBinding "listImplementations" "oe.lsp_implementation"} - end - ''; - settings = { - FormattingOptions = { - # Enables support for reading code style, naming convention and analyzer - # settings from .editorconfig. - EnableEditorConfigSupport = true; - # Specifies whether 'using' directives should be grouped and sorted during - # document formatting. - OrganizeImports = null; - }; - MsBuild = { - # If true, MSBuild project system will only load projects for files that - # were opened in the editor. This setting is useful for big C# codebases - # and allows for faster initialization of code navigation features only - # for projects that are relevant to code that is being edited. With this - # setting enabled OmniSharp may load fewer projects and may thus display - # incomplete reference lists for symbols. - LoadProjectsOnDemand = null; - }; - RoslynExtensionsOptions = { - # Enables support for roslyn analyzers, code fixes and rulesets. - EnableAnalyzersSupport = null; - # Enables support for showing unimported types and unimported extension - # methods in completion lists. When committed, the appropriate using - # directive will be added at the top of the current file. This option can - # have a negative impact on initial completion responsiveness; - # particularly for the first few completion sessions after opening a - # solution. - EnableImportCompletion = null; - # Only run analyzers against open files when 'enableRoslynAnalyzers' is - # true - AnalyzeOpenDocumentsOnly = null; - # Enables the possibility to see the code in external nuget dependencies - EnableDecompilationSupport = null; - }; - RenameOptions = { - RenameInComments = null; - RenameOverloads = null; - RenameInStrings = null; - }; - Sdk = { - # Specifies whether to include preview versions of the .NET SDK when - # determining which version to use for project loading. - IncludePrereleases = true; - }; - }; - }; - - csharp_ls = { - cmd = [(lib.getExe pkgs.csharp-ls) "--features" "razor-support"]; - filetypes = ["cs" "razor"]; - root_dir = mkLuaInline '' - function(bufnr, on_dir) - local function find_root_pattern(fname, lua_pattern) - return vim.fs.root(0, function(name, path) - return name:match(lua_pattern) - end) - end - - local fname = vim.api.nvim_buf_get_name(bufnr) - on_dir(find_root_pattern(fname, "%.sln$") or find_root_pattern(fname, "%.csproj$")) - end - ''; - init_options = { - AutomaticWorkspaceInit = true; - }; - }; - - roslyn_ls = { - cmd = mkLuaInline '' - { - ${toLuaObject (getExe pkgs.roslyn-ls)}, - '--logLevel=Warning', - '--extensionLogDirectory=' .. vim.fs.dirname(vim.lsp.get_log_path()), - '--stdio', - } - ''; - - filetypes = ["cs"]; - root_dir = mkLuaInline '' - function(bufnr, on_dir) - local function find_root_pattern(fname, lua_pattern) - return vim.fs.root(0, function(name, path) - return name:match(lua_pattern) - end) - end - - local fname = vim.api.nvim_buf_get_name(bufnr) - on_dir(find_root_pattern(fname, "%.sln$") or find_root_pattern(fname, "%.csproj$")) - end - ''; - init_options = {}; - }; - - roslyn = let - pkg = pkgs.vscode-extensions.ms-dotnettools.csharp; - pluginRoot = "${pkg}/share/vscode/extensions/ms-dotnettools.csharp"; - exe = "${pluginRoot}/.roslyn/Microsoft.CodeAnalysis.LanguageServer"; - razorSourceGenerator = "${pluginRoot}/.razorExtension/Microsoft.CodeAnalysis.LanguageServer"; - razorDesignTimePath = "${pluginRoot}/.razorExtension/Targets/Microsoft.NET.Sdk.Razor.DesignTime.targets"; - razorExtension = "${pluginRoot}/.razorExtension/Microsoft.VisualStudioCode.RazorExtension.dll"; - in { - cmd = mkLuaInline '' - { - "dotnet", - "${exe}.dll", - "--stdio", - "--logLevel=Information", - "--extensionLogDirectory=" .. vim.fs.dirname(vim.lsp.get_log_path()), - "--razorSourceGenerator=${razorSourceGenerator}", - "--razorDesignTimePath=${razorDesignTimePath}", - "--extension=${razorExtension}", - } - ''; - - filetypes = ["cs" "razor"]; - root_dir = mkLuaInline '' - function(bufnr, on_dir) - local function find_root_pattern(fname, lua_pattern) - return vim.fs.root(0, function(name, path) - return name:match(lua_pattern) - end) - end - - local fname = vim.api.nvim_buf_get_name(bufnr) - on_dir(find_root_pattern(fname, "%.sln$") or find_root_pattern(fname, "%.csproj$")) - end - ''; - init_options = {}; - }; - }; + inherit (lib.nvim.types) mkGrammarOption mkPluginSetupOption; extraServerPlugins = { omnisharp = ["omnisharp-extended-lsp-nvim"]; @@ -204,6 +17,8 @@ roslyn_ls = []; roslyn = ["roslyn-nvim"]; }; + defaultServers = ["csharp-ls"]; + servers = ["csharp-ls" "omnisharp" "roslyn-ls"]; cfg = config.vim.languages.csharp; in { @@ -214,15 +29,15 @@ in { ::: {.note} This feature will not work if the .NET SDK is not installed. - Both `roslyn` (with `roslyn-nvim`) and `csharp_ls` require the .NET SDK to function properly with Razor. + Both `roslyn-ls` (with `roslyn-nvim`) and `csharp-ls` require the .NET SDK to function properly with Razor. Ensure that the .NET SDK is installed. Check for version compatibility for optimal performance. ::: ::: {.warning} - At the moment, only `roslyn`(with roslyn-nvim) provides full Razor support. - `csharp_ls` is limited to `.cshtml` files. + At the moment, only `roslyn-ls`(with roslyn-nvim) provides full Razor support. + `csharp-ls` is limited to `.cshtml` files. ::: ''; @@ -232,7 +47,7 @@ in { Roslyn LSP plugin for neovim ::: {.note} - This feature only works for `roslyn` (not `roslyn_ls`). + This feature only works for `roslyn-ls`. ::: ''; setupOpts = mkPluginSetupOption "roslyn-nvim" {}; @@ -259,7 +74,7 @@ in { }; servers = mkOption { description = "C# LSP server to use"; - type = deprecatedSingleOrListOf "vim.language.csharp.lsp.servers" (enum (attrNames servers)); + type = listOf (enum servers); default = defaultServers; }; }; @@ -288,12 +103,12 @@ in { }, } ''; - lsp.servers = - mapListToAttrs (name: { - inherit name; - value = servers.${name}; - }) - cfg.lsp.servers; + lsp = { + presets = genAttrs cfg.lsp.servers (_: {enable = true;}); + servers = genAttrs cfg.lsp.servers (_: { + filetypes = ["cs" "razor" "vb"]; + }); + }; }; }) (mkIf cfg.extensions.roslyn-nvim.enable { From c73e781a3d9cc212a57b097c2db236dbc3a259d1 Mon Sep 17 00:00:00 2001 From: CaueAnjos <141049846+CaueAnjos@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:05:30 -0300 Subject: [PATCH 5/8] languages/csharp: add roslyn-nvim extension --- modules/plugins/languages/csharp.nix | 77 +++++++++++++++++++++++----- npins/sources.json | 6 +-- 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/modules/plugins/languages/csharp.nix b/modules/plugins/languages/csharp.nix index e7ca2d39..8ab81f6c 100644 --- a/modules/plugins/languages/csharp.nix +++ b/modules/plugins/languages/csharp.nix @@ -5,17 +5,20 @@ ... }: let inherit (builtins) concatMap; + inherit (builtins) elem; inherit (lib) genAttrs; + inherit (lib.generators) mkLuaInline; inherit (lib.options) mkEnableOption mkOption literalExpression; inherit (lib.types) enum listOf; inherit (lib.modules) mkIf mkMerge; - inherit (lib.nvim.types) mkGrammarOption mkPluginSetupOption; + inherit (lib.nvim.types) mkGrammarOption mkPluginSetupOption enumWithRename luaInline; + inherit (lib.nvim.lua) toLuaObject; + inherit (lib.nvim.dag) entryAnywhere; extraServerPlugins = { omnisharp = ["omnisharp-extended-lsp-nvim"]; csharp_ls = ["csharpls-extended-lsp-nvim"]; - roslyn_ls = []; - roslyn = ["roslyn-nvim"]; + roslyn-ls = []; }; defaultServers = ["csharp-ls"]; servers = ["csharp-ls" "omnisharp" "roslyn-ls"]; @@ -44,13 +47,56 @@ in { extensions = { roslyn-nvim = { enable = mkEnableOption '' - Roslyn LSP plugin for neovim + Roslyn LSP plugin for Neovim that adds Razor support and works with multiple solutions ::: {.note} This feature only works for `roslyn-ls`. ::: ''; - setupOpts = mkPluginSetupOption "roslyn-nvim" {}; + setupOpts = mkPluginSetupOption "roslyn-nvim" { + filewatching = mkOption { + description = '' + "auto" | "roslyn" | "off" + + - "auto": Does nothing for filewatching, leaving everything as default + - "roslyn": Turns off neovim filewatching which will make roslyn do the filewatching + - "off": Hack to turn off all filewatching. + + ::: {.tip} + Set to "off" if you notice performance issues + ::: + ''; + type = enum ["auto" "roslyn" "off"]; + default = "auto"; + }; + extensions.razor = { + enabled = + (mkEnableOption "Additional roslyn extensions (for example Roslynator/Razor)") + // {default = true;}; + config = mkOption { + description = "Configuration for the additional roslyn extensions"; + type = luaInline; + default = let + pkg = pkgs.vscode-extensions.ms-dotnettools.csharp; + pluginRoot = "${pkg}/share/vscode/extensions/ms-dotnettools.csharp"; + razorExtension = "${pluginRoot}/.razorExtension/Microsoft.VisualStudioCode.RazorExtension.dll"; + razorSourceGenerator = "${pluginRoot}/.razorExtension/Microsoft.CodeAnalysis.Razor.Compiler.dll"; + razorDesignTimePath = "${pluginRoot}/.razorExtension/Targets/Microsoft.NET.Sdk.Razor.DesignTime.targets"; + in + mkLuaInline '' + function() + return { + path = '${razorExtension}', + args = { + '--razorSourceGenerator=${razorSourceGenerator}', + '--razorDesignTimePath=${razorDesignTimePath}', + }, + } + end + ''; + }; + }; + }; }; }; @@ -74,7 +120,11 @@ in { }; servers = mkOption { description = "C# LSP server to use"; - type = listOf (enum servers); + type = listOf (enumWithRename + "vim.languages.csharp.lsp.servers" + servers { + roslyn_ls = "roslyn-ls"; + }); default = defaultServers; }; }; @@ -111,12 +161,15 @@ in { }; }; }) - (mkIf cfg.extensions.roslyn-nvim.enable { - vim = mkMerge [ - { - startPlugins = ["roslyn-nvim"]; - } - ]; + (mkIf (cfg.lsp.enable + && cfg.extensions.roslyn-nvim.enable + && (elem "roslyn-ls" cfg.lsp.servers)) { + vim = { + startPlugins = ["roslyn-nvim"]; + pluginRC.roslyn-nvim = entryAnywhere "require('roslyn').setup(${toLuaObject cfg.extensions.roslyn-nvim.setupOpts})"; + lsp.servers.roslyn-ls.enable = false; + extraPackages = with pkgs; [roslyn-ls]; + }; }) ]); } diff --git a/npins/sources.json b/npins/sources.json index 51a79319..1e2b4526 100644 --- a/npins/sources.json +++ b/npins/sources.json @@ -2524,9 +2524,9 @@ }, "branch": "main", "submodules": false, - "revision": "24f7c91ee5e09c63104deaab68f932620f25c24a", - "url": "https://github.com/seblyng/roslyn.nvim/archive/24f7c91ee5e09c63104deaab68f932620f25c24a.tar.gz", - "hash": "sha256-a/Slmkrz/4P/rfRhPa1W5kGV7joQNTN0Un7bbncCnk0=" + "revision": "946e559db19bfc5c7bf8be524a9c1acfde8ffffe", + "url": "https://github.com/seblyng/roslyn.nvim/archive/946e559db19bfc5c7bf8be524a9c1acfde8ffffe.tar.gz", + "hash": "sha256-pkWZ9z2eLnaVUAjTaj1+AdgqR1GLY+RT6pxbpe50SOU=" }, "rtp-nvim": { "type": "Git", From 3c8885b75a42d6cc3b95a10985170c055d4edb0f Mon Sep 17 00:00:00 2001 From: CaueAnjos <141049846+CaueAnjos@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:16:40 -0300 Subject: [PATCH 6/8] languages/csharp: add omnisharp-extended-lsp-nvim extension --- docs/manual/release-notes/rl-0.9.md | 1 + modules/plugins/languages/csharp.nix | 69 ++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/docs/manual/release-notes/rl-0.9.md b/docs/manual/release-notes/rl-0.9.md index 82c5b601..74a48ae3 100644 --- a/docs/manual/release-notes/rl-0.9.md +++ b/docs/manual/release-notes/rl-0.9.md @@ -111,6 +111,7 @@ [CaueAnjos](https://github.com/caueanjos) - Renamed `roslyn_ls` to `roslyn-ls` +- Turned `omnisharp-extended-lsp-nvim` into an extension disabled by default ## Changelog {#sec-release-0-9-changelog} diff --git a/modules/plugins/languages/csharp.nix b/modules/plugins/languages/csharp.nix index 8ab81f6c..7ae6b555 100644 --- a/modules/plugins/languages/csharp.nix +++ b/modules/plugins/languages/csharp.nix @@ -2,6 +2,7 @@ lib, pkgs, config, + options, ... }: let inherit (builtins) concatMap; @@ -9,16 +10,18 @@ inherit (lib) genAttrs; inherit (lib.generators) mkLuaInline; inherit (lib.options) mkEnableOption mkOption literalExpression; + inherit (config.vim.lib) mkMappingOption; inherit (lib.types) enum listOf; inherit (lib.modules) mkIf mkMerge; inherit (lib.nvim.types) mkGrammarOption mkPluginSetupOption enumWithRename luaInline; inherit (lib.nvim.lua) toLuaObject; inherit (lib.nvim.dag) entryAnywhere; + inherit (lib.nvim.binds) addDescriptionsToMappings; extraServerPlugins = { - omnisharp = ["omnisharp-extended-lsp-nvim"]; csharp_ls = ["csharpls-extended-lsp-nvim"]; roslyn-ls = []; + omnisharp = []; }; defaultServers = ["csharp-ls"]; servers = ["csharp-ls" "omnisharp" "roslyn-ls"]; @@ -98,6 +101,23 @@ in { }; }; }; + omnisharp-extended-lsp-nvim = { + enable = mkEnableOption '' + Extended 'textDocument/definition' handler for OmniSharp Neovim LSP + + ::: {.note} + This feature only works for `omnisharp`. + ::: + ''; + mappings = let + inherit (config.vim.lsp) mappings; + in { + goToDefinition = mkMappingOption "Go to definition [omnisharp-extended-lsp-nvim]" mappings.goToDefinition; + goToType = mkMappingOption "Go to type [omnisharp-extended-lsp-nvim]" mappings.goToType; + listReferences = mkMappingOption "List references [omnisharp-extended-lsp-nvim]" mappings.listReferences; + listImplementations = mkMappingOption "List implementations [omnisharp-extended-lsp-nvim]" mappings.listImplementations; + }; + }; }; treesitter = { @@ -139,20 +159,15 @@ in { (mkIf cfg.lsp.enable { vim = { - startPlugins = concatMap (server: extraServerPlugins.${server}) cfg.lsp.servers; - luaConfigRC.razorFileTypes = - /* - lua - */ - '' - -- Set unknown file types! - vim.filetype.add { - extension = { - razor = "razor", - cshtml = "razor", - }, - } - ''; + luaConfigRC.razorFileTypes = '' + -- Set unknown file types! + vim.filetype.add { + extension = { + razor = "razor", + cshtml = "razor", + }, + } + ''; lsp = { presets = genAttrs cfg.lsp.servers (_: {enable = true;}); servers = genAttrs cfg.lsp.servers (_: { @@ -171,5 +186,29 @@ in { extraPackages = with pkgs; [roslyn-ls]; }; }) + (mkIf (cfg.lsp.enable + && cfg.extensions.omnisharp-extended-lsp-nvim.enable + && (elem "omnisharp" cfg.lsp.servers)) { + vim = { + startPlugins = ["omnisharp-extended-lsp-nvim"]; + lsp.servers.omnisharp.on_attach = let + mappingDefinitions = options.vim.languages.csharp.extensions.omnisharp-extended-lsp-nvim.mappings; + mappings = addDescriptionsToMappings cfg.extensions.omnisharp-extended-lsp-nvim.mappings mappingDefinitions; + mkBinding = binding: action: + if binding.value != null + then "vim.keymap.set('n', ${toLuaObject binding.value}, ${action}, {buffer=bufnr, noremap=true, silent=true, desc=${toLuaObject binding.description}})" + else ""; + in + mkLuaInline + '' + function(client, bufnr) + ${mkBinding mappings.goToDefinition "require('omnisharp_extended').lsp_definition"} + ${mkBinding mappings.goToType "require('omnisharp_extended').lsp_type_definition"} + ${mkBinding mappings.listReferences "require('omnisharp_extended').lsp_references"} + ${mkBinding mappings.listImplementations "require('omnisharp_extended').lsp_implementation"} + end + ''; + }; + }) ]); } From 1628aa93b779aa85fc7ece515c4e47b5f33033a8 Mon Sep 17 00:00:00 2001 From: CaueAnjos <141049846+CaueAnjos@users.noreply.github.com> Date: Sat, 18 Apr 2026 11:51:44 -0300 Subject: [PATCH 7/8] languages/csharp: add csharpls-extended-lsp-nvim extension --- docs/manual/release-notes/rl-0.9.md | 1 + modules/plugins/languages/csharp.nix | 35 ++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/manual/release-notes/rl-0.9.md b/docs/manual/release-notes/rl-0.9.md index 74a48ae3..f0eefabc 100644 --- a/docs/manual/release-notes/rl-0.9.md +++ b/docs/manual/release-notes/rl-0.9.md @@ -112,6 +112,7 @@ - Renamed `roslyn_ls` to `roslyn-ls` - Turned `omnisharp-extended-lsp-nvim` into an extension disabled by default +- Turned `csharpls-extended-lsp-nvim` into an extension disabled by default ## Changelog {#sec-release-0-9-changelog} diff --git a/modules/plugins/languages/csharp.nix b/modules/plugins/languages/csharp.nix index 7ae6b555..cad0ceb7 100644 --- a/modules/plugins/languages/csharp.nix +++ b/modules/plugins/languages/csharp.nix @@ -5,7 +5,6 @@ options, ... }: let - inherit (builtins) concatMap; inherit (builtins) elem; inherit (lib) genAttrs; inherit (lib.generators) mkLuaInline; @@ -18,13 +17,8 @@ inherit (lib.nvim.dag) entryAnywhere; inherit (lib.nvim.binds) addDescriptionsToMappings; - extraServerPlugins = { - csharp_ls = ["csharpls-extended-lsp-nvim"]; - roslyn-ls = []; - omnisharp = []; - }; - defaultServers = ["csharp-ls"]; - servers = ["csharp-ls" "omnisharp" "roslyn-ls"]; + defaultServers = ["csharp_ls"]; + servers = ["csharp_ls" "omnisharp" "roslyn-ls"]; cfg = config.vim.languages.csharp; in { @@ -35,7 +29,7 @@ in { ::: {.note} This feature will not work if the .NET SDK is not installed. - Both `roslyn-ls` (with `roslyn-nvim`) and `csharp-ls` require the .NET SDK to function properly with Razor. + Both `roslyn-ls` (with `roslyn-nvim`) and `csharp_ls` require the .NET SDK to function properly with Razor. Ensure that the .NET SDK is installed. Check for version compatibility for optimal performance. @@ -43,7 +37,7 @@ in { ::: {.warning} At the moment, only `roslyn-ls`(with roslyn-nvim) provides full Razor support. - `csharp-ls` is limited to `.cshtml` files. + `csharp_ls` is limited to `.cshtml` files. ::: ''; @@ -118,6 +112,15 @@ in { listImplementations = mkMappingOption "List implementations [omnisharp-extended-lsp-nvim]" mappings.listImplementations; }; }; + csharpls-extended-lsp-nvim = { + enable = mkEnableOption '' + Extended 'textDocument/definition' handler for csharp_ls Neovim LSP + + ::: {.note} + This feature only works for `csharp_ls`. + ::: + ''; + }; }; treesitter = { @@ -210,5 +213,17 @@ in { ''; }; }) + (mkIf (cfg.lsp.enable + && cfg.extensions.csharpls-extended-lsp-nvim.enable + && (elem "csharp_ls" cfg.lsp.servers)) { + vim = { + startPlugins = ["csharpls-extended-lsp-nvim"]; + lsp.servers.csharp_ls.on_attach = mkLuaInline '' + function(client, bufnr) + require('csharpls_extended').buf_read_cmd_bind() + end + ''; + }; + }) ]); } From e60803d9d636dadbb0cc187e570690d646606b56 Mon Sep 17 00:00:00 2001 From: CaueAnjos <141049846+CaueAnjos@users.noreply.github.com> Date: Sat, 18 Apr 2026 21:50:51 -0300 Subject: [PATCH 8/8] languages/csharp: add csharpier formatter and assertions --- docs/manual/release-notes/rl-0.9.md | 1 + modules/plugins/languages/csharp.nix | 71 +++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/docs/manual/release-notes/rl-0.9.md b/docs/manual/release-notes/rl-0.9.md index f0eefabc..3d1fef06 100644 --- a/docs/manual/release-notes/rl-0.9.md +++ b/docs/manual/release-notes/rl-0.9.md @@ -494,6 +494,7 @@ https://github.com/gorbit99/codewindow.nvim [CaueAnjos](https://github.com/caueanjos) - Added razor support for `roslyn_ls` and `csharp_ls` +- Added `csharpier` formatter to csharp language [mputz86](https://github.com/mputz86) diff --git a/modules/plugins/languages/csharp.nix b/modules/plugins/languages/csharp.nix index cad0ceb7..de552f86 100644 --- a/modules/plugins/languages/csharp.nix +++ b/modules/plugins/languages/csharp.nix @@ -5,8 +5,8 @@ options, ... }: let - inherit (builtins) elem; - inherit (lib) genAttrs; + inherit (builtins) elem filter attrNames; + inherit (lib) genAttrs getExe; inherit (lib.generators) mkLuaInline; inherit (lib.options) mkEnableOption mkOption literalExpression; inherit (config.vim.lib) mkMappingOption; @@ -16,10 +16,26 @@ inherit (lib.nvim.lua) toLuaObject; inherit (lib.nvim.dag) entryAnywhere; inherit (lib.nvim.binds) addDescriptionsToMappings; + inherit (lib.nvim.attrsets) mapListToAttrs; defaultServers = ["csharp_ls"]; servers = ["csharp_ls" "omnisharp" "roslyn-ls"]; + defaultFormat = []; + formats = { + csharpier = { + command = getExe pkgs.csharpier; + }; + }; + + # Verbose names for clarity. + shouldEnableExclusiveLspExtension = extension: lsp: cfg.lsp.enable && cfg.extensions.${extension}.enable && (elem lsp cfg.lsp.servers); + mkAlertForMisuseOfExclusiveLspExtension = extension: lsp: (mkIf (cfg.lsp.enable + && cfg.extensions.${extension}.enable) { + assertion = elem lsp cfg.lsp.servers; + message = "${extension} requires ${lsp} to be listed in vim.languages.csharp.lsp.servers."; + }); + cfg = config.vim.languages.csharp; in { options = { @@ -151,6 +167,21 @@ in { default = defaultServers; }; }; + + format = { + enable = + mkEnableOption "C# formatting" + // { + default = config.vim.languages.enableFormat; + defaultText = literalExpression "config.vim.languages.enableFormat"; + }; + + type = mkOption { + description = "C# formatter to use"; + type = listOf (enum (attrNames formats)); + default = defaultFormat; + }; + }; }; }; @@ -179,9 +210,31 @@ in { }; }; }) - (mkIf (cfg.lsp.enable - && cfg.extensions.roslyn-nvim.enable - && (elem "roslyn-ls" cfg.lsp.servers)) { + + (mkIf cfg.format.enable { + vim.formatter.conform-nvim = { + enable = true; + setupOpts = { + formatters_by_ft.cs = cfg.format.type; + formatters = + mapListToAttrs (name: { + inherit name; + value = formats.${name}; + }) + (filter (name: name != "lsp") cfg.format.type); + }; + }; + }) + + { + assertions = [ + (mkAlertForMisuseOfExclusiveLspExtension "roslyn-nvim" "roslyn-ls") + (mkAlertForMisuseOfExclusiveLspExtension "csharpls-extended-lsp-nvim" "csharp_ls") + (mkAlertForMisuseOfExclusiveLspExtension "omnisharp-extended-lsp-nvim" "omnisharp") + ]; + } + + (mkIf (shouldEnableExclusiveLspExtension "roslyn-nvim" "roslyn-ls") { vim = { startPlugins = ["roslyn-nvim"]; pluginRC.roslyn-nvim = entryAnywhere "require('roslyn').setup(${toLuaObject cfg.extensions.roslyn-nvim.setupOpts})"; @@ -189,9 +242,7 @@ in { extraPackages = with pkgs; [roslyn-ls]; }; }) - (mkIf (cfg.lsp.enable - && cfg.extensions.omnisharp-extended-lsp-nvim.enable - && (elem "omnisharp" cfg.lsp.servers)) { + (mkIf (shouldEnableExclusiveLspExtension "omnisharp-extended-lsp-nvim" "omnisharp") { vim = { startPlugins = ["omnisharp-extended-lsp-nvim"]; lsp.servers.omnisharp.on_attach = let @@ -213,9 +264,7 @@ in { ''; }; }) - (mkIf (cfg.lsp.enable - && cfg.extensions.csharpls-extended-lsp-nvim.enable - && (elem "csharp_ls" cfg.lsp.servers)) { + (mkIf (shouldEnableExclusiveLspExtension "csharpls-extended-lsp-nvim" "csharp_ls") { vim = { startPlugins = ["csharpls-extended-lsp-nvim"]; lsp.servers.csharp_ls.on_attach = mkLuaInline ''