diff --git a/configuration.nix b/configuration.nix index 832bf95..cbab23e 100644 --- a/configuration.nix +++ b/configuration.nix @@ -75,6 +75,7 @@ isMaximal: { enable = isMaximal; crates.enable = isMaximal; }; + csharp.enable = isMaximal; }; visuals = { diff --git a/docs/release-notes/rl-0.7.md b/docs/release-notes/rl-0.7.md index d5f050a..0d560f8 100644 --- a/docs/release-notes/rl-0.7.md +++ b/docs/release-notes/rl-0.7.md @@ -189,6 +189,9 @@ To migrate to `nixfmt`, simply change `vim.languages.nix.format.type` to - Add sorting function options for completion sources under [](#opt-vim.autocomplete.nvim-cmp.setupOpts.sorting.comparators) +- Add C# support under `vim.languages.csharp`, with support for both + omnisharp-roslyn and csharp-language-server. + [Neovim documentation on `vim.cmd`]: https://neovim.io/doc/user/lua.html#vim.cmd() - Make Neovim's configuration file entirely Lua based. This comes with a few @@ -230,7 +233,8 @@ To migrate to `nixfmt`, simply change `vim.languages.nix.format.type` to `vim.languages.ts.extensions.ts-error-translator` to aid with Typescript development. -- Add [neo-tree.nvim] as an alternative file-tree plugin. It will be available under `vim.filetree.neo-tree`, similar to nvimtree. +- Add [neo-tree.nvim] as an alternative file-tree plugin. It will be available + under `vim.filetree.neo-tree`, similar to nvimtree. - Add `nvf-print-config` & `nvf-print-config-path` helper scripts to Neovim closure. Both of those scripts have been automatically added to your PATH upon @@ -298,5 +302,9 @@ To migrate to `nixfmt`, simply change `vim.languages.nix.format.type` to [nezia1](https://github.com/nezia1): -- Add [biome](https://github.com/biomejs/biome) support for Typescript, CSS and Svelte. Enable them via [](#opt-vim.languages.ts.format.type), [](#opt-vim.languages.css.format.type) and [](#opt-vim.languages.svelte.format.type) respectively. -- Replace [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt) with [nixfmt](https://github.com/NixOS/nixfmt) (nixfmt-rfc-style). +- Add [biome](https://github.com/biomejs/biome) support for Typescript, CSS and + Svelte. Enable them via [](#opt-vim.languages.ts.format.type), + [](#opt-vim.languages.css.format.type) and + [](#opt-vim.languages.svelte.format.type) respectively. +- Replace [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt) with + [nixfmt](https://github.com/NixOS/nixfmt) (nixfmt-rfc-style). diff --git a/flake.lock b/flake.lock index 34a2243..4e5ec65 100644 --- a/flake.lock +++ b/flake.lock @@ -460,6 +460,22 @@ "type": "github" } }, + "plugin-csharpls-extended": { + "flake": false, + "locked": { + "lastModified": 1728438370, + "narHash": "sha256-sOLPV5IhOvQ0+u7CDAfG3X0ZbRCicz18QyYXQ0dxpwQ=", + "owner": "Decodetalkers", + "repo": "csharpls-extended-lsp.nvim", + "rev": "b647e1bd1f9c0410f5ef4a1517a331cbac322d9a", + "type": "github" + }, + "original": { + "owner": "Decodetalkers", + "repo": "csharpls-extended-lsp.nvim", + "type": "github" + } + }, "plugin-dashboard-nvim": { "flake": false, "locked": { @@ -1453,6 +1469,22 @@ "type": "github" } }, + "plugin-omnisharp-extended": { + "flake": false, + "locked": { + "lastModified": 1719701797, + "narHash": "sha256-P1ZCaW8w+e3H3oBhbEjDc7vuR+XuxJmb/7IbPL3KWi4=", + "owner": "Hoffs", + "repo": "omnisharp-extended-lsp.nvim", + "rev": "aad7bf06b4ca0de816b919d475a75b30f5f62b61", + "type": "github" + }, + "original": { + "owner": "Hoffs", + "repo": "omnisharp-extended-lsp.nvim", + "type": "github" + } + }, "plugin-onedark": { "flake": false, "locked": { @@ -1932,6 +1964,7 @@ "plugin-copilot-cmp": "plugin-copilot-cmp", "plugin-copilot-lua": "plugin-copilot-lua", "plugin-crates-nvim": "plugin-crates-nvim", + "plugin-csharpls-extended": "plugin-csharpls-extended", "plugin-dashboard-nvim": "plugin-dashboard-nvim", "plugin-diffview-nvim": "plugin-diffview-nvim", "plugin-dracula": "plugin-dracula", @@ -1994,6 +2027,7 @@ "plugin-nvim-ts-autotag": "plugin-nvim-ts-autotag", "plugin-nvim-web-devicons": "plugin-nvim-web-devicons", "plugin-obsidian-nvim": "plugin-obsidian-nvim", + "plugin-omnisharp-extended": "plugin-omnisharp-extended", "plugin-onedark": "plugin-onedark", "plugin-orgmode-nvim": "plugin-orgmode-nvim", "plugin-otter-nvim": "plugin-otter-nvim", diff --git a/flake.nix b/flake.nix index d12bdc5..00b56c5 100644 --- a/flake.nix +++ b/flake.nix @@ -211,6 +211,16 @@ flake = false; }; + plugin-omnisharp-extended = { + url = "github:Hoffs/omnisharp-extended-lsp.nvim"; + flake = false; + }; + + plugin-csharpls-extended = { + url = "github:Decodetalkers/csharpls-extended-lsp.nvim"; + flake = false; + }; + # Copying/Registers plugin-registers = { url = "github:tversteeg/registers.nvim"; diff --git a/modules/plugins/languages/csharp.nix b/modules/plugins/languages/csharp.nix new file mode 100644 index 0000000..5011c5c --- /dev/null +++ b/modules/plugins/languages/csharp.nix @@ -0,0 +1,122 @@ +{ + lib, + pkgs, + config, + options, + ... +}: let + inherit (builtins) attrNames; + inherit (lib.options) mkEnableOption mkOption; + inherit (lib.types) either listOf package str enum; + inherit (lib.modules) mkIf mkMerge; + inherit (lib.lists) isList; + inherit (lib.strings) optionalString; + inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.nvim.lua) expToLua; + + 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}'})"; + + # Omnisharp doesn't have colors in popup docs for some reason, and I've also + # seen mentions of it being way slower, so until someone finds missing + # functionality, this will be the default. + defaultServer = "csharp_ls"; + servers = { + omnisharp = { + package = pkgs.omnisharp-roslyn; + internalFormatter = true; + lspConfig = '' + lspconfig.omnisharp.setup { + capabilities = capabilities, + on_attach = function(client, bufnr) + default_on_attach(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, + cmd = ${ + if isList cfg.lsp.package + then expToLua cfg.lsp.package + else "{'${cfg.lsp.package}/bin/OmniSharp'}" + } + } + ''; + }; + + csharp_ls = { + package = pkgs.csharp-ls; + internalFormatter = true; + lspConfig = '' + local extended_handler = require("csharpls_extended").handler + + lspconfig.csharp_ls.setup { + capabilities = capabilities, + on_attach = default_on_attach, + handlers = { + ["textDocument/definition"] = extended_handler, + ["textDocument/typeDefinition"] = extended_handler + }, + cmd = ${ + if isList cfg.lsp.package + then expToLua cfg.lsp.package + else "{'${cfg.lsp.package}/bin/csharp-ls'}" + } + } + ''; + }; + }; + + extraServerPlugins = { + omnisharp = ["omnisharp-extended"]; + csharp_ls = ["csharpls-extended"]; + }; + + cfg = config.vim.languages.csharp; +in { + options = { + vim.languages.csharp = { + enable = mkEnableOption "C# language support"; + + treesitter = { + enable = mkEnableOption "C# treesitter" // {default = config.vim.languages.enableTreesitter;}; + package = mkGrammarOption pkgs "c-sharp"; + }; + + lsp = { + enable = mkEnableOption "C# LSP support" // {default = config.vim.languages.enableLSP;}; + server = mkOption { + description = "C# LSP server to use"; + type = enum (attrNames servers); + default = defaultServer; + }; + + package = mkOption { + description = "C# LSP server package, or the command to run as a list of strings"; + type = either package (listOf str); + default = servers.${cfg.lsp.server}.package; + }; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + (mkIf cfg.treesitter.enable { + vim.treesitter.enable = true; + vim.treesitter.grammars = [cfg.treesitter.package]; + }) + + (mkIf cfg.lsp.enable { + vim.startPlugins = extraServerPlugins.${cfg.lsp.server} or []; + vim.lsp.lspconfig.enable = true; + vim.lsp.lspconfig.sources.csharp-lsp = servers.${cfg.lsp.server}.lspConfig; + }) + ]); +} diff --git a/modules/plugins/languages/default.nix b/modules/plugins/languages/default.nix index 54a9e1c..9dec325 100644 --- a/modules/plugins/languages/default.nix +++ b/modules/plugins/languages/default.nix @@ -28,6 +28,7 @@ in { ./ts.nix ./typst.nix ./zig.nix + ./csharp.nix ]; options.vim.languages = { diff --git a/modules/plugins/languages/kotlin.nix b/modules/plugins/languages/kotlin.nix index 5637b4f..0343321 100644 --- a/modules/plugins/languages/kotlin.nix +++ b/modules/plugins/languages/kotlin.nix @@ -8,7 +8,7 @@ inherit (lib.modules) mkIf mkMerge; inherit (lib.meta) getExe; inherit (lib.nvim.languages) diagnosticsToLua; - inherit (lib.types) package; + inherit (lib.types) either package listOf str; inherit (lib.nvim.types) mkGrammarOption diagnostics; inherit (lib.lists) isList; inherit (lib.nvim.lua) expToLua; @@ -43,17 +43,17 @@ in { package = mkOption { description = "kotlin_language_server package with Kotlin runtime"; - type = package; + type = either package (listOf str); example = literalExpression '' pkgs.symlinkJoin { - name = "kotlin-language-server-wrapped"; - paths = [pkgs.kotlin-language-server]; - nativeBuildInputs = [pkgs.makeWrapper]; - postBuild = ''' - wrapProgram $out/bin/kotlin-language-server \ - --prefix PATH : ''${pkgs.kotlin}/bin - '''; - }; + name = "kotlin-language-server-wrapped"; + paths = [pkgs.kotlin-language-server]; + nativeBuildInputs = [pkgs.makeBinaryWrapper]; + postBuild = ''' + wrapProgram $out/bin/kotlin-language-server \ + --prefix PATH : ''${pkgs.kotlin}/bin + '''; + }; ''; default = pkgs.kotlin-language-server; }; diff --git a/modules/plugins/languages/nix.nix b/modules/plugins/languages/nix.nix index 82dded6..ffb69e9 100644 --- a/modules/plugins/languages/nix.nix +++ b/modules/plugins/languages/nix.nix @@ -145,7 +145,7 @@ in { enable = mkEnableOption "Nix LSP support" // {default = config.vim.languages.enableLSP;}; server = mkOption { description = "Nix LSP server to use"; - type = str; + type = enum (attrNames servers); default = defaultServer; }; diff --git a/modules/plugins/lsp/config.nix b/modules/plugins/lsp/config.nix index 52dc15b..dbf6cdb 100644 --- a/modules/plugins/lsp/config.nix +++ b/modules/plugins/lsp/config.nix @@ -18,7 +18,7 @@ mappings = addDescriptionsToMappings cfg.mappings mappingDefinitions; mkBinding = binding: action: if binding.value != null - then "vim.api.nvim_buf_set_keymap(bufnr, 'n', '${binding.value}', 'lua ${action}', {noremap=true, silent=true, desc='${binding.description}'})" + then "vim.keymap.set('n', '${binding.value}', ${action}, {buffer=bufnr, noremap=true, silent=true, desc='${binding.description}'})" else ""; in { config = mkIf cfg.enable { @@ -31,26 +31,26 @@ in { vim.g.formatsave = ${boolToString cfg.formatOnSave}; local attach_keymaps = function(client, bufnr) - ${mkBinding mappings.goToDeclaration "vim.lsp.buf.declaration()"} - ${mkBinding mappings.goToDefinition "vim.lsp.buf.definition()"} - ${mkBinding mappings.goToType "vim.lsp.buf.type_definition()"} - ${mkBinding mappings.listImplementations "vim.lsp.buf.implementation()"} - ${mkBinding mappings.listReferences "vim.lsp.buf.references()"} - ${mkBinding mappings.nextDiagnostic "vim.diagnostic.goto_next()"} - ${mkBinding mappings.previousDiagnostic "vim.diagnostic.goto_prev()"} - ${mkBinding mappings.openDiagnosticFloat "vim.diagnostic.open_float()"} - ${mkBinding mappings.documentHighlight "vim.lsp.buf.document_highlight()"} - ${mkBinding mappings.listDocumentSymbols "vim.lsp.buf.document_symbol()"} - ${mkBinding mappings.addWorkspaceFolder "vim.lsp.buf.add_workspace_folder()"} - ${mkBinding mappings.removeWorkspaceFolder "vim.lsp.buf.remove_workspace_folder()"} - ${mkBinding mappings.listWorkspaceFolders "print(vim.inspect(vim.lsp.buf.list_workspace_folders()))"} - ${mkBinding mappings.listWorkspaceSymbols "vim.lsp.buf.workspace_symbol()"} - ${mkBinding mappings.hover "vim.lsp.buf.hover()"} - ${mkBinding mappings.signatureHelp "vim.lsp.buf.signature_help()"} - ${mkBinding mappings.renameSymbol "vim.lsp.buf.rename()"} - ${mkBinding mappings.codeAction "vim.lsp.buf.code_action()"} - ${mkBinding mappings.format "vim.lsp.buf.format()"} - ${mkBinding mappings.toggleFormatOnSave "vim.b.disableFormatSave = not vim.b.disableFormatSave"} + ${mkBinding mappings.goToDeclaration "vim.lsp.buf.declaration"} + ${mkBinding mappings.goToDefinition "vim.lsp.buf.definition"} + ${mkBinding mappings.goToType "vim.lsp.buf.type_definition"} + ${mkBinding mappings.listImplementations "vim.lsp.buf.implementation"} + ${mkBinding mappings.listReferences "vim.lsp.buf.references"} + ${mkBinding mappings.nextDiagnostic "vim.diagnostic.goto_next"} + ${mkBinding mappings.previousDiagnostic "vim.diagnostic.goto_prev"} + ${mkBinding mappings.openDiagnosticFloat "vim.diagnostic.open_float"} + ${mkBinding mappings.documentHighlight "vim.lsp.buf.document_highlight"} + ${mkBinding mappings.listDocumentSymbols "vim.lsp.buf.document_symbol"} + ${mkBinding mappings.addWorkspaceFolder "vim.lsp.buf.add_workspace_folder"} + ${mkBinding mappings.removeWorkspaceFolder "vim.lsp.buf.remove_workspace_folder"} + ${mkBinding mappings.listWorkspaceFolders "function() vim.notify(vim.inspect(vim.lsp.buf.list_workspace_folders())) end"} + ${mkBinding mappings.listWorkspaceSymbols "vim.lsp.buf.workspace_symbol"} + ${mkBinding mappings.hover "vim.lsp.buf.hover"} + ${mkBinding mappings.signatureHelp "vim.lsp.buf.signature_help"} + ${mkBinding mappings.renameSymbol "vim.lsp.buf.rename"} + ${mkBinding mappings.codeAction "vim.lsp.buf.code_action"} + ${mkBinding mappings.format "vim.lsp.buf.format"} + ${mkBinding mappings.toggleFormatOnSave "function() vim.b.disableFormatSave = not vim.b.disableFormatSave end"} end -- Enable formatting