feat(client): conceal colour spans down to their coloured text
A :VimwikiColorize'd word showed the literal `<span style="color:NAME">TEXT</span>` 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 `<span …>` / `</span>` 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) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
" autoload/nuwiki/colors.vim — conceal colour spans down to their text.
|
||||||
|
"
|
||||||
|
" `:VimwikiColorize NAME` wraps text in `<span style="color:NAME">TEXT</span>`.
|
||||||
|
" 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 `<span style="color:…">` 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, '<span style="color:') >= 0
|
||||||
|
let l:pos = 0
|
||||||
|
while 1
|
||||||
|
let l:mp = matchstrpos(l:line, '<span style="color:[^"]\{-}">', 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 `<span …>` / `</span>` 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=/<span style="color:' . l:esc . '">/'
|
||||||
|
\ . ' end=#</span>#'
|
||||||
|
\ . ' 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
|
||||||
@@ -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
|
\ . l:open_tag . strpart(l:line, l:sc - 1, l:ec - l:sc + 1) . l:close_tag
|
||||||
\ . strpart(l:line, l:ec)
|
\ . strpart(l:line, l:ec)
|
||||||
call setline(l:sp[1], l:new)
|
call setline(l:sp[1], l:new)
|
||||||
|
call nuwiki#colors#refresh()
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
@@ -912,6 +913,7 @@ function! nuwiki#commands#colorize(...) abort
|
|||||||
\ . l:open_tag . l:word . l:close_tag
|
\ . l:open_tag . l:word . l:close_tag
|
||||||
\ . strpart(l:line, l:right - 1)
|
\ . strpart(l:line, l:right - 1)
|
||||||
call setline('.', l:new)
|
call setline('.', l:new)
|
||||||
|
call nuwiki#colors#refresh()
|
||||||
endfunction
|
endfunction
|
||||||
" Cluster C — link helpers.
|
" Cluster C — link helpers.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
Put the cursor on a word and run `:VimwikiColorize red` (or press
|
||||||
`<Leader>wc` and type a colour); or visually select a phrase and press
|
`<Leader>wc` and type a colour); or visually select a phrase and press
|
||||||
`<Leader>wc`. Each wraps the target in `<span style="color:…">…</span>`.
|
`<Leader>wc`. Each wraps the target in `<span style="color:…">…</span>` —
|
||||||
|
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
|
- colorize this important word
|
||||||
- now select and colour this whole phrase
|
- now select and colour this whole phrase
|
||||||
|
|||||||
@@ -515,6 +515,36 @@ call s:record(
|
|||||||
\ 'colorize.ranged_command_no_error',
|
\ 'colorize.ranged_command_no_error',
|
||||||
\ 'err=' . s:cz_err . ' got ' . string(getline(1)))
|
\ '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', '<span style="color:red">word</span> 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, '</span>') + 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 resolves + opens in the current window (regression) =====
|
||||||
" follow_link_or_create() must resolve the definition itself and `:edit` the
|
" 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
|
" target in the CURRENT window — including a page that does not exist yet
|
||||||
|
|||||||
@@ -773,6 +773,22 @@ vim.defer_fn(function()
|
|||||||
error('ranged command did not wrap selection: ' .. got)
|
error('ranged command did not wrap selection: ' .. got)
|
||||||
end
|
end
|
||||||
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', '<span style="color:red">word</span> 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('</span>')))[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) =====
|
-- ===== 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
|
||||||
|
|||||||
@@ -140,6 +140,18 @@ fix site.
|
|||||||
`colorize.visual_wraps_selection`, `cmd.Colorize_range_wraps_selection` in
|
`colorize.visual_wraps_selection`, `cmd.Colorize_range_wraps_selection` in
|
||||||
`test-keymaps.lua` and `colorize.{command_wraps_cword,visual_wraps_selection,
|
`test-keymaps.lua` and `colorize.{command_wraps_cword,visual_wraps_selection,
|
||||||
ranged_command_no_error}` in `test-keymaps-vim.vim`.
|
ranged_command_no_error}` in `test-keymaps-vim.vim`.
|
||||||
|
- [x] **Colour spans aren't concealed** — a colorized word shows the literal
|
||||||
|
`<span style="color:…">…</span>` 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 `<span …>`/`</span>`
|
||||||
|
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
|
- [ ] **`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
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ setlocal concealcursor=c
|
|||||||
|
|
||||||
let b:undo_ftplugin = 'setlocal commentstring< comments< formatoptions< suffixesadd< iskeyword< conceallevel< concealcursor<'
|
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! * <buffer>
|
||||||
|
autocmd TextChanged,TextChangedI,InsertLeave <buffer> call nuwiki#colors#refresh()
|
||||||
|
augroup END
|
||||||
|
call nuwiki#colors#refresh()
|
||||||
|
let b:undo_ftplugin .= ' | execute "autocmd! NuwikiColorConceal * <buffer>"'
|
||||||
|
|
||||||
if !has('nvim')
|
if !has('nvim')
|
||||||
" ===== Plain Vim path =====
|
" ===== Plain Vim path =====
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -989,6 +989,7 @@ function M.colorize(color, visual)
|
|||||||
.. close_tag
|
.. close_tag
|
||||||
.. sline:sub(ec + 2)
|
.. sline:sub(ec + 2)
|
||||||
vim.api.nvim_buf_set_lines(0, sl - 1, sl, false, { new_line })
|
vim.api.nvim_buf_set_lines(0, sl - 1, sl, false, { new_line })
|
||||||
|
vim.fn['nuwiki#colors#refresh']()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- Multi-line or no usable selection: fall through to the cword.
|
-- 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 after = line:sub(right)
|
||||||
local new_line = before .. open_tag .. word .. close_tag .. after
|
local new_line = before .. open_tag .. word .. close_tag .. after
|
||||||
vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line })
|
vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line })
|
||||||
|
vim.fn['nuwiki#colors#refresh']()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Cluster C — link helpers.
|
-- Cluster C — link helpers.
|
||||||
|
|||||||
+10
-1
@@ -54,9 +54,12 @@ if has('nvim')
|
|||||||
|
|
||||||
" Misc.
|
" Misc.
|
||||||
highlight default link @vimwikiKeyword Todo
|
highlight default link @vimwikiKeyword Todo
|
||||||
highlight default link @vimwikiColor Constant
|
|
||||||
highlight default link @vimwikiComment Comment
|
highlight default link @vimwikiComment Comment
|
||||||
highlight default link @vimwikiDefinitionTerm Statement
|
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
|
endif
|
||||||
|
|
||||||
" ===== Static fallback (no LSP required) =====
|
" ===== Static fallback (no LSP required) =====
|
||||||
@@ -205,4 +208,10 @@ endfunction
|
|||||||
|
|
||||||
call s:nuwiki_setup_nested()
|
call s:nuwiki_setup_nested()
|
||||||
|
|
||||||
|
" Conceal `:VimwikiColorize` spans (`<span style="color:…">…</span>`) 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'
|
let b:current_syntax = 'vimwiki'
|
||||||
|
|||||||
Reference in New Issue
Block a user