feat: real range/bang/visual handling for the deferred command gaps
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:
+66
-15
@@ -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
|
||||
|
||||
@@ -174,7 +174,8 @@ function M.attach(bufnr, mappings)
|
||||
map('n', '<Tab>', link.next, { desc = 'nuwiki: next wikilink' }, bufnr)
|
||||
map('n', '<S-Tab>', link.prev, { desc = 'nuwiki: prev wikilink' }, bufnr)
|
||||
map('n', '+', cmd.normalize_link, { desc = 'nuwiki: wrap word as wikilink' }, bufnr)
|
||||
map('x', '+', cmd.normalize_link, { desc = 'nuwiki: wrap word as wikilink' }, bufnr)
|
||||
map('x', '+', function() cmd.normalize_link(true) end,
|
||||
{ desc = 'nuwiki: wrap selection as wikilink' }, bufnr)
|
||||
map('n', '<Leader>wn', cmd.wiki_goto_page, { desc = 'nuwiki: goto page' }, bufnr)
|
||||
map('n', '<Leader>wd', cmd.delete_file, { desc = 'nuwiki: delete page' }, bufnr)
|
||||
map('n', '<Leader>wr', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr)
|
||||
|
||||
Reference in New Issue
Block a user