diff --git a/autoload/nuwiki/colors.vim b/autoload/nuwiki/colors.vim
new file mode 100644
index 0000000..fda67e3
--- /dev/null
+++ b/autoload/nuwiki/colors.vim
@@ -0,0 +1,61 @@
+" autoload/nuwiki/colors.vim — conceal colour spans down to their text.
+"
+" `:VimwikiColorize NAME` wraps text in `TEXT`.
+" This hides the two tags (conceal) and paints TEXT in NAME, so the buffer
+" shows just the coloured word — matching how the HTML export renders it.
+"
+" Pure syntax + `:highlight`, no LSP needed, so it works the same in Vim,
+" coc.nvim, and Neovim. New colours appear as the user runs :VimwikiColorize,
+" so the ftplugin re-runs refresh() on text changes; we track which colours
+" already have a syntax group in b:nuwiki_color_seen to stay idempotent.
+
+" Scan the buffer for `` and define a concealing,
+" colouring syntax region for each colour not yet seen.
+function! nuwiki#colors#refresh() abort
+ if !exists('b:nuwiki_color_seen')
+ let b:nuwiki_color_seen = {}
+ endif
+ let l:n = line('$')
+ let l:lnum = 1
+ while l:lnum <= l:n
+ let l:line = getline(l:lnum)
+ if stridx(l:line, '', l:pos)
+ if l:mp[1] < 0
+ break
+ endif
+ let l:pos = l:mp[2]
+ call s:define(matchstr(l:mp[0], 'color:\zs[^"]\{-}\ze">'))
+ endwhile
+ endif
+ let l:lnum += 1
+ endwhile
+endfunction
+
+" Define (once per colour) a region that conceals `` / `` and
+" paints the wrapped text in that colour.
+function! s:define(color) abort
+ if a:color ==# '' || has_key(b:nuwiki_color_seen, a:color)
+ return
+ endif
+ let b:nuwiki_color_seen[a:color] = 1
+ let l:grp = 'nuwikiColorSpan_' . substitute(a:color, '[^A-Za-z0-9]', '_', 'g')
+ let l:esc = escape(a:color, '/\.*$^~[]')
+ " Clear first so re-runs / load-order never stack duplicate regions.
+ silent! execute 'syntax clear ' . l:grp
+ " `concealends` hides the start/end matches (the tags); the region body
+ " (the text) keeps the l:grp highlight. `oneline` since spans don't wrap.
+ execute 'syntax region ' . l:grp
+ \ . ' matchgroup=Conceal'
+ \ . ' start=//'
+ \ . ' end=##'
+ \ . ' oneline keepend concealends'
+ " guifg works for names and #hex under 'termguicolors'. ctermfg only accepts
+ " named colours, so try it for non-hex and swallow E254 for unknown names.
+ execute 'highlight default ' . l:grp . ' guifg=' . a:color
+ if a:color !~# '^#'
+ silent! execute 'highlight ' . l:grp . ' ctermfg=' . a:color
+ endif
+endfunction
diff --git a/autoload/nuwiki/commands.vim b/autoload/nuwiki/commands.vim
index ff0cfaa..b19d535 100644
--- a/autoload/nuwiki/commands.vim
+++ b/autoload/nuwiki/commands.vim
@@ -887,6 +887,7 @@ function! nuwiki#commands#colorize(...) abort
\ . 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)
+ call nuwiki#colors#refresh()
return
endif
endif
@@ -912,6 +913,7 @@ function! nuwiki#commands#colorize(...) abort
\ . l:open_tag . l:word . l:close_tag
\ . strpart(l:line, l:right - 1)
call setline('.', l:new)
+ call nuwiki#colors#refresh()
endfunction
" Cluster C — link helpers.
diff --git a/development/_common.sh b/development/_common.sh
index 1da5d49..1e297e7 100644
--- a/development/_common.sh
+++ b/development/_common.sh
@@ -97,7 +97,9 @@ super^script^ and sub,,script,,, and inline math $e^{i\pi} + 1 = 0$.
Put the cursor on a word and run `:VimwikiColorize red` (or press
`wc` and type a colour); or visually select a phrase and press
-`wc`. Each wraps the target in `…`.
+`wc`. Each wraps the target in `…` —
+the tags are concealed, so you see just the word painted in that colour
+(move the cursor onto it to reveal the raw markup).
- colorize this important word
- now select and colour this whole phrase
diff --git a/development/tests/test-keymaps-vim.vim b/development/tests/test-keymaps-vim.vim
index 18ba507..ef9baeb 100644
--- a/development/tests/test-keymaps-vim.vim
+++ b/development/tests/test-keymaps-vim.vim
@@ -515,6 +515,36 @@ call s:record(
\ 'colorize.ranged_command_no_error',
\ 'err=' . s:cz_err . ' got ' . string(getline(1)))
+" Colour spans are concealed down to their text. Put the span on a non-cursor
+" line so concealcursor (=c) doesn't reveal it, then refresh + inspect.
+call s:set_buf(['plain line', 'word tail'])
+call cursor(1, 1)
+call nuwiki#colors#refresh()
+let s:cz_l = getline(2)
+let s:cz_open = synconcealed(2, 1)[0]
+let s:cz_word = synconcealed(2, stridx(s:cz_l, 'word') + 1)
+let s:cz_close = synconcealed(2, stridx(s:cz_l, '') + 1)[0]
+call s:record(
+ \ s:cz_open == 1 && s:cz_close == 1 && s:cz_word[0] == 0
+ \ && synIDattr(synID(2, stridx(s:cz_l, 'word') + 1, 1), 'name') ==# 'nuwikiColorSpan_red' ? 1 : 0,
+ \ 'colorize.conceal_hides_tags_shows_text',
+ \ 'open=' . s:cz_open . ' close=' . s:cz_close . ' word=' . s:cz_word[0]
+ \ . ' grp=' . synIDattr(synID(2, stridx(s:cz_l, 'word') + 1, 1), 'name'))
+
+" Running the command conceals the new span immediately (colorize refreshes;
+" no manual step). Move the cursor off the line so concealcursor reveals nothing.
+call s:set_buf(['target word', 'other line'])
+call cursor(1, 1)
+silent VimwikiColorize teal
+call cursor(2, 1)
+let s:cz_l2 = getline(1)
+call s:record(
+ \ synconcealed(1, 1)[0] == 1
+ \ && synIDattr(synID(1, stridx(s:cz_l2, 'target') + 1, 1), 'name') ==# 'nuwikiColorSpan_teal' ? 1 : 0,
+ \ 'colorize.command_conceals_immediately',
+ \ 'concealed=' . synconcealed(1, 1)[0]
+ \ . ' grp=' . synIDattr(synID(1, stridx(s:cz_l2, 'target') + 1, 1), 'name'))
+
" ===== 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 0aaca16..153e29c 100644
--- a/development/tests/test-keymaps.lua
+++ b/development/tests/test-keymaps.lua
@@ -773,6 +773,22 @@ vim.defer_fn(function()
error('ranged command did not wrap selection: ' .. got)
end
end)
+ -- Colour spans are concealed down to their coloured text. Put the span on a
+ -- non-cursor line so concealcursor (=c) doesn't reveal it, then refresh.
+ tobj_case('colorize.conceal_hides_tags_shows_text', function()
+ set_buf({ 'plain line', 'word tail' })
+ vim.api.nvim_win_set_cursor(0, { 1, 0 })
+ vim.fn['nuwiki#colors#refresh']()
+ local l = vim.fn.getline(2)
+ local wcol = (l:find('word')) -- 1-based
+ local open = vim.fn.synconcealed(2, 1)[1]
+ local close = vim.fn.synconcealed(2, (l:find('')))[1]
+ local word = vim.fn.synconcealed(2, wcol)[1]
+ local grp = vim.fn.synIDattr(vim.fn.synID(2, wcol, 1), 'name')
+ if not (open == 1 and close == 1 and word == 0 and grp == 'nuwikiColorSpan_red') then
+ error(string.format('open=%s close=%s word=%s grp=%s', open, close, word, grp))
+ 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 a2e27ec..26b2bb5 100644
--- a/development/vimwiki-gap.md
+++ b/development/vimwiki-gap.md
@@ -140,6 +140,18 @@ fix site.
`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`.
+- [x] **Colour spans aren't concealed** — a colorized word shows the literal
+ `…` markup instead of just the coloured text.
+ _Fix:_ `autoload/nuwiki/colors.vim` `nuwiki#colors#refresh()` scans the buffer
+ and defines, per colour, a syntax region that conceals the ``/``
+ tags (`concealends`) and paints the wrapped text in the span's *actual* colour
+ (`guifg`/`ctermfg`). Wired from the syntax file (initial + on `:colorscheme`
+ reload), an `ftplugin` `TextChanged` autocmd, and directly from `colorize()`
+ for immediate effect. The LSP `@vimwikiColor` token no longer links to
+ `Constant` so it doesn't override the real colour on the concealed text in
+ Neovim. Works in Vim, coc, and Neovim (pure syntax + `:highlight`, no LSP
+ needed). Covered by `colorize.conceal_hides_tags_shows_text` (both harnesses)
+ and `colorize.command_conceals_immediately` (`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 ec79ad5..79f152f 100644
--- a/ftplugin/vimwiki.vim
+++ b/ftplugin/vimwiki.vim
@@ -27,6 +27,16 @@ setlocal concealcursor=c
let b:undo_ftplugin = 'setlocal commentstring< comments< formatoptions< suffixesadd< iskeyword< conceallevel< concealcursor<'
+" Conceal `:VimwikiColorize` spans down to their coloured text. New colours
+" appear as the user colorizes, so refresh the syntax groups on edits (and now,
+" for any spans already in the buffer). Both clients — it's pure syntax.
+augroup NuwikiColorConceal
+ autocmd! *
+ autocmd TextChanged,TextChangedI,InsertLeave call nuwiki#colors#refresh()
+augroup END
+call nuwiki#colors#refresh()
+let b:undo_ftplugin .= ' | execute "autocmd! NuwikiColorConceal * "'
+
if !has('nvim')
" ===== Plain Vim path =====
"
diff --git a/lua/nuwiki/commands.lua b/lua/nuwiki/commands.lua
index 233a292..14c3eb5 100644
--- a/lua/nuwiki/commands.lua
+++ b/lua/nuwiki/commands.lua
@@ -989,6 +989,7 @@ function M.colorize(color, visual)
.. close_tag
.. sline:sub(ec + 2)
vim.api.nvim_buf_set_lines(0, sl - 1, sl, false, { new_line })
+ vim.fn['nuwiki#colors#refresh']()
return
end
-- Multi-line or no usable selection: fall through to the cword.
@@ -1013,6 +1014,7 @@ function M.colorize(color, visual)
local after = line:sub(right)
local new_line = before .. open_tag .. word .. close_tag .. after
vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line })
+ vim.fn['nuwiki#colors#refresh']()
end
-- Cluster C — link helpers.
diff --git a/syntax/vimwiki.vim b/syntax/vimwiki.vim
index c9af182..1375426 100644
--- a/syntax/vimwiki.vim
+++ b/syntax/vimwiki.vim
@@ -54,9 +54,12 @@ if has('nvim')
" Misc.
highlight default link @vimwikiKeyword Todo
- highlight default link @vimwikiColor Constant
highlight default link @vimwikiComment Comment
highlight default link @vimwikiDefinitionTerm Statement
+ " NOTE: no default for @vimwikiColor. Colour spans are coloured client-side
+ " in their *actual* colour by nuwiki#colors#refresh() (conceal + per-colour
+ " highlight); linking the LSP token to a fixed group here would override that
+ " real colour on the concealed text in Neovim.
endif
" ===== Static fallback (no LSP required) =====
@@ -205,4 +208,10 @@ endfunction
call s:nuwiki_setup_nested()
+" Conceal `:VimwikiColorize` spans (`…`) down to
+" their coloured text. Reset the per-buffer cache first so a syntax reload
+" (e.g. on :colorscheme) re-establishes the regions cleared by `:syntax clear`.
+let b:nuwiki_color_seen = {}
+call nuwiki#colors#refresh()
+
let b:current_syntax = 'vimwiki'