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
+77 -8
View File
@@ -529,6 +529,26 @@ endfunction
function! nuwiki#commands#toggle_list_item() abort
call s:exec_pos('nuwiki.list.toggleCheckbox')
endfunction
" Apply a per-line action across [l1, l2] (visual-range commands). Each call
" sends its own request for its line; the server's edits are version-less and
" single-line, so applying them as they return never conflicts.
function! s:over_range(l1, l2, Fn) abort
let l:save = getcurpos()
for l:ln in range(a:l1, a:l2)
call cursor(l:ln, 1)
call a:Fn()
endfor
call setpos('.', l:save)
endfunction
function! nuwiki#commands#toggle_list_item_range(l1, l2) abort
call s:over_range(a:l1, a:l2, function('nuwiki#commands#toggle_list_item'))
endfunction
function! nuwiki#commands#list_cycle_symbol_range(direction, l1, l2) abort
call s:over_range(a:l1, a:l2, { -> nuwiki#commands#list_cycle_symbol(a:direction) })
endfunction
function! nuwiki#commands#cycle_list_item() abort
call s:exec_pos('nuwiki.list.cycleCheckbox')
endfunction
@@ -573,9 +593,12 @@ endfunction
function! nuwiki#commands#links_generate() abort
call s:exec('nuwiki.links.generate', [{'uri': s:buf_uri()}])
endfunction
function! nuwiki#commands#check_links() abort
function! nuwiki#commands#check_links(...) abort
" a:1 = range count; when >0, restrict the report to the current buffer's
" [a:2, a:3] lines (`:'<,'>VimwikiCheckLinks`). No range → whole workspace.
let l:rng = (a:0 >= 3 && a:1 > 0) ? [a:2, a:3] : []
call s:exec('nuwiki.workspace.checkLinks', [{ 'uri': s:buf_uri() }],
\ {n -> s:results_to_qf(n, 'broken links')})
\ {n -> s:results_to_qf(n, 'broken links', l:rng)})
endfunction
function! nuwiki#commands#find_orphans() abort
@@ -583,7 +606,10 @@ function! nuwiki#commands#find_orphans() abort
\ {n -> s:results_to_qf(n, 'orphans')})
endfunction
function! s:results_to_qf(notification, title) abort
function! s:results_to_qf(notification, title, ...) abort
" Optional a:1 = [line1, line2]: keep only current-buffer rows in that range.
let l:rng = a:0 >= 1 ? a:1 : []
let l:cur = empty(l:rng) ? '' : resolve(expand('%:p'))
let l:rows = get(get(a:notification, 'response', {}), 'result', [])
if type(l:rows) != type([]) || empty(l:rows)
echom 'nuwiki: no ' . a:title
@@ -598,9 +624,18 @@ function! s:results_to_qf(notification, title) abort
let l:lnum = l:row['range']['start']['line'] + 1
let l:col = l:row['range']['start']['character'] + 1
endif
if !empty(l:rng)
if resolve(l:path) !=# l:cur || l:lnum < l:rng[0] || l:lnum > l:rng[1]
continue
endif
endif
let l:text = get(l:row, 'message', get(l:row, 'name', ''))
call add(l:items, { 'filename': l:path, 'lnum': l:lnum, 'col': l:col, 'text': l:text })
endfor
if empty(l:items)
echom 'nuwiki: no ' . a:title
return
endif
call setqflist([], ' ', { 'title': 'nuwiki ' . a:title, 'items': l:items })
copen
endfunction
@@ -624,8 +659,11 @@ function! nuwiki#commands#tags_generate_links(tag) abort
call s:exec('nuwiki.tags.generateLinks', [l:args])
endfunction
function! nuwiki#commands#tags_rebuild() abort
call s:exec('nuwiki.tags.rebuild', [{ 'uri': s:buf_uri() }])
function! nuwiki#commands#tags_rebuild(...) abort
" a:1 (the command's <bang>) → re-index every configured wiki.
let l:all = a:0 >= 1 && a:1
call s:exec('nuwiki.tags.rebuild',
\ [{ 'uri': s:buf_uri(), 'all': l:all ? v:true : v:false }])
endfunction
" ===== HTML export =====
@@ -1205,15 +1243,46 @@ function! nuwiki#commands#smart_shift_tab() abort
return "\<Cmd>call nuwiki#commands#_table_jump_to_cell(" . line('.') . ", " . l:target_idx . ")\<CR>"
endfunction
" `:VimwikiNormalizeLink` — wrap the word at cursor as `[[word]]`
" without following. Pure-VimL.
function! nuwiki#commands#normalize_link() abort
" `:VimwikiNormalizeLink [1]` — wrap text as `[[…]]` without following.
" With a:1 (upstream's visual `1` arg, also the `x`-mode mapping) wrap the
" `'<`/`'>` selection; otherwise the word under the cursor. Pure-VimL.
function! nuwiki#commands#normalize_link(...) abort
let l:visual = a:0 >= 1 && a:1
if l:visual
call s:wrap_visual_as_wikilink()
return
endif
if s:cursor_inside_wikilink()
return
endif
call s:wrap_cword_as_wikilink()
endfunction
" Wrap the single-line `'<`/`'>` visual selection as `[[selection]]`.
function! s:wrap_visual_as_wikilink() abort
let l:sp = getpos("'<")
let l:ep = getpos("'>")
if l:sp[1] <= 0 || l:sp[1] != l:ep[1]
return
endif
let l:line = getline(l:sp[1])
let l:sc = l:sp[2]
let l:ec = l:ep[2]
if l:ec > strlen(l:line)
let l:ec = strlen(l:line)
endif
if l:ec < l:sc
return
endif
let l:sel = strpart(l:line, l:sc - 1, l:ec - l:sc + 1)
" Don't double-wrap if the selection is already a bracketed link.
if l:sel =~# '^\[\[.*\]\]$'
return
endif
let l:new = strpart(l:line, 0, l:sc - 1) . '[[' . l:sel . ']]' . strpart(l:line, l:ec)
call setline(l:sp[1], l:new)
endfunction
function! s:echo_url_from(response, ...) abort
let l:result = get(get(a:response, 'response', {}), 'result', '')
if type(l:result) == type('') && l:result !=# ''