feat: real range/bang/visual handling for the deferred command gaps
CI / cargo fmt --check (push) Successful in 27s
CI / cargo clippy (push) Successful in 22s
CI / cargo test (push) Successful in 53s
CI / editor keymaps (push) Successful in 1m29s

Implements the four items previously left as "needs handler work" — all
fully wired, not cosmetic.

- ToggleListItem / IncrementListItem / DecrementListItem are now -range
  (both clients). The client loops the per-line op over [<line1>,<line2>]
  (toggle_list_item_range / list_cycle_symbol_range). The server's edits are
  version-less single-line WorkspaceEdits, so each line applies independently
  — no server protocol change. `:'<,'>VimwikiToggleListItem` toggles the whole
  selection.
- VimwikiRebuildTags gains -bang: `:…RebuildTags!` passes { all: true } and
  the server (tags_rebuild) re-indexes every configured wiki via
  wikis_snapshot(), not just the current one.
- VimwikiNormalizeLink is -nargs=?: with `1` (upstream's visual flag; the
  x-mode `+` mapping now passes it) it wraps the '< / '> selection as
  [[selection]] (new wrap_visual_as_wikilink in both clients).
- VimwikiCheckLinks is -range: a ranged invocation filters the broken-link
  report to the current buffer's selected lines (client-side filter in
  results_to_qf / check_links).

Tests: cmd.toggle_list_item_range + cmd.normalize_link_visual_wraps_selection
(test-keymaps.lua), normalize.visual_wraps_selection (test-keymaps-vim.vim).
README + doc/nuwiki.txt + gap doc updated. fmt/clippy clean; nvim 274,
vim 266+18, all Rust tests + config-parity green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 17:57:03 +00:00
parent 212b300fb4
commit db9c1c5c11
10 changed files with 297 additions and 105 deletions
+66 -15
View File
@@ -361,6 +361,27 @@ function M.cycle_list_item_back()
exec('nuwiki.list.cycleCheckbox', a)
end
M.reject_list_item = _exec_pos('nuwiki.list.rejectCheckbox')
-- Apply a per-line action across [l1, l2] (visual-range commands). Each call
-- targets its own line; the server's version-less single-line edits apply
-- independently as they return, so no conflict.
local function over_range(l1, l2, fn)
local save = vim.api.nvim_win_get_cursor(0)
for ln = l1, l2 do
vim.api.nvim_win_set_cursor(0, { ln, 0 })
fn()
end
pcall(vim.api.nvim_win_set_cursor, 0, save)
end
function M.toggle_list_item_range(l1, l2)
over_range(l1, l2, M.toggle_list_item)
end
function M.list_cycle_symbol_range(direction, l1, l2)
over_range(l1, l2, function() M.list_cycle_symbol(direction) end)
end
M.heading_add_level = _exec_pos('nuwiki.heading.addLevel')
M.heading_remove_level = _exec_pos('nuwiki.heading.removeLevel')
@@ -380,7 +401,11 @@ end
M.toc_generate = function() exec('nuwiki.toc.generate', uri_args()) end
M.links_generate = function() exec('nuwiki.links.generate', uri_args()) end
function M.check_links()
-- range>0 restricts the report to the current buffer's [l1, l2] lines
-- (`:'<,'>VimwikiCheckLinks`); otherwise the whole workspace.
function M.check_links(range, l1, l2)
local filter = type(range) == 'number' and range > 0
local cur = filter and vim.api.nvim_buf_get_name(0) or nil
exec('nuwiki.workspace.checkLinks', uri_args(), function(r)
if type(r) ~= 'table' or #r == 0 then
vim.notify('nuwiki: no broken links')
@@ -388,12 +413,20 @@ function M.check_links()
end
local items = {}
for _, b in ipairs(r) do
table.insert(items, {
filename = vim.uri_to_fname(b.uri),
lnum = (b.range and b.range.start and b.range.start.line or 0) + 1,
col = (b.range and b.range.start and b.range.start.character or 0) + 1,
text = (b.kind or '?') .. ': ' .. (b.message or ''),
})
local fname = vim.uri_to_fname(b.uri)
local lnum = (b.range and b.range.start and b.range.start.line or 0) + 1
if not filter or (fname == cur and lnum >= l1 and lnum <= l2) then
table.insert(items, {
filename = fname,
lnum = lnum,
col = (b.range and b.range.start and b.range.start.character or 0) + 1,
text = (b.kind or '?') .. ': ' .. (b.message or ''),
})
end
end
if #items == 0 then
vim.notify('nuwiki: no broken links')
return
end
vim.fn.setqflist({}, ' ', { title = 'nuwiki broken links', items = items })
vim.cmd('copen')
@@ -451,10 +484,14 @@ function M.tags_generate_links(tag)
exec('nuwiki.tags.generateLinks', { args })
end
function M.tags_rebuild()
exec('nuwiki.tags.rebuild', uri_args(), function(r)
function M.tags_rebuild(all)
local a = uri_args()
a[1].all = all and true or false
exec('nuwiki.tags.rebuild', a, function(r)
if r and r.pages then
vim.notify(string.format('nuwiki: re-indexed %d page(s)', r.pages))
local scope = (r.all and (r.wikis or 0) ~= 1)
and string.format(' across %d wikis', r.wikis or 0) or ''
vim.notify(string.format('nuwiki: re-indexed %d page(s)%s', r.pages, scope))
end
end)
end
@@ -1027,11 +1064,25 @@ function M.paste_url()
exec('nuwiki.link.pasteUrl', pos_args())
end
--- Vimwiki `:VimwikiNormalizeLink` — wrap the word at cursor as
--- `[[word]]` without following. The `<CR>` two-step does the same
--- thing under the hood; expose it as a standalone command + keymap
--- for users who want to bind it to `+` per vimwiki defaults.
function M.normalize_link()
-- Wrap the single-line `'<`/`'>` visual selection as `[[selection]]`.
local function wrap_visual_as_wikilink()
local sl, sc, el, ec = visual_selection_range()
if not (sl and sl == el) then return end
local line = vim.api.nvim_buf_get_lines(0, sl - 1, sl, false)[1] or ''
local sel = line:sub(sc + 1, ec + 1)
if sel == '' or sel:match('^%[%[.*%]%]$') then return end
local new = line:sub(1, sc) .. '[[' .. sel .. ']]' .. line:sub(ec + 2)
vim.api.nvim_buf_set_lines(0, sl - 1, sl, false, { new })
end
--- Vimwiki `:VimwikiNormalizeLink [1]` — wrap text as `[[…]]` without
--- following. With `visual` (upstream's `1` arg / the `x`-mode `+` mapping)
--- wrap the selection; otherwise the word at the cursor.
function M.normalize_link(visual)
if visual then
wrap_visual_as_wikilink()
return
end
if cursor_inside_wikilink() then return end
wrap_cword_as_wikilink()
end