Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b8a7726
feat(lsp): install the tools a bundle declares, not just its servers
Chiarandini Sep 1, 2026
726c00e
refactor(run): one run table behind both runners, and add run-project
Chiarandini Sep 1, 2026
5aea59f
fix(go): start gopls, which the bundle never did
Chiarandini Sep 1, 2026
3024adf
fix(java): start jdtls and register its debug configurations
Chiarandini Sep 1, 2026
2a41d69
fix(python): point the debug adapter at an interpreter that has debugpy
Chiarandini Sep 1, 2026
39e2127
fix(rust): report test results correctly, and quiet a scratch .rs file
Chiarandini Sep 1, 2026
3e26e82
feat(c-cpp): run tests through CTest, and format with clang-format
Chiarandini Sep 1, 2026
367024f
docs: regenerate the bundle reference
Chiarandini Sep 1, 2026
6c30308
fix(run): run TypeScript with node, and declare the parsers web-dev d…
Chiarandini Sep 1, 2026
442737e
ci: grade the language capability matrix nightly
Chiarandini Sep 1, 2026
ea42784
fix(tests): stop the capability harness reporting a coin flip as a re…
Chiarandini Sep 1, 2026
88fb50e
docs: document the run keymaps
Chiarandini Sep 1, 2026
77a56fb
ci: run the capability matrix on the changes that can break it
Chiarandini Sep 1, 2026
04c63cd
fix(ci): match the tool-bundle paths, and correct two convention slips
Chiarandini Sep 1, 2026
e74e284
test(capability): grade C++ as its own row, and stop committing machi…
Chiarandini Sep 1, 2026
7ef2fe9
fix(web-dev): debug JavaScript with the DAP server, not the VS Code one
Chiarandini Sep 1, 2026
d1789ea
ci: install maven, and survive PEP 668 on the runner
Chiarandini Sep 1, 2026
6e4a536
fix: close the four gaps the first CI run exposed
Chiarandini Sep 1, 2026
869b29e
test(capability): make the ctest failure say which of three things broke
Chiarandini Sep 1, 2026
e00c953
test(capability): track the ctest Linux gap rather than fail on it
Chiarandini Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions .github/workflows/capability.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: capability matrix

# Grades every language bundle against the eight checkpoints in the
# language-bundle contract: a language server attaches, treesitter parses,
# the formatter reformats, diagnostics appear, tests run, the debugger stops on
# a breakpoint, and the file and project both run.
#
# Two triggers, because there are two ways this can break.
#
# A change to a language bundle, to the shared run table, or to the LSP stack
# can break a checkpoint, so those paths run it on the pull request that made
# the change -- attributed to that change, while the author is looking. It is
# slow (it provisions Rust, Go, Python, a JDK, Node and LLVM, then a Mason
# package set), which is why it is path-filtered rather than universal:
# check.yml and docs.yml stay the fast gates every PR pays for.
#
# The matrix can also break with no commit at all. Every checkpoint depends on
# something outside this repository -- a plugin, a Mason package, a language
# server, a toolchain -- and all of them move. That is what the schedule is
# for, and weekly is the honest cadence for it: nightly on a repository whose
# dependencies change slower than that mostly re-verifies unchanged code and
# trains you to ignore the result.
on:
pull_request:
paths:
- "lua/noethervim/bundles/languages/**"
- "lua/noethervim/bundles/tools/debug.lua"
- "lua/noethervim/bundles/tools/test.lua"
- "lua/noethervim/bundles/tools/task-runner.lua"
- "lua/noethervim/util/run.lua"
- "lua/noethervim/util/mason_install.lua"
- "lua/noethervim/plugins/lsp.lua"
- "lua/noethervim/lsp/**"
- "tests/capability*"
- "tests/fixtures/lang/**"
- ".github/workflows/capability.yml"
schedule:
# Mondays 04:00 UTC: a drift canary, not a per-change gate.
- cron: "0 4 * * 1"
workflow_dispatch:
inputs:
language:
description: "One language (rust, go, python, c, java, web, latex) or --all"
required: false
default: "--all"

concurrency:
group: capability-${{ github.ref }}
cancel-in-progress: true

jobs:
matrix:
name: language capability matrix
runs-on: ubuntu-latest
# The budget is dominated by provisioning, not by the checks.
timeout-minutes: 60

steps:
- uses: actions/checkout@v5

- name: Install Neovim
uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: stable

