Match upstream vimwiki's insert-mode list editing:
<C-D> list_change_level(-1) dedent current item
<C-T> list_change_level(+1) indent current item
<C-L><C-J> list_cycle_symbol(+1) cycle marker forward
<C-L><C-K> list_cycle_symbol(-1) cycle marker backward
<C-L><C-M> list_toggle_or_add_chk toggle if has checkbox, else add `[ ]`
Cycle order matches vimwiki's canonical run:
- → * → # → 1. → 1) → a) → A) → i) → I) → (wrap)
Both Lua and VimL paths wired. Lua callbacks fire directly (no `<C-o>`
dance) since list_change_level / changeSymbol mutate the buffer via
the LSP applyEdit pipeline, which works cleanly in insert mode. VimL
keeps the `<C-o>:call …<CR>` idiom since that's the standard there.
Harness adds 5 cases covering the new insert-mode bindings plus one
direct LSP roundtrip for changeSymbol — surfaced a long-standing
stale-binary footgun in test-keymaps.sh (rebuilt only when bin was
missing; now rebuilds every run since incremental cargo is fast).
Harness also captures server log_messages and dumps them on failure
so future swallowed-error bugs (Err → log + Ok(None)) are visible.
Gates: 421 Rust / 25 Neovim / 12 Vim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closing out §13.1's smallest cluster. Three commands move from
"not yet implemented" stubs to real behaviour:
- `nuwiki.link.pasteWikilink` (server, executeCommand) — derives
the current page name from the source URI + wiki root, returns a
`WorkspaceEdit` inserting `[[<page>]]` at the requested cursor
position.
- `nuwiki.link.pasteUrl` (server) — same lookup, but the inserted
text is the page's relative HTML output URL
(`<page>.html`, including subdir segments) so the snippet survives
when the export root moves.
- `nuwiki.link.normalize` (client) — wraps the word at cursor as
`[[word]]` without following. Reuses the `wrap_cword_as_wikilink`
helper that already powers the `<CR>` two-step. Pure-VimL on the
Vim path; pure-Lua on the Neovim path. No LSP round-trip.
Keymaps:
- `+` (normal + visual) now actually calls `normalize_link` on both
editor paths instead of stubbing with a "deferred" notification.
Tests:
- 5 new Rust unit tests in `cluster_c_link_helpers.rs` covering
command-list presence + the page-name derivation for root and
subdirectory pages + the URL / wikilink shape strings.
- Neovim keymap harness gains a `links.normalize_via_+` case (20
passing now, up from 19). Vim harness inherits the same
command-presence check via its existing smoke tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI log for the keymaps job showed Lua crashing with
`attempt to call field 'get_clients' (a nil value)` plus a flood of
`W18: Invalid character in group name` on the `@vimwiki*` highlight
links. Both signal that apt's `neovim` package is too old (Ubuntu
LTS ships 0.6/0.7) — `vim.lsp.get_clients` only landed in 0.10, and
Tree-sitter-style `@group.modifier` highlight names only became
valid sometime in the 0.8-ish era.
Switched the installer to pull the official static tarball
(`neovim/neovim/releases/v0.11.3/nvim-linux-x86_64.tar.gz`) and
symlink it into `/usr/local/bin/nvim`. Vim from apt is kept — it
only runs the pure-VimL harness so its age doesn't matter.
Also added a defensive fallback in the harness:
`get_lsp_clients = vim.lsp.get_clients or vim.lsp.get_active_clients`,
plus a `server_capabilities` nil guard. Won't matter on the pinned
0.11 runner, but means a future regression that drops the deps will
fail with a clear `lsp.attached` FAIL message instead of a Lua
stack trace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`scripts/test-keymaps.sh` boots `nvim --headless` against a scratch
wiki, sources `scripts/test-keymaps.lua`, and asserts the post-state
of each buffer-local mapping registered by `ftplugin/vimwiki.vim` +
`lua/nuwiki/keymaps.lua`.
The harness covers the bindings that actually have observable effects
on the buffer or cursor:
- **Lists** (LSP round-trip): `<C-Space>` / `<C-@>` / `<Nul>` toggle,
`gln` cycle, `glx` reject, plus a `[X]→[ ]` round-trip.
- **Tasks**: `gnt` jumps to the next unfinished `[ ]`.
- **Headings** (LSP round-trip): `=` adds a level, `-` removes one,
`=` on h6 clamps without growing.
- **Header nav** (pure Lua): `]]` next, `[[` prev, `]u` parent.
- **Link nav** (pure Lua): `<Tab>` to the next `[[…]]`.
- **`<CR>` two-step**: first press wraps a bare word as `[[word]]`
and stops (matches vimwiki's review-then-follow).
- **Bullet continuation**: `o` / `O` keep the marker, and `o` on a
`[ ]` line carries the checkbox forward.
Each case sets buffer lines, places the cursor (1-based), fires
`nvim_feedkeys` with `replace_termcodes`, waits long enough for the
LSP `executeCommand` round-trip (800 ms by default; 100 ms for the
pure-client maps), and asserts either the resulting lines, a specific
line, the cursor's new line, or the buffer's new name.
Output goes via `$NUWIKI_KEYMAP_RESULTS` (env-var IPC keeps it
robust to `--clean -u` argument parsing). Exit code mirrors the
harness: 0 on green, 1 on any FAIL. Local run yields:
SUMMARY: 19 passed, 0 failed
CI: new `keymaps` job in `.gitea/workflows/ci.yaml` installs Neovim
and runs the script, so a regression in `lua/nuwiki/keymaps.lua`,
`autoload/nuwiki/commands.vim`, or the LSP command surface is caught
on every push.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>