From 15ba774a28beaddf95a9f192ffedee886b726cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Sun, 31 May 2026 16:01:21 +0000 Subject: [PATCH] feat(client): conceal colour spans down to their coloured text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A :VimwikiColorize'd word showed the literal `TEXT` markup. Now the tags are concealed and TEXT is painted in NAME, so the buffer shows just the coloured word — matching the HTML export. New autoload/nuwiki/colors.vim: nuwiki#colors#refresh() scans the buffer and, per distinct colour, defines a syntax region that conceals the `` / `` tags (concealends) and highlights the body with the span's actual colour (guifg always; ctermfg for named colours). Idempotent (clears the group before redefining) and tracked in b:nuwiki_color_seen. Refresh is driven from three places so it's both correct and immediate: - syntax/vimwiki.vim: initial pass + re-establish after :colorscheme reload (resets the per-buffer cache since :syntax clear drops the regions); - ftplugin TextChanged/InsertLeave autocmd for spans that arrive via paste/ undo/external edits; - colorize() itself calls refresh() right after wrapping, so a freshly colorized word conceals instantly (TextChanged doesn't fire reliably mid command / in headless ex-mode). The LSP @vimwikiColor token no longer links to Constant, so in Neovim it doesn't override the real per-span colour on the concealed text. Pure syntax + :highlight — works identically in Vim, coc.nvim, and Neovim, no LSP needed. Tests: colorize.conceal_hides_tags_shows_text (both harnesses) and colorize.command_conceals_immediately (vim). Sample wiki's colorize section notes the conceal. nvim 269, vim 259+18, config-parity green. Co-Authored-By: Claude Opus 4.8 (1M context) --- autoload/nuwiki/colors.vim | 61 ++++++++++++++++++++++++++ autoload/nuwiki/commands.vim | 2 + development/_common.sh | 4 +- development/tests/test-keymaps-vim.vim | 30 +++++++++++++ development/tests/test-keymaps.lua | 16 +++++++ development/vimwiki-gap.md | 12 +++++ ftplugin/vimwiki.vim | 10 +++++ lua/nuwiki/commands.lua | 2 + syntax/vimwiki.vim | 11 ++++- 9 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 autoload/nuwiki/colors.vim 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'