# Each language brings its own toolchain. A missing one is reported as
# UNCOVERED rather than FAIL, so a provisioning failure here degrades the
# run honestly instead of turning it red for the wrong reason -- but the
# point of installing them is that UNCOVERED should stay empty.
- name: Rust toolchain
run: |
rustup component add rust-analyzer rustfmt
# neotest-rust drives `cargo nextest`, not `cargo test`.
curl -fsSL https://get.nexte.st/latest/linux \
| tar zxf - -C "${CARGO_HOME:-$HOME/.cargo}/bin"

- uses: actions/setup-go@v5
with: { go-version: "stable" }

- uses: actions/setup-python@v5
with: { python-version: "3.12" }

- uses: actions/setup-java@v4
# 21, not 17: jdtls itself runs on 21, and on 17 it does not start at
# all, which reads as "the Java bundle attaches no language server".
with: { distribution: temurin, java-version: "21" }

- uses: actions/setup-node@v4
with: { node-version: "22" }

- name: C, CMake, Maven and the rest
run: |
sudo apt-get update
# maven is what neotest-java builds and runs through; the java row's
# test cell needs it, and the toolchain gate is on `java`, so its
# absence would read as a failure rather than as UNCOVERED.
sudo apt-get install -y build-essential cmake clangd maven
# The tree-sitter CLI is what nvim-treesitter builds parsers with,
# and core disables parser installation without it. Not an apt
# package; npm is the supported route.
npm install -g tree-sitter-cli
# PEP 668 marks the runner's Python as externally managed, so a plain
# `pip install` fails there. Try it first anyway, so this keeps
# working on an image that has not adopted the marker.
python3 -m pip install --quiet pytest \
|| python3 -m pip install --quiet --break-system-packages pytest

- name: Run the matrix
# The harness owns its own isolated NVIM_APPNAME and XDG root, so it
# cannot touch a real config even on a runner.
#
# No TeX distribution is installed: it is gigabytes for one bundle, and
# the harness gates every row on its toolchain, so latex reports
# UNCOVERED here rather than failing. Its one real capability, the
# compile, is covered by tests/behave_latex.lua.
#
# `pipefail` so a crash in the harness fails the step even though the
# output is piped through tee.
shell: bash
run: |
set -o pipefail
bash tests/capability.sh "${{ inputs.language || '--all' }}" 2>&1 | tee capability.log

# The matrix is the artifact worth keeping: it is the record of what the
# distribution could actually do on a given day.
- name: Publish the matrix
if: always()
run: |
{
echo '## Language capability matrix'
echo
echo '| language | checkpoints |'
echo '| --- | --- |'
grep '^MATRIX' capability.log 2>/dev/null \
| sed 's/^MATRIX \([a-z]*\) /| `\1` | /; s/$/ |/' \
|| echo '| (no matrix rows; see the job log) | |'
} >> "$GITHUB_STEP_SUMMARY"
41 changes: 40 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
lazy-lock.json
tests/

