fix(client): VimwikiColorize accepts a range (no more E481 on selection)
CI / cargo fmt --check (push) Successful in 31s
CI / cargo clippy (push) Successful in 24s
CI / cargo test (push) Successful in 39s
CI / editor keymaps (push) Successful in 1m31s

Selecting text and running :VimwikiColorize {color} raised
"E481: No range allowed" — Vim prepends '<,'> to the command when invoked
from a visual selection, but the command was defined without -range.

All four colorize command defs (Vim + Neovim, Vimwiki* + Nuwiki*) are now
-range and forward the range count to the handler. A ranged (visual)
invocation wraps the '< / '> selection; a bare invocation wraps the cword.
The x-mode <Leader>wc mapping passes the visual flag explicitly.

The Vim-branch colorize() gained selection support (it was cword-only),
matching the Neovim branch, so both clients wrap a visual selection
identically. visual_selection_range() (Lua) no longer gates on
vim.fn.visualmode() — the explicit visual flag now carries that intent, and
the marks check guards unset selections.

Tests: cmd.Colorize_range_wraps_selection (nvim) and
colorize.{command_wraps_cword,visual_wraps_selection,ranged_command_no_error}
(vim). nvim 268, vim 257+18, all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 15:37:09 +00:00
parent 279dacff7a
commit 2facb40265
6 changed files with 98 additions and 14 deletions
+28 -2
View File
@@ -855,10 +855,14 @@ function! nuwiki#commands#table_move_right() abort
\ }]
call s:exec('nuwiki.table.moveColumn', l:args)
endfunction
" `:VimwikiColorize {color}` — pure-VimL wrap as inline span tag,
" matching vimwiki's `<span style="color:%c">%s</span>` template.
" `:VimwikiColorize {color}` — wrap text in an inline colour span, matching
" vimwiki's `<span style="color:%c">%s</span>` template. Wraps the visual
" selection when invoked with a range (`:'<,'>VimwikiColorize` / the `x`-mode
" <Leader>wc mapping), otherwise the word under the cursor.
" a:1 = colour (empty → prompt); a:2 = range count (>0 → use the selection).
function! nuwiki#commands#colorize(...) abort
let l:color = a:0 >= 1 ? a:1 : ''
let l:visual = a:0 >= 2 && a:2 > 0
if l:color ==# ''
let l:color = input('Colour: ')
endif
@@ -867,6 +871,28 @@ function! nuwiki#commands#colorize(...) abort
endif
let l:open_tag = '<span style="color:' . l:color . '">'
let l:close_tag = '</span>'
if l:visual
let l:sp = getpos("'<")
let l:ep = getpos("'>")
if l:sp[1] > 0 && l:sp[1] == l:ep[1]
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
let l:new = strpart(l:line, 0, l:sc - 1)
\ . l:open_tag . strpart(l:line, l:sc - 1, l:ec - l:sc + 1) . l:close_tag
\ . strpart(l:line, l:ec)
call setline(l:sp[1], l:new)
return
endif
endif
" Multi-line or empty selection: fall through to the cword.
endif
let l:line = getline('.')
let l:col = col('.')
" Word boundaries around cursor (1-based).
+38
View File
@@ -477,6 +477,44 @@ call s:record(
\ 'ext=' . get(get(s:wl, 1, {}), 'ext', ''))
unlet g:nuwiki_wikis
" ===== Colorize (cword, visual selection, ranged command) =====
" The command is -nargs=1 -range: normal invocation wraps the cword; a visual
" range (`:'<,'>VimwikiColorize` / the x-mode <Leader>wc map) wraps the
" selection. Run these while still on the vimwiki buffer (the buffer-local
" command must be defined).
call s:set_buf(['word here'])
call cursor(1, 1)
silent VimwikiColorize red
call s:record(
\ getline(1) ==# '<span style="color:red">word</span> here' ? 1 : 0,
\ 'colorize.command_wraps_cword',
\ 'got ' . string(getline(1)))
" Visual selection → wraps the selection (visual flag passed explicitly).
call s:set_buf(['one two three'])
call cursor(1, 5)
call feedkeys("viw\<Esc>", 'tx')
call nuwiki#commands#colorize('blue', 2)
call s:record(
\ getline(1) ==# 'one <span style="color:blue">two</span> three' ? 1 : 0,
\ 'colorize.visual_wraps_selection',
\ 'got ' . string(getline(1)))
" Ranged command must not raise E481 and wraps the selection.
call s:set_buf(['alpha beta gamma'])
call cursor(1, 8)
call feedkeys("viw\<Esc>", 'tx')
let s:cz_err = ''
try
silent '<,'>VimwikiColorize green
catch
let s:cz_err = v:exception
endtry
call s:record(
\ s:cz_err ==# '' && getline(1) ==# 'alpha <span style="color:green">beta</span> gamma' ? 1 : 0,
\ 'colorize.ranged_command_no_error',
\ 'err=' . s:cz_err . ' got ' . string(getline(1)))
" ===== Follow-link resolves + opens in the current window (regression) =====
" follow_link_or_create() must resolve the definition itself and `:edit` the
" target in the CURRENT window — including a page that does not exist yet
+12
View File
@@ -761,6 +761,18 @@ vim.defer_fn(function()
error('visual colorize did not wrap selection: ' .. got)
end
end)
-- Invoking the command with a range (`:'<,'>VimwikiColorize`, as Vim builds
-- it when you select then type the command) must not error (-range) and
-- wraps the selection.
tobj_case('cmd.Colorize_range_wraps_selection', function()
set_buf({ 'pick this word' })
vim.cmd('normal! 0wviw\27') -- select 'this'
vim.cmd("'<,'>VimwikiColorize orange")
local got = vim.api.nvim_get_current_line()
if got ~= 'pick <span style="color:orange">this</span> word' then
error('ranged command did not wrap selection: ' .. got)
end
end)
-- ===== Wiki picker (config-driven, no LSP) =====
-- The picker must build its list from config so it works from any buffer
+11 -5
View File
@@ -129,11 +129,17 @@ fix site.
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`.
`colorize(color, visual)` now takes an explicit `visual` flag; the command and
normal mapping always wrap the cword. Third bug: the commands were not
`-range`, so selecting text and running `:VimwikiColorize` (which Vim turns
into `:'<,'>VimwikiColorize`) raised `E481: No range allowed`. All four
command defs are now `-range` and pass the range to the handler — a ranged
(visual) invocation wraps the `'<`/`'>` selection on both clients; the
`x`-mode `<Leader>wc` mapping passes the visual flag directly. Covered by
`cmd.VimwikiColorize_uses_arg`, `cmd.Colorize_ignores_stale_visual_selection`,
`colorize.visual_wraps_selection`, `cmd.Colorize_range_wraps_selection` in
`test-keymaps.lua` and `colorize.{command_wraps_cword,visual_wraps_selection,
ranged_command_no_error}` in `test-keymaps-vim.vim`.
- [ ] **`VimwikiTableMoveColumn{Left,Right}` dispatch to different backing names
per client** — Vim branch → `table_move_left/right`; Neovim branch →
`table_move_column_left/right`. Harmless today (each name exists in its own
+5 -5
View File
@@ -103,7 +103,7 @@ if !has('nvim')
command! -buffer -nargs=1 VimwikiTable call nuwiki#commands#table_insert()
command! -buffer VimwikiTableMoveColumnLeft call nuwiki#commands#table_move_left()
command! -buffer VimwikiTableMoveColumnRight call nuwiki#commands#table_move_right()
command! -buffer -nargs=1 VimwikiColorize call nuwiki#commands#colorize(<q-args>)
command! -buffer -nargs=1 -range VimwikiColorize call nuwiki#commands#colorize(<q-args>, <range>)
command! -buffer VimwikiPasteLink call nuwiki#commands#paste_link()
command! -buffer VimwikiPasteUrl call nuwiki#commands#paste_url()
command! -buffer VimwikiCatUrl call nuwiki#commands#cat_url()
@@ -174,7 +174,7 @@ if !has('nvim')
command! -buffer -nargs=1 NuwikiTable call nuwiki#commands#table_insert()
command! -buffer NuwikiTableMoveColumnLeft call nuwiki#commands#table_move_left()
command! -buffer NuwikiTableMoveColumnRight call nuwiki#commands#table_move_right()
command! -buffer -nargs=1 NuwikiColorize call nuwiki#commands#colorize(<q-args>)
command! -buffer -nargs=1 -range NuwikiColorize call nuwiki#commands#colorize(<q-args>, <range>)
command! -buffer NuwikiPasteLink call nuwiki#commands#paste_link()
command! -buffer NuwikiPasteUrl call nuwiki#commands#paste_url()
command! -buffer NuwikiCatUrl call nuwiki#commands#cat_url()
@@ -216,7 +216,7 @@ if !has('nvim')
nnoremap <silent><buffer> <Leader>wd :call nuwiki#commands#delete_file()<CR>
nnoremap <silent><buffer> <Leader>wr :call nuwiki#commands#rename_file()<CR>
nnoremap <silent><buffer> <Leader>wc :call nuwiki#commands#colorize('')<CR>
xnoremap <silent><buffer> <Leader>wc :<C-u>call nuwiki#commands#colorize('')<CR>
xnoremap <silent><buffer> <Leader>wc :<C-u>call nuwiki#commands#colorize('', 2)<CR>
endif
" Diary nav
@@ -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 VimwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left()
command! -buffer VimwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right()
command! -buffer -nargs=1 VimwikiColorize lua require('nuwiki.commands').colorize(<q-args>)
command! -buffer -nargs=1 -range VimwikiColorize lua require('nuwiki.commands').colorize(<q-args>, <range> > 0)
command! -buffer VimwikiPasteLink lua require('nuwiki.commands').paste_link()
command! -buffer VimwikiPasteUrl lua require('nuwiki.commands').paste_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 NuwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left()
command! -buffer NuwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right()
command! -buffer -nargs=1 NuwikiColorize lua require('nuwiki.commands').colorize(<q-args>)
command! -buffer -nargs=1 -range NuwikiColorize lua require('nuwiki.commands').colorize(<q-args>, <range> > 0)
command! -buffer NuwikiPasteLink lua require('nuwiki.commands').paste_link()
command! -buffer NuwikiPasteUrl lua require('nuwiki.commands').paste_url()
command! -buffer NuwikiCatUrl lua require('nuwiki.commands').cat_url()
+4 -2
View File
@@ -951,9 +951,11 @@ end
--- the template is `<span style="color:%c">%s</span>` with `%c`
--- replaced by the user-supplied colour and `%s` by the wrapped text.
--- Pure-Lua; no server roundtrip needed.
-- Read the last visual selection from the `'<`/`'>` marks. Only meaningful
-- when the caller knows it's acting on a selection (colorize's `visual` flag);
-- we deliberately don't gate on vim.fn.visualmode() here since that reflects
-- session state, not whether the marks are the selection we want right now.
local function visual_selection_range()
local mode = vim.fn.visualmode()
if mode == '' then return nil end
local s = vim.api.nvim_buf_get_mark(0, '<')
local e = vim.api.nvim_buf_get_mark(0, '>')
if s[1] == 0 or e[1] == 0 then return nil end