Skip to content

Opacity with undefined bg for popup highlight group result a fully transparent popup#20414

Open
shadowwa wants to merge 13 commits into
vim:masterfrom
shadowwa:opacity-on-undefined-bg
Open

Opacity with undefined bg for popup highlight group result a fully transparent popup#20414
shadowwa wants to merge 13 commits into
vim:masterfrom
shadowwa:opacity-on-undefined-bg

Conversation

@shadowwa
Copy link
Copy Markdown
Contributor

@shadowwa shadowwa commented Jun 2, 2026

Problem: When using popup with a highlight group without background color set, and opacity set between 1 and 99 the blending fail and the popup is completely transparent.
I'm expecting a fadeout toward the underlying background color.

it occurs when popup highlight or highlights option are set to a group:

  • Normal
  • a group linked to Normal
  • a cleared group
  • a group with no ctermbg and guibg set or set to NONE

Moreover in the case ctermfg and guifg are not set, the underlying text color bleed on the popup text if this one does not have a color attribute (see the $${\color{cyan}OV\color{black}E\color{green}RANDABO\color{black}V\color{pink}E}$$ text in the screenshots)

Before this patch:
opacity-before-patch

After this patch:
opacity-after-patch

Since gui and cterm code are separate, I wanted to add similar screendump for gui but it works only for terminal, is there any way to do this kind of test for gvim ?

As the blocs guarded by if (popup_attr > HL_ALL) are not conditional any more, to ease the review on github, I've removed the bloc brackets and reindent in a separate commit.

To test it:
open a vim file containing

" DUMB CODE TO HAVE COLORED TEXT UNDER POPUP
" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" 0123456789 abcdef    TODO

highlight grup cterm=underline ctermbg=1 guibg=#00ff00

          let s:uvar = &filetype . 'under' + 1


" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" 0123456789 abcdef    TODO

highlight grup cterm=underline ctermbg=1 guibg=#00ff00

          let s:uvar = &filetype . 'under' + 1


" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" 0123456789 abcdef    TODO

highlight grup cterm=underline ctermbg=1 guibg=#00ff00

          let s:uvar = &filetype . 'under' + 1


" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" 0123456789 abcdef    TODO

highlight grup cterm=underline ctermbg=1 guibg=#00ff00

          let s:uvar = &filetype . 'under' + 1


" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" 0123456789 abcdef    TODO

highlight grup cterm=underline ctermbg=1 guibg=#00ff00

          let s:uvar = &filetype . 'under' + 1


" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" 0123456789 abcdef    TODO

highlight grup cterm=underline ctermbg=1 guibg=#00ff00

          let s:uvar = &filetype . 'under' + 1


" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░ █▓▒░
" 0123456789 abcdef    TODO

highlight grup cterm=underline ctermbg=1 guibg=#00ff00

          let s:uvar = &filetype . 'under' + 1


" ===== END OF DUMB CODE =====
"hi! link Pmenu Normal
function! Custompopup(hlgroup, position, opacity) abort
    let popup_content = [
                    \ '" popup content',
                    \ '" A C E  TODO         █▓▒░ ',
                    \ 'hi p cterm=nocombine ctermbg=1 guibg=#ccc',
                    \ '                  OVERANDABOVE',
                    \ 'let s:pvar = &filetype . "popup" + 1',
                    \ '']
    if (has('nvim'))
        let bufnr = nvim_create_buf(v:false, v:true)
        let opts = {
                    \  'relative': 'win',
                    \  'row': a:position['line'] -1,
                    \  'border': [ '', '' ,'', '', '', '', '', '' ],
                    \  'width': 41,
                    \  'height': 7,
                    \  'fixed': 1,
                    \  'col': a:position['col'] -1,
                    \  'title': 'Opacity ' . a:opacity . '% - Highlight: ' . a:hlgroup,
                    \ }
        call nvim_buf_set_lines(bufnr, 0, -1, v:true, popup_content)
        let winid = nvim_open_win(bufnr, 0, opts)
        call nvim_win_set_option(winid, 'winblend', 100 - a:opacity)
        call nvim_win_set_option(winid, 'filetype', 'vim')
        call nvim_set_option_value('winhl', 'Normal:' . a:hlgroup, {'win': winid})
    else
        let opts = {
                    \  'border': [1, 1, 1, 1],
                    \  'line': a:position['line'],
                    \  'close': 'button',
                    \  'minheight': 7,
                    \  'minwidth': 40,
                    \  'opacity': a:opacity,
                    \  'fixed': 1,
                    \  'col': a:position['col'],
                    \  'highlights': 'Normal:' . a:hlgroup,
                    \  'title': 'Opacity ' . a:opacity . '% - Highlight: ' . a:hlgroup,
                    \ }
        let winid = popup_create(popup_content, opts)
        call setbufvar(winbufnr(winid), '&filetype', 'vim')
        endif
    return winid
endfunction

highlight link LinkedToNormal Normal
highlight clear ClearedGroup
highlight OnlyFg ctermfg=blue guifg=blue
highlight OnlyBg ctermbg=green guibg=green
highlight BgNone ctermfg=red guifg=red ctermbg=NONE guibg=NONE
let popupline = 2
for hlgroup in ['Pmenu', 'Normal', 'LinkedToNormal', 'ClearedGroup', 'OnlyFg', 'OnlyBg', 'BgNone']
    let win1 = Custompopup(hlgroup, {'line': popupline, 'col': 10}, 80)
    let popupline = popupline + 9
endfor

set wildoptions=pum,fuzzy
" to compare popup and pum opacity
if (has('nvim'))
    let pumblend=20
else
    set pumopt=opacity:80
endif

source it:

:source

shadowwa added 6 commits June 2, 2026 21:26
Problem:  when the Popup highlight group has no guibg/ctermbg the popup
          became fully transparent.
Solution: Create an entry if no popup_attr exists (highlight group
          cleared for example), and test if popup_attr exists but
          without guibg/ctermbg attributes to fallaback to normal bg
          color.
previously the blending used guibg and guifg even when termguicolor was
not set, here the text color is white
now the if the guibg color is still used, for the text color without
termguicolor, only a ctermfg will be used and since &background=light
the resulting popup text color is black
@64-bitman
Copy link
Copy Markdown
Contributor

Since gui and cterm code are separate, I wanted to add similar screendump for gui but it works only for terminal, is there any way to do this kind of test for gvim ?

You can add CheckGui at the start of the test function. It will skip the test if the Vim being tested is not gvim

@shadowwa shadowwa marked this pull request as draft June 2, 2026 21:11
@shadowwa
Copy link
Copy Markdown
Contributor Author

shadowwa commented Jun 2, 2026

You can add CheckGui at the start of the test function. It will skip the test if the Vim being tested is not gvim

I mean, I've started to add in src/testdir/test_gui.vim a test like

func Test_gui_popup_opacity_undefined_popup_highlight()
CheckGui
CheckScreendump
"[…]
call VerifyScreenDump(…

to get an error E117: Unknown function: VerifyScreenDump after that, I realised that VerifyScreenDump was explicitly for terminal use

" Verify that Vim running in terminal buffer "buf" matches the screen dump.

but did not found a similar function for gui use.

@64-bitman
Copy link
Copy Markdown
Contributor

64-bitman commented Jun 3, 2026

You can add CheckGui at the start of the test function. It will skip the test if the Vim being tested is not gvim

I mean, I've started to add in src/testdir/test_gui.vim a test like

func Test_gui_popup_opacity_undefined_popup_highlight()

CheckGui

CheckScreendump

"[…]

call VerifyScreenDump(…

to get an error E117: Unknown function: VerifyScreenDump after that, I realised that VerifyScreenDump was explicitly for terminal use

" Verify that Vim running in terminal buffer "buf" matches the screen dump.

but did not found a similar function for gui use.

VerifyScreenDump should work in the GUI, it uses the terminal feature, not the terminal Vim is running in. I think the error you are getting is because test_gui.vim doesn't source src/screendump.vim. However you shouldnt be putting the tests in test_gui, just put them in test_popupwin. The CI will run Vim in the GUI against all the tests anyways, you can test using make GUI_FLAG=-g test_popupwin

@shadowwa
Copy link
Copy Markdown
Contributor Author

shadowwa commented Jun 3, 2026

VerifyScreenDump should work in the GUI, it uses the terminal feature, not the terminal Vim is running in. I think the error you are getting is because test_gui.vim doesn't source src/screendump.vim. However you shouldnt be putting the tests in test_gui, just put them in test_popupwin. The CI will run Vim in the GUI against all the tests anyways, you can test using make GUI_FLAG=-g test_popupwin

Thanks, I've missed this option when reading the doc.

@shadowwa shadowwa marked this pull request as ready for review June 3, 2026 18:48
Copy link
Copy Markdown
Member

@h-east h-east left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pull request!
By the way, adjusting the columns is easier using {Visual}gq (:h v_gq).

Scope: src/highlight.c, function hl_blend_attr().

1. Removed early-return changes behavior for the both-zero case

The early return

// If both attrs are 0, return 0
if (char_attr == 0 && popup_attr == 0)
    return 0;

was removed. This is necessary for the fix (a colorless popup must still blend
instead of being skipped), so the direction is correct. However, the removal also
affects the trivial case where both the underlying cell and the popup resolve to
attr 0: it used to return plain 0, and now it synthesizes a popup entry with the
Normal background and produces a blended attribute entry.

hl_blend_attr() is also called from pum opacity and popup border/background paths
(popupwin.c, screen.c), so this is a behavior change for those callers too, not
only for the reported case. In practice get_attr_entry() deduplicates the result,
so the cost is bounded, and the visible result should match (Normal bg equals the
terminal default). Still, it would be good to call out this intentional behavior
change in the commit message / PR description so it is not mistaken for an
accidental side effect.

2. A popup attr in the range 1..HL_ALL loses its raw attribute flags

When popup_attr <= HL_ALL the code now builds a synthetic entry with
CLEAR_FIELD(tmp_en), i.e. ae_attr == 0. If popup_attr is a non-zero raw
attribute (1 <= popup_attr <= HL_ALL, e.g. a highlight that is only "bold" with no
colors), those flags are silently dropped, because for blend_fg == FALSE the code
does new_en.ae_attr = popup_aep->ae_attr; (== 0). The old code skipped the whole
blend block in this case, so the underlying attribute was preserved instead.

This value range is unusual for a popup highlight, so the impact is small, but it is
a real corner that changed. Consider preserving popup_attr into the synthetic
entry (e.g. tmp_en.ae_attr = popup_attr <= HL_ALL ? popup_attr : 0;) if keeping
bold/underline-only popup highlights matters.

3. GUI vs cterm asymmetry for the foreground fallback

In the cterm branch the fix adds a foreground fallback when the popup has no fg and
text is opaque (blend_fg == FALSE): popup fg -> cterm_normal_fg_color ->
(*p_bg == 'l') ? 1 : 16. This is what stops the underlying text color from
bleeding through (the OVERANDABOVE case).

The GUI branch does not do the equivalent: its synthetic entry leaves
fg_color = INVALCOLOR, and for blend_fg == FALSE it assigns
new_en.ae_u.gui.fg_color = popup_aep->ae_u.gui.fg_color; (== INVALCOLOR). GUI
resolves INVALCOLOR to the default foreground, so it usually looks acceptable, but
the "fall back to Normal fg" intent that was added for cterm is not mirrored on the
GUI side. Worth aligning the two paths, or at least confirming the GUI result is
intentional. (The author already notes the GUI path is untested because the
screendump only covers the terminal.)

4. Duplicated fallback snippet (optional)

The pattern

if (COLOR_INVALID(popup_bg_rgb) && popup_aep->ae_u.cterm.bg_color == 0)
    popup_bg_rgb = fallback_bg_rgb;

appears twice in the cterm (no-termguicolors) block, and a similar
if (COLOR_INVALID(popup_bg)) popup_bg = fallback_bg_rgb; in the termguicolors
block. Each operates on a slightly different field, so this is not required, but a
small helper (or computing the fallback once) would reduce repetition and the chance
of the three copies drifting apart later.

5. Test coverage

The added cterm screendumps cover the core cases well
(Test_popup_opacity_undefined_popup_highlight for the Normal / linked-to-Normal /
cleared / bg=NONE groups, and Test_popup_opacity_only_cterm_or_gui_set). The GUI
rendering path has no test, which the author acknowledges; since the fix is applied
separately in the GUI branch, that untested path is the main coverage gap.

Overall

The approach (synthesize a popup entry with the Normal background when the popup
highlight has no real entry, and fall back for fg) is sound and matches the
before/after screenshots. The points above are mostly clarifications and minor
robustness items rather than blockers.

Comment thread src/highlight.c Outdated
Comment thread src/highlight.c Outdated
Comment thread src/highlight.c Outdated
Comment thread src/highlight.c Outdated
Comment thread src/highlight.c Outdated
Comment thread src/highlight.c Outdated
Comment thread src/highlight.c Outdated
@shadowwa
Copy link
Copy Markdown
Contributor Author

shadowwa commented Jun 5, 2026

  1. Removed early-return changes behavior for the both-zero case
    hl_blend_attr() is also called from pum opacity and popup border/background paths

I was planning to do the same modification for the pum in another PR so the behavior should be the same at the end.

  1. A popup attr in the range 1..HL_ALL loses its raw attribute flags

Fixed: bold, underline,… attributes are keep even if no colors are set.

  1. GUI vs cterm asymmetry for the foreground fallback

The GUI result was ok, I've just added a comment, while testing this I found another asymmetry for the background color that needed a fix for the gui branch.

  1. Duplicated fallback snippet (optional)

Fixed: I'm calculating now the fallback_bg_rgb once in the cterm branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants