diff --git a/.github/typos.toml b/.github/typos.toml index 2cd18dde..378d3e2d 100644 --- a/.github/typos.toml +++ b/.github/typos.toml @@ -5,6 +5,8 @@ default.extend-ignore-words-re = [ "befores", "annote", "viw", + "edn", + "esy", "BA", # somehow "BANanaD3V" is valid, but BA is not... ] diff --git a/configuration.nix b/configuration.nix index 68776638..750862af 100644 --- a/configuration.nix +++ b/configuration.nix @@ -31,6 +31,7 @@ isMaximal: { lspSignature.enable = !isMaximal; # conflicts with blink in maximal otter-nvim.enable = isMaximal; nvim-docs-view.enable = isMaximal; + harper-ls.enable = isMaximal; }; debugger = { @@ -56,6 +57,7 @@ isMaximal: { clang.enable = isMaximal; css.enable = isMaximal; html.enable = isMaximal; + json.enable = isMaximal; sql.enable = isMaximal; java.enable = isMaximal; kotlin.enable = isMaximal; @@ -84,8 +86,11 @@ isMaximal: { ocaml.enable = false; elixir.enable = false; haskell.enable = false; + hcl.enable = false; ruby.enable = false; fsharp.enable = false; + just.enable = false; + qml.enable = false; tailwind.enable = false; svelte.enable = false; @@ -188,6 +193,7 @@ isMaximal: { vim-wakatime.enable = false; diffview-nvim.enable = true; yanky-nvim.enable = false; + qmk-nvim.enable = false; # requires hardware specific options icon-picker.enable = isMaximal; surround.enable = isMaximal; leetcode-nvim.enable = isMaximal; diff --git a/docs/manual/configuring/languages.md b/docs/manual/configuring/languages.md index 252163fb..bd2e487d 100644 --- a/docs/manual/configuring/languages.md +++ b/docs/manual/configuring/languages.md @@ -1,10 +1,10 @@ # Language Support {#ch-languages} Language specific support means there is a combination of language specific -plugins, `treesitter` support, `nvim-lspconfig` language servers, and `null-ls` -integration. This gets you capabilities ranging from autocompletion to -formatting to diagnostics. The following languages have sections under the -`vim.languages` attribute. +plugins, `treesitter` support, `nvim-lspconfig` language servers, `conform-nvim` +formatters, and `nvim-lint` linter integration. This gets you capabilities +ranging from autocompletion to formatting to diagnostics. The following +languages have sections under the `vim.languages` attribute. - Rust: [vim.languages.rust.enable](#opt-vim.languages.rust.enable) - Nix: [vim.languages.nix.enable](#opt-vim.languages.nix.enable) @@ -20,6 +20,35 @@ formatting to diagnostics. The following languages have sections under the - Lua: [vim.languages.lua.enable](#opt-vim.languages.lua.enable) - PHP: [vim.languages.php.enable](#opt-vim.languages.php.enable) - F#: [vim.languages.fsharp.enable](#opt-vim.languages.fsharp.enable) +- Assembly: [vim.languages.assembly.enable](#opt-vim.languages.assembly.enable) +- Astro: [vim.languages.astro.enable](#opt-vim.languages.astro.enable) +- Bash: [vim.languages.bash.enable](#opt-vim.languages.bash.enable) +- Clang: [vim.languages.clang.enable](#opt-vim.languages.clang.enable) +- Clojure: [vim.languages.clojure.enable](#opt-vim.languages.clojure.enable) +- C#: [vim.languages.csharp.enable](#opt-vim.languages.csharp.enable) +- CSS: [vim.languages.css.enable](#opt-vim.languages.css.enable) +- CUE: [vim.languages.cue.enable](#opt-vim.languages.cue.enable) +- Elixir: [vim.languages.elixir.enable](#opt-vim.languages.elixir.enable) +- Gleam: [vim.languages.gleam.enable](#opt-vim.languages.gleam.enable) +- HCL: [vim.languages.hcl.enable](#opt-vim.languages.hcl.enable) +- Helm: [vim.languages.helm.enable](#opt-vim.languages.helm.enable) +- Julia: [vim.languages.julia.enable](#opt-vim.languages.julia.enable) +- Kotlin: [vim.languages.kotlin.enable](#opt-vim.languages.kotlin.enable) +- Nim: [vim.languages.nim.enable](#opt-vim.languages.nim.enable) +- Nu: [vim.languages.nu.enable](#opt-vim.languages.nu.enable) +- OCaml: [vim.languages.ocaml.enable](#opt-vim.languages.ocaml.enable) +- Odin: [vim.languages.odin.enable](#opt-vim.languages.odin.enable) +- R: [vim.languages.r.enable](#opt-vim.languages.r.enable) +- Ruby: [vim.languages.ruby.enable](#opt-vim.languages.ruby.enable) +- Scala: [vim.languages.scala.enable](#opt-vim.languages.scala.enable) +- Svelte: [vim.languages.svelte.enable](#opt-vim.languages.svelte.enable) +- Tailwind: [vim.languages.tailwind.enable](#opt-vim.languages.tailwind.enable) +- Terraform: + [vim.languages.terraform.enable](#opt-vim.languages.terraform.enable) +- Typst: [vim.languages.typst.enable](#opt-vim.languages.typst.enable) +- Vala: [vim.languages.vala.enable](#opt-vim.languages.vala.enable) +- WGSL: [vim.languages.wgsl.enable](#opt-vim.languages.wgsl.enable) +- YAML: [vim.languages.yaml.enable](#opt-vim.languages.yaml.enable) Adding support for more languages, and improving support for existing ones are great places where you can contribute with a PR. diff --git a/docs/release-notes/rl-0.8.md b/docs/release-notes/rl-0.8.md index 74a94b9c..83839245 100644 --- a/docs/release-notes/rl-0.8.md +++ b/docs/release-notes/rl-0.8.md @@ -111,6 +111,11 @@ - Add [hunk.nvim], Neovim plugin & tool for splitting diffs in Neovim. Available as `vim.git.hunk-nvim` +[sjcobb2022](https://github.com/sjcobb2022): + +- Migrate all current lsp configurations to `vim.lsp.server` and remove internal + dependency on `nvim-lspconfig` + [amadaluzia](https://github.com/amadaluzia): [haskell-tools.nvim]: https://github.com/MrcJkb/haskell-tools.nvim @@ -319,6 +324,7 @@ - Add global function `nvf_lint` under `vim.diagnostics.nvim-lint.lint_function`. - Deprecate `vim.scrollOffset` in favor of `vim.options.scrolloff`. +- Fix `svelte-language-server` not reloading .js/.ts files on change. [Sc3l3t0n](https://github.com/Sc3l3t0n): @@ -389,9 +395,12 @@ [poz](https://poz.pet): [everforest]: https://github.com/sainnhe/everforest +[oil]: https://github.com/stevearc/oil.nvim +[oil-git-status]: https://github.com/refractalize/oil-git-status.nvim - Fix gitsigns null-ls issue. - Add [everforest] theme support. +- Add [oil-git-status] support to [oil] module. [Haskex](https://github.com/haskex): @@ -480,19 +489,62 @@ - fix broken `neorg` grammars - remove obsolete warning in the `otter` module -[Cool-Game-Dev](https://github.com/Cool-Game-Dev): - -[nvim-biscuits]: https://github.com/code-biscuits/nvim-biscuits - -- Add [nvim-biscuits] to show block context. Available at - `vim.utility.nvim-biscuits`. - [JManch](https://github.com/JManch): - Fix default [blink.cmp] sources "path" and "buffer" not working when `autocomplete.nvim-cmp.enable` was disabled and `autocomplete.nvim-cmp.sources` had not been modified. +[Poseidon](https://github.com/poseidon-rises): + +[nvim-biscuits]: https://github.com/code-biscuits/nvim-biscuits +[just-lsp]: https://github.com/terror/just-lsp +[roslyn-ls]: https://github.com/dotnet/vscode-csharp +[jsonls]: https://github.com/microsoft/vscode/tree/1.101.2/extensions/json-language-features/server +[jsonfmt]: https://github.com/caarlos0/jsonfmt +[superhtml]: https://github.com/kristoff-it/superhtml +[htmlHINT]: https://github.com/htmlhint/HTMLHint +[qmk-nvim]: https://github.com/codethread/qmk.nvim +[qmlls]: https://doc.qt.io/qt-6/qtqml-tooling-qmlls.html +[qmlformat]: https://doc.qt.io/qt-6/qtqml-tooling-qmlformat.html + +- Add [nvim-biscuits] support under `vim.utility.nvim-biscuits`. +- Add just support under `vim.languages.just` using [just-lsp]. +- Add [roslyn-ls] to the `vim.languages.csharp` module. +- Add JSON support under `vim.languages.json` using [jsonls] and [jsonfmt]. +- Add advanced HTML support under `vim.languages.html` using [superhtml] and + [htmlHINT]. +- Add QMK support under `vim.utility.qmk-nvim` via [qmk-nvim]. +- Add QML support under `vim.languages.qml` using [qmlls] and [qmlformat]. + +[Morsicus](https://github.com/Morsicus): + +- Add [EEx Treesitter Grammar](https://github.com/connorlay/tree-sitter-eex) for + Elixir +- Add + [HEEx Treesitter Grammar](https://github.com/phoenixframework/tree-sitter-heex) + for Elixir + +[diced](https://github.com/diced): + +- Fixed `typescript` treesitter grammar not being included by default. + +[valterschutz](https://github.com/valterschutz): + +[ruff]: (https://github.com/astral-sh/ruff) + +- Add [ruff-fix] as a formatter option in `vim.languages.python.format.type`. + +[gmvar](https://github.com/gmvar): + +[harper-ls]: https://github.com/Automattic/harper + +- Add [harper-ls] to the `vim.lsp` module. + +[derethil](https://github.com/derethil): + +- Fix `vim.lazy.plugins..enabled` Lua evaluation. + [Jules](https://github.com/jules-sommer): [nvim-highlight-colors]: https://github.com/brenoprata10/nvim-highlight-colors diff --git a/lib/languages.nix b/lib/languages.nix index c4074144..899d9ea8 100644 --- a/lib/languages.nix +++ b/lib/languages.nix @@ -1,7 +1,7 @@ {lib}: let inherit (builtins) isString getAttr; inherit (lib.options) mkOption; - inherit (lib.types) listOf bool str submodule attrsOf anything either nullOr; + inherit (lib.types) listOf bool str submodule attrsOf anything either nullOr uniq; inherit (lib.nvim.attrsets) mapListToAttrs; inherit (lib.nvim.types) luaInline; in { @@ -46,7 +46,7 @@ in { capabilities = mkOption { type = nullOr (either luaInline (attrsOf anything)); default = null; - description = "LSP capabilitiess to pass to lspconfig"; + description = "LSP capabilities to pass to LSP server configuration"; }; on_attach = mkOption { @@ -58,11 +58,11 @@ in { filetypes = mkOption { type = nullOr (listOf str); default = null; - description = "Filetypes to auto-attach LSP in"; + description = "Filetypes to auto-attach LSP server in"; }; cmd = mkOption { - type = nullOr (listOf str); + type = nullOr (either luaInline (uniq (listOf str))); default = null; description = "Command used to start the LSP server"; }; diff --git a/lib/types/custom.nix b/lib/types/custom.nix index c42cd2ce..ae509f59 100644 --- a/lib/types/custom.nix +++ b/lib/types/custom.nix @@ -1,7 +1,8 @@ {lib}: let inherit (lib.options) mergeEqualOption; + inherit (lib.lists) singleton; inherit (lib.strings) isString stringLength match; - inherit (lib.types) listOf mkOptionType; + inherit (lib.types) listOf mkOptionType coercedTo; in { mergelessListOf = elemType: let super = listOf elemType; @@ -27,4 +28,6 @@ in { description = "RGB color in hex format"; check = v: isString v && (match "#?[0-9a-fA-F]{6}" v) != null; }; + + singleOrListOf = t: coercedTo t singleton (listOf t); } diff --git a/lib/types/default.nix b/lib/types/default.nix index 044a8221..c91473a2 100644 --- a/lib/types/default.nix +++ b/lib/types/default.nix @@ -10,5 +10,5 @@ in { inherit (typesDag) dagOf; inherit (typesPlugin) pluginsOpt extraPluginType mkPluginSetupOption luaInline pluginType borderType; inherit (typesLanguage) diagnostics mkGrammarOption; - inherit (customTypes) char hexColor mergelessListOf; + inherit (customTypes) char hexColor mergelessListOf singleOrListOf; } diff --git a/modules/extra/deprecations.nix b/modules/extra/deprecations.nix index 0a5733ab..77f4bcc6 100644 --- a/modules/extra/deprecations.nix +++ b/modules/extra/deprecations.nix @@ -1,5 +1,6 @@ {lib, ...}: let - inherit (lib.modules) mkRemovedOptionModule mkRenamedOptionModule; + inherit (builtins) head warn; + inherit (lib.modules) mkRemovedOptionModule mkRenamedOptionModule doRename; inherit (lib.lists) concatLists; inherit (lib.nvim.config) batchRenameOptions; @@ -20,6 +21,27 @@ # 2025-02-07 scrollOffset = "scrolloff"; }; + + mkRemovedLspOpt = lang: (mkRemovedOptionModule ["vim" "languages" lang "lsp" "opts"] '' + `vim.languages.${lang}.lsp.opts` is now moved to `vim.lsp.servers..init_options` + ''); + + mkRemovedLspPackage = lang: (mkRemovedOptionModule ["vim" "languages" lang "lsp" "package"] '' + `vim.languages.${lang}.lsp.package` is now moved to `vim.lsp.servers..cmd` + ''); + + mkRenamedLspServer = lang: + doRename + { + from = ["vim" "languages" lang "lsp" "server"]; + to = ["vim" "languages" lang "lsp" "servers"]; + visible = false; + warn = true; + use = x: + warn + "Obsolete option `vim.languages.${lang}.lsp.server` used, use `vim.languages.${lang}.lsp.servers` instead." + (head x); + }; in { imports = concatLists [ [ @@ -120,6 +142,123 @@ in { in 'vim.clipboard.registers'. Please see the documentation for the new module for more details, or open an issue if you are confused. '') + + # 2025-07-12 + (mkRenamedLspServer "assembly") + + (mkRenamedLspServer "astro") + (mkRemovedLspPackage "astro") + + (mkRenamedLspServer "bash") + (mkRemovedLspPackage "bash") + + (mkRemovedLspOpt "clang") + (mkRemovedLspPackage "clang") + (mkRenamedLspServer "clang") + + (mkRemovedLspPackage "clojure") + + (mkRenamedLspServer "csharp") + (mkRemovedLspPackage "csharp") + + (mkRenamedLspServer "css") + (mkRemovedLspPackage "css") + + (mkRemovedLspPackage "cue") + + (mkRenamedLspServer "dart") + (mkRemovedLspPackage "dart") + (mkRemovedLspOpt "dart") + + (mkRenamedLspServer "elixir") + (mkRemovedLspPackage "elixir") + + (mkRenamedLspServer "fsharp") + (mkRemovedLspPackage "fsharp") + + (mkRenamedLspServer "gleam") + (mkRemovedLspPackage "gleam") + + (mkRenamedLspServer "go") + (mkRemovedLspPackage "go") + + (mkRemovedLspPackage "haskell") + + (mkRemovedLspPackage "hcl") + + (mkRenamedLspServer "helm") + (mkRemovedLspPackage "helm") + + (mkRemovedLspPackage "java") + + (mkRenamedLspServer "julia") + (mkRemovedLspPackage "julia") + + (mkRemovedLspPackage "kotlin") + + (mkRemovedLspPackage "lua") + + (mkRenamedLspServer "markdown") + (mkRemovedLspPackage "markdown") + + (mkRenamedLspServer "nim") + (mkRemovedLspPackage "nim") + + (mkRenamedLspServer "nix") + (mkRemovedLspPackage "nix") + (mkRemovedOptionModule ["vim" "languages" "nix" "lsp" "options"] '' + `vim.languages.nix.lsp.options` has been moved to `vim.lsp.servers..init_options`. + '') + + (mkRenamedLspServer "nu") + (mkRemovedLspPackage "nu") + + (mkRenamedLspServer "ocaml") + (mkRemovedLspPackage "ocaml") + + (mkRenamedLspServer "odin") + (mkRemovedLspPackage "odin") + + (mkRenamedLspServer "php") + (mkRemovedLspPackage "php") + + (mkRenamedLspServer "python") + (mkRemovedLspPackage "python") + + (mkRenamedLspServer "r") + (mkRemovedLspPackage "r") + + (mkRenamedLspServer "ruby") + (mkRemovedLspPackage "ruby") + + (mkRenamedLspServer "sql") + (mkRemovedLspPackage "sql") + + (mkRenamedLspServer "svelte") + (mkRemovedLspPackage "svelte") + + (mkRenamedLspServer "tailwind") + (mkRemovedLspPackage "tailwind") + + (mkRemovedLspPackage "terraform") + + (mkRenamedLspServer "ts") + (mkRemovedLspPackage "ts") + + (mkRenamedLspServer "typst") + (mkRemovedLspPackage "typst") + + (mkRenamedLspServer "vala") + (mkRemovedLspPackage "vala") + + (mkRenamedLspServer "wgsl") + (mkRemovedLspPackage "wgsl") + + (mkRenamedLspServer "yaml") + (mkRemovedLspPackage "yaml") + + (mkRenamedLspServer "zig") + (mkRemovedLspPackage "zig") ] # Migrated via batchRenameOptions. Further batch renames must be below this line. diff --git a/modules/neovim/init/default.nix b/modules/neovim/init/default.nix index 30a481a1..3f195085 100644 --- a/modules/neovim/init/default.nix +++ b/modules/neovim/init/default.nix @@ -8,5 +8,6 @@ ./highlight.nix ./lsp.nix ./spellcheck.nix + ./util.nix ]; } diff --git a/modules/neovim/init/lsp.nix b/modules/neovim/init/lsp.nix index b89c3fde..6c18b98e 100644 --- a/modules/neovim/init/lsp.nix +++ b/modules/neovim/init/lsp.nix @@ -16,6 +16,7 @@ cfg = config.vim.lsp; + # TODO: lspConfigurations filter on enabledServers instead of cfg.servers? lspConfigurations = mapAttrsToList ( name: value: '' diff --git a/modules/neovim/init/util.nix b/modules/neovim/init/util.nix new file mode 100644 index 00000000..cc98d10a --- /dev/null +++ b/modules/neovim/init/util.nix @@ -0,0 +1,178 @@ +{ + config, + lib, + ... +}: let + inherit (builtins) filter; + inherit (lib.modules) mkIf mkMerge; + inherit (lib.nvim.dag) entryBefore; + + cfg = config.vim.lsp; +in { + config = mkMerge [ + (mkIf (cfg.servers != {}) { + vim.luaConfigRC.lsp-util = + entryBefore ["lsp-servers"] + /* + lua + */ + '' + -- Port of nvim-lspconfig util + local util = { path = {} } + + util.default_config = { + log_level = vim.lsp.protocol.MessageType.Warning, + message_level = vim.lsp.protocol.MessageType.Warning, + settings = vim.empty_dict(), + init_options = vim.empty_dict(), + handlers = {}, + autostart = true, + capabilities = vim.lsp.protocol.make_client_capabilities(), + } + + -- global on_setup hook + util.on_setup = nil + + do + local validate = vim.validate + local api = vim.api + local lsp = vim.lsp + local nvim_eleven = vim.fn.has 'nvim-0.11' == 1 + + local iswin = vim.uv.os_uname().version:match 'Windows' + + local function escape_wildcards(path) + return path:gsub('([%[%]%?%*])', '\\%1') + end + + local function is_fs_root(path) + if iswin then + return path:match '^%a:$' + else + return path == '/' + end + end + + local function traverse_parents(path, cb) + path = vim.uv.fs_realpath(path) + local dir = path + -- Just in case our algo is buggy, don't infinite loop. + for _ = 1, 100 do + dir = vim.fs.dirname(dir) + if not dir then + return + end + -- If we can't ascend further, then stop looking. + if cb(dir, path) then + return dir, path + end + if is_fs_root(dir) then + break + end + end + end + + util.root_pattern = function(...) + local patterns = util.tbl_flatten { ... } + return function(startpath) + startpath = util.strip_archive_subpath(startpath) + for _, pattern in ipairs(patterns) do + local match = util.search_ancestors(startpath, function(path) + for _, p in ipairs(vim.fn.glob(table.concat({ escape_wildcards(path), pattern }, '/'), true, true)) do + if vim.uv.fs_stat(p) then + return path + end + end + end) + + if match ~= nil then + return match + end + end + end + end + + util.root_markers_with_field = function(root_files, new_names, field, fname) + local path = vim.fn.fnamemodify(fname, ':h') + local found = vim.fs.find(new_names, { path = path, upward = true }) + + for _, f in ipairs(found or {}) do + -- Match the given `field`. + for line in io.lines(f) do + if line:find(field) then + root_files[#root_files + 1] = vim.fs.basename(f) + break + end + end + end + + return root_files + end + + util.insert_package_json = function(root_files, field, fname) + return util.root_markers_with_field(root_files, { 'package.json', 'package.json5' }, field, fname) + end + + util.strip_archive_subpath = function(path) + -- Matches regex from zip.vim / tar.vim + path = vim.fn.substitute(path, 'zipfile://\\(.\\{-}\\)::[^\\\\].*$', '\\1', ''') + path = vim.fn.substitute(path, 'tarfile:\\(.\\{-}\\)::.*$', '\\1', ''') + return path + end + + util.get_typescript_server_path = function(root_dir) + local project_roots = vim.fs.find('node_modules', { path = root_dir, upward = true, limit = math.huge }) + for _, project_root in ipairs(project_roots) do + local typescript_path = project_root .. '/typescript' + local stat = vim.loop.fs_stat(typescript_path) + if stat and stat.type == 'directory' then + return typescript_path .. '/lib' + end + end + return ''' + end + + util.search_ancestors = function(startpath, func) + if nvim_eleven then + validate('func', func, 'function') + end + if func(startpath) then + return startpath + end + local guard = 100 + for path in vim.fs.parents(startpath) do + -- Prevent infinite recursion if our algorithm breaks + guard = guard - 1 + if guard == 0 then + return + end + + if func(path) then + return path + end + end + end + + util.path.is_descendant = function(root, path) + if not path then + return false + end + + local function cb(dir, _) + return dir == root + end + + local dir, _ = traverse_parents(path, cb) + + return dir == root + end + + util.tbl_flatten = function(t) + --- @diagnostic disable-next-line:deprecated + return nvim_eleven and vim.iter(t):flatten(math.huge):totable() or vim.tbl_flatten(t) + end + end + ''; + }) + ]; +} diff --git a/modules/plugins/languages/asm.nix b/modules/plugins/languages/asm.nix index 9fe58a76..9d296755 100644 --- a/modules/plugins/languages/asm.nix +++ b/modules/plugins/languages/asm.nix @@ -4,12 +4,24 @@ lib, ... }: let + inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.types) package; - inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.types) enum; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.meta) getExe; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.assembly; + defaultServers = ["asm-lsp"]; + servers = { + asm-lsp = { + enable = true; + cmd = [(getExe pkgs.asm-lsp)]; + filetypes = ["asm" "vmasm"]; + root_markers = [".asm-lsp.toml" ".git"]; + }; + }; in { options.vim.languages.assembly = { enable = mkEnableOption "Assembly support"; @@ -20,12 +32,11 @@ in { }; lsp = { - enable = mkEnableOption "Assembly LSP support (asm-lsp)" // {default = config.vim.lsp.enable;}; - - package = mkOption { - type = package; - default = pkgs.asm-lsp; - description = "asm-lsp package"; + enable = mkEnableOption "Assembly LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "Assembly LSP server to use"; }; }; }; @@ -36,14 +47,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.asm-lsp = '' - lspconfig.asm_lsp.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = {"${cfg.lsp.package}/bin/asm-lsp"}, - } - ''; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/astro.nix b/modules/plugins/languages/astro.nix index 21b6e5a3..a16f286e 100644 --- a/modules/plugins/languages/astro.nix +++ b/modules/plugins/languages/astro.nix @@ -7,29 +7,36 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; inherit (lib.meta) getExe; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption diagnostics; + inherit (lib.types) enum package; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.nvim.types) mkGrammarOption diagnostics singleOrListOf; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.astro; - defaultServer = "astro"; + defaultServers = ["astro"]; servers = { astro = { - package = pkgs.astro-language-server; - lspConfig = '' - lspconfig.astro.setup { - capabilities = capabilities, - on_attach = attach_keymaps, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/astro-ls", "--stdio"}'' - } - } - ''; + enable = true; + cmd = [(getExe pkgs.astro-language-server) "--stdio"]; + filetypes = ["astro"]; + root_markers = ["package.json" "tsconfig.json" "jsconfig.json" ".git"]; + init_options = { + typescript = {}; + }; + before_init = + mkLuaInline + /* + lua + */ + '' + function(_, config) + if config.init_options and config.init_options.typescript and not config.init_options.typescript.tsdk then + config.init_options.typescript.tsdk = util.get_typescript_server_path(config.root_dir) + end + end + ''; }; }; @@ -81,34 +88,26 @@ in { lsp = { enable = mkEnableOption "Astro LSP support" // {default = config.vim.lsp.enable;}; - - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Astro LSP server to use"; }; - - package = mkOption { - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - example = ''[lib.getExe pkgs.astro-language-server "--minify" "--stdio"]''; - description = "Astro LSP server package, or the command to run as a list of strings"; - }; }; format = { enable = mkEnableOption "Astro formatting" // {default = config.vim.languages.enableFormat;}; type = mkOption { - description = "Astro formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "Astro formatter to use"; }; package = mkOption { - description = "Astro formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "Astro formatter package"; }; }; @@ -130,8 +129,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.astro-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/bash.nix b/modules/plugins/languages/bash.nix index 9a5e3e13..4597666a 100644 --- a/modules/plugins/languages/bash.nix +++ b/modules/plugins/languages/bash.nix @@ -5,31 +5,28 @@ ... }: let inherit (builtins) attrNames; - inherit (lib.options) mkOption mkEnableOption literalExpression; + inherit (lib.options) mkOption mkEnableOption; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either package listOf str bool; - inherit (lib.nvim.types) diagnostics mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.types) enum package bool; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.types) diagnostics mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.bash; - defaultServer = "bash-ls"; + defaultServers = ["bash-ls"]; servers = { bash-ls = { - package = pkgs.bash-language-server; - lspConfig = '' - lspconfig.bashls.setup{ - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/bash-language-server", "start"}'' + enable = true; + cmd = [(getExe pkgs.bash-language-server) "start"]; + filetypes = ["bash" "sh"]; + root_markers = [".git"]; + settings = { + basheIde = { + globPattern = mkLuaInline "vim.env.GLOB_PATTERN or '*@(.sh|.inc|.bash|.command)'"; }; - } - ''; + }; }; }; @@ -56,38 +53,30 @@ in { }; lsp = { - enable = mkEnableOption "Enable Bash LSP support" // {default = config.vim.lsp.enable;}; - - server = mkOption { + enable = mkEnableOption "Bash LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Bash LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "bash-language-server package, or the command to run as a list of strings"; - example = literalExpression ''[lib.getExe pkgs.bash-language-server "start"]''; - type = either package (listOf str); - default = pkgs.bash-language-server; }; }; format = { enable = mkOption { - description = "Enable Bash formatting"; type = bool; default = config.vim.languages.enableFormat; + description = "Enable Bash formatting"; }; type = mkOption { - description = "Bash formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "Bash formatter to use"; }; package = mkOption { - description = "Bash formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "Bash formatter package"; }; }; @@ -108,8 +97,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.bash-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/clang.nix b/modules/plugins/languages/clang.nix index 2db178e9..487bc151 100644 --- a/modules/plugins/languages/clang.nix +++ b/modules/plugins/languages/clang.nix @@ -5,47 +5,143 @@ ... }: let inherit (builtins) attrNames; - inherit (lib.lists) isList; - inherit (lib.strings) optionalString; inherit (lib.options) mkEnableOption mkOption; - inherit (lib.types) bool enum package either listOf str nullOr; + inherit (lib.types) bool enum package; + inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; inherit (lib.nvim.dag) entryAfter; - packageToCmd = package: defaultCmd: - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{ "${cfg.lsp.package}/bin/${defaultCmd}" }''; - cfg = config.vim.languages.clang; - defaultServer = "clangd"; + defaultServers = ["clangd"]; servers = { ccls = { - package = pkgs.ccls; - lspConfig = '' - lspconfig.ccls.setup{ - capabilities = capabilities; - on_attach=default_on_attach; - cmd = ${packageToCmd cfg.lsp.package "ccls"}; - ${optionalString (cfg.lsp.opts != null) "init_options = ${cfg.lsp.opts}"} - } + cmd = [(getExe pkgs.ccls)]; + filetypes = ["c" "cpp" "objc" "objcpp" "cuda"]; + offset_encoding = "utf-32"; + root_markers = ["compile_commands.json" ".ccls" ".git"]; + workspace_required = true; + on_attach = mkLuaInline '' + function(client, bufnr) + default_on_attach(client, bufnr) + + local function switch_source_header(bufnr) + local method_name = "textDocument/switchSourceHeader" + local params = vim.lsp.util.make_text_document_params(bufnr) + client:request(method_name, params, function(err, result) + if err then + error(tostring(err)) + end + if not result then + vim.notify('corresponding file cannot be determined') + return + end + vim.cmd.edit(vim.uri_to_fname(result)) + end, bufnr) + end + + vim.api.nvim_buf_create_user_command( + bufnr, + "LspCclsSwitchSourceHeader", + function(arg) + switch_source_header(client, 0) + end, + {desc = "Switch between source/header"} + ) + end ''; }; + clangd = { - package = pkgs.clang-tools; - lspConfig = '' - local clangd_cap = capabilities - -- use same offsetEncoding as null-ls - clangd_cap.offsetEncoding = {"utf-16"} - lspconfig.clangd.setup{ - capabilities = clangd_cap; - on_attach=default_on_attach; - cmd = ${packageToCmd cfg.lsp.package "clangd"}; - ${optionalString (cfg.lsp.opts != null) "init_options = ${cfg.lsp.opts}"} - } + cmd = ["${pkgs.clang-tools}/bin/clangd"]; + filetypes = ["c" "cpp" "objc" "objcpp" "cuda" "proto"]; + root_markers = [ + ".clangd" + ".clang-tidy" + ".clang-format" + "compile_commands.json" + "compile_flags.txt" + "configure.ac" + ".git" + ]; + capabilities = { + textDocument = { + completion = { + editsNearCursor = true; + }; + }; + offsetEncoding = ["utf-8" "utf-16"]; + }; + on_attach = mkLuaInline '' + function(client, bufnr) + default_on_attach(client, bufnr) + + local function switch_source_header(bufnr) + local method_name = "textDocument/switchSourceHeader" + local client = vim.lsp.get_clients({ bufnr = bufnr, name = "clangd", })[1] + if not client then + return vim.notify(('method %s is not supported by any servers active on the current buffer'):format(method_name)) + end + local params = vim.lsp.util.make_text_document_params(bufnr) + client.request(method_name, params, function(err, result) + if err then + error(tostring(err)) + end + if not result then + vim.notify('corresponding file cannot be determined') + return + end + vim.cmd.edit(vim.uri_to_fname(result)) + end, bufnr) + end + + local function symbol_info() + local bufnr = vim.api.nvim_get_current_buf() + local clangd_client = vim.lsp.get_clients({ bufnr = bufnr, name = "clangd" })[1] + if not clangd_client or not clangd_client.supports_method 'textDocument/symbolInfo' then + return vim.notify('Clangd client not found', vim.log.levels.ERROR) + end + local win = vim.api.nvim_get_current_win() + local params = vim.lsp.util.make_position_params(win, clangd_client.offset_encoding) + clangd_client:request('textDocument/symbolInfo', params, function(err, res) + if err or #res == 0 then + -- Clangd always returns an error, there is not reason to parse it + return + end + local container = string.format('container: %s', res[1].containerName) ---@type string + local name = string.format('name: %s', res[1].name) ---@type string + vim.lsp.util.open_floating_preview({ name, container }, "", { + height = 2, + width = math.max(string.len(name), string.len(container)), + focusable = false, + focus = false, + border = 'single', + title = 'Symbol Info', + }) + end, bufnr) + end + + vim.api.nvim_buf_create_user_command( + bufnr, + "ClangdSwitchSourceHeader", + function(arg) + switch_source_header(0) + end, + {desc = "Switch between source/header"} + ) + + vim.api.nvim_buf_create_user_command( + bufnr, + "ClangdShowSymbolInfo", + function(arg) + symbol_info() + end, + {desc = "Show symbol info"} + ) + end ''; }; }; @@ -100,23 +196,10 @@ in { lsp = { enable = mkEnableOption "clang LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { description = "The clang LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "clang LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server " - data " " ~/.cache/jdtls/workspace "]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - }; - - opts = mkOption { - description = "Options to pass to clang LSP server"; - type = nullOr str; - default = null; + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; }; }; @@ -150,9 +233,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - - vim.lsp.lspconfig.sources.clang-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (name: { + inherit name; + value = servers.${name}; + }) + cfg.lsp.servers; }) (mkIf cfg.dap.enable { diff --git a/modules/plugins/languages/clojure.nix b/modules/plugins/languages/clojure.nix index 0b932708..442df2b7 100644 --- a/modules/plugins/languages/clojure.nix +++ b/modules/plugins/languages/clojure.nix @@ -4,15 +4,27 @@ lib, ... }: let + inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; inherit (lib.meta) getExe; inherit (lib.lists) isList; - inherit (lib.types) either listOf package str; + inherit (lib.types) enum either listOf package str; inherit (lib.nvim.types) mkGrammarOption; inherit (lib.nvim.lua) expToLua; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.clojure; + + defaultServers = ["clojure-lsp"]; + servers = { + clojure-lsp = { + enable = true; + cmd = [(getExe pkgs.clojure-lsp)]; + filetypes = ["clojure" "edn"]; + root_markers = ["project.clj" "deps.edn" "build.boot" "shadow-cljs.edn" ".git" "bb.edn"]; + }; + }; in { options.vim.languages.clojure = { enable = mkEnableOption "Clojure language support"; @@ -24,28 +36,22 @@ in { lsp = { enable = mkEnableOption "Clojure LSP support" // {default = config.vim.lsp.enable;}; - package = mkOption { - type = either package (listOf str); - default = pkgs.clojure-lsp; - description = "Clojure LSP"; + servers = mkOption { + type = listOf (enum (attrNames servers)); + default = defaultServers; + description = "Clojure LSP server to use"; }; }; }; config = mkIf cfg.enable (mkMerge [ (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.clojure-lsp = '' - lspconfig.clojure_lsp.setup { - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${getExe cfg.lsp.package}"}'' - }; - } - ''; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.treesitter.enable { diff --git a/modules/plugins/languages/csharp.nix b/modules/plugins/languages/csharp.nix index 49ae4985..9e13a5ef 100644 --- a/modules/plugins/languages/csharp.nix +++ b/modules/plugins/languages/csharp.nix @@ -5,14 +5,16 @@ options, ... }: let - inherit (builtins) attrNames; + inherit (builtins) attrNames concatMap; inherit (lib.options) mkEnableOption mkOption; - inherit (lib.types) either listOf package str enum; + inherit (lib.types) enum; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; + inherit (lib.meta) getExe; + inherit (lib.generators) mkLuaInline; inherit (lib.strings) optionalString; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.lua) toLuaObject; + inherit (lib.nvim.attrsets) mapListToAttrs; lspKeyConfig = config.vim.lsp.mappings; lspKeyOptions = options.vim.lsp.mappings; @@ -25,58 +27,148 @@ # 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"; + defaultServers = ["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) + 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) + 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'}" - } - } + 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 = { - package = pkgs.csharp-ls; - internalFormatter = true; - lspConfig = '' - local extended_handler = require("csharpls_extended").handler + cmd = [(lib.getExe pkgs.csharp-ls)]; + 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 - 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'}" - } + 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 = {}; }; }; extraServerPlugins = { omnisharp = ["omnisharp-extended-lsp-nvim"]; csharp_ls = ["csharpls-extended-lsp-nvim"]; + roslyn_ls = []; }; cfg = config.vim.languages.csharp; @@ -92,16 +184,10 @@ in { lsp = { enable = mkEnableOption "C# LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = 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; + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; }; }; }; @@ -114,9 +200,13 @@ in { }) (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; + vim.startPlugins = concatMap (server: extraServerPlugins.${server}) cfg.lsp.servers; + vim.lsp.servers = + mapListToAttrs (name: { + inherit name; + value = servers.${name}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/css.nix b/modules/plugins/languages/css.nix index 1075bbc7..2d767ffc 100644 --- a/modules/plugins/languages/css.nix +++ b/modules/plugins/languages/css.nix @@ -8,34 +8,25 @@ inherit (lib.options) mkEnableOption mkOption; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.types) enum package; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.css; - defaultServer = "vscode-langservers-extracted"; + defaultServer = ["cssls"]; servers = { - vscode-langservers-extracted = { - package = pkgs.vscode-langservers-extracted; - lspConfig = '' - -- enable (broadcasting) snippet capability for completion - -- see - local css_capabilities = vim.lsp.protocol.make_client_capabilities() - css_capabilities.textDocument.completion.completionItem.snippetSupport = true - - -- cssls setup - lspconfig.cssls.setup { - capabilities = css_capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/vscode-css-language-server", "--stdio"}'' - } - } - ''; + cssls = { + cmd = ["${pkgs.vscode-langservers-extracted}/bin/vscode-css-language-server" "--stdio"]; + filetypes = ["css" "scss" "less"]; + # needed to enable formatting + init_options = {provideFormatter = true;}; + root_markers = [".git" "package.json"]; + settings = { + css.validate = true; + scss.validate = true; + less.validate = true; + }; }; }; @@ -82,17 +73,10 @@ in { lsp = { enable = mkEnableOption "CSS LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - description = "CSS LSP server to use"; - type = enum (attrNames servers); + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); default = defaultServer; - }; - - package = mkOption { - description = "CSS LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server " - data " " ~/.cache/jdtls/workspace "]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; + description = "CSS LSP server to use"; }; }; @@ -120,8 +104,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.css-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (name: { + inherit name; + value = servers.${name}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/cue.nix b/modules/plugins/languages/cue.nix index bd446cbf..a22ff91c 100644 --- a/modules/plugins/languages/cue.nix +++ b/modules/plugins/languages/cue.nix @@ -4,11 +4,17 @@ lib, ... }: let - inherit (lib.options) mkEnableOption mkOption; + inherit (lib.options) mkEnableOption; + inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.types) package; inherit (lib.nvim.types) mkGrammarOption; + lspOptions = { + cmd = [(getExe pkgs.cue) "lsp"]; + filetypes = ["cue"]; + root_markers = ["cue.mod" ".git"]; + }; + cfg = config.vim.languages.cue; in { options.vim.languages.cue = { @@ -22,12 +28,6 @@ in { lsp = { enable = mkEnableOption "CUE LSP support" // {default = config.vim.lsp.enable;}; - - package = mkOption { - type = package; - default = pkgs.cue; - description = "cue lsp implementation"; - }; }; }; @@ -38,14 +38,7 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.cue-lsp = '' - lspconfig.cue.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = {"${cfg.lsp.package}/bin/cue", "lsp"}, - } - ''; + vim.lsp.servers.cue = lspOptions; }) ]); } diff --git a/modules/plugins/languages/dart.nix b/modules/plugins/languages/dart.nix index c2a1810d..52dcd560 100644 --- a/modules/plugins/languages/dart.nix +++ b/modules/plugins/languages/dart.nix @@ -6,34 +6,38 @@ }: let inherit (builtins) attrNames; inherit (lib.modules) mkIf mkMerge; + inherit (lib.meta) getExe; inherit (lib.trivial) boolToString; - inherit (lib.lists) isList; inherit (lib.options) mkEnableOption mkOption; - inherit (lib.types) enum either listOf package nullOr str bool; + inherit (lib.types) enum package nullOr str bool; inherit (lib.strings) optionalString; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; inherit (lib.nvim.dag) entryAfter; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.dart; ftcfg = cfg.flutter-tools; - defaultServer = "dart"; + defaultServers = ["dart"]; servers = { dart = { - package = pkgs.dart; - lspConfig = '' - lspconfig.dartls.setup{ - capabilities = capabilities; - on_attach=default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/dart", "language-server", "--protocol=lsp"}'' + enable = true; + cmd = [(getExe pkgs.dart) "language-server" "--protocol=lsp"]; + filetypes = ["dart"]; + root_markers = ["pubspec.yaml"]; + init_options = { + onlyAnalyzeProjectsWithOpenFiles = true; + suggestFromUnimportedLibraries = true; + closingLabels = true; + outline = true; + flutterOutline = true; + }; + settings = { + dart = { + completeFunctionCalls = true; + showTodos = true; }; - ${optionalString (cfg.lsp.opts != null) "init_options = ${cfg.lsp.dartOpts}"} - } - ''; + }; }; }; in { @@ -46,23 +50,11 @@ in { }; lsp = { - enable = mkEnableOption "Dart LSP support"; - server = mkOption { - description = "The Dart LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - package = mkOption { - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]''; - description = "Dart LSP server package, or the command to run as a list of strings"; - }; - - opts = mkOption { - type = nullOr str; - default = null; - description = "Options to pass to Dart LSP server"; + enable = mkEnableOption "Dart LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "Dart LSP server to use"; }; }; @@ -131,19 +123,23 @@ in { }; }; - config.vim = mkIf cfg.enable (mkMerge [ + config = mkIf cfg.enable (mkMerge [ (mkIf cfg.treesitter.enable { - treesitter.enable = true; - treesitter.grammars = [cfg.treesitter.package]; + vim.treesitter.enable = true; + vim.treesitter.grammars = [cfg.treesitter.package]; }) (mkIf cfg.lsp.enable { - lsp.lspconfig.enable = true; - lsp.lspconfig.sources.dart-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf ftcfg.enable { - startPlugins = [ + vim.startPlugins = [ ( if ftcfg.enableNoResolvePatch then "flutter-tools-patched" @@ -152,7 +148,7 @@ in { "plenary-nvim" ]; - pluginRC.flutter-tools = entryAfter ["lsp-setup"] '' + vim.pluginRC.flutter-tools = entryAfter ["lsp-servers"] '' require('flutter-tools').setup { ${optionalString (ftcfg.flutterPackage != null) "flutter_path = \"${ftcfg.flutterPackage}/bin/flutter\","} lsp = { diff --git a/modules/plugins/languages/default.nix b/modules/plugins/languages/default.nix index 961d7cc5..fd45758f 100644 --- a/modules/plugins/languages/default.nix +++ b/modules/plugins/languages/default.nix @@ -21,6 +21,7 @@ in { ./html.nix ./haskell.nix ./java.nix + ./json.nix ./lua.nix ./markdown.nix ./nim.nix @@ -29,6 +30,7 @@ in { ./ocaml.nix ./php.nix ./python.nix + ./qml.nix ./r.nix ./rust.nix ./scala.nix @@ -46,6 +48,7 @@ in { ./wgsl.nix ./yaml.nix ./ruby.nix + ./just.nix # This is now a hard deprecation. (mkRenamedOptionModule ["vim" "languages" "enableLSP"] ["vim" "lsp" "enable"]) diff --git a/modules/plugins/languages/elixir.nix b/modules/plugins/languages/elixir.nix index 241efd8e..12c8a517 100644 --- a/modules/plugins/languages/elixir.nix +++ b/modules/plugins/languages/elixir.nix @@ -7,30 +7,36 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.meta) getExe; + inherit (lib.types) enum package; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; inherit (lib.nvim.dag) entryAnywhere; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.elixir; - defaultServer = "elixirls"; + defaultServers = ["elixirls"]; servers = { elixirls = { - package = pkgs.elixir-ls; - lspConfig = '' - -- elixirls setup - lspconfig.elixirls.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/elixir-ls"}'' - } - } - ''; + enable = true; + cmd = [(getExe pkgs.elixir-ls)]; + filetypes = ["elixir" "eelixir" "heex" "surface"]; + root_dir = + mkLuaInline + /* + lua + */ + '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + local matches = vim.fs.find({ 'mix.exs' }, { upward = true, limit = 2, path = fname }) + local child_or_root_path, maybe_umbrella_path = unpack(matches) + local root_dir = vim.fs.dirname(maybe_umbrella_path or child_or_root_path) + + on_dir(root_dir) + end + ''; }; }; @@ -50,22 +56,16 @@ in { treesitter = { enable = mkEnableOption "Elixir treesitter" // {default = config.vim.languages.enableTreesitter;}; package = mkGrammarOption pkgs "elixir"; + heexPackage = mkGrammarOption pkgs "heex"; + eexPackage = mkGrammarOption pkgs "eex"; }; lsp = { enable = mkEnableOption "Elixir LSP support" // {default = config.vim.lsp.enable;}; - - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Elixir LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "Elixir LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server " - data " " ~/.cache/jdtls/workspace "]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; @@ -73,15 +73,15 @@ in { enable = mkEnableOption "Elixir formatting" // {default = config.vim.languages.enableFormat;}; type = mkOption { - description = "Elixir formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "Elixir formatter to use"; }; package = mkOption { - description = "Elixir formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "Elixir formatter package"; }; }; @@ -93,12 +93,20 @@ in { config = mkIf cfg.enable (mkMerge [ (mkIf cfg.treesitter.enable { vim.treesitter.enable = true; - vim.treesitter.grammars = [cfg.treesitter.package]; + vim.treesitter.grammars = [ + cfg.treesitter.package + cfg.treesitter.heexPackage + cfg.treesitter.eexPackage + ]; }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.elixir-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/fsharp.nix b/modules/plugins/languages/fsharp.nix index 6ed0ccab..9ead9a48 100644 --- a/modules/plugins/languages/fsharp.nix +++ b/modules/plugins/languages/fsharp.nix @@ -6,29 +6,48 @@ }: let inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; - inherit (lib.types) either listOf package str enum; + inherit (lib.types) package enum; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; - defaultServer = "fsautocomplete"; + defaultServer = ["fsautocomplete"]; servers = { fsautocomplete = { - package = pkgs.fsautocomplete; - internalFormatter = false; - lspConfig = '' - lspconfig.fsautocomplete.setup { - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else "{'${cfg.lsp.package}/bin/fsautocomplete'}" - }, - } + cmd = [(getExe pkgs.fsautocomplete) "--adaptive-lsp-server-enabled"]; + filetypes = ["fsharp"]; + root_dir = mkLuaInline '' + function(bufnr, on_dir) + on_dir(vim.fs.root(bufnr, function(name, path) + return name == ".git" or name:match("%.sln$") or name:match("%.fsproj$") + end)) + end ''; + init_options = { + AutomaticWorkspaceInit = true; + }; + settings = { + FSharp = { + keywordsAutocomplete = true; + ExternalAutocomplete = false; + Linter = true; + UnionCaseStubGeneration = true; + UnionCaseStubGenerationBody = ''failwith "Not Implemented"''; + RecordStubGeneration = true; + RecordStubGenerationBody = ''failwith "Not Implemented"''; + InterfaceStubGeneration = true; + InterfaceStubGenerationObjectIdentifier = "this"; + InterfaceStubGenerationMethodBody = ''failwith "Not Implemented"''; + UnusedOpensAnalyzer = true; + UnusedDeclarationsAnalyzer = true; + UseSdkScripts = true; + SimplifyNameAnalyzer = true; + ResolveNamespaces = true; + EnableReferenceCodeLens = true; + }; + }; }; }; @@ -52,18 +71,11 @@ in { lsp = { enable = mkEnableOption "F# LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - type = enum (attrNames servers); + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); default = defaultServer; description = "F# LSP server to use"; }; - - package = mkOption { - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - example = ''[lib.getExe pkgs.fsautocomplete "--state-directory" "~/.cache/fsautocomplete"]''; - description = "F# LSP server package, or the command to run as a list of strings"; - }; }; format = { enable = mkEnableOption "F# formatting" // {default = config.vim.languages.enableFormat;}; @@ -90,8 +102,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.fsharp-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (name: { + inherit name; + value = servers.${name}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/gleam.nix b/modules/plugins/languages/gleam.nix index 272ef51e..984d21aa 100644 --- a/modules/plugins/languages/gleam.nix +++ b/modules/plugins/languages/gleam.nix @@ -7,28 +7,20 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.meta) getExe; + inherit (lib.types) enum; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.gleam; - defaultServer = "gleam"; + defaultServers = ["gleam"]; servers = { gleam = { - package = pkgs.gleam; - lspConfig = '' - lspconfig.gleam.setup{ - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/gleam", "lsp"}'' - } - } - ''; + enable = true; + cmd = [(getExe pkgs.gleam) "lsp"]; + filetypes = ["gleam"]; + root_markers = ["gleam.toml" ".git"]; }; }; in { @@ -42,18 +34,11 @@ in { lsp = { enable = mkEnableOption "Gleam LSP support" // {default = config.vim.lsp.enable;}; - - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Gleam LSP server to use"; }; - - package = mkOption { - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - description = "Gleam LSP server package, or the command to run as a list of strings"; - }; }; }; @@ -64,8 +49,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.gleam-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/go.nix b/modules/plugins/languages/go.nix index bab0ff4a..5b1aeebb 100644 --- a/modules/plugins/languages/go.nix +++ b/modules/plugins/languages/go.nix @@ -8,28 +8,53 @@ inherit (lib.options) mkEnableOption mkOption literalMD; inherit (lib.modules) mkIf mkMerge; inherit (lib.meta) getExe; - inherit (lib.lists) isList; - inherit (lib.types) bool enum either listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.generators) mkLuaInline; + inherit (lib.types) bool enum package; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; inherit (lib.nvim.dag) entryAfter; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.go; - defaultServer = "gopls"; + defaultServers = ["gopls"]; servers = { gopls = { - package = pkgs.gopls; - lspConfig = '' - lspconfig.gopls.setup { - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/gopls", "serve"}'' - }, - } + cmd = [(getExe pkgs.gopls)]; + filetypes = ["go" "gomod" "gowork" "gotmpl"]; + root_dir = mkLuaInline '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + + local function get_root(fname) + if _G.nvf_gopls_mod_cache and fname:sub(1, #_G.nvf_gopls_mod_cache) == _G.nvf_gopls_mod_cache then + local clients = vim.lsp.get_clients { name = 'gopls' } + if #clients > 0 then + return clients[#clients].config.root_dir + end + end + return vim.fs.root(fname, 'go.work') or vim.fs.root(fname, 'go.mod') or vim.fs.root(fname, '.git') + end + + -- see: https://github.com/neovim/nvim-lspconfig/issues/804 + if _G.nvf_gopls_mod_cache then + on_dir(get_root(fname)) + return + end + local cmd = { 'go', 'env', 'GOMODCACHE' } + local ok, err = pcall(vim.system, cmd, { text = true }, function(output) + if output.code == 0 then + if output.stdout then + _G.nvf_gopls_mod_cache = vim.trim(output.stdout) + end + on_dir(get_root(fname)) + else + vim.schedule(function() + vim.notify(('[gopls] cmd failed with code %d: %s\n%s'):format(output.code, cmd, output.stderr)) + end) + end + end) + if not ok then vim.notify(('[gopls] cmd failed: %s\n%s'):format(cmd, err)) end + end ''; }; }; @@ -69,17 +94,10 @@ in { lsp = { enable = mkEnableOption "Go LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Go LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "Go LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server " - data " " ~/.cache/jdtls/workspace "]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; @@ -134,8 +152,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.go-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (name: { + inherit name; + value = servers.${name}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/haskell.nix b/modules/plugins/languages/haskell.nix index f50c9f09..600c7724 100644 --- a/modules/plugins/languages/haskell.nix +++ b/modules/plugins/languages/haskell.nix @@ -4,17 +4,67 @@ pkgs, ... }: let - inherit (builtins) isList; - inherit (lib.types) either package listOf str; + inherit (builtins) isList attrNames; + inherit (lib.types) either package enum listOf str; inherit (lib.options) mkEnableOption mkOption; inherit (lib.strings) optionalString; inherit (lib.modules) mkIf mkMerge; inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.dag) entryAfter; + inherit (lib.nvim.dag) entryAfter entryBefore; inherit (lib.nvim.lua) expToLua; + inherit (lib.meta) getExe'; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; inherit (pkgs) haskellPackages; + inherit (lib.nvim.lua) toLuaObject; cfg = config.vim.languages.haskell; + + defaultServers = ["hls"]; + servers = { + hls = { + enable = false; + cmd = [(getExe' pkgs.haskellPackages.haskell-language-server "haskell-language-server-wrapper") "--lsp"]; + filetypes = ["haskell" "lhaskell"]; + on_attach = + mkLuaInline + /* + lua + */ + '' + function(client, bufnr) + local ht = require("haskell-tools") + default_on_attach(client, bufnr, ht) + local opts = { noremap = true, silent = true, buffer = bufnr } + vim.keymap.set('n', 'cl', vim.lsp.codelens.run, opts) + vim.keymap.set('n', 'hs', ht.hoogle.hoogle_signature, opts) + vim.keymap.set('n', 'ea', ht.lsp.buf_eval_all, opts) + vim.keymap.set('n', 'rr', ht.repl.toggle, opts) + vim.keymap.set('n', 'rf', function() + ht.repl.toggle(vim.api.nvim_buf_get_name(0)) + end, opts) + vim.keymap.set('n', 'rq', ht.repl.quit, opts) + end + ''; + root_dir = + mkLuaInline + /* + lua + */ + '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + on_dir(util.root_pattern('hie.yaml', 'stack.yaml', 'cabal.project', '*.cabal', 'package.yaml')(fname)) + end + ''; + settings = { + haskell = { + formattingProvider = "ormolu"; + cabalFormattingProvider = "cabalfmt"; + }; + }; + }; + }; in { options.vim.languages.haskell = { enable = mkEnableOption "Haskell support"; @@ -25,21 +75,20 @@ in { }; lsp = { - enable = mkEnableOption "LSP support for Haskell" // {default = config.vim.lsp.enable;}; - package = mkOption { - description = "Haskell LSP package or command to run the Haskell LSP"; - example = ''[ (lib.getExe pkgs.haskellPackages.haskell-language-server) "--debug" ]''; - default = haskellPackages.haskell-language-server; - type = either package (listOf str); + enable = mkEnableOption "Haskell LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = listOf (enum (attrNames servers)); + default = defaultServers; + description = "Haskell LSP server to use"; }; }; dap = { enable = mkEnableOption "DAP support for Haskell" // {default = config.vim.languages.enableDAP;}; package = mkOption { - description = "Haskell DAP package or command to run the Haskell DAP"; default = haskellPackages.haskell-debug-adapter; type = either package (listOf str); + description = "Haskell DAP package or command to run the Haskell DAP"; }; }; }; @@ -57,7 +106,7 @@ in { startPlugins = ["haskell-tools-nvim"]; luaConfigRC.haskell-tools-nvim = entryAfter - ["lsp-setup"] + ["lsp-servers"] '' vim.g.haskell_tools = { ${optionalString cfg.lsp.enable '' @@ -67,25 +116,7 @@ in { enable = true, }, }, - hls = { - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/haskell-language-server-wrapper", "--lsp"}'' - }, - on_attach = function(client, bufnr, ht) - default_on_attach(client, bufnr, ht) - local opts = { noremap = true, silent = true, buffer = bufnr } - vim.keymap.set('n', 'cl', vim.lsp.codelens.run, opts) - vim.keymap.set('n', 'hs', ht.hoogle.hoogle_signature, opts) - vim.keymap.set('n', 'ea', ht.lsp.buf_eval_all, opts) - vim.keymap.set('n', 'rr', ht.repl.toggle, opts) - vim.keymap.set('n', 'rf', function() - ht.repl.toggle(vim.api.nvim_buf_get_name(0)) - end, opts) - vim.keymap.set('n', 'rq', ht.repl.quit, opts) - end, - }, + hls = ${toLuaObject servers.hls}, ''} ${optionalString cfg.dap.enable '' dap = { diff --git a/modules/plugins/languages/hcl.nix b/modules/plugins/languages/hcl.nix index e702170c..ca00f600 100644 --- a/modules/plugins/languages/hcl.nix +++ b/modules/plugins/languages/hcl.nix @@ -8,22 +8,19 @@ inherit (lib.options) mkEnableOption mkOption; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.types) package bool enum; + inherit (lib.types) package bool enum listOf; inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.hcl; - defaultServer = "terraform-ls"; + defaultServers = ["terraform-ls"]; servers = { terraform-ls = { - package = pkgs.terraform-ls; - lspConfig = '' - lspconfig.terraformls.setup { - capabilities = capabilities, - on_attach=default_on_attach, - cmd = {"${lib.getExe cfg.lsp.package}", "serve"}, - } - ''; + enable = true; + cmd = [(getExe pkgs.terraform-ls) "serve"]; + filetypes = ["terraform" "terraform-vars"]; + root_markers = [".terraform" ".git"]; }; }; @@ -43,12 +40,11 @@ in { }; lsp = { - enable = mkEnableOption "HCL LSP support (terraform-ls)" // {default = config.vim.lsp.enable;}; - # TODO: (maybe, is it better?) it would be cooler to use vscode-extensions.hashicorp.hcl probably, shouldn't be too hard - package = mkOption { - type = package; - default = servers.${defaultServer}.package; - description = "HCL language server package (terraform-ls)"; + enable = mkEnableOption "HCL LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = listOf (enum (attrNames servers)); + default = defaultServers; + description = "HCL LSP server to use"; }; }; @@ -96,10 +92,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources = lib.optionalAttrs (! config.vim.languages.terraform.lsp.enable) { - terraform-ls = servers.${cfg.lsp.server}.lspConfig; - }; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/helm.nix b/modules/plugins/languages/helm.nix index ffe115c9..1971eb31 100644 --- a/modules/plugins/languages/helm.nix +++ b/modules/plugins/languages/helm.nix @@ -4,44 +4,37 @@ lib, ... }: let - inherit (builtins) attrNames; + inherit (builtins) attrNames head; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.meta) getExe; + inherit (lib.types) enum; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.helm; yamlCfg = config.vim.languages.yaml; - helmCmd = - if isList cfg.lsp.package - then cfg.lsp.package - else ["${cfg.lsp.package}/bin/helm_ls" "serve"]; - yamlCmd = - if isList yamlCfg.lsp.package - then builtins.elemAt yamlCfg.lsp.package 0 - else "${yamlCfg.lsp.package}/bin/yaml-language-server"; - - defaultServer = "helm-ls"; + defaultServers = ["helm-ls"]; servers = { helm-ls = { - package = pkgs.helm-ls; - lspConfig = '' - lspconfig.helm_ls.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${expToLua helmCmd}, - settings = { - ['helm-ls'] = { - yamlls = { - path = "${yamlCmd}" - } - } - } - } - ''; + enable = true; + cmd = [(getExe pkgs.helm-ls) "serve"]; + filetypes = ["helm" "yaml.helm-values"]; + root_markers = ["Chart.yaml"]; + capabilities = { + didChangeWatchedFiles = { + dynamicRegistration = true; + }; + }; + settings = { + helm-ls = { + yamlls = { + # TODO: Determine if this is a good enough solution + path = (head yamlCfg.lsp.servers).cmd; + }; + }; + }; }; }; in { @@ -55,17 +48,10 @@ in { lsp = { enable = mkEnableOption "Helm LSP support" // {default = config.vim.lsp.enable;}; - - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Helm LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "Helm LSP server package"; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; }; @@ -77,8 +63,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.helm-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) { diff --git a/modules/plugins/languages/html.nix b/modules/plugins/languages/html.nix index 0bef2767..bcc6b842 100644 --- a/modules/plugins/languages/html.nix +++ b/modules/plugins/languages/html.nix @@ -4,14 +4,44 @@ lib, ... }: let + inherit (builtins) attrNames; + inherit (lib.meta) getExe; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.types) bool; + inherit (lib.types) bool enum package; inherit (lib.lists) optional; - inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.nvim.types) mkGrammarOption diagnostics singleOrListOf; inherit (lib.nvim.dag) entryAnywhere; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.html; + + defaultServers = ["superhtml"]; + servers = { + superhtml = { + cmd = [(getExe pkgs.superhtml) "lsp"]; + filetypes = ["html" "shtml" "htm"]; + root_markers = ["index.html" ".git"]; + }; + }; + + defaultFormat = "superhtml"; + formats = { + superhtml = { + package = pkgs.writeShellApplication { + name = "superhtml_fmt"; + runtimeInputs = [pkgs.superhtml]; + text = "superhtml fmt -"; + }; + }; + }; + + defaultDiagnosticsProvider = ["htmlhint"]; + diagnosticsProviders = { + htmlhint = { + config.cmd = getExe pkgs.htmlhint; + }; + }; in { options.vim.languages.html = { enable = mkEnableOption "HTML language support"; @@ -19,9 +49,44 @@ in { enable = mkEnableOption "HTML treesitter support" // {default = config.vim.languages.enableTreesitter;}; package = mkGrammarOption pkgs "html"; autotagHtml = mkOption { - description = "Enable autoclose/autorename of html tags (nvim-ts-autotag)"; type = bool; default = true; + description = "Enable autoclose/autorename of html tags (nvim-ts-autotag)"; + }; + }; + + lsp = { + enable = mkEnableOption "HTML LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "HTML LSP server to use"; + }; + }; + + format = { + enable = mkEnableOption "HTML formatting" // {default = config.vim.languages.enableFormat;}; + + type = mkOption { + type = enum (attrNames formats); + default = defaultFormat; + description = "HTML formatter to use"; + }; + + package = mkOption { + type = package; + default = formats.${cfg.format.type}.package; + description = "HTML formatter package"; + }; + }; + + extraDiagnostics = { + enable = mkEnableOption "extra HTML diagnostics" // {default = config.vim.languages.enableExtraDiagnostics;}; + + types = diagnostics { + langDesc = "HTML"; + inherit diagnosticsProviders; + inherit defaultDiagnosticsProvider; }; }; }; @@ -41,5 +106,35 @@ in { ''); }; }) + + (mkIf cfg.lsp.enable { + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; + }) + + (mkIf (cfg.format.enable && !cfg.lsp.enable) { + vim.formatter.conform-nvim = { + enable = true; + setupOpts.formatters_by_ft.html = [cfg.format.type]; + setupOpts.formatters.${cfg.format.type} = { + command = getExe cfg.format.package; + }; + }; + }) + + (mkIf cfg.extraDiagnostics.enable { + vim.diagnostics.nvim-lint = { + enable = true; + linters_by_ft.html = cfg.extraDiagnostics.types; + linters = mkMerge (map (name: { + ${name} = diagnosticsProviders.${name}.config; + }) + cfg.extraDiagnostics.types); + }; + }) ]); } diff --git a/modules/plugins/languages/java.nix b/modules/plugins/languages/java.nix index 2e26feea..82fae184 100644 --- a/modules/plugins/languages/java.nix +++ b/modules/plugins/languages/java.nix @@ -7,12 +7,59 @@ inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; inherit (lib.meta) getExe; - inherit (lib.lists) isList; - inherit (lib.types) either listOf package str; + inherit (builtins) attrNames; + inherit (lib.types) listOf enum; inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.nvim.dag) entryBefore; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.java; + + defaultServers = ["jdtls"]; + servers = { + jdtls = { + enable = true; + cmd = + mkLuaInline + /* + lua + */ + '' + { + '${getExe pkgs.jdt-language-server}', + '-configuration', + get_jdtls_config_dir(), + '-data', + get_jdtls_workspace_dir(), + get_jdtls_jvm_args(), + } + ''; + filetypes = ["java"]; + root_markers = [ + # Multi-module projects + ".git" + "build.gradle" + "build.gradle.kts" + # Single-module projects + "build.xml" # Ant + "pom.xml" # Maven + "settings.gradle" # Gradle + "settings.gradle.kts" # Gradle + ]; + init_options = { + workspace = mkLuaInline "get_jdtls_workspace_dir()"; + jvm_args = {}; + os_config = mkLuaInline "nil"; + }; + handlers = { + "textDocument/codeAction" = mkLuaInline "jdtls_on_textdocument_codeaction"; + "textDocument/rename" = mkLuaInline "jdtls_on_textdocument_rename"; + "workspace/applyEdit" = mkLuaInline "jdtls_on_workspace_applyedit"; + "language/status" = mkLuaInline "vim.schedule_wrap(jdtls_on_language_status)"; + }; + }; + }; in { options.vim.languages.java = { enable = mkEnableOption "Java language support"; @@ -23,30 +70,107 @@ in { }; lsp = { - enable = mkEnableOption "Java LSP support (java-language-server)" // {default = config.vim.lsp.enable;}; - package = mkOption { - description = "java language server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]''; - type = either package (listOf str); - default = pkgs.jdt-language-server; + enable = mkEnableOption "Java LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = listOf (enum (attrNames servers)); + default = defaultServers; + description = "Java LSP server to use"; }; }; }; config = mkIf cfg.enable (mkMerge [ (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.jdtls = '' - lspconfig.jdtls.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${getExe cfg.lsp.package}", "-data", vim.fn.stdpath("cache").."/jdtls/workspace"}'' - }, - } - ''; + vim.luaConfigRC.jdtls-util = + entryBefore ["lsp-servers"] + /* + lua + */ + '' + local jdtls_handlers = require 'vim.lsp.handlers' + + local jdtls_env = { + HOME = vim.uv.os_homedir(), + XDG_CACHE_HOME = os.getenv 'XDG_CACHE_HOME', + JDTLS_JVM_ARGS = os.getenv 'JDTLS_JVM_ARGS', + } + + local function get_cache_dir() + return jdtls_env.XDG_CACHE_HOME and jdtls_env.XDG_CACHE_HOME or jdtls_env.HOME .. '/.cache' + end + + local function get_jdtls_cache_dir() + return get_cache_dir() .. '/jdtls' + end + + local function get_jdtls_config_dir() + return get_jdtls_cache_dir() .. '/config' + end + + local function get_jdtls_workspace_dir() + return get_jdtls_cache_dir() .. '/workspace' + end + + local function get_jdtls_jvm_args() + local args = {} + for a in string.gmatch((jdtls_env.JDTLS_JVM_ARGS or '''), '%S+') do + local arg = string.format('--jvm-arg=%s', a) + table.insert(args, arg) + end + return unpack(args) + end + + -- TextDocument version is reported as 0, override with nil so that + -- the client doesn't think the document is newer and refuses to update + -- See: https://github.com/eclipse/eclipse.jdt.ls/issues/1695 + local function jdtls_fix_zero_version(workspace_edit) + if workspace_edit and workspace_edit.documentChanges then + for _, change in pairs(workspace_edit.documentChanges) do + local text_document = change.textDocument + if text_document and text_document.version and text_document.version == 0 then + text_document.version = nil + end + end + end + return workspace_edit + end + + local function jdtls_on_textdocument_codeaction(err, actions, ctx) + for _, action in ipairs(actions) do + -- TODO: (steelsojka) Handle more than one edit? + if action.command == 'java.apply.workspaceEdit' then -- 'action' is Command in java format + action.edit = jdtls_fix_zero_version(action.edit or action.arguments[1]) + elseif type(action.command) == 'table' and action.command.command == 'java.apply.workspaceEdit' then -- 'action' is CodeAction in java format + action.edit = jdtls_fix_zero_version(action.edit or action.command.arguments[1]) + end + end + + jdtls_handlers[ctx.method](err, actions, ctx) + end + + local function jdtls_on_textdocument_rename(err, workspace_edit, ctx) + jdtls_handlers[ctx.method](err, jdtls_fix_zero_version(workspace_edit), ctx) + end + + local function jdtls_on_workspace_applyedit(err, workspace_edit, ctx) + jdtls_handlers[ctx.method](err, jdtls_fix_zero_version(workspace_edit), ctx) + end + + -- Non-standard notification that can be used to display progress + local function jdtls_on_language_status(_, result) + local command = vim.api.nvim_command + command 'echohl ModeMsg' + command(string.format('echo "%s"', result.message)) + command 'echohl None' + end + ''; + + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.treesitter.enable { diff --git a/modules/plugins/languages/json.nix b/modules/plugins/languages/json.nix new file mode 100644 index 00000000..26349710 --- /dev/null +++ b/modules/plugins/languages/json.nix @@ -0,0 +1,100 @@ +{ + config, + pkgs, + lib, + ... +}: let + inherit (builtins) attrNames; + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.meta) getExe' getExe; + inherit (lib.modules) mkIf mkMerge; + inherit (lib.types) enum package; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; + + cfg = config.vim.languages.json; + + defaultServers = ["jsonls"]; + servers = { + jsonls = { + cmd = [(getExe' pkgs.vscode-langservers-extracted "vscode-json-languageserver") "--stdio"]; + filetypes = ["json" "jsonc"]; + init_options = {provideFormatter = true;}; + root_markers = [".git"]; + }; + }; + + defaultFormat = "jsonfmt"; + + formats = { + jsonfmt = { + package = pkgs.writeShellApplication { + name = "jsonfmt"; + runtimeInputs = [pkgs.jsonfmt]; + text = "jsonfmt -w -"; + }; + }; + }; +in { + options.vim.languages.json = { + enable = mkEnableOption "JSON language support"; + + treesitter = { + enable = mkEnableOption "JSON treesitter" // {default = config.vim.languages.enableTreesitter;}; + + package = mkGrammarOption pkgs "json"; + }; + + lsp = { + enable = mkEnableOption "JSON LSP support" // {default = config.vim.lsp.enable;}; + + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "JSON LSP server to use"; + }; + }; + + format = { + enable = mkEnableOption "JSON formatting" // {default = config.vim.languages.enableFormat;}; + + type = mkOption { + description = "JSON formatter to use"; + type = enum (attrNames formats); + default = defaultFormat; + }; + + package = mkOption { + description = "JSON formatter package"; + type = package; + default = formats.${cfg.format.type}.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.lsp.servers = + mapListToAttrs (name: { + inherit name; + value = servers.${name}; + }) + cfg.lsp.servers; + }) + + (mkIf cfg.format.enable { + vim.formatter.conform-nvim = { + enable = true; + setupOpts.formatters_by_ft.json = [cfg.format.type]; + setupOpts.formatters.${cfg.format.type} = { + command = getExe cfg.format.package; + }; + }; + }) + ]); +} diff --git a/modules/plugins/languages/julia.nix b/modules/plugins/languages/julia.nix index 8c48b070..c9be8d49 100644 --- a/modules/plugins/languages/julia.nix +++ b/modules/plugins/languages/julia.nix @@ -4,63 +4,82 @@ config, ... }: let - inherit (builtins) attrNames isList; + inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; - inherit (lib.types) either listOf package str enum bool nullOr; + inherit (lib.types) enum; inherit (lib.modules) mkIf mkMerge; - inherit (lib.strings) optionalString; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.meta) getExe; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.nvim.dag) entryBefore; - defaultServer = "julials"; + defaultServers = ["julials"]; servers = { julials = { - package = pkgs.julia.withPackages ["LanguageServer"]; - internalFormatter = true; - lspConfig = '' - lspconfig.julials.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else '' - { - "${optionalString (cfg.lsp.package != null) "${cfg.lsp.package}/bin/"}julia", - "--startup-file=no", - "--history-file=no", - "--eval", - [[ - using LanguageServer - - depot_path = get(ENV, "JULIA_DEPOT_PATH", "") - project_path = let - dirname(something( - ## 1. Finds an explicitly set project (JULIA_PROJECT) - Base.load_path_expand(( - p = get(ENV, "JULIA_PROJECT", nothing); - p === nothing ? nothing : isempty(p) ? nothing : p - )), - ## 2. Look for a Project.toml file in the current working directory, - ## or parent directories, with $HOME as an upper boundary - Base.current_project(), - ## 3. First entry in the load path - get(Base.load_path(), 1, nothing), - ## 4. Fallback to default global environment, - ## this is more or less unreachable - Base.load_path_expand("@v#.#"), - )) - end - @info "Running language server" VERSION pwd() project_path depot_path - server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path) - server.runlinter = true - run(server) - ]] - } - '' - } - } - ''; + enable = true; + cmd = + mkLuaInline + /* + lua + */ + '' + { + '${getExe (pkgs.julia.withPackages ["LanguageServer"])}', + '--startup-file=no', + '--history-file=no', + '-e', + [[ + # Load LanguageServer.jl: attempt to load from ~/.julia/environments/nvim-lspconfig + # with the regular load path as a fallback + ls_install_path = joinpath( + get(DEPOT_PATH, 1, joinpath(homedir(), ".julia")), + "environments", "nvim-lspconfig" + ) + pushfirst!(LOAD_PATH, ls_install_path) + using LanguageServer + popfirst!(LOAD_PATH) + depot_path = get(ENV, "JULIA_DEPOT_PATH", "") + project_path = let + dirname(something( + ## 1. Finds an explicitly set project (JULIA_PROJECT) + Base.load_path_expand(( + p = get(ENV, "JULIA_PROJECT", nothing); + p === nothing ? nothing : isempty(p) ? nothing : p + )), + ## 2. Look for a Project.toml file in the current working directory, + ## or parent directories, with $HOME as an upper boundary + Base.current_project(), + ## 3. First entry in the load path + get(Base.load_path(), 1, nothing), + ## 4. Fallback to default global environment, + ## this is more or less unreachable + Base.load_path_expand("@v#.#"), + )) + end + @info "Running language server" VERSION pwd() project_path depot_path + server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path) + server.runlinter = true + run(server) + ]], + } + ''; + filetypes = ["julia"]; + root_markers = ["Project.toml" "JuliaProject.toml"]; + on_attach = + mkLuaInline + /* + lua + */ + '' + function(_, bufnr) + vim.api.nvim_buf_create_user_command(bufnr, 'LspJuliaActivateEnv', activate_julia_env, { + desc = 'Activate a Julia environment', + nargs = '?', + complete = 'file', + }) + end + ''; }; }; @@ -76,38 +95,26 @@ in { }; lsp = { - enable = mkOption { - type = bool; - default = config.vim.lsp.enable; + enable = mkEnableOption "Julia LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = '' - Whether to enable Julia LSP support. + Julia LSP Server to Use ::: {.note} The entirety of Julia is bundled with nvf, if you enable this option, since there is no way to provide only the LSP server. If you want to avoid that, you have to change - [](#opt-vim.languages.julia.lsp.package) to use the Julia binary - in {env}`PATH` (set it to `null`), and add the `LanguageServer` package to - Julia in your devshells. + [vim.lsp.servers.julials.cmd](#opt-vim.lsp.servers._name_.cmd) to use + the Julia binary in {env}`PATH`, and add the `LanguageServer` + package to Julia in your devshells. + + Check the source file of this option for the full `cmd`. ::: ''; }; - - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; - description = "Julia LSP server to use"; - }; - - package = mkOption { - description = '' - Julia LSP server package, `null` to use the Julia binary in {env}`PATH`, or - the command to run as a list of strings. - ''; - type = nullOr (either package (listOf str)); - default = servers.${cfg.lsp.server}.package; - }; }; }; }; @@ -119,8 +126,70 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.julia-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.luaConfigRC.julia-util = + entryBefore ["lsp-servers"] + /* + lua + */ + '' + local function activate_julia_env(path) + assert(vim.fn.has 'nvim-0.10' == 1, 'requires Nvim 0.10 or newer') + local bufnr = vim.api.nvim_get_current_buf() + local julials_clients = vim.lsp.get_clients { bufnr = bufnr, name = 'julials' } + assert( + #julials_clients > 0, + 'method julia/activateenvironment is not supported by any servers active on the current buffer' + ) + local function _activate_env(environment) + if environment then + for _, julials_client in ipairs(julials_clients) do + julials_client:notify('julia/activateenvironment', { envPath = environment }) + end + vim.notify('Julia environment activated: \n`' .. environment .. '`', vim.log.levels.INFO) + end + end + if path then + path = vim.fs.normalize(vim.fn.fnamemodify(vim.fn.expand(path), ':p')) + local found_env = false + for _, project_file in ipairs(root_files) do + local file = vim.uv.fs_stat(vim.fs.joinpath(path, project_file)) + if file and file.type then + found_env = true + break + end + end + if not found_env then + vim.notify('Path is not a julia environment: \n`' .. path .. '`', vim.log.levels.WARN) + return + end + _activate_env(path) + else + local depot_paths = vim.env.JULIA_DEPOT_PATH + and vim.split(vim.env.JULIA_DEPOT_PATH, vim.fn.has 'win32' == 1 and ';' or ':') + or { vim.fn.expand '~/.julia' } + local environments = {} + vim.list_extend(environments, vim.fs.find(root_files, { type = 'file', upward = true, limit = math.huge })) + for _, depot_path in ipairs(depot_paths) do + local depot_env = vim.fs.joinpath(vim.fs.normalize(depot_path), 'environments') + vim.list_extend( + environments, + vim.fs.find(function(name, env_path) + return vim.tbl_contains(root_files, name) and string.sub(env_path, #depot_env + 1):match '^/[^/]*$' + end, { path = depot_env, type = 'file', limit = math.huge }) + ) + end + environments = vim.tbl_map(vim.fs.dirname, environments) + vim.ui.select(environments, { prompt = 'Select a Julia environment' }, _activate_env) + end + end + ''; + + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/just.nix b/modules/plugins/languages/just.nix new file mode 100644 index 00000000..7645f255 --- /dev/null +++ b/modules/plugins/languages/just.nix @@ -0,0 +1,64 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit (builtins) attrNames; + inherit (lib.options) mkEnableOption mkOption; + inherit (lib.modules) mkIf mkMerge; + inherit (lib.meta) getExe; + inherit (lib.types) enum listOf; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.nvim.types) mkGrammarOption; + + cfg = config.vim.languages.just; + + defaultServers = ["just-lsp"]; + servers = { + just-lsp = { + enable = true; + cmd = [(getExe pkgs.just-lsp)]; + filetypes = ["just"]; + root_markers = [".git" "justfile"]; + }; + }; +in { + options.vim.languages.just = { + enable = mkEnableOption "Just support"; + + treesitter = { + enable = + mkEnableOption "Just treesitter" // {default = config.vim.languages.enableTreesitter;}; + package = mkGrammarOption pkgs "just"; + }; + + lsp = { + enable = + mkEnableOption "Just LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = listOf (enum (attrNames servers)); + default = defaultServers; + description = "Just LSP server to use"; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + (mkIf cfg.treesitter.enable { + vim.treesitter = { + enable = true; + grammars = [cfg.treesitter.package]; + }; + }) + + (mkIf cfg.lsp.enable { + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; + }) + ]); +} diff --git a/modules/plugins/languages/kotlin.nix b/modules/plugins/languages/kotlin.nix index 1118afdf..f316c4cb 100644 --- a/modules/plugins/languages/kotlin.nix +++ b/modules/plugins/languages/kotlin.nix @@ -4,16 +4,47 @@ lib, ... }: let - inherit (lib.options) mkEnableOption mkOption literalExpression; + inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.meta) getExe; - inherit (lib.types) either package listOf str; + inherit (lib.meta) getExe' getExe; + inherit (builtins) attrNames; + inherit (lib.types) enum listOf; inherit (lib.nvim.types) mkGrammarOption diagnostics; - inherit (lib.lists) isList; - inherit (lib.nvim.lua) expToLua; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.kotlin; + defaultServers = ["kotlin-language-server"]; + servers = { + kotlin-language-server = { + enable = true; + cmd = [(getExe' pkgs.kotlin-language-server "kotlin-language-server")]; + filetypes = ["kotlin"]; + root_markers = [ + "settings.gradle" # Gradle (multi-project) + "settings.gradle.kts" # Gradle (multi-project) + "build.xml" # Ant + "pom.xml" # Maven + "build.gradle" # Gradle + "build.gradle.kts" # gradle + ]; + init_options = { + storagePath = mkLuaInline " + vim.fs.root(vim.fn.expand '%:p:h', + { + 'settings.gradle', -- Gradle (multi-project) + 'settings.gradle.kts', -- Gradle (multi-project) + 'build.xml', -- Ant + 'pom.xml', -- Maven + 'build.gradle', -- Gradle + 'build.gradle.kts', -- Gradle + } + )"; + }; + }; + }; + defaultDiagnosticsProvider = ["ktlint"]; diagnosticsProviders = { ktlint = { @@ -31,22 +62,10 @@ in { lsp = { enable = mkEnableOption "Kotlin LSP support" // {default = config.vim.lsp.enable;}; - - package = mkOption { - description = "kotlin_language_server package with Kotlin runtime"; - type = either package (listOf str); - example = literalExpression '' - pkgs.symlinkJoin { - 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; + servers = mkOption { + type = listOf (enum (attrNames servers)); + default = defaultServers; + description = "Kotlin LSP server to use"; }; }; @@ -78,23 +97,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.kotlin_language_server = '' - lspconfig.kotlin_language_server.setup { - capabilities = capabilities, - root_dir = lspconfig.util.root_pattern("main.kt", ".git"), - on_attach=default_on_attach, - init_options = { - -- speeds up the startup time for the LSP - storagePath = vim.fn.stdpath('state') .. '/kotlin', - }, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/kotlin-language-server"}'' - }, - } - ''; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/lua.nix b/modules/plugins/languages/lua.nix index 9f56cbea..e454eaa8 100644 --- a/modules/plugins/languages/lua.nix +++ b/modules/plugins/languages/lua.nix @@ -8,13 +8,32 @@ inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; inherit (lib.meta) getExe; - inherit (lib.lists) isList; - inherit (lib.types) bool either enum listOf package str; + inherit (lib.types) bool enum listOf package; inherit (lib.nvim.types) diagnostics mkGrammarOption; - inherit (lib.nvim.lua) expToLua; inherit (lib.nvim.dag) entryBefore; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.lua; + + defaultServers = ["lua-language-server"]; + servers = { + lua-language-server = { + enable = true; + cmd = [(getExe pkgs.lua-language-server)]; + filetypes = ["lua"]; + root_markers = [ + ".luarc.json" + ".luarc.jsonc" + ".luacheckrc" + ".stylua.toml" + "stylua.toml" + "selene.toml" + "selene.yml" + ".git" + ]; + }; + }; + defaultFormat = "stylua"; formats = { stylua = { @@ -43,12 +62,11 @@ in { }; lsp = { - enable = mkEnableOption "Lua LSP support via LuaLS" // {default = config.vim.lsp.enable;}; - - package = mkOption { - description = "LuaLS package, or the command to run as a list of strings"; - type = either package (listOf str); - default = pkgs.lua-language-server; + enable = mkEnableOption "Lua LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = listOf (enum (attrNames servers)); + default = defaultServers; + description = "Lua LSP server to use"; }; lazydev.enable = mkEnableOption "lazydev.nvim integration, useful for neovim plugin developers"; @@ -91,23 +109,17 @@ in { (mkIf cfg.enable (mkMerge [ (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.lua-lsp = '' - lspconfig.lua_ls.setup { - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${getExe cfg.lsp.package}"}'' - }; - } - ''; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.lsp.lazydev.enable { vim.startPlugins = ["lazydev-nvim"]; - vim.pluginRC.lazydev = entryBefore ["lua-lsp"] '' + vim.pluginRC.lazydev = entryBefore ["lsp-servers"] '' require("lazydev").setup({ enabled = function(root_dir) return not vim.uv.fs_stat(root_dir .. "/.luarc.json") diff --git a/modules/plugins/languages/markdown.nix b/modules/plugins/languages/markdown.nix index 23848835..0a3f0945 100644 --- a/modules/plugins/languages/markdown.nix +++ b/modules/plugins/languages/markdown.nix @@ -8,28 +8,20 @@ inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; inherit (lib.options) mkEnableOption mkOption; - inherit (lib.lists) isList; - inherit (lib.types) bool enum either package listOf str nullOr; - inherit (lib.nvim.lua) expToLua toLuaObject; - inherit (lib.nvim.types) diagnostics mkGrammarOption mkPluginSetupOption; + inherit (lib.types) bool enum package listOf str nullOr; + inherit (lib.nvim.lua) toLuaObject; + inherit (lib.nvim.types) diagnostics mkGrammarOption mkPluginSetupOption singleOrListOf; inherit (lib.nvim.dag) entryAnywhere; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.markdown; - defaultServer = "marksman"; + defaultServers = ["marksman"]; servers = { marksman = { - package = pkgs.marksman; - lspConfig = '' - lspconfig.marksman.setup{ - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/marksman", "server"}'' - }, - } - ''; + enable = true; + cmd = [(getExe pkgs.marksman) "server"]; + filetypes = ["markdown" "markdown.mdx"]; + root_markers = [".marksman.toml" ".git"]; }; }; @@ -67,19 +59,12 @@ in { }; lsp = { - enable = mkEnableOption "Enable Markdown LSP support" // {default = config.vim.lsp.enable;}; + enable = mkEnableOption "Markdown LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; + servers = mkOption { description = "Markdown LSP server to use"; - }; - - package = mkOption { - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - example = ''[lib.getExe pkgs.jdt-language-server " - data " " ~/.cache/jdtls/workspace "]''; - description = "Markdown LSP server package, or the command to run as a list of strings"; + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; }; }; @@ -161,8 +146,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.markdown-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/nim.nix b/modules/plugins/languages/nim.nix index 6e5885e2..b4fab356 100644 --- a/modules/plugins/languages/nim.nix +++ b/modules/plugins/languages/nim.nix @@ -6,31 +6,34 @@ }: let inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; + inherit (lib.meta) getExe'; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.types) enum package; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.nim; - defaultServer = "nimlsp"; + defaultServers = ["nimlsp"]; servers = { nimlsp = { - package = pkgs.nimlsp; - lspConfig = '' - lspconfig.nimls.setup{ - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else '' - {"${cfg.lsp.package}/bin/nimlsp"} - '' - }; - } - ''; + enable = true; + cmd = [(getExe' pkgs.nimlsp "nimlsp")]; + filetypes = ["nim"]; + root_dir = + mkLuaInline + /* + lua + */ + '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + on_dir( + util.root_pattern '*.nimble'(fname) or vim.fs.dirname(vim.fs.find('.git', { path = fname, upward = true })[1]) + ) + end + ''; }; }; @@ -54,32 +57,26 @@ in { lsp = { enable = mkEnableOption "Nim LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - description = "Nim LSP server to use"; - type = str; - default = defaultServer; - }; - package = mkOption { - description = "Nim LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.nimlsp]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "Nim LSP server to use"; }; }; format = { enable = mkEnableOption "Nim formatting" // {default = config.vim.languages.enableFormat;}; type = mkOption { - description = "Nim formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "Nim formatter to use"; }; package = mkOption { - description = "Nim formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "Nim formatter package"; }; }; }; @@ -100,8 +97,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.nim-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/nix.nix b/modules/plugins/languages/nix.nix index 83022adf..6127f78e 100644 --- a/modules/plugins/languages/nix.nix +++ b/modules/plugins/languages/nix.nix @@ -9,90 +9,43 @@ inherit (lib.meta) getExe; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.strings) optionalString; - inherit (lib.types) anything attrsOf enum either listOf nullOr package str; - inherit (lib.nvim.types) mkGrammarOption diagnostics; - inherit (lib.nvim.lua) expToLua toLuaObject; + inherit (lib.types) enum package; + inherit (lib.nvim.types) mkGrammarOption diagnostics singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.nix; - useFormat = "on_attach = default_on_attach"; - noFormat = "on_attach = attach_keymaps"; + formattingCmd = mkIf (cfg.format.enable && cfg.lsp.enable) { + formatting = mkMerge [ + (mkIf (cfg.format.type == "alejandra") { + command = ["${cfg.format.package}/bin/alejandra" "--quiet"]; + }) + (mkIf (cfg.format.type == "nixfmt") { + command = ["${cfg.format.package}/bin/nixfmt"]; + }) + ]; + }; - defaultServer = "nil"; - packageToCmd = package: defaultCmd: - if isList package - then expToLua package - else ''{"${package}/bin/${defaultCmd}"}''; + defaultServers = ["nil"]; servers = { nil = { - package = pkgs.nil; - internalFormatter = true; - lspConfig = '' - lspconfig.nil_ls.setup{ - capabilities = capabilities, - ${ - if cfg.format.enable - then useFormat - else noFormat - }, - cmd = ${packageToCmd cfg.lsp.package "nil"}, - ${optionalString cfg.format.enable '' - settings = { - ["nil"] = { - ${optionalString (cfg.format.type == "alejandra") - '' - formatting = { - command = {"${cfg.format.package}/bin/alejandra", "--quiet"}, - }, - ''} - ${optionalString (cfg.format.type == "nixfmt") - '' - formatting = { - command = {"${cfg.format.package}/bin/nixfmt"}, - }, - ''} - }, - }, - ''} - } - ''; + enable = true; + cmd = [(getExe pkgs.nil)]; + settings = { + nil = formattingCmd; + }; + filetypes = ["nix"]; + root_markers = [".git" "flake.nix"]; }; nixd = { - package = pkgs.nixd; - internalFormatter = true; - lspConfig = '' - lspconfig.nixd.setup{ - capabilities = capabilities, - ${ - if cfg.format.enable - then useFormat - else noFormat - }, - cmd = ${packageToCmd cfg.lsp.package "nixd"}, - ${optionalString cfg.format.enable '' - settings = { - nixd = { - ${optionalString (cfg.format.type == "alejandra") - '' - formatting = { - command = {"${cfg.format.package}/bin/alejandra", "--quiet"}, - }, - ''} - ${optionalString (cfg.format.type == "nixfmt") - '' - formatting = { - command = {"${cfg.format.package}/bin/nixfmt"}, - }, - ''} - options = ${toLuaObject cfg.lsp.options}, - }, - }, - ''} - } - ''; + enable = true; + cmd = [(getExe pkgs.nixd)]; + settings = { + nixd = formattingCmd; + }; + filetypes = ["nix"]; + root_markers = [".git" "flake.nix"]; }; }; @@ -144,23 +97,10 @@ in { lsp = { enable = mkEnableOption "Nix LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Nix LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "Nix LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - }; - - options = mkOption { - type = nullOr (attrsOf anything); - default = null; - description = "Options to pass to nixd LSP server"; }; }; @@ -201,13 +141,6 @@ in { ${concatStringsSep ", " (attrNames formats)} ''; } - { - assertion = cfg.lsp.server != "rnix"; - message = '' - rnix-lsp has been archived upstream. Please use one of the following available language servers: - ${concatStringsSep ", " (attrNames servers)} - ''; - } ]; } @@ -217,11 +150,15 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.nix-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) - (mkIf (cfg.format.enable && (!cfg.lsp.enable || !servers.${cfg.lsp.server}.internalFormatter)) { + (mkIf (cfg.format.enable && !cfg.lsp.enable) { vim.formatter.conform-nvim = { enable = true; setupOpts.formatters_by_ft.nix = [cfg.format.type]; diff --git a/modules/plugins/languages/nu.nix b/modules/plugins/languages/nu.nix index bd007fc8..d58e39a3 100644 --- a/modules/plugins/languages/nu.nix +++ b/modules/plugins/languages/nu.nix @@ -5,27 +5,30 @@ ... }: let inherit (lib.options) mkEnableOption mkOption; - inherit (lib.types) str either package listOf; + inherit (lib.types) enum; inherit (lib.modules) mkIf mkMerge; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption; - inherit (builtins) isList; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.meta) getExe; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (builtins) attrNames; - defaultServer = "nushell"; + defaultServers = ["nushell"]; servers = { nushell = { - package = pkgs.nushell; - lspConfig = '' - lspconfig.nushell.setup{ - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/nu", "--no-config-file", "--lsp"}'' - } - } - ''; + enable = true; + cmd = [(getExe pkgs.nushell) "--no-config-file" "--lsp"]; + filetypes = ["nu"]; + root_dir = + mkLuaInline + /* + lua + */ + '' + function(bufnr, on_dir) + on_dir(vim.fs.root(bufnr, { '.git' }) or vim.fs.dirname(vim.api.nvim_buf_get_name(bufnr))) + end + ''; }; }; @@ -41,17 +44,11 @@ in { lsp = { enable = mkEnableOption "Nu LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - type = str; - default = defaultServer; - description = "Nu LSP server to use"; - }; - package = mkOption { - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - example = ''[(lib.getExe pkgs.nushell) "--lsp"]''; - description = "Nu LSP server package, or the command to run as a list of strings"; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "Nu LSP server to use"; }; }; }; @@ -63,8 +60,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.nu-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/ocaml.nix b/modules/plugins/languages/ocaml.nix index 995ca04d..cf9fdd93 100644 --- a/modules/plugins/languages/ocaml.nix +++ b/modules/plugins/languages/ocaml.nix @@ -8,28 +8,50 @@ inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; inherit (lib.meta) getExe; - inherit (lib.lists) isList; - inherit (lib.types) either enum listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.types) enum package; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.ocaml; - defaultServer = "ocaml-lsp"; + defaultServers = ["ocaml-lsp"]; servers = { ocaml-lsp = { - package = pkgs.ocamlPackages.ocaml-lsp; - lspConfig = '' - lspconfig.ocamllsp.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${getExe cfg.lsp.package}"}'' - }; - } - ''; + enable = true; + cmd = [(getExe pkgs.ocamlPackages.ocaml-lsp)]; + filetypes = ["ocaml" "menhir" "ocamlinterface" "ocamllex" "reason" "dune"]; + root_dir = + mkLuaInline + /* + lua + */ + '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + on_dir(util.root_pattern('*.opam', 'esy.json', 'package.json', '.git', 'dune-project', 'dune-workspace')(fname)) + end + ''; + get_language_id = + mkLuaInline + /* + lua + */ + '' + function(_, ftype) + local language_id_of = { + menhir = 'ocaml.menhir', + ocaml = 'ocaml', + ocamlinterface = 'ocaml.interface', + ocamllex = 'ocaml.ocamllex', + reason = 'reason', + dune = 'dune', + } + + return language_id_of[ftype] + + end + ''; }; }; @@ -49,38 +71,38 @@ in { }; lsp = { - enable = mkEnableOption "OCaml LSP support (ocaml-lsp)" // {default = config.vim.lsp.enable;}; - server = mkOption { - description = "OCaml LSP server to user"; - type = enum (attrNames servers); - default = defaultServer; - }; - package = mkOption { - description = "OCaml language server package, or the command to run as a list of strings"; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; + enable = mkEnableOption "OCaml LSP support" // {default = config.vim.lsp.enable;}; + + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "OCaml LSP server to use"; }; }; format = { enable = mkEnableOption "OCaml formatting support (ocamlformat)" // {default = config.vim.languages.enableFormat;}; type = mkOption { - description = "OCaml formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "OCaml formatter to use"; }; package = mkOption { - description = "OCaml formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "OCaml formatter package"; }; }; }; config = mkIf cfg.enable (mkMerge [ (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.ocaml-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.treesitter.enable { diff --git a/modules/plugins/languages/odin.nix b/modules/plugins/languages/odin.nix index 6d20351c..f18a3383 100644 --- a/modules/plugins/languages/odin.nix +++ b/modules/plugins/languages/odin.nix @@ -7,26 +7,28 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) either listOf package str enum; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.types) enum; + inherit (lib.meta) getExe; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; - defaultServer = "ols"; + defaultServers = ["ols"]; servers = { ols = { - package = pkgs.ols; - lspConfig = '' - lspconfig.ols.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else "{'${cfg.lsp.package}/bin/ols'}" - } - } - ''; + enable = true; + cmd = [(getExe pkgs.ols)]; + filetypes = ["odin"]; + root_dir = + mkLuaInline + /* + lua + */ + '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + on_dir(util.root_pattern('ols.json', '.git', '*.odin')(fname)) + end''; }; }; @@ -43,17 +45,11 @@ in { lsp = { enable = mkEnableOption "Odin LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Odin LSP server to use"; }; - - package = mkOption { - description = "Ols package, or the command to run as a list of strings"; - type = either package (listOf str); - default = pkgs.ols; - }; }; }; @@ -64,8 +60,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.odin-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/php.nix b/modules/plugins/languages/php.nix index 61ff0425..353b1137 100644 --- a/modules/plugins/languages/php.nix +++ b/modules/plugins/languages/php.nix @@ -8,81 +8,60 @@ inherit (lib.options) mkEnableOption mkOption; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.types) enum; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.php; - defaultServer = "phpactor"; + defaultServers = ["phpactor"]; servers = { phpactor = { - package = pkgs.phpactor; - lspConfig = '' - lspconfig.phpactor.setup{ - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else '' - { - "${getExe cfg.lsp.package}", - "language-server" - }, - '' - } - } - ''; + enable = true; + cmd = [(getExe pkgs.phpactor) "language-server"]; + filetypes = ["php"]; + root_markers = [".git" "composer.json" ".phpactor.json" ".phpactor.yml"]; + workspace_required = true; }; phan = { - package = pkgs.php81Packages.phan; - lspConfig = '' - lspconfig.phan.setup{ - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else '' - { - "${getExe cfg.lsp.package}", - "-m", - "json", - "--no-color", - "--no-progress-bar", - "-x", - "-u", - "-S", - "--language-server-on-stdin", - "--allow-polyfill-parser" - }, - '' - } - } - ''; + enable = true; + cmd = [ + (getExe pkgs.php81Packages.phan) + "-m" + "json" + "--no-color" + "--no-progress-bar" + "-x" + "-u" + "-S" + "--language-server-on-stdin" + "--allow-polyfill-parser" + ]; + filetypes = ["php"]; + root_dir = + mkLuaInline + /* + lua + */ + '' + function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + local cwd = assert(vim.uv.cwd()) + local root = vim.fs.root(fname, { 'composer.json', '.git' }) + + -- prefer cwd if root is a descendant + on_dir(root and vim.fs.relpath(cwd, root) and cwd) + end + ''; }; intelephense = { - package = pkgs.intelephense; - lspConfig = '' - lspconfig.intelephense.setup{ - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else '' - { - "${getExe cfg.lsp.package}", - "--stdio" - }, - '' - } - } - ''; + enable = true; + cmd = [(getExe pkgs.intelephense) "--stdio"]; + filetypes = ["php"]; + root_markers = ["composer.json" ".git"]; }; }; in { @@ -97,17 +76,10 @@ in { lsp = { enable = mkEnableOption "PHP LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "PHP LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "PHP LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server " - data " " ~/.cache/jdtls/workspace "]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; }; @@ -117,11 +89,14 @@ in { vim.treesitter.enable = true; vim.treesitter.grammars = [cfg.treesitter.package]; }) + (mkIf cfg.lsp.enable { - vim.lsp.lspconfig = { - enable = true; - sources.php-lsp = servers.${cfg.lsp.server}.lspConfig; - }; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/python.nix b/modules/plugins/languages/python.nix index bec7ec8b..f4d749f4 100644 --- a/modules/plugins/languages/python.nix +++ b/modules/plugins/languages/python.nix @@ -8,57 +8,115 @@ inherit (lib.options) mkEnableOption mkOption literalExpression; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str bool; - inherit (lib.nvim.lua) expToLua; + inherit (lib.types) enum package bool; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.nvim.types) singleOrListOf; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.dag) entryBefore; cfg = config.vim.languages.python; - defaultServer = "basedpyright"; + defaultServers = ["basedpyright"]; servers = { pyright = { - package = pkgs.pyright; - lspConfig = '' - lspconfig.pyright.setup{ - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/pyright-langserver", "--stdio"}'' - } - } + enable = true; + cmd = [(getExe pkgs.pyright) "--stdio"]; + filetypes = ["python"]; + root_markers = [ + "pyproject.toml" + "setup.py" + "setup.cfg" + "requirements.txt" + "Pipfile" + "pyrightconfig.json" + ".git" + ]; + settings = { + python = { + analysis = { + autoSearchPaths = true; + useLibraryCodeForTypes = true; + diagnosticMode = "openFilesOnly"; + }; + }; + }; + on_attach = mkLuaInline '' + function(client, bufnr) + vim.api.nvim_buf_create_user_command(bufnr, 'LspPyrightOrganizeImports', function() + client:exec_cmd({ + command = 'pyright.organizeimports', + arguments = { vim.uri_from_bufnr(bufnr) }, + }) + end, { + desc = 'Organize Imports', + }) + vim.api.nvim_buf_create_user_command(bufnr, 'LspPyrightSetPythonPath', function(opts) + set_python_path('pyright', opts.args) + end, { + desc = 'Reconfigure pyright with the provided python path', + nargs = 1, + complete = 'file', + }) + end ''; }; basedpyright = { - package = pkgs.basedpyright; - lspConfig = '' - lspconfig.basedpyright.setup{ - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/basedpyright-langserver", "--stdio"}'' - } - } + enable = true; + cmd = [(getExe pkgs.basedpyright) "--stdio"]; + filetypes = ["python"]; + root_markers = [ + "pyproject.toml" + "setup.py" + "setup.cfg" + "requirements.txt" + "Pipfile" + "pyrightconfig.json" + ".git" + ]; + settings = { + basedpyright = { + analysis = { + autoSearchPaths = true; + useLibraryCodeForTypes = true; + diagnosticMode = "openFilesOnly"; + }; + }; + }; + on_attach = mkLuaInline '' + function(client, bufnr) + vim.api.nvim_buf_create_user_command(bufnr, 'LspPyrightOrganizeImports', function() + client:exec_cmd({ + command = 'basedpyright.organizeimports', + arguments = { vim.uri_from_bufnr(bufnr) }, + }) + end, { + desc = 'Organize Imports', + }) + + vim.api.nvim_buf_create_user_command(bufnr, 'LspPyrightSetPythonPath', function(opts) + set_python_path('basedpyright', opts.args) + end, { + desc = 'Reconfigure basedpyright with the provided python path', + nargs = 1, + complete = 'file', + }) + end ''; }; python-lsp-server = { - package = pkgs.python3Packages.python-lsp-server; - lspConfig = '' - lspconfig.pylsp.setup{ - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/pylsp"}'' - } - } - ''; + enable = true; + cmd = [(getExe pkgs.python3Packages.python-lsp-server)]; + filetypes = ["python"]; + root_markers = [ + "pyproject.toml" + "setup.py" + "setup.cfg" + "requirements.txt" + "Pipfile" + ".git" + ]; }; }; @@ -91,6 +149,16 @@ ''; }; }; + + ruff-check = { + package = pkgs.writeShellApplication { + name = "ruff-check"; + runtimeInputs = [pkgs.ruff]; + text = '' + ruff check --fix --exit-zero - + ''; + }; + }; }; defaultDebugger = "debugpy"; @@ -171,17 +239,10 @@ in { lsp = { enable = mkEnableOption "Python LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Python LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "python LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; @@ -189,30 +250,30 @@ in { enable = mkEnableOption "Python formatting" // {default = config.vim.languages.enableFormat;}; type = mkOption { - description = "Python formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "Python formatter to use"; }; package = mkOption { - description = "Python formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "Python formatter package"; }; }; # TODO this implementation is very bare bones, I don't know enough python to implement everything dap = { enable = mkOption { - description = "Enable Python Debug Adapter"; type = bool; default = config.vim.languages.enableDAP; + description = "Enable Python Debug Adapter"; }; debugger = mkOption { - description = "Python debugger to use"; type = enum (attrNames debuggers); default = defaultDebugger; + description = "Python debugger to use"; }; package = mkOption { @@ -234,8 +295,34 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.python-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.luaConfigRC.python-util = + entryBefore ["lsp-servers"] + /* + lua + */ + '' + local function set_python_path(server_name, path) + local clients = vim.lsp.get_clients { + bufnr = vim.api.nvim_get_current_buf(), + name = server_name, + } + for _, client in ipairs(clients) do + if client.settings then + client.settings.python = vim.tbl_deep_extend('force', client.settings.python or {}, { pythonPath = path }) + else + client.config.settings = vim.tbl_deep_extend('force', client.config.settings, { python = { pythonPath = path } }) + end + client.notify('workspace/didChangeConfiguration', { settings = nil }) + end + end + ''; + + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/qml.nix b/modules/plugins/languages/qml.nix new file mode 100644 index 00000000..6750a55a --- /dev/null +++ b/modules/plugins/languages/qml.nix @@ -0,0 +1,98 @@ +{ + config, + pkgs, + lib, + ... +}: let + inherit (builtins) attrNames; + inherit (lib.meta) getExe getExe'; + inherit (lib.options) mkEnableOption mkOption; + inherit (lib.modules) mkIf mkMerge; + inherit (lib.types) enum package; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; + + cfg = config.vim.languages.qml; + + qmlPackage = pkgs.kdePackages.qtdeclarative; + + defaultServers = ["qmlls"]; + servers = { + qmlls = { + cmd = [(getExe' qmlPackage "qmlls")]; + filetypes = ["qml" "qmljs"]; + rootmarkers = [".git"]; + }; + }; + + defaultFormat = "qmlformat"; + formats = { + qmlformat = { + package = pkgs.writeShellApplication { + name = "qmlformat"; + runtimeInputs = [qmlPackage]; + text = "qmlformat -"; + }; + }; + }; +in { + options.vim.languages.qml = { + enable = mkEnableOption "QML language support"; + treesitter = { + enable = mkEnableOption "QML treesitter support" // {default = config.vim.languages.enableTreesitter;}; + package = mkGrammarOption pkgs "qmljs"; + }; + + lsp = { + enable = mkEnableOption "QML LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "QML LSP server to use"; + }; + }; + + format = { + enable = mkEnableOption "QML formatting" // {default = config.vim.languages.enableFormat;}; + + type = mkOption { + type = enum (attrNames formats); + default = defaultFormat; + description = "QML formatter to use"; + }; + + package = mkOption { + type = package; + default = formats.${cfg.format.type}.package; + description = "QML formatter package"; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + (mkIf cfg.treesitter.enable { + vim.treesitter = { + enable = true; + grammars = [cfg.treesitter.package]; + }; + }) + (mkIf cfg.lsp.enable { + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; + }) + + (mkIf (cfg.format.enable && !cfg.lsp.enable) { + vim.formatter.conform-nvim = { + enable = true; + setupOpts.formatters_by_ft.qml = [cfg.format.type]; + setupOpts.formatters.${cfg.format.type} = { + command = getExe cfg.format.package; + }; + }; + }) + ]); +} diff --git a/modules/plugins/languages/r.nix b/modules/plugins/languages/r.nix index 894c63f6..873edaa3 100644 --- a/modules/plugins/languages/r.nix +++ b/modules/plugins/languages/r.nix @@ -5,12 +5,13 @@ ... }: let inherit (builtins) attrNames; - inherit (lib.options) mkEnableOption mkOption literalExpression; + inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.types) enum package; + inherit (lib.meta) getExe; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.r; @@ -50,22 +51,16 @@ }; }; - defaultServer = "r_language_server"; + defaultServers = ["r_language_server"]; servers = { r_language_server = { - package = pkgs.writeShellScriptBin "r_lsp" '' - ${r-with-languageserver}/bin/R --slave -e "languageserver::run()" - ''; - lspConfig = '' - lspconfig.r_language_server.setup{ - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${lib.getExe cfg.lsp.package}"}'' - } - } + enable = true; + cmd = [(getExe r-with-languageserver) "--no-echo" "-e" "languageserver::run()"]; + filetypes = ["r" "rmd" "quarto"]; + root_dir = mkLuaInline '' + function(bufnr, on_dir) + on_dir(vim.fs.root(bufnr, '.git') or vim.uv.os_homedir()) + end ''; }; }; @@ -81,17 +76,10 @@ in { lsp = { enable = mkEnableOption "R LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "R LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "R LSP server package, or the command to run as a list of strings"; - example = literalExpression "[ (lib.getExe pkgs.jdt-language-server) \"-data\" \"~/.cache/jdtls/workspace\" ]"; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; @@ -127,8 +115,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.r-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/ruby.nix b/modules/plugins/languages/ruby.nix index 1f3ce82f..94e27277 100644 --- a/modules/plugins/languages/ruby.nix +++ b/modules/plugins/languages/ruby.nix @@ -8,45 +8,42 @@ inherit (lib.options) mkEnableOption mkOption; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.nvim.types) mkGrammarOption diagnostics; - inherit (lib.nvim.lua) expToLua; - inherit (lib.lists) isList; - inherit (lib.types) either listOf package str enum; + inherit (lib.nvim.types) mkGrammarOption diagnostics singleOrListOf; + inherit (lib.types) package enum; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.ruby; - defaultServer = "rubyserver"; + defaultServers = ["solargraph"]; servers = { - rubyserver = { - package = pkgs.rubyPackages.solargraph; - lspConfig = '' - lspconfig.solargraph.setup { - capabilities = capabilities, - on_attach = attach_keymaps, - flags = { - debounce_text_changes = 150, - }, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{ "${cfg.lsp.package}/bin/solargraph", "stdio" }'' - } - } - ''; + ruby_lsp = { + enable = true; + cmd = [(getExe pkgs.ruby-lsp)]; + filetypes = ["ruby" "eruby"]; + root_markers = ["Gemfile" ".git"]; + init_options = { + formatter = "auto"; + }; }; - rubylsp = { - package = pkgs.ruby-lsp; - lspConfig = '' - lspconfig.ruby_lsp.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{ "${cfg.lsp.package}/bin/ruby-lsp" }'' - } - } - ''; + + solargraph = { + enable = true; + cmd = [(getExe pkgs.rubyPackages.solargraph) "stdio"]; + filetypes = ["ruby"]; + root_markers = ["Gemfile" ".git"]; + settings = { + solargraph = { + diagnostics = true; + }; + }; + + flags = { + debounce_text_changes = 150; + }; + + init_options = { + formatting = true; + }; }; }; @@ -79,17 +76,11 @@ in { lsp = { enable = mkEnableOption "Ruby LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Ruby LSP server to use"; }; - - package = mkOption { - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - description = "Ruby LSP server package, or the command to run as a list of strings"; - }; }; format = { @@ -128,8 +119,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.ruby-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/sql.nix b/modules/plugins/languages/sql.nix index add46c15..c1ea6678 100644 --- a/modules/plugins/languages/sql.nix +++ b/modules/plugins/languages/sql.nix @@ -8,31 +8,27 @@ inherit (lib.options) mkEnableOption mkOption; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) diagnostics; + inherit (lib.types) enum package str; + inherit (lib.nvim.types) diagnostics singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.sql; sqlfluffDefault = pkgs.sqlfluff; - defaultServer = "sqls"; + defaultServers = ["sqls"]; servers = { sqls = { - package = pkgs.sqls; - lspConfig = '' - lspconfig.sqls.setup { - on_attach = function(client) - client.server_capabilities.execute_command = true - on_attach_keymaps(client, bufnr) - require'sqls'.setup{} - end, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{ "${cfg.lsp.package}/bin/sqls", "-config", string.format("%s/config.yml", vim.fn.getcwd()) }'' - } - } + enable = true; + cmd = [(getExe pkgs.sqls)]; + filetypes = ["sql" "mysql"]; + root_markers = ["config.yml"]; + settings = {}; + on_attach = mkLuaInline '' + function(client, bufnr) + client.server_capabilities.execute_command = true + require'sqls'.setup{} + end ''; }; }; @@ -63,35 +59,28 @@ in { enable = mkEnableOption "SQL language support"; dialect = mkOption { - description = "SQL dialect for sqlfluff (if used)"; type = str; default = "ansi"; + description = "SQL dialect for sqlfluff (if used)"; }; treesitter = { enable = mkEnableOption "SQL treesitter" // {default = config.vim.languages.enableTreesitter;}; package = mkOption { - description = "SQL treesitter grammar to use"; type = package; default = pkgs.vimPlugins.nvim-treesitter.builtGrammars.sql; + description = "SQL treesitter grammar to use"; }; }; lsp = { enable = mkEnableOption "SQL LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "SQL LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "SQL LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; @@ -99,15 +88,15 @@ in { enable = mkEnableOption "SQL formatting" // {default = config.vim.languages.enableFormat;}; type = mkOption { - description = "SQL formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "SQL formatter to use"; }; package = mkOption { - description = "SQL formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "SQL formatter package"; }; }; @@ -132,10 +121,12 @@ in { vim = { startPlugins = ["sqls-nvim"]; - lsp.lspconfig = { - enable = true; - sources.sql-lsp = servers.${cfg.lsp.server}.lspConfig; - }; + lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }; }) diff --git a/modules/plugins/languages/svelte.nix b/modules/plugins/languages/svelte.nix index 4cc9ffe9..301a51d7 100644 --- a/modules/plugins/languages/svelte.nix +++ b/modules/plugins/languages/svelte.nix @@ -7,28 +7,48 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; inherit (lib.meta) getExe; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption diagnostics; + inherit (lib.types) enum package; + inherit (lib.nvim.types) mkGrammarOption diagnostics singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.svelte; - defaultServer = "svelte"; + defaultServers = ["svelte"]; servers = { svelte = { - package = pkgs.svelte-language-server; - lspConfig = '' - lspconfig.svelte.setup { - capabilities = capabilities; - on_attach = attach_keymaps, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/svelteserver", "--stdio"}'' - } - } + enable = true; + cmd = [(getExe pkgs.svelte-language-server) "--stdio"]; + filetypes = ["svelte"]; + root_dir = mkLuaInline '' + function(bufnr, on_dir) + local root_files = { 'package.json', '.git' } + local fname = vim.api.nvim_buf_get_name(bufnr) + -- Svelte LSP only supports file:// schema. https://github.com/sveltejs/language-tools/issues/2777 + if vim.uv.fs_stat(fname) ~= nil then + on_dir(vim.fs.dirname(vim.fs.find(root_files, { path = fname, upward = true })[1])) + end + end + ''; + on_attach = mkLuaInline '' + function(client, bufnr) + vim.api.nvim_create_autocmd('BufWritePost', { + pattern = { '*.js', '*.ts' }, + group = vim.api.nvim_create_augroup('svelte_js_ts_file_watch', {}), + callback = function(ctx) + -- internal API to sync changes that have not yet been saved to the file system + client:notify('$/onDidChangeTsOrJsFile', { uri = ctx.match }) + end, + }) + + vim.api.nvim_buf_create_user_command(bufnr, 'LspMigrateToSvelte5', function() + client:exec_cmd({ + command = 'migrate_to_svelte_5', + arguments = { vim.uri_from_bufnr(bufnr) }, + }) + end, { desc = 'Migrate Component to Svelte 5 Syntax' }) + end ''; }; }; @@ -78,17 +98,10 @@ in { lsp = { enable = mkEnableOption "Svelte LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Svelte LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "Svelte LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; @@ -96,15 +109,15 @@ in { enable = mkEnableOption "Svelte formatting" // {default = config.vim.languages.enableFormat;}; type = mkOption { - description = "Svelte formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "Svelte formatter to use"; }; package = mkOption { - description = "Svelte formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "Svelte formatter package"; }; }; @@ -126,8 +139,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.svelte-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) (mkIf cfg.format.enable { diff --git a/modules/plugins/languages/tailwind.nix b/modules/plugins/languages/tailwind.nix index fbe707ba..4e63a8d8 100644 --- a/modules/plugins/languages/tailwind.nix +++ b/modules/plugins/languages/tailwind.nix @@ -7,26 +7,142 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.lua) expToLua; + inherit (lib.meta) getExe; + inherit (lib.types) enum; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.nvim.types) singleOrListOf; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.tailwind; - defaultServer = "tailwindcss-language-server"; + defaultServers = ["tailwindcss"]; servers = { - tailwindcss-language-server = { - package = pkgs.tailwindcss-language-server; - lspConfig = '' - lspconfig.tailwindcss.setup { - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/tailwindcss-language-server", "--stdio"}'' - } - } + tailwindcss = { + enable = true; + cmd = [(getExe pkgs.tailwindcss-language-server) "--stdio"]; + filetypes = [ + # html + "aspnetcorerazor" + "astro" + "astro-markdown" + "blade" + "clojure" + "django-html" + "htmldjango" + "edge" + "eelixir" + "elixir" + "ejs" + "erb" + "eruby" + "gohtml" + "gohtmltmpl" + "haml" + "handlebars" + "hbs" + "html" + "htmlangular" + "html-eex" + "heex" + "jade" + "leaf" + "liquid" + "markdown" + "mdx" + "mustache" + "njk" + "nunjucks" + "php" + "razor" + "slim" + "twig" + # css + "css" + "less" + "postcss" + "sass" + "scss" + "stylus" + "sugarss" + # js + "javascript" + "javascriptreact" + "reason" + "rescript" + "typescript" + "typescriptreact" + # mixed + "vue" + "svelte" + "templ" + ]; + settings = { + tailwindCSS = { + validate = true; + lint = { + cssConflict = "warning"; + invalidApply = "error"; + invalidScreen = "error"; + invalidVariant = "error"; + invalidConfigPath = "error"; + invalidTailwindDirective = "error"; + recommendedVariantOrder = "warning"; + }; + classAttributes = [ + "class" + "className" + "class:list" + "classList" + "ngClass" + ]; + includeLanguages = { + eelixir = "html-eex"; + elixir = "phoenix-heex"; + eruby = "erb"; + heex = "phoenix-heex"; + htmlangular = "html"; + templ = "html"; + }; + }; + }; + before_init = mkLuaInline '' + function(_, config) + if not config.settings then + config.settings = {} + end + if not config.settings.editor then + config.settings.editor = {} + end + if not config.settings.editor.tabSize then + config.settings.editor.tabSize = vim.lsp.util.get_effective_tabstop() + end + end + ''; + workspace_required = true; + root_dir = mkLuaInline '' + function(bufnr, on_dir) + local root_files = { + -- Generic + 'tailwind.config.js', + 'tailwind.config.cjs', + 'tailwind.config.mjs', + 'tailwind.config.ts', + 'postcss.config.js', + 'postcss.config.cjs', + 'postcss.config.mjs', + 'postcss.config.ts', + -- Django + 'theme/static_src/tailwind.config.js', + 'theme/static_src/tailwind.config.cjs', + 'theme/static_src/tailwind.config.mjs', + 'theme/static_src/tailwind.config.ts', + 'theme/static_src/postcss.config.js', + } + local fname = vim.api.nvim_buf_get_name(bufnr) + root_files = util.insert_package_json(root_files, 'tailwindcss', fname) + root_files = util.root_markers_with_field(root_files, { 'mix.lock', 'Gemfile.lock' }, 'tailwind', fname) + on_dir(vim.fs.dirname(vim.fs.find(root_files, { path = fname, upward = true })[1])) + end ''; }; }; @@ -37,25 +153,22 @@ in { lsp = { enable = mkEnableOption "Tailwindcss LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Tailwindcss LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "Tailwindcss LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server " - data " " ~/.cache/jdtls/workspace "]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; }; config = mkIf cfg.enable (mkMerge [ (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.tailwindcss-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/terraform.nix b/modules/plugins/languages/terraform.nix index 095da072..5f3952df 100644 --- a/modules/plugins/languages/terraform.nix +++ b/modules/plugins/languages/terraform.nix @@ -4,12 +4,25 @@ lib, ... }: let + inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.types) package; + inherit (lib.meta) getExe; + inherit (lib.types) enum listOf; inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.terraform; + + defaultServers = ["terraformls"]; + servers = { + terraformls = { + enable = true; + cmd = [(getExe pkgs.terraform-ls) "serve"]; + filetypes = ["terraform" "terraform-vars"]; + root_markers = [".terraform" ".git"]; + }; + }; in { options.vim.languages.terraform = { enable = mkEnableOption "Terraform/HCL support"; @@ -22,10 +35,10 @@ in { lsp = { enable = mkEnableOption "Terraform LSP support (terraform-ls)" // {default = config.vim.lsp.enable;}; - package = mkOption { - description = "terraform-ls package"; - type = package; - default = pkgs.terraform-ls; + servers = mkOption { + type = listOf (enum (attrNames servers)); + default = defaultServers; + description = "Terraform LSP server to use"; }; }; }; @@ -36,14 +49,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.terraform-ls = '' - lspconfig.terraformls.setup { - capabilities = capabilities, - on_attach=default_on_attach, - cmd = {"${cfg.lsp.package}/bin/terraform-ls", "serve"}, - } - ''; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/ts.nix b/modules/plugins/languages/ts.nix index 6064da98..40e598ae 100644 --- a/modules/plugins/languages/ts.nix +++ b/modules/plugins/languages/ts.nix @@ -4,73 +4,177 @@ lib, ... }: let - inherit (builtins) attrNames; + inherit (builtins) attrNames elem; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; inherit (lib.meta) getExe; - inherit (lib.types) enum either listOf package str bool; - inherit (lib.nvim.lua) expToLua toLuaObject; - inherit (lib.nvim.types) mkGrammarOption diagnostics mkPluginSetupOption; - inherit (lib.nvim.dag) entryAnywhere; + inherit (lib.types) enum package bool; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.nvim.lua) toLuaObject; + inherit (lib.nvim.types) mkGrammarOption diagnostics mkPluginSetupOption singleOrListOf; + inherit (lib.nvim.dag) entryAnywhere entryBefore; cfg = config.vim.languages.ts; - defaultServer = "ts_ls"; - servers = { + defaultServers = ["ts_ls"]; + servers = let ts_ls = { - package = pkgs.typescript-language-server; - lspConfig = '' - lspconfig.ts_ls.setup { - capabilities = capabilities, - on_attach = function(client, bufnr) - attach_keymaps(client, bufnr); - client.server_capabilities.documentFormattingProvider = false; - end, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/typescript-language-server", "--stdio"}'' - } - } + cmd = [(getExe pkgs.typescript-language-server) "--stdio"]; + init_options = {hostInfo = "neovim";}; + filetypes = [ + "javascript" + "javascriptreact" + "javascript.jsx" + "typescript" + "typescriptreact" + "typescript.tsx" + ]; + root_markers = ["tsconfig.json" "jsconfig.json" "package.json" ".git"]; + handlers = { + # handle rename request for certain code actions like extracting functions / types + "_typescript.rename" = mkLuaInline '' + function(_, result, ctx) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + vim.lsp.util.show_document({ + uri = result.textDocument.uri, + range = { + start = result.position, + ['end'] = result.position, + }, + }, client.offset_encoding) + vim.lsp.buf.rename() + return vim.NIL + end + ''; + }; + on_attach = mkLuaInline '' + function(client, bufnr) + default_on_attach(client, bufnr); + + -- ts_ls provides `source.*` code actions that apply to the whole file. These only appear in + -- `vim.lsp.buf.code_action()` if specified in `context.only`. + vim.api.nvim_buf_create_user_command(0, 'LspTypescriptSourceAction', function() + local source_actions = vim.tbl_filter(function(action) + return vim.startswith(action, 'source.') + end, client.server_capabilities.codeActionProvider.codeActionKinds) + + vim.lsp.buf.code_action({ + context = { + only = source_actions, + }, + }) + end, {}) + end ''; }; - - denols = { - package = pkgs.deno; - lspConfig = '' - vim.g.markdown_fenced_languages = { "ts=typescript" } - lspconfig.denols.setup { - capabilities = capabilities; - on_attach = attach_keymaps, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/deno", "lsp"}'' - } - } - ''; - }; - + in { + inherit ts_ls; # Here for backwards compatibility. Still consider tsserver a valid # configuration in the enum, but assert if it's set to *properly* # redirect the user to the correct server. - tsserver = { - package = pkgs.typescript-language-server; - lspConfig = '' - lspconfig.ts_ls.setup { - capabilities = capabilities; - on_attach = attach_keymaps, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/typescript-language-server", "--stdio"}'' - } - } + tsserver = ts_ls; + + denols = { + cmd = [(getExe pkgs.deno) "lsp"]; + cmd_env = {NO_COLOR = true;}; + filetypes = [ + "javascript" + "javascriptreact" + "javascript.jsx" + "typescript" + "typescriptreact" + "typescript.tsx" + ]; + root_markers = ["deno.json" "deno.jsonc" ".git"]; + settings = { + deno = { + enable = true; + suggest = { + imports = { + hosts = { + "https://deno.land" = true; + }; + }; + }; + }; + }; + handlers = { + "textDocument/definition" = mkLuaInline "nvf_denols_handler"; + "textDocument/typeDefinition" = mkLuaInline "nvf_denols_handler"; + "textDocument/references" = mkLuaInline "nvf_denols_handler"; + }; + on_attach = mkLuaInline '' + function(client, bufnr) + default_on_attach(client, bufnr) + vim.api.nvim_buf_create_user_command(0, 'LspDenolsCache', function() + client:exec_cmd({ + command = 'deno.cache', + arguments = { {}, vim.uri_from_bufnr(bufnr) }, + }, { bufnr = bufnr }, function(err, _result, ctx) + if err then + local uri = ctx.params.arguments[2] + vim.api.nvim_err_writeln('cache command failed for ' .. vim.uri_to_fname(uri)) + end + end) + end, { + desc = 'Cache a module and all of its dependencies.', + }) + end ''; }; }; + denols_handlers = '' + local function nvf_denols_virtual_text_document_handler(uri, res, client) + if not res then + return nil + end + + local lines = vim.split(res.result, '\n') + local bufnr = vim.uri_to_bufnr(uri) + + local current_buf = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + if #current_buf ~= 0 then + return nil + end + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + vim.api.nvim_set_option_value('readonly', true, { buf = bufnr }) + vim.api.nvim_set_option_value('modified', false, { buf = bufnr }) + vim.api.nvim_set_option_value('modifiable', false, { buf = bufnr }) + vim.lsp.buf_attach_client(bufnr, client.id) + end + + local function nvf_denols_virtual_text_document(uri, client) + local params = { + textDocument = { + uri = uri, + }, + } + local result = client.request_sync('deno/virtualTextDocument', params) + nvf_denols_virtual_text_document_handler(uri, result, client) + end + + local function nvf_denols_handler(err, result, ctx, config) + if not result or vim.tbl_isempty(result) then + return nil + end + + local client = vim.lsp.get_client_by_id(ctx.client_id) + for _, res in pairs(result) do + local uri = res.uri or res.targetUri + if uri:match '^deno:' then + nvf_denols_virtual_text_document(uri, client) + res['uri'] = uri + res['targetUri'] = uri + end + end + + vim.lsp.handlers[ctx.method](err, result, ctx, config) + end + ''; + # TODO: specify packages defaultFormat = "prettier"; formats = { @@ -115,24 +219,18 @@ in { treesitter = { enable = mkEnableOption "Typescript/Javascript treesitter" // {default = config.vim.languages.enableTreesitter;}; - tsPackage = mkGrammarOption pkgs "tsx"; + tsPackage = mkGrammarOption pkgs "typescript"; + tsxPackage = mkGrammarOption pkgs "tsx"; jsPackage = mkGrammarOption pkgs "javascript"; }; lsp = { enable = mkEnableOption "Typescript/Javascript LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Typescript/Javascript LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "Typescript/Javascript LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; @@ -186,12 +284,25 @@ in { config = mkIf cfg.enable (mkMerge [ (mkIf cfg.treesitter.enable { vim.treesitter.enable = true; - vim.treesitter.grammars = [cfg.treesitter.tsPackage cfg.treesitter.jsPackage]; + vim.treesitter.grammars = [ + cfg.treesitter.tsPackage + cfg.treesitter.tsxPackage + cfg.treesitter.jsPackage + ]; }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.ts-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (name: { + inherit name; + value = servers.${name}; + }) + cfg.lsp.servers; + }) + + (mkIf (cfg.lsp.enable && elem "denols" cfg.lsp.servers) { + vim.globals.markdown_fenced_languages = ["ts=typescript"]; + vim.luaConfigRC.denols_handlers = entryBefore ["lsp-servers"] denols_handlers; }) (mkIf cfg.format.enable { @@ -234,7 +345,7 @@ in { { assertions = [ { - assertion = cfg.lsp.enable -> cfg.lsp.server != "tsserver"; + assertion = cfg.lsp.enable -> !(elem "tsserver" cfg.lsp.servers); message = '' As of a recent lspconfig update, the `tsserver` configuration has been renamed to `ts_ls` to match upstream behaviour of `lspconfig`, and the name `tsserver` diff --git a/modules/plugins/languages/typst.nix b/modules/plugins/languages/typst.nix index 08a2252b..fbeb0ce2 100644 --- a/modules/plugins/languages/typst.nix +++ b/modules/plugins/languages/typst.nix @@ -6,52 +6,87 @@ }: let inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) nullOr enum either attrsOf listOf package str; + inherit (lib.types) nullOr enum attrsOf listOf package str; inherit (lib.attrsets) attrNames; inherit (lib.meta) getExe; - inherit (lib.nvim.lua) expToLua toLuaObject; - inherit (lib.nvim.types) mkGrammarOption mkPluginSetupOption; + inherit (lib.nvim.types) mkGrammarOption mkPluginSetupOption singleOrListOf; inherit (lib.nvim.dag) entryAnywhere; + inherit (lib.nvim.lua) toLuaObject; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.typst; - defaultServer = "tinymist"; + defaultServers = ["tinymist"]; servers = { - typst-lsp = { - package = pkgs.typst-lsp; - lspConfig = '' - lspconfig.typst_lsp.setup { - capabilities = capabilities, - on_attach = function(client, bufnr) - -- Disable semantic tokens as a workaround for a semantic token error when using non-english characters - client.server_capabilities.semanticTokensProvider = nil - end, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/typst-lsp"}'' - }, - } + typst_lsp = { + enable = true; + cmd = [(getExe pkgs.typst-lsp)]; + filetypes = ["typst"]; + root_markers = [".git"]; + on_attach = mkLuaInline '' + function(client, bufnr) + -- Disable semantic tokens as a workaround for a semantic token error when using non-english characters + client.server_capabilities.semanticTokensProvider = nil + end ''; }; tinymist = { - package = pkgs.tinymist; - lspConfig = '' - lspconfig.tinymist.setup { - capabilities = capabilities, - single_file_support = true, - on_attach = function(client, bufnr) - -- Disable semantic tokens as a workaround for a semantic token error when using non-english characters - client.server_capabilities.semanticTokensProvider = nil - end, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/tinymist"}'' - }, - } + enable = true; + cmd = [(getExe pkgs.tinymist)]; + filetypes = ["typst"]; + root_markers = [".git"]; + on_attach = mkLuaInline '' + function(client, bufnr) + local function create_tinymist_command(command_name, client, bufnr) + local export_type = command_name:match 'tinymist%.export(%w+)' + local info_type = command_name:match 'tinymist%.(%w+)' + if info_type and info_type:match '^get' then + info_type = info_type:gsub('^get', 'Get') + end + local cmd_display = export_type or info_type + local function run_tinymist_command() + local arguments = { vim.api.nvim_buf_get_name(bufnr) } + local title_str = export_type and ('Export ' .. cmd_display) or cmd_display + local function handler(err, res) + if err then + return vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR) + end + vim.notify(export_type and res or vim.inspect(res), vim.log.levels.INFO) + end + if vim.fn.has 'nvim-0.11' == 1 then + return client:exec_cmd({ + title = title_str, + command = command_name, + arguments = arguments, + }, { bufnr = bufnr }, handler) + else + return vim.notify('Tinymist commands require Neovim 0.11+', vim.log.levels.WARN) + end + end + local cmd_name = export_type and ('LspTinymistExport' .. cmd_display) or ('LspTinymist' .. cmd_display) + local cmd_desc = export_type and ('Export to ' .. cmd_display) or ('Get ' .. cmd_display) + return run_tinymist_command, cmd_name, cmd_desc + end + + for _, command in ipairs { + 'tinymist.exportSvg', + 'tinymist.exportPng', + 'tinymist.exportPdf', + 'tinymist.exportMarkdown', + 'tinymist.exportText', + 'tinymist.exportQuery', + 'tinymist.exportAnsiHighlight', + 'tinymist.getServerInfo', + 'tinymist.getDocumentTrace', + 'tinymist.getWorkspaceLabels', + 'tinymist.getDocumentMetrics', + } do + local cmd_func, cmd_name, cmd_desc = create_tinymist_command(command, client, bufnr) + vim.api.nvim_buf_create_user_command(bufnr, cmd_name, cmd_func, { nargs = 0, desc = cmd_desc }) + end + end ''; }; }; @@ -78,17 +113,10 @@ in { lsp = { enable = mkEnableOption "Typst LSP support (typst-lsp)" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Typst LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "typst-lsp package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; @@ -96,15 +124,15 @@ in { enable = mkEnableOption "Typst document formatting" // {default = config.vim.languages.enableFormat;}; type = mkOption { - description = "Typst formatter to use"; type = enum (attrNames formats); default = defaultFormat; + description = "Typst formatter to use"; }; package = mkOption { - description = "Typst formatter package"; type = package; default = formats.${cfg.format.type}.package; + description = "Typst formatter package"; }; }; @@ -131,7 +159,7 @@ in { dependencies_bin = mkOption { type = attrsOf str; default = { - "tinymist" = getExe servers.tinymist.package; + "tinymist" = getExe pkgs.tinymist; "websocat" = getExe pkgs.websocat; }; @@ -169,8 +197,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.typst-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) # Extensions diff --git a/modules/plugins/languages/vala.nix b/modules/plugins/languages/vala.nix index 220926d0..722b0d3b 100644 --- a/modules/plugins/languages/vala.nix +++ b/modules/plugins/languages/vala.nix @@ -7,36 +7,57 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.meta) getExe; + inherit (lib.types) enum; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.generators) mkLuaInline; cfg = config.vim.languages.vala; - defaultServer = "vala_ls"; + defaultServers = ["vala_ls"]; servers = { vala_ls = { - package = pkgs.symlinkJoin { - name = "vala-language-server-wrapper"; - paths = [pkgs.vala-language-server]; - buildInputs = [pkgs.makeBinaryWrapper]; - postBuild = '' - wrapProgram $out/bin/vala-language-server \ - --prefix PATH : ${pkgs.uncrustify}/bin - ''; - }; - internalFormatter = true; - lspConfig = '' - lspconfig.vala_ls.setup { - capabilities = capabilities; - on_attach = default_on_attach; - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/vala-language-server"}'' - }, - } + enable = true; + cmd = [ + (getExe (pkgs.symlinkJoin { + name = "vala-language-server-wrapper"; + paths = [pkgs.vala-language-server]; + buildInputs = [pkgs.makeBinaryWrapper]; + postBuild = '' + wrapProgram $out/bin/vala-language-server \ + --prefix PATH : ${pkgs.uncrustify}/bin + ''; + })) + ]; + filetypes = ["vala" "genie"]; + root_dir = mkLuaInline '' + function(bufnr, on_dir) + local meson_matcher = function(path) + local pattern = 'meson.build' + local f = vim.fn.glob(table.concat({ path, pattern }, '/')) + if f == ''' then + return nil + end + for line in io.lines(f) do + -- skip meson comments + if not line:match '^%s*#.*' then + local str = line:gsub('%s+', ''') + if str ~= ''' then + if str:match '^project%(' then + return path + else + break + end + end + end + end + end + + local fname = vim.api.nvim_buf_get_name(bufnr) + local root = vim.iter(vim.fs.parents(fname)):find(meson_matcher) + on_dir(root or vim.fs.dirname(vim.fs.find('.git', { path = fname, upward = true })[1])) + end ''; }; }; @@ -51,16 +72,10 @@ in { lsp = { enable = mkEnableOption "Vala LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Vala LSP server to use"; - type = enum (attrNames servers); - default = defaultServer; - }; - - package = mkOption { - description = "Vala LSP server package, or the command to run as a list of strings"; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; }; }; }; @@ -72,8 +87,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.vala_ls = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/wgsl.nix b/modules/plugins/languages/wgsl.nix index 44323092..8c33e581 100644 --- a/modules/plugins/languages/wgsl.nix +++ b/modules/plugins/languages/wgsl.nix @@ -5,31 +5,23 @@ ... }: let inherit (builtins) attrNames; - inherit (lib.lists) isList; inherit (lib.modules) mkIf mkMerge; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.options) literalExpression mkEnableOption mkOption; - inherit (lib.types) either enum listOf package str; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.options) mkEnableOption mkOption; + inherit (lib.types) enum; + inherit (lib.meta) getExe; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.wgsl; - defaultServer = "wgsl-analyzer"; + defaultServers = ["wgsl-analyzer"]; servers = { wgsl-analyzer = { - package = pkgs.wgsl-analyzer; - internalFormatter = true; - lspConfig = '' - lspconfig.wgsl_analyzer.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else "{'${cfg.lsp.package}/bin/wgsl-analyzer'}" - } - } - ''; + enable = true; + cmd = [(getExe pkgs.wgsl-analyzer)]; + filetypes = ["wgsl"]; + root_markers = [".git"]; + settings = {}; }; }; in { @@ -44,18 +36,11 @@ in { lsp = { enable = mkEnableOption "WGSL LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "WGSL LSP server to use"; }; - - package = mkOption { - description = "wgsl-analyzer package, or the command to run as a list of strings"; - example = literalExpression "[(lib.getExe pkgs.wgsl-analyzer)]"; - type = either package (listOf str); - default = pkgs.wgsl-analyzer; - }; }; }; @@ -68,12 +53,12 @@ in { }) (mkIf cfg.lsp.enable { - vim = { - lsp.lspconfig = { - enable = true; - sources.wgsl_analyzer = servers.${cfg.lsp.server}.lspConfig; - }; - }; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/yaml.nix b/modules/plugins/languages/yaml.nix index a75a921f..1869b1c1 100644 --- a/modules/plugins/languages/yaml.nix +++ b/modules/plugins/languages/yaml.nix @@ -7,10 +7,10 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge; - inherit (lib.lists) isList; - inherit (lib.types) enum either listOf package str; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.meta) getExe; + inherit (lib.types) enum; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.yaml; @@ -25,23 +25,22 @@ end'' else "on_attach = default_on_attach"; - defaultServer = "yaml-language-server"; + defaultServers = ["yaml-language-server"]; servers = { yaml-language-server = { - package = pkgs.yaml-language-server; - lspConfig = '' - - - lspconfig.yamlls.setup { - capabilities = capabilities, - ${onAttach}, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{"${cfg.lsp.package}/bin/yaml-language-server", "--stdio"}'' - }, - } - ''; + enable = true; + cmd = [(getExe pkgs.yaml-language-server) "--stdio"]; + filetypes = ["yaml" "yaml.docker-compose" "yaml.gitlab" "yaml.helm-values"]; + root_markers = [".git"]; + on_attach = onAttach; + # -- https://github.com/redhat-developer/vscode-redhat-telemetry#how-to-disable-telemetry-reporting + settings = { + redhat = { + telemetry = { + enabled = false; + }; + }; + }; }; }; in { @@ -55,18 +54,11 @@ in { }; lsp = { - enable = mkEnableOption "YAML LSP support" // {default = config.vim.lsp.enable;}; - - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; - description = "YAML LSP server to use"; - }; - - package = mkOption { - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - description = "YAML LSP server package"; + enable = mkEnableOption "Yaml LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; + description = "Yaml LSP server to use"; }; }; }; @@ -78,8 +70,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.yaml-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; }) ]); } diff --git a/modules/plugins/languages/zig.nix b/modules/plugins/languages/zig.nix index 89307eab..966e9369 100644 --- a/modules/plugins/languages/zig.nix +++ b/modules/plugins/languages/zig.nix @@ -7,29 +7,21 @@ inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.modules) mkIf mkMerge mkDefault; - inherit (lib.lists) isList; - inherit (lib.types) bool either listOf package str enum; - inherit (lib.nvim.lua) expToLua; - inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.types) bool package enum; + inherit (lib.nvim.types) mkGrammarOption singleOrListOf; + inherit (lib.meta) getExe; + inherit (lib.nvim.attrsets) mapListToAttrs; cfg = config.vim.languages.zig; - defaultServer = "zls"; + defaultServers = ["zls"]; servers = { zls = { - package = pkgs.zls; - internalFormatter = true; - lspConfig = '' - lspconfig.zls.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else "{'${cfg.lsp.package}/bin/zls'}" - } - } - ''; + enable = true; + cmd = [(getExe pkgs.zls)]; + filetypes = ["zig" "zir"]; + root_markers = ["zls.json" "build.zig" ".git"]; + workspace_required = false; }; }; @@ -74,17 +66,11 @@ in { lsp = { enable = mkEnableOption "Zig LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; + servers = mkOption { + type = singleOrListOf (enum (attrNames servers)); + default = defaultServers; description = "Zig LSP server to use"; }; - - package = mkOption { - description = "ZLS package, or the command to run as a list of strings"; - type = either package (listOf str); - default = pkgs.zls; - }; }; dap = { @@ -118,10 +104,12 @@ in { (mkIf cfg.lsp.enable { vim = { - lsp.lspconfig = { - enable = true; - sources.zig-lsp = servers.${cfg.lsp.server}.lspConfig; - }; + lsp.servers = + mapListToAttrs (n: { + name = n; + value = servers.${n}; + }) + cfg.lsp.servers; # nvf handles autosaving already globals.zig_fmt_autosave = mkDefault 0; diff --git a/modules/plugins/lsp/default.nix b/modules/plugins/lsp/default.nix index eb694583..ed20685d 100644 --- a/modules/plugins/lsp/default.nix +++ b/modules/plugins/lsp/default.nix @@ -7,6 +7,7 @@ ./lspconfig ./lspsaga ./null-ls + ./harper-ls # lsp plugins ./lspsaga diff --git a/modules/plugins/lsp/harper-ls/config.nix b/modules/plugins/lsp/harper-ls/config.nix new file mode 100644 index 00000000..d6e27bfc --- /dev/null +++ b/modules/plugins/lsp/harper-ls/config.nix @@ -0,0 +1,19 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib.modules) mkIf; + inherit (lib.meta) getExe; + + cfg = config.vim.lsp; +in { + config = mkIf (cfg.enable && cfg.harper-ls.enable) { + vim.lsp.servers.harper-ls = { + root_markers = [".git"]; + cmd = [(getExe pkgs.harper) "--stdio"]; + settings = {harper-ls = cfg.harper-ls.settings;}; + }; + }; +} diff --git a/modules/plugins/lsp/harper-ls/default.nix b/modules/plugins/lsp/harper-ls/default.nix new file mode 100644 index 00000000..9b4b3ec7 --- /dev/null +++ b/modules/plugins/lsp/harper-ls/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./harper-ls.nix + ./config.nix + ]; +} diff --git a/modules/plugins/lsp/harper-ls/harper-ls.nix b/modules/plugins/lsp/harper-ls/harper-ls.nix new file mode 100644 index 00000000..9fab45fe --- /dev/null +++ b/modules/plugins/lsp/harper-ls/harper-ls.nix @@ -0,0 +1,35 @@ +{lib, ...}: let + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.types) anything attrsOf; +in { + options.vim.lsp.harper-ls = { + enable = mkEnableOption "Harper grammar checking LSP"; + settings = mkOption { + type = attrsOf anything; + default = {}; + example = { + userDictPath = ""; + workspaceDictPath = ""; + fileDictPath = ""; + linters = { + BoringWords = true; + PossessiveNoun = true; + SentenceCapitalization = false; + SpellCheck = false; + }; + codeActions = { + ForceStable = false; + }; + markdown = { + IgnoreLinkTitle = false; + }; + diagnosticSeverity = "hint"; + isolateEnglish = false; + dialect = "American"; + maxFileLength = 120000; + ignoredLintsPath = {}; + }; + description = "Settings to pass to harper-ls"; + }; + }; +} diff --git a/modules/plugins/utility/default.nix b/modules/plugins/utility/default.nix index 349fb47a..2e00e270 100644 --- a/modules/plugins/utility/default.nix +++ b/modules/plugins/utility/default.nix @@ -19,6 +19,7 @@ ./oil-nvim ./outline ./preview + ./qmk-nvim ./sleuth ./smart-splits ./snacks-nvim diff --git a/modules/plugins/utility/oil-nvim/config.nix b/modules/plugins/utility/oil-nvim/config.nix index dca4de60..9344c305 100644 --- a/modules/plugins/utility/oil-nvim/config.nix +++ b/modules/plugins/utility/oil-nvim/config.nix @@ -3,18 +3,38 @@ lib, ... }: let + inherit (lib.attrsets) optionalAttrs recursiveUpdate; + inherit (lib.lists) optionals; inherit (lib.modules) mkIf; inherit (lib.nvim.lua) toLuaObject; - inherit (lib.nvim.dag) entryAnywhere; + inherit (lib.nvim.dag) entryAfter entryAnywhere; cfg = config.vim.utility.oil-nvim; in { config = mkIf cfg.enable { vim = { - startPlugins = ["oil-nvim"]; - pluginRC.oil-nvim = entryAnywhere '' - require("oil").setup(${toLuaObject cfg.setupOpts}); - ''; + startPlugins = ["oil-nvim"] ++ (optionals cfg.gitStatus.enable ["oil-git-status.nvim"]); + + pluginRC.oil-nvim = let + gitStatusDefaultOpts = { + # https://github.com/refractalize/oil-git-status.nvim?tab=readme-ov-file#configuration + win_options = { + signcolumn = "yes:2"; + }; + }; + + setupOpts = + recursiveUpdate + (optionalAttrs cfg.gitStatus.enable gitStatusDefaultOpts) + cfg.setupOpts; + in + entryAnywhere '' + require("oil").setup(${toLuaObject setupOpts}); + ''; + + pluginRC.oil-git-status-nvim = mkIf cfg.gitStatus.enable (entryAfter ["oil-nvim"] '' + require("oil-git-status").setup(${toLuaObject cfg.gitStatus.setupOpts}) + ''); }; }; } diff --git a/modules/plugins/utility/oil-nvim/oil-nvim.nix b/modules/plugins/utility/oil-nvim/oil-nvim.nix index 557f3db6..426188bc 100644 --- a/modules/plugins/utility/oil-nvim/oil-nvim.nix +++ b/modules/plugins/utility/oil-nvim/oil-nvim.nix @@ -8,5 +8,13 @@ in { ''; setupOpts = mkPluginSetupOption "oil-nvim" {}; + + gitStatus = { + enable = mkEnableOption '' + Git status on [oil-nvim] directory listings + ''; + + setupOpts = mkPluginSetupOption "oil-git-status-nvim" {}; + }; }; } diff --git a/modules/plugins/utility/qmk-nvim/config.nix b/modules/plugins/utility/qmk-nvim/config.nix new file mode 100644 index 00000000..c86a483a --- /dev/null +++ b/modules/plugins/utility/qmk-nvim/config.nix @@ -0,0 +1,36 @@ +{ + config, + lib, + ... +}: let + inherit (lib.modules) mkIf; + inherit (lib.nvim.lua) toLuaObject; + inherit (lib.nvim.dag) entryAfter; + + cfg = config.vim.utility.qmk-nvim; +in { + config = mkIf cfg.enable { + vim = { + startPlugins = ["qmk-nvim"]; + + pluginRC.qmk-nvim = entryAfter ["nvim-notify"] '' + require('qmk').setup(${toLuaObject cfg.setupOpts}) + ''; + }; + + assertions = [ + { + assertion = cfg.setupOpts.variant == "qmk" && cfg.setupOpts.comment_preview.position != "inside"; + message = "comment_preview.position can only be set to inside when using the qmk layoyt"; + } + { + assertion = cfg.setupOpts.name != null; + message = "qmk-nvim requires 'vim.utility.qmk.setupOpts.name' to be set."; + } + { + assertion = cfg.setupOpts.layout != null; + message = "qmk-nvim requires 'vim.utility.qmk.setupOpts.layout' to be set."; + } + ]; + }; +} diff --git a/modules/plugins/utility/qmk-nvim/default.nix b/modules/plugins/utility/qmk-nvim/default.nix new file mode 100644 index 00000000..4162ff2e --- /dev/null +++ b/modules/plugins/utility/qmk-nvim/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./config.nix + ./qmk-nvim.nix + ]; +} diff --git a/modules/plugins/utility/qmk-nvim/qmk-nvim.nix b/modules/plugins/utility/qmk-nvim/qmk-nvim.nix new file mode 100644 index 00000000..2c541e64 --- /dev/null +++ b/modules/plugins/utility/qmk-nvim/qmk-nvim.nix @@ -0,0 +1,49 @@ +{lib, ...}: let + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.types) attrsOf nullOr enum lines str; + inherit (lib.nvim.types) mkPluginSetupOption; +in { + options.vim.utility.qmk-nvim = { + enable = mkEnableOption "QMK and ZMK keymaps in nvim"; + + setupOpts = mkPluginSetupOption "qmk.nvim" { + name = mkOption { + type = nullOr str; + default = null; + description = "The name of the layout"; + }; + + layout = mkOption { + type = nullOr lines; + default = null; + description = '' + The keyboard key layout + see for more details + ''; + }; + + variant = mkOption { + type = enum ["qmk" "zmk"]; + default = "qmk"; + description = "Chooses the expected hardware target"; + }; + + comment_preview = { + position = mkOption { + type = enum ["top" "bottom" "inside" "none"]; + default = "top"; + description = "Controls the position of the preview"; + }; + + keymap_overrides = mkOption { + type = attrsOf str; + default = {}; + description = '' + Key codes to text replacements + see for more details + ''; + }; + }; + }; + }; +} diff --git a/modules/wrapper/build/config.nix b/modules/wrapper/build/config.nix index a1807388..03c42787 100644 --- a/modules/wrapper/build/config.nix +++ b/modules/wrapper/build/config.nix @@ -22,9 +22,7 @@ passthru.vimPlugin = false; }; - # build a vim plugin with the given name and arguments - # if the plugin is nvim-treesitter, warn the user to use buildTreesitterPlug - # instead + # Build a Vim plugin with the given name and arguments. buildPlug = attrs: let pin = getPin attrs.pname; in @@ -36,6 +34,7 @@ // attrs ); + # Build a given Treesitter grammar. buildTreesitterPlug = grammars: vimPlugins.nvim-treesitter.withPlugins (_: grammars); pluginBuilders = { @@ -48,6 +47,9 @@ doCheck = false; }; + # Get plugins built from source from self.packages + # If adding a new plugin to be built from source, it must also be inherited + # here. inherit (inputs.self.packages.${pkgs.stdenv.system}) blink-cmp avante-nvim; }; @@ -71,29 +73,38 @@ # Wrap the user's desired (unwrapped) Neovim package with arguments that'll be used to # generate a wrapped Neovim package. neovim-wrapped = inputs.mnw.lib.wrap {inherit pkgs;} { + appName = "nvf"; neovim = config.vim.package; + initLua = config.vim.builtLuaConfigRC; + luaFiles = config.vim.extraLuaFiles; + + # Plugin configurations plugins = { start = buildConfigPlugins config.vim.startPlugins; opt = buildConfigPlugins config.vim.optPlugins; }; - appName = "nvf"; - extraBinPath = config.vim.extraPackages; - initLua = config.vim.builtLuaConfigRC; - luaFiles = config.vim.extraLuaFiles; + + # Providers for Neovim providers = { + ruby.enable = config.vim.withRuby; + nodeJs.enable = config.vim.withNodeJs; python3 = { enable = config.vim.withPython3; extraPackages = ps: map (flip builtins.getAttr ps) config.vim.python3Packages; }; - ruby.enable = config.vim.withRuby; - nodeJs.enable = config.vim.withNodeJs; }; + + # Aliases to link `nvim` to aliases = lib.optional config.vim.viAlias "vi" ++ lib.optional config.vim.vimAlias "vim"; + # Additional packages or Lua packages to be made available to Neovim + extraBinPath = config.vim.extraPackages; extraLuaPackages = ps: map (flip builtins.getAttr ps) config.vim.luaPackages; }; + # A store path representing the built Lua configuration. dummyInit = pkgs.writeText "nvf-init.lua" config.vim.builtLuaConfigRC; + # Additional helper scripts for printing and displaying nvf configuration # in your commandline. printConfig = pkgs.writers.writeDashBin "nvf-print-config" "cat ${dummyInit}"; @@ -106,10 +117,20 @@ paths = [neovim-wrapped printConfig printConfigPath]; postBuild = "echo Helpers added"; - # Allow evaluating config.vim, i.e., config.vim from the packages' passthru - # attribute. For example, packages.x86_64-linux.neovim.passthru.neovimConfig - # will return the configuration in full. - passthru.neovimConfig = config.vim; + passthru = { + # Allow evaluating config.vim, i.e., config.vim from the packages' passthru + # attribute. For example, packages.x86_64-linux.neovim.passthru.neovimConfig + # will return the configuration in full. + neovimConfig = config.vim; + + # Also expose the helper scripts in passthru. + nvfPrintConfig = printConfig; + nvfPrintConfigPath = printConfigPath; + + # In systems where we only have a package and no module, this can be used + # to access the built init.lua + initLua = dummyInit; + }; meta = neovim-wrapped.meta diff --git a/modules/wrapper/environment/options.nix b/modules/wrapper/environment/options.nix index c401f506..d3b28b0f 100644 --- a/modules/wrapper/environment/options.nix +++ b/modules/wrapper/environment/options.nix @@ -3,7 +3,7 @@ lib, ... }: let - inherit (lib.options) mkOption mkEnableOption literalMD; + inherit (lib.options) mkOption mkEnableOption literalMD literalExpression; inherit (lib.types) package bool str listOf attrsOf; inherit (lib.nvim.types) pluginsOpt extraPluginType; in { @@ -11,6 +11,7 @@ in { package = mkOption { type = package; default = pkgs.neovim-unwrapped; + defaultText = literalExpression "pkgs.neovim-unwrapped"; description = '' The neovim package to use for the wrapper. This corresponds to the package that will be wrapped @@ -27,21 +28,20 @@ in { viAlias = mkOption { type = bool; default = true; + example = false; description = "Enable the `vi` alias for `nvim`"; }; vimAlias = mkOption { type = bool; default = true; + example = false; description = "Enable the `vim` alias for `nvim`"; }; startPlugins = pluginsOpt { default = ["plenary-nvim"]; - example = '' - [pkgs.vimPlugins.telescope-nvim] - ''; - + example = literalExpression "[pkgs.vimPlugins.telescope-nvim]"; description = '' List of plugins to load on startup. This is used internally to add plugins to Neovim's runtime. @@ -54,9 +54,7 @@ in { optPlugins = pluginsOpt { default = []; - example = '' - [pkgs.vimPlugins.vim-ghost] - ''; + example = literalExpression "[pkgs.vimPlugins.vim-ghost]"; description = '' List of plugins to optionally load on startup. @@ -108,7 +106,7 @@ in { ''; }; - # this defaults to `true` in the wrapper + # This defaults to `true` in the wrapper # and since we pass this value to the wrapper # with an inherit, it should be `true` here as well withRuby = @@ -120,14 +118,14 @@ in { }; withNodeJs = mkEnableOption '' - NodeJs support in the Neovim wrapper + NodeJS support in the Neovim wrapper ''; luaPackages = mkOption { type = listOf str; default = []; example = ''["magick" "serpent"]''; - description = "List of lua packages to install"; + description = "List of Lua packages to install"; }; withPython3 = mkEnableOption '' @@ -144,7 +142,7 @@ in { pluginOverrides = mkOption { type = attrsOf package; default = {}; - example = '' + example = literalExpression '' { lazydev-nvim = pkgs.fetchFromGitHub { owner = "folke"; diff --git a/modules/wrapper/lazy/lazy.nix b/modules/wrapper/lazy/lazy.nix index eb1f5cdf..cef54360 100644 --- a/modules/wrapper/lazy/lazy.nix +++ b/modules/wrapper/lazy/lazy.nix @@ -1,7 +1,7 @@ {lib, ...}: let inherit (lib.options) mkOption mkEnableOption; inherit (lib.types) enum listOf submodule nullOr str bool int attrsOf anything either oneOf lines; - inherit (lib.nvim.types) pluginType; + inherit (lib.nvim.types) pluginType luaInline; inherit (lib.nvim.config) mkBool; lznKeysSpec = submodule { @@ -98,7 +98,7 @@ # lz.n options enabled = mkOption { - type = nullOr (either bool str); + type = nullOr (either bool luaInline); default = null; description = "When false, or if the lua function returns false, this plugin will not be included in the spec"; }; diff --git a/npins/sources.json b/npins/sources.json index a33129d5..bad7156c 100644 --- a/npins/sources.json +++ b/npins/sources.json @@ -2088,6 +2088,19 @@ "url": "https://github.com/epwalsh/obsidian.nvim/archive/14e0427bef6c55da0d63f9a313fd9941be3a2479.tar.gz", "hash": "15ycmhn48ryaqzch6w3w6llq2qgmjx8xwkb9dn0075z60dybpflr" }, + "oil-git-status.nvim": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "refractalize", + "repo": "oil-git-status.nvim" + }, + "branch": "main", + "submodules": false, + "revision": "4b5cf53842c17a09420919e655a6a559da3112d7", + "url": "https://github.com/refractalize/oil-git-status.nvim/archive/4b5cf53842c17a09420919e655a6a559da3112d7.tar.gz", + "hash": "1jzw6lkvi9xxzqy8xz056xlb45byr1arklh87zmyf0nj9plm2nsp" + }, "oil-nvim": { "type": "Git", "repository": { @@ -2231,6 +2244,19 @@ "url": "https://github.com/kevinhwang91/promise-async/archive/119e8961014c9bfaf1487bf3c2a393d254f337e2.tar.gz", "hash": "0q4a0rmy09hka6zvydzjj2gcm2j5mlbrhbxfcdjj33ngpblkmqzm" }, + "qmk-nvim": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "codethread", + "repo": "qmk.nvim" + }, + "branch": "main", + "submodules": false, + "revision": "3c804c1480991e4837514900b22b9358cfd64fa1", + "url": "https://github.com/codethread/qmk.nvim/archive/3c804c1480991e4837514900b22b9358cfd64fa1.tar.gz", + "hash": "03fsx6qsn8b36jp5m0fkdla6gkkzdv2j18y378y3cqpjclgq995a" + }, "rainbow-delimiters-nvim": { "type": "Git", "repository": {