fix(client): VimwikiColorize passes its {color} arg on Neovim + visual fix
CI / cargo fmt --check (push) Successful in 18s
CI / cargo clippy (push) Successful in 27s
CI / cargo test (push) Successful in 29s
CI / editor keymaps (push) Successful in 1m23s

The Neovim command defs for :VimwikiColorize / :NuwikiColorize were
-nargs=1 but called colorize() without <q-args>, so the colour name was
silently dropped and the user was prompted instead (the Vim branch passed
it correctly). Pass <q-args>.

While verifying, found a second bug in the Lua colorize(): it inferred
visual-vs-normal from vim.fn.visualmode(), which returns the *last* visual
mode used in the session along with stale '< / '> marks. So running
:VimwikiColorize in normal mode after any earlier visual selection wrapped
that leftover selection instead of the word under the cursor (produced an
empty, misplaced span). colorize(color, visual) now takes an explicit
visual flag, passed only by the x-mode <Leader>wc mapping; the command and
the normal-mode mapping always wrap the cword.

Tests: cmd.VimwikiColorize_uses_arg, cmd.NuwikiColorize_uses_arg,
cmd.Colorize_ignores_stale_visual_selection, colorize.visual_wraps_selection
in test-keymaps.lua. nvim suite 267, vim suite 254+18, all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 15:18:31 +00:00
parent 39692ba99f
commit 66bfd61a63
5 changed files with 85 additions and 20 deletions
+46
View File
@@ -716,6 +716,52 @@ vim.defer_fn(function()
-- a populated diary, but at least confirm the maps fire without -- a populated diary, but at least confirm the maps fire without
-- error. -- error.
-- ===== Colorize commands pass their {color} arg (regression) =====
-- :VimwikiColorize / :NuwikiColorize are -nargs=1; the Neovim defs used to
-- call colorize() without <q-args>, silently dropping the colour and
-- prompting instead. Run the real commands and assert the colour lands.
tobj_case('cmd.VimwikiColorize_uses_arg', function()
set_buf({ 'word here' })
vim.api.nvim_win_set_cursor(0, { 1, 0 })
vim.cmd('VimwikiColorize red')
local got = vim.api.nvim_get_current_line()
if got ~= '<span style="color:red">word</span> here' then
error('VimwikiColorize dropped its arg: ' .. got)
end
end)
tobj_case('cmd.NuwikiColorize_uses_arg', function()
set_buf({ 'second word' })
vim.api.nvim_win_set_cursor(0, { 1, 0 })
vim.cmd('NuwikiColorize #00ff00')
local got = vim.api.nvim_get_current_line()
if got ~= '<span style="color:#00ff00">second</span> word' then
error('NuwikiColorize dropped its arg: ' .. got)
end
end)
-- Establish a stale visual selection, then confirm the normal-mode command
-- still wraps the cword (not the leftover selection) — the visualmode()
-- heuristic regression.
tobj_case('cmd.Colorize_ignores_stale_visual_selection', function()
set_buf({ 'alpha beta gamma' })
vim.cmd('normal! 0vee\27') -- select 'alpha beta', leave marks behind
vim.api.nvim_win_set_cursor(0, { 1, 11 }) -- on 'gamma'
vim.cmd('VimwikiColorize blue')
local got = vim.api.nvim_get_current_line()
if got ~= 'alpha beta <span style="color:blue">gamma</span>' then
error('command wrapped stale selection instead of cword: ' .. got)
end
end)
-- The visual mapping path (explicit visual=true) wraps the selection.
tobj_case('colorize.visual_wraps_selection', function()
set_buf({ 'one two three' })
vim.cmd('normal! 0wviw\27') -- visually select 'two', marks set on exit
require('nuwiki.commands').colorize('red', true)
local got = vim.api.nvim_get_current_line()
if got ~= 'one <span style="color:red">two</span> three' then
error('visual colorize did not wrap selection: ' .. got)
end
end)
-- ===== Wiki picker (config-driven, no LSP) ===== -- ===== Wiki picker (config-driven, no LSP) =====
-- The picker must build its list from config so it works from any buffer -- The picker must build its list from config so it works from any buffer
-- before the server attaches. -- before the server attaches.
+13 -5
View File
@@ -121,11 +121,19 @@ fix site.
- [ ] `:VimwikiSearch` / `VWS` uses `lvimgrep`, not vimwiki's search engine. - [ ] `:VimwikiSearch` / `VWS` uses `lvimgrep`, not vimwiki's search engine.
- [ ] `VimwikiIndex` family is buffer-local in nuwiki (upstream defines globally - [ ] `VimwikiIndex` family is buffer-local in nuwiki (upstream defines globally
in `plugin/`); only `:…UISelect` is a global entry point. in `plugin/`); only `:…UISelect` is a global entry point.
- [ ] **`VimwikiColorize` / `NuwikiColorize` drop their argument in the Neovim - [x] **`VimwikiColorize` / `NuwikiColorize` drop their argument in the Neovim
branch** — both are `-nargs=1`, but the Neovim defs call `colorize()` with no branch** — both are `-nargs=1`, but the Neovim defs called `colorize()` with no
`<q-args>` (`ftplugin/vimwiki.vim:445,517`) while the Vim branch passes it `<q-args>` (`ftplugin/vimwiki.vim:445,517`) while the Vim branch passes it. The
(`:106,177`). The color name is silently ignored under Neovim (real bug, not color name was silently ignored under Neovim (real bug, not just parity).
just parity). _Fix:_ `ftplugin/vimwiki.vim` Neovim branch. _Fix:_ Neovim command defs now pass `<q-args>`. While verifying, found a second
bug: `colorize()` inferred visual-vs-normal from `vim.fn.visualmode()`, which
returns the *last* session visual mode with stale `'<`/`'>` marks — so the
normal-mode command wrapped a leftover selection instead of the cword.
`colorize(color, visual)` now takes an explicit `visual` flag passed only by
the `x`-mode `<Leader>wc` mapping (`lua/nuwiki/keymaps.lua`); the command and
normal mapping always wrap the cword. Covered by `cmd.VimwikiColorize_uses_arg`,
`cmd.Colorize_ignores_stale_visual_selection`, and
`colorize.visual_wraps_selection` in `development/tests/test-keymaps.lua`.
- [ ] **`VimwikiTableMoveColumn{Left,Right}` dispatch to different backing names - [ ] **`VimwikiTableMoveColumn{Left,Right}` dispatch to different backing names
per client** — Vim branch → `table_move_left/right`; Neovim branch → per client** — Vim branch → `table_move_left/right`; Neovim branch →
`table_move_column_left/right`. Harmless today (each name exists in its own `table_move_column_left/right`. Harmless today (each name exists in its own
+2 -2
View File
@@ -442,7 +442,7 @@ command! -buffer -nargs=? VimwikiGenerateTags lua require('nuwiki.commands'
command! -buffer -nargs=1 VimwikiTable lua require('nuwiki.commands').table_insert() command! -buffer -nargs=1 VimwikiTable lua require('nuwiki.commands').table_insert()
command! -buffer VimwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left() command! -buffer VimwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left()
command! -buffer VimwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right() command! -buffer VimwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right()
command! -buffer -nargs=1 VimwikiColorize lua require('nuwiki.commands').colorize() command! -buffer -nargs=1 VimwikiColorize lua require('nuwiki.commands').colorize(<q-args>)
command! -buffer VimwikiPasteLink lua require('nuwiki.commands').paste_link() command! -buffer VimwikiPasteLink lua require('nuwiki.commands').paste_link()
command! -buffer VimwikiPasteUrl lua require('nuwiki.commands').paste_url() command! -buffer VimwikiPasteUrl lua require('nuwiki.commands').paste_url()
command! -buffer VimwikiCatUrl lua require('nuwiki.commands').cat_url() command! -buffer VimwikiCatUrl lua require('nuwiki.commands').cat_url()
@@ -514,7 +514,7 @@ command! -buffer NuwikiTableAlign lua require('nuwiki.command
command! -buffer -nargs=1 NuwikiTable lua require('nuwiki.commands').table_insert() command! -buffer -nargs=1 NuwikiTable lua require('nuwiki.commands').table_insert()
command! -buffer NuwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left() command! -buffer NuwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left()
command! -buffer NuwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right() command! -buffer NuwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right()
command! -buffer -nargs=1 NuwikiColorize lua require('nuwiki.commands').colorize() command! -buffer -nargs=1 NuwikiColorize lua require('nuwiki.commands').colorize(<q-args>)
command! -buffer NuwikiPasteLink lua require('nuwiki.commands').paste_link() command! -buffer NuwikiPasteLink lua require('nuwiki.commands').paste_link()
command! -buffer NuwikiPasteUrl lua require('nuwiki.commands').paste_url() command! -buffer NuwikiPasteUrl lua require('nuwiki.commands').paste_url()
command! -buffer NuwikiCatUrl lua require('nuwiki.commands').cat_url() command! -buffer NuwikiCatUrl lua require('nuwiki.commands').cat_url()
+23 -12
View File
@@ -960,27 +960,38 @@ local function visual_selection_range()
return s[1], s[2], e[1], e[2] return s[1], s[2], e[1], e[2]
end end
function M.colorize(color) -- colorize(color, visual): wrap text in an inline colour span.
--
-- `visual` MUST be passed only by the visual-mode mapping — it means "act on
-- the `'<`/`'>` selection". We can't infer it from `vim.fn.visualmode()`,
-- which returns the *last* visual mode used in the session (with stale marks),
-- so the normal-mode command / mapping would otherwise wrap whatever was last
-- selected instead of the word under the cursor.
function M.colorize(color, visual)
if not color or color == '' then if not color or color == '' then
color = vim.fn.input('Colour: ') color = vim.fn.input('Colour: ')
if color == '' then return end if color == '' then return end
end end
local sl, sc, el, ec = visual_selection_range()
local row = vim.api.nvim_win_get_cursor(0)[1] local row = vim.api.nvim_win_get_cursor(0)[1]
local line = vim.api.nvim_get_current_line() local line = vim.api.nvim_get_current_line()
local open_tag = string.format('<span style="color:%s">', color) local open_tag = string.format('<span style="color:%s">', color)
local close_tag = '</span>' local close_tag = '</span>'
if sl and sl == el then if visual then
-- Single-line visual selection. local sl, sc, el, ec = visual_selection_range()
local new_line = line:sub(1, sc) if sl and sl == el then
.. open_tag -- Single-line visual selection.
.. line:sub(sc + 1, ec + 1) local sline = vim.api.nvim_buf_get_lines(0, sl - 1, sl, false)[1] or ''
.. close_tag local new_line = sline:sub(1, sc)
.. line:sub(ec + 2) .. open_tag
vim.api.nvim_buf_set_lines(0, sl - 1, sl, false, { new_line }) .. sline:sub(sc + 1, ec + 1)
return .. close_tag
.. sline:sub(ec + 2)
vim.api.nvim_buf_set_lines(0, sl - 1, sl, false, { new_line })
return
end
-- Multi-line or no usable selection: fall through to the cword.
end end
-- Fall back to the cword. -- Wrap the cword.
local cword = vim.fn.expand('<cword>') local cword = vim.fn.expand('<cword>')
if cword == '' then return end if cword == '' then return end
local col = vim.api.nvim_win_get_cursor(0)[2] local col = vim.api.nvim_win_get_cursor(0)[2]
+1 -1
View File
@@ -176,7 +176,7 @@ function M.attach(bufnr, mappings)
map('n', '<Leader>wr', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr) map('n', '<Leader>wr', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr)
map('n', '<Leader>wc', function() cmd.colorize() end, map('n', '<Leader>wc', function() cmd.colorize() end,
{ desc = 'nuwiki: wrap word in colour span' }, bufnr) { desc = 'nuwiki: wrap word in colour span' }, bufnr)
map('x', '<Leader>wc', function() cmd.colorize() end, map('x', '<Leader>wc', function() cmd.colorize(nil, true) end,
{ desc = 'nuwiki: wrap selection in colour span' }, bufnr) { desc = 'nuwiki: wrap selection in colour span' }, bufnr)
end end