# tests/ is private, EXCEPT the language capability harness. That is maintainer
# infrastructure rather than the user-facing suite the rest of tests/ holds: it
# grades every language bundle against the eight checkpoints in
# dev-docs/language-bundle-contract.md, and the nightly workflow has to be able
# to check it out and run it.
#
# The exclusion is on tests/* rather than tests/, because git does not descend
# into an excluded directory and a negation inside one never applies.
tests/*
!tests/capability.sh
!tests/capability_assert.lua
!tests/capability_provision.lua
!tests/fixtures/
tests/fixtures/*
!tests/fixtures/lang/

# Fixture build output: regenerated by the harness on every run, and large.
tests/fixtures/lang/*/target/
tests/fixtures/lang/*/build/
tests/fixtures/lang/*/node_modules/
tests/fixtures/lang/*/__pycache__/
tests/fixtures/lang/*/.pytest_cache/
tests/fixtures/lang/*/package-lock.json
tests/fixtures/lang/c/main
tests/fixtures/lang/c/main_debug
tests/fixtures/lang/cpp/main
tests/fixtures/lang/cpp/main_debug
tests/fixtures/lang/go/capfixture
tests/fixtures/lang/rust/Cargo.lock
# latexindent drops a log beside whatever it formats.
tests/fixtures/lang/latex/indent.log
# Generated per run: it embeds an absolute directory.
tests/fixtures/lang/*/compile_commands.json
tests/fixtures/lang/c/doctest.h
tests/fixtures/lang/*/*.dSYM/
# jdtls writes an Eclipse project into whatever it opens.
tests/fixtures/lang/java/.classpath
tests/fixtures/lang/java/.project
tests/fixtures/lang/java/.settings/
.DS_Store
.cache
.claude/
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ header.
<!-- BEGIN GENERATED: bundle-requirements -->
- `c-cpp` needs compile_commands.json
- `go` needs Go toolchain
- `java` needs a JDK
- `java` needs a JDK 21 or newer
- `latex` needs latexmk
- `python` needs Python 3
- `rust` needs rust-analyzer, Cargo
Expand Down
44 changes: 44 additions & 0 deletions doc/noethervim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,28 @@ can be turned off with a single flag in your init.lua, set before
With the flag set, `nvim` opens straight into an empty buffer. Set it
to `true` or leave unset to keep the dashboard (default).

*noethervim-auto-install*
Declining toolchain auto-install: ~

Enabling a bundle is how you ask for the tools it drives. A language
bundle names its language server, formatter, linter and debug adapter,
and NoetherVim fetches through Mason whatever is missing, the same way
language servers have always arrived. Enabling `languages/rust` together
with `tools/debug` gets you codelldb; `languages/python` gets you black.

To decline, set this in your init.lua before `lazy.setup`: >lua
vim.g.noethervim_auto_install = false
<
Nothing is then fetched on your behalf. Bundles still declare what they
need, `:checkhealth noethervim` still names anything missing, and
`:Mason` still installs on request. Set this when a toolchain is managed
outside the editor, by Nix, by system packages, or by a project-local
environment, where a second copy under Mason is at best redundant and at
worst a version you did not choose.

Language servers are unaffected: they arrive through `ensure_installed`
and are not gated by this flag.

==============================================================================
9. KEYMAP NAMESPACES *noethervim-keymaps* *neothervim-keymaps*

Expand Down Expand Up @@ -1294,6 +1316,28 @@ Prefixes: ~
every buffer it can before forcing out.
`ZR` (scratch sweep) deletes unnamed scratch buffers.

*noethervim-running*
Running code: ~

`<Leader>rf` Run the current file
`<Leader>rp` Run the project around it
`<Leader>rc` Run the current file in a floating window
`<Leader>rT` Send the run command to a betterTerm terminal

What each language runs is one table, so the four agree. `rf` and `rp`
need the task-runner bundle; `rc` and `rT` are core, and `rT` also
needs the better-term bundle.

The difference between `rf` and `rp` is the project: in a Cargo crate
`rf` runs `cargo run`, and on a loose .rs file it compiles that file
alone. `rp` runs the project's own entry point (cargo, go.mod, npm,
Maven, make) and does nothing when there is no project around the
buffer. Interpreted languages have `rf` only.

Version managers are honored: an interpreter is resolved through
mise, asdf, pyenv, rbenv, nodenv or goenv for the buffer's directory
before it runs, so a project pinned to an older Python gets that one.

Other notable keymaps: ~

Normal: ~
Expand Down
2 changes: 2 additions & 0 deletions doc/tags
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ neothervim-install noethervim.txt /*neothervim-install*
neothervim-keymaps noethervim.txt /*neothervim-keymaps*
noethervim noethervim.txt /*noethervim*
noethervim-Z noethervim.txt /*noethervim-Z*
noethervim-auto-install noethervim.txt /*noethervim-auto-install*
noethervim-brackets noethervim.txt /*noethervim-brackets*
noethervim-bundle-toggle noethervim.txt /*noethervim-bundle-toggle*
noethervim-bundles noethervim.txt /*noethervim-bundles*
Expand Down Expand Up @@ -49,6 +50,7 @@ noethervim-picker-notifications noethervim.txt /*noethervim-picker-notifications
noethervim-q-close noethervim.txt /*noethervim-q-close*
noethervim-quickstart noethervim.txt /*noethervim-quickstart*
noethervim-resize-arrows noethervim.txt /*noethervim-resize-arrows*
noethervim-running noethervim.txt /*noethervim-running*
noethervim-semicolon noethervim.txt /*noethervim-semicolon*
noethervim-shadowed noethervim.txt /*noethervim-shadowed*
noethervim-snippet-stop noethervim.txt /*noethervim-snippet-stop*
Expand Down
20 changes: 12 additions & 8 deletions docs-site/src/content/docs/guides/bundles.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ reads. Run that for the state of the bundles you actually have enabled.

### `c-cpp`

Installs clangd on demand and adds the C and C++ treesitter parsers. With the debug bundle also enabled, registers the codelldb adapter and launch configurations for both languages.
Installs clangd on demand and adds the C and C++ treesitter parsers. With the debug bundle also enabled, registers the codelldb adapter and launch configurations for both languages; with the test bundle, runs CTest through neotest, whatever framework the project uses.

```lua
{ import = "noethervim.bundles.languages.c-cpp" }
Expand All @@ -36,6 +36,8 @@ Requires:
<br />CMake writes it with CMAKE_EXPORT_COMPILE_COMMANDS=ON; Make users usually generate it with bear
- **codelldb** *(optional)*: stepping through C and C++, when the debug bundle is also enabled
<br />:MasonInstall codelldb
- **CTest** *(optional)*: running tests, when the test bundle is also enabled; ships with CMake
<br />https://cmake.org/download/ (CMake 3.21 or newer)

### `go`

Expand All @@ -54,16 +56,16 @@ Requires:

### `java`

jdtls needs workspace management and jar paths that plain lspconfig cannot supply, so it gets a dedicated client. It starts on the first .java buffer. Install the server itself with :MasonInstall jdtls. With the test bundle also enabled, registers the JUnit adapter.
jdtls needs workspace management and jar paths that plain lspconfig cannot supply, so it gets a dedicated client, started per buffer with a workspace directory of its own. With the debug bundle also enabled it loads the Java debug and test jars into the server, which is what makes breakpoints and the JUnit adapter work.

```lua
{ import = "noethervim.bundles.languages.java" }
```

Requires:

- **a JDK**: jdtls will not start without one
<br />JDK 17 or newer; jdtls itself installs via Mason
- **a JDK 21 or newer**: jdtls itself runs on 21; the project it indexes may target older
<br />https://adoptium.net/
- **Maven or Gradle** *(optional)*: neotest-java builds and runs through the project's own tool, when the test bundle is also enabled
<br />whichever your project already uses

Expand Down Expand Up @@ -103,7 +105,7 @@ Requires:

### `rust`

rustaceanvim goes past plain rust-analyzer with macro expansion, runnables and debuggables, the crate graph, hover actions and structural search-replace. It manages its own LSP client, so no lspconfig entry is needed. With the test bundle also enabled, it supplies its own neotest adapter.
rustaceanvim goes past plain rust-analyzer with macro expansion, runnables and debuggables, the crate graph, hover actions and structural search-replace. It manages its own LSP client, so no lspconfig entry is needed. With the test bundle also enabled, cargo tests run through neotest; with the debug bundle, it loads debug targets from rust-analyzer once an adapter is installed.

```lua
{ import = "noethervim.bundles.languages.rust" }
Expand All @@ -115,8 +117,10 @@ Requires:
<br />rustup component add rust-analyzer
- **Cargo**: building and running from the editor
<br />https://rustup.rs/
- **codelldb or lldb** *(optional)*: stepping through Rust, when the debug bundle is also enabled
<br />:MasonInstall codelldb
- **cargo-nextest** *(optional)*: running tests, when the test bundle is also enabled; neotest-rust drives nextest rather than cargo test
<br />cargo install cargo-nextest
- **codelldb** *(optional)*: stepping through Rust, when the debug bundle is also enabled
<br />:MasonInstall codelldb, or put lldb-dap on PATH

### `web-dev`

Expand Down Expand Up @@ -252,7 +256,7 @@ Requires:

### `task-runner`

overseer.nvim runs and tracks tasks, compiler.nvim wraps it in a project compiler UI, and &lt;leader&gt;rf runs the current file in a way that respects filetype and version managers.
overseer.nvim runs and tracks tasks, compiler.nvim wraps it in a project compiler UI, and &lt;leader&gt;rf and &lt;leader&gt;rp run the current file or the project around it, respecting filetype, project markers and version managers.

```lua
{ import = "noethervim.bundles.tools.task-runner" }
Expand Down
6 changes: 6 additions & 0 deletions init.lua.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ vim.g.mapsearchleader = "<space>" -- search/navigation prefix (default: <Spac
-- empty buffer instead.
-- vim.g.noethervim_dashboard = false

-- Enabling a bundle is how you ask for the tools it drives: the formatter,
-- linter and debug adapter it names are fetched through Mason, the same way
-- its language server already was. Uncomment to decline, and install those
-- yourself (see :help noethervim-auto-install).
-- vim.g.noethervim_auto_install = false

-- ── 2. Bootstrap lazy.nvim and NoetherVim ─────────────────────────────────
-- Clone both so they are on the runtime path before lazy.setup() runs.
-- Nothing else can install lazy.nvim, and NoetherVim has to be present for
Expand Down
Loading
Loading