parity(2): insert-mode list bindings (<C-D>/<C-T>/<C-L><C-?>)

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>
This commit is contained in:
2026-05-12 21:11:53 +00:00
parent ee0309b3c2
commit 46ae618b5f
6 changed files with 208 additions and 7 deletions
+71
View File
@@ -101,6 +101,16 @@ local function wait_for_lsp(timeout_ms)
return nil
end
-- ===== Capture server log_message events =====
local server_log = {}
local orig_handler = vim.lsp.handlers['window/logMessage']
vim.lsp.handlers['window/logMessage'] = function(err, result, ctx, cfg)
if result and result.message then
table.insert(server_log, '[' .. tostring(result.type) .. '] ' .. result.message)
end
if orig_handler then return orig_handler(err, result, ctx, cfg) end
end
-- ===== Smoke: LSP attached =====
vim.defer_fn(function()
@@ -272,6 +282,58 @@ vim.defer_fn(function()
wait_ms = 100,
})
-- ===== Insert-mode list bindings (Cluster 2) =====
run('insert.C-L_C-M_adds_checkbox', {
lines = { '- task' },
cursor = { 1, 0 },
keys = 'A<C-L><C-M><Esc>',
expect_line = '- [ ] task',
expect_at = 1,
wait_ms = 200,
})
run('insert.C-L_C-M_toggles_existing_checkbox', {
lines = { '- [ ] task' },
cursor = { 1, 0 },
keys = 'A<C-L><C-M><Esc>',
expect_line = '- [X] task',
expect_at = 1,
})
-- End-to-end regression for changeSymbol over LSP — direct call
-- (no keymap) so we can spot a stale `bin/nuwiki-ls` or a dispatch
-- error fast. Toggle has separate coverage above via `<C-Space>`.
do
local ok, err = pcall(function()
set_buf({ '- [ ] item', '- [ ] two' })
sleep(200)
vim.api.nvim_win_set_cursor(0, { 1, 0 })
require('nuwiki.commands').list_change_symbol('*', false)
sleep(1500)
local got = get_line(1)
if got ~= '* [ ] item' then
error(string.format('change_symbol failed: got %q', got))
end
end)
record(ok, 'direct.list_change_symbol_via_lsp', ok and '' or tostring(err))
end
run('insert.C-L_C-J_cycles_marker_forward', {
lines = { '- item' },
cursor = { 1, 0 },
keys = 'A<C-L><C-J><Esc>',
expect_line = '* item',
expect_at = 1,
wait_ms = 1500,
})
run('insert.C-L_C-K_cycles_marker_backward', {
-- From `-` going backward wraps to the end of the order (`I)`).
lines = { '- item' },
cursor = { 1, 0 },
keys = 'A<C-L><C-K><Esc>',
expect_line = 'I) item',
expect_at = 1,
wait_ms = 1500,
})
-- ===== Diary nav =====
-- Tested via the LSP open-* responses; can't fully validate without
-- a populated diary, but at least confirm the maps fire without
@@ -280,6 +342,15 @@ vim.defer_fn(function()
-- ===== Wrap up =====
table.insert(out_lines, '')
-- Dump captured server log_messages only on failure — useful for
-- diagnosing dispatch errors that the LSP swallows into log_message.
if fail > 0 and #server_log > 0 then
table.insert(out_lines, '--- server log_messages ---')
for _, l in ipairs(server_log) do
table.insert(out_lines, l)
end
table.insert(out_lines, '')
end
table.insert(out_lines, string.format('SUMMARY: %d passed, %d failed', pass, fail))
vim.fn.writefile(out_lines, OUT)
vim.cmd(fail == 0 and 'qall!' or 'cquit!')
+8 -7
View File
@@ -20,14 +20,15 @@ trap 'rm -rf "$TMP"' EXIT
log() { printf '\033[1;34m[keymap-test]\033[0m %s\n' "$*"; }
# Build the LSP binary if missing.
# Always rebuild the LSP binary so harness runs against current sources.
# A previous version only built when the symlink was missing, which let
# stale binaries pass tests against unrelated code — incremental cargo
# build is fast, so just always run it.
BIN="$REPO_ROOT/bin/nuwiki-ls"
if [[ ! -x "$BIN" ]]; then
log 'building nuwiki-ls (release)…'
cargo build --release -p nuwiki-ls --manifest-path "$REPO_ROOT/Cargo.toml" >/dev/null
mkdir -p "$REPO_ROOT/bin"
ln -sf "$REPO_ROOT/target/release/nuwiki-ls" "$BIN"
fi
log 'building nuwiki-ls (release, incremental)…'
cargo build --release -p nuwiki-ls --manifest-path "$REPO_ROOT/Cargo.toml" >/dev/null
mkdir -p "$REPO_ROOT/bin"
ln -sf "$REPO_ROOT/target/release/nuwiki-ls" "$BIN"
mkdir -p "$TMP/wiki"
echo "= seed =" > "$TMP/wiki/index.wiki"