From 784cd436a27eaa0d648911173cb580ca25b8f7cb Mon Sep 17 00:00:00 2001 From: "Thing-han, Lim" <15379156+potsrevennil@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:07:16 +0800 Subject: [PATCH 1/3] refactor(lsp): default project toolchains off Split the language toggles into editor-wide tooling (config, shell, typst: on) and project toolchains (python, c, rust, go, js, haskell, lean: off). A project's language server has to match its own compiler or runtime, so it comes from the project's flake devShell via direnv; lua/plugins/lsp.lua only names the servers and picks up whatever is first on PATH. Installing them globally was mostly redundant, and some (HLS) carry multi-GiB closures that every nixpkgs bump re-downloads. Neovim skips a server whose cmd is not executable (logged to lsp.log, no notification), so opening such a file outside a project shell just runs without that LSP. Co-Authored-By: Claude Opus 5.5 --- lsp/languages.nix | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lsp/languages.nix b/lsp/languages.nix index c51724f..218be22 100644 --- a/lsp/languages.nix +++ b/lsp/languages.nix @@ -9,14 +9,24 @@ # for `? default` values in its own signature -- the actual package attrs # (prettierd, gopls, etc.) stay declared where they always were. { + # Editor-wide: useful on any file, independent of a project's toolchain, + # and small. On by default. config = true; # generic formatter (prettierd) + TOML/YAML LSPs -- not markdown-specific - python = true; shell = true; typst = true; - c = true; - rust = true; - go = true; - js = true; + + # Project toolchains: the server has to match the project's own + # compiler/runtime (HLS <-> GHC, gopls <-> go, pylsp <-> the project's + # Python packages, clangd <-> its compile_commands.json, ...), so each + # project provides them from its flake devShell and direnv puts them + # first on PATH. lua/plugins/lsp.lua only names the servers, so it picks + # up whichever binary that is. Off by default; turn one on for a machine + # without project flakes, e.g. `KAPI_VIM_LANGS=go nix run --impure .#bundle`. + python = false; + c = false; + rust = false; + go = false; + js = false; haskell = false; lean = false; } From 1aed06801994f23465a16d74d8a0dab06f2f0182 Mon Sep 17 00:00:00 2001 From: "Thing-han, Lim" <15379156+potsrevennil@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:22:38 +0800 Subject: [PATCH 2/3] fix(lsp): stay quiet when project formatters/linters are missing Follow-up to defaulting project toolchains off: those toggles also carry the per-language formatters and linters, which now come from the project's devShell too. Make the config treat them like servers: - conform: notify_no_formatters = false, so a .go/.py/.c buffer outside a project shell doesn't warn about goimports/black/clang-format. - none-ls: gate golangci_lint on a runtime_condition, so it is skipped instead of erroring on spawn when golangci-lint isn't on PATH. - js back on by default: it is just nodejs, which commitlint already pulls into the closure, so turning it off saved nothing. - Fix comments still calling packages.default the every-language build, and document `.override { enable_ = true; }` for re-enabling. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 6 +++--- flake.nix | 9 +++++---- lsp/languages.nix | 16 +++++++++------- lua/plugins/conform.lua | 4 ++++ lua/plugins/none-ls.lua | 9 +++++++-- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 328c5ef..a23f16e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,9 +53,9 @@ jobs: # bundle to read $KAPI_VIM_LANGS) is applied uniformly -- it's a no-op for # the lite entries, which never call builtins.getEnv. # - # packages.default (the full, every-language experience) is intentionally - # not built here -- that's slow (10+ minutes cold, no caching) and belongs - # in a separate, less-frequent workflow, not this one. + # packages.default (editor-wide tooling, per-language toolchains off) is + # intentionally not built here -- it belongs in a separate, less-frequent + # workflow, not this one. # # Restores every lazy.nvim plugin over the network to the exact commit # recorded in lazy-lock.json (mason is disabled for LSP servers, but diff --git a/flake.nix b/flake.nix index 131721f..5071109 100644 --- a/flake.nix +++ b/flake.nix @@ -146,7 +146,8 @@ }; packages = { - # nvim with built-in dependencies, but without any configuration + # nvim with editor-wide tooling (see lsp/languages.nix; per-language + # toolchains are off by default), but without any configuration default = pkgs.lib.makeOverridable (wrapKapiVim { pname = "kapi-vim"; @@ -167,9 +168,9 @@ # from lsp/languages.nix, so this list can never drift from what # lsp/default.nix actually knows how to gate) -- just the base # nixd + lua-language-server + editor tooling, fast enough for - # CI to build and smoke-test on every run. default/bundle (the - # full, every-language experience) are exercised in a separate, - # slower workflow. + # CI to build and smoke-test on every run. default (editor-wide + # tooling) and bundle (whichever languages are requested) are + # exercised in a separate, slower workflow. lite = pkgs.lib.makeOverridable (wrapKapiVim { pname = "kapi-vim-lite"; conf = bundleConf; }) allLanguagesOff; diff --git a/lsp/languages.nix b/lsp/languages.nix index 218be22..3ddd077 100644 --- a/lsp/languages.nix +++ b/lsp/languages.nix @@ -14,19 +14,21 @@ config = true; # generic formatter (prettierd) + TOML/YAML LSPs -- not markdown-specific shell = true; typst = true; + js = true; # just nodejs; commitlint pulls it into the closure regardless - # Project toolchains: the server has to match the project's own - # compiler/runtime (HLS <-> GHC, gopls <-> go, pylsp <-> the project's - # Python packages, clangd <-> its compile_commands.json, ...), so each + # Project toolchains: the server, formatter and linter have to match the + # project's own compiler/runtime and its CI (HLS <-> GHC, gopls <-> go, + # black/clang-format <-> the versions CI checks with, ...), so each # project provides them from its flake devShell and direnv puts them - # first on PATH. lua/plugins/lsp.lua only names the servers, so it picks - # up whichever binary that is. Off by default; turn one on for a machine - # without project flakes, e.g. `KAPI_VIM_LANGS=go nix run --impure .#bundle`. + # first on PATH. The Lua config only names the tools and stays quiet when + # one is missing (Neovim skips non-executable servers; conform.lua and + # none-ls.lua are set up to match). Off by default; turn one on for a + # machine without project flakes with `kapi-vim.override { enable_go = + # true; }`, or `KAPI_VIM_LANGS=go nix run --impure .#bundle`. python = false; c = false; rust = false; go = false; - js = false; haskell = false; lean = false; } diff --git a/lua/plugins/conform.lua b/lua/plugins/conform.lua index 6586068..1f0317e 100644 --- a/lua/plugins/conform.lua +++ b/lua/plugins/conform.lua @@ -20,6 +20,10 @@ return { lsp_format = "fallback", }, log_level = vim.log.levels.DEBUG, + -- Per-language formatters (black, goimports, clang-format, ...) come + -- from each project's devShell, so outside one they are expected to + -- be missing; don't warn about it, same as Neovim does for servers. + notify_no_formatters = false, formatters = { shfmt = { args = { "-s", "-i", "2", "-ci", "-fn", "-filename", "$FILENAME" }, diff --git a/lua/plugins/none-ls.lua b/lua/plugins/none-ls.lua index fd4a388..0ec6000 100644 --- a/lua/plugins/none-ls.lua +++ b/lua/plugins/none-ls.lua @@ -7,8 +7,13 @@ return { local opts = { debug = false, sources = { - -- go - diagnostics.golangci_lint, + -- go: golangci-lint comes from the project's devShell; skip + -- it (rather than erroring on spawn) where it isn't on PATH. + diagnostics.golangci_lint.with({ + runtime_condition = function() + return vim.fn.executable("golangci-lint") == 1 + end, + }), -- nix diagnostics.deadnix, code_actions.statix, From 7c5f2a8491cf58ffbf8c54be415aa2b82d8f98fa Mon Sep 17 00:00:00 2001 From: "Thing-han, Lim" <15379156+potsrevennil@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:33:48 +0800 Subject: [PATCH 3/3] fix(lsp): keep conform's warning, document project toolchains Drop notify_no_formatters = false: it was never needed. Every buffer also gets codespell (formatters_by_ft["*"]), which is always installed, and conform only warns when none of a buffer's formatters are available. A missing goimports/black/clang-format is skipped quietly, and the warning stays for genuinely broken setups (e.g. editor-wide formatters missing). README: explain that per-language toolchains come from the project's devShell via direnv, and how to turn them on without project flakes. Co-Authored-By: Claude Opus 5.5 --- README.md | 9 +++++++++ lsp/languages.nix | 5 +++-- lua/plugins/conform.lua | 4 ---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 38b39ff..e86154f 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,12 @@ https://github.com/potsrevennil/kapi-vim/blob/702ba0e14e0776db24d08924b2695d4d8c - `nix build github:potsrevennil/kapi-vim#bundle` for an opinionated Neovim configuration, including LSP runtime dependencies. - `nix profile install github:potsrevennil/kapi-vim#bundle` for installing the neovim configuration to system path - check [kapi-sysconf](https://github.com/potsrevennil/kapi-sysconf) for how to integrate with other flake modules + +## Language toolchains + +The packages ship editor-wide tooling only: nixd, lua-language-server, plus the config (prettierd, taplo, yaml-language-server), shell, typst and js (nodejs) toggles. Per-language toolchains (python, c, rust, go, haskell, lean) are off by default, because their server, formatter and linter have to match each project's compiler and CI. Provide them from the project's flake devShell and load it with direnv before starting `nvim`; the config only names the tools, so it picks up whatever is first on `PATH`, and skips any that are missing without warnings. + +On a machine without project flakes, turn languages on explicitly: + +- `KAPI_VIM_LANGS=python,go nix run --impure github:potsrevennil/kapi-vim#bundle` +- `kapi-vim.override { enable_go = true; }` when consuming the package or overlay diff --git a/lsp/languages.nix b/lsp/languages.nix index 3ddd077..8e1f1c4 100644 --- a/lsp/languages.nix +++ b/lsp/languages.nix @@ -21,8 +21,9 @@ # black/clang-format <-> the versions CI checks with, ...), so each # project provides them from its flake devShell and direnv puts them # first on PATH. The Lua config only names the tools and stays quiet when - # one is missing (Neovim skips non-executable servers; conform.lua and - # none-ls.lua are set up to match). Off by default; turn one on for a + # one is missing (Neovim skips non-executable servers, conform skips + # unavailable formatters while codespell still runs, and none-ls.lua gates + # golangci-lint on PATH). Off by default; turn one on for a # machine without project flakes with `kapi-vim.override { enable_go = # true; }`, or `KAPI_VIM_LANGS=go nix run --impure .#bundle`. python = false; diff --git a/lua/plugins/conform.lua b/lua/plugins/conform.lua index 1f0317e..6586068 100644 --- a/lua/plugins/conform.lua +++ b/lua/plugins/conform.lua @@ -20,10 +20,6 @@ return { lsp_format = "fallback", }, log_level = vim.log.levels.DEBUG, - -- Per-language formatters (black, goimports, clang-format, ...) come - -- from each project's devShell, so outside one they are expected to - -- be missing; don't warn about it, same as Neovim does for servers. - notify_no_formatters = false, formatters = { shfmt = { args = { "-s", "-i", "2", "-ci", "-fn", "-filename", "$FILENAME" },