diff --git a/autoload/nuwiki/commands.vim b/autoload/nuwiki/commands.vim
index 881f42d..ff0cfaa 100644
--- a/autoload/nuwiki/commands.vim
+++ b/autoload/nuwiki/commands.vim
@@ -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 `%s` template.
+" `:VimwikiColorize {color}` — wrap text in an inline colour span, matching
+" vimwiki's `%s` template. Wraps the visual
+" selection when invoked with a range (`:'<,'>VimwikiColorize` / the `x`-mode
+" 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 = ''
let l:close_tag = ''
+
+ 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).
diff --git a/development/tests/test-keymaps-vim.vim b/development/tests/test-keymaps-vim.vim
index 5f63144..18ba507 100644
--- a/development/tests/test-keymaps-vim.vim
+++ b/development/tests/test-keymaps-vim.vim
@@ -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 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) ==# 'word 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\", 'tx')
+call nuwiki#commands#colorize('blue', 2)
+call s:record(
+ \ getline(1) ==# 'one two 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\", '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 beta 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
diff --git a/development/tests/test-keymaps.lua b/development/tests/test-keymaps.lua
index af3fbce..0aaca16 100644
--- a/development/tests/test-keymaps.lua
+++ b/development/tests/test-keymaps.lua
@@ -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 this 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
diff --git a/development/vimwiki-gap.md b/development/vimwiki-gap.md
index 76245d5..a2e27ec 100644
--- a/development/vimwiki-gap.md
+++ b/development/vimwiki-gap.md
@@ -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 `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 `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
diff --git a/ftplugin/vimwiki.vim b/ftplugin/vimwiki.vim
index f483a19..ec79ad5 100644
--- a/ftplugin/vimwiki.vim
+++ b/ftplugin/vimwiki.vim
@@ -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()
+ command! -buffer -nargs=1 -range VimwikiColorize call nuwiki#commands#colorize(, )
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()
+ command! -buffer -nargs=1 -range NuwikiColorize call nuwiki#commands#colorize(, )
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 wd :call nuwiki#commands#delete_file()
nnoremap wr :call nuwiki#commands#rename_file()
nnoremap wc :call nuwiki#commands#colorize('')
- xnoremap wc :call nuwiki#commands#colorize('')
+ xnoremap wc :call nuwiki#commands#colorize('', 2)
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()
+command! -buffer -nargs=1 -range VimwikiColorize lua require('nuwiki.commands').colorize(, > 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()
+command! -buffer -nargs=1 -range NuwikiColorize lua require('nuwiki.commands').colorize(, > 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()
diff --git a/lua/nuwiki/commands.lua b/lua/nuwiki/commands.lua
index 1a159c4..233a292 100644
--- a/lua/nuwiki/commands.lua
+++ b/lua/nuwiki/commands.lua
@@ -951,9 +951,11 @@ end
--- the template is `%s` 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