diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index d9dbcd8336..b0f3f1c72b 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -7007,7 +7007,8 @@ A jump table for the options with a short description can be found at |Q_op|. of 'fillchars' option. opacity:{n} opacity percentage 0-100 (default 100). When less than 100, background content shows - through the popup menu. + through the popup menu. Requires the GUI, + 'termguicolors', or a 256-color terminal. Flags (no value): margin adds one-cell spacing inside the left and diff --git a/runtime/doc/popup.txt b/runtime/doc/popup.txt index 6b99ac47ee..250127b9b6 100644 --- a/runtime/doc/popup.txt +++ b/runtime/doc/popup.txt @@ -1,4 +1,4 @@ -*popup.txt* For Vim version 9.2. Last change: 2026 Apr 06 +*popup.txt* For Vim version 9.2. Last change: 2026 May 01 VIM REFERENCE MANUAL by Bram Moolenaar @@ -1067,8 +1067,9 @@ The opacity value ranges from 0 to 100: 1-99 Partially transparent - the popup background is blended with the underlying text, making both partially visible. -The transparency effect requires using the GUI or having 'termguicolors' -enabled in the terminal. Without it, the opacity setting has no effect. +The transparency effect requires using the GUI, having 'termguicolors' +enabled, or running in a 256-color terminal. On terminals with fewer +than 256 colors the opacity setting has no effect. When a popup is transparent: - The popup's background color is blended with the background text diff --git a/src/highlight.c b/src/highlight.c index 67b551c67e..b21e489e61 100644 --- a/src/highlight.c +++ b/src/highlight.c @@ -3126,9 +3126,19 @@ hl_combine_attr(int char_attr, int prim_attr) return get_attr_entry(&term_attr_table, &new_en); } -#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) +// ANSI color order: Black, Red, Green, Yellow, Blue, Magenta, +// Cyan, White, then bright variants. Approximate RGB values used when +// only a cterm color number is known (no guifg/guibg). Real terminal +// palettes may differ if the user reconfigured their emulator, but +// these are reasonable xterm-ish defaults. +static const long_u cterm_color_16[16] = { + 0x000000, 0xc00000, 0x008000, 0x808000, + 0x0000c0, 0xc000c0, 0x004080, 0xc0c0c0, + 0x808080, 0xff8080, 0x00ff00, 0xffff00, + 0x6060ff, 0xff40ff, 0x00ffff, 0xffffff +}; -# ifdef FEAT_TERMGUICOLORS +#ifdef FEAT_TERMGUICOLORS /* * Convert a cterm color number (1-16) to an RGB value. * Used as a fallback when 'termguicolors' is set but only cterm colors are @@ -3138,21 +3148,156 @@ hl_combine_attr(int char_attr, int prim_attr) static guicolor_T cterm_color_to_rgb(int color_nr) { - // ANSI color order: Black, Red, Green, Yellow, Blue, Magenta, - // Cyan, White, then bright variants. - static const guicolor_T cterm_color_16[16] = { - 0x000000, 0xc00000, 0x008000, 0x808000, - 0x0000c0, 0xc000c0, 0x004080, 0xc0c0c0, - 0x808080, 0xff8080, 0x00ff00, 0xffff00, - 0x6060ff, 0xff40ff, 0x00ffff, 0xffffff - }; - if (color_nr < 1 || color_nr > 16) return INVALCOLOR; - return cterm_color_16[color_nr - 1]; + return (guicolor_T)cterm_color_16[color_nr - 1]; } -# endif +#endif +/* + * Convert an xterm 256-color index (0-255) to an approximate RGB triple. + * Uses the standard xterm palette: 0-15 ANSI (cterm_color_16), 16-231 + * 6x6x6 cube, 232-255 grayscale ramp. + */ + static void +cterm_idx_to_rgb(int idx, int *r, int *g, int *b) +{ + static const int cube[] = { 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF }; + + if (idx < 0 || idx > 255) + { + *r = *g = *b = 0; + return; + } + if (idx < 16) + { + long_u rgb = cterm_color_16[idx]; + *r = (rgb >> 16) & 0xFF; + *g = (rgb >> 8) & 0xFF; + *b = rgb & 0xFF; + } + else if (idx < 232) + { + int n = idx - 16; + *r = cube[n / 36 % 6]; + *g = cube[n / 6 % 6]; + *b = cube[n % 6]; + } + else + { + int v = 8 + (idx - 232) * 10; + *r = *g = *b = v; + } +} + +/* + * Approximate an RGB triple to the nearest xterm 256-color index. + * Searches the 6x6x6 cube and the grayscale ramp; returns 0-255. + */ + static int +rgb_to_cterm_idx(int r, int g, int b) +{ + static const int cube[] = { 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF }; + int best = 0; + long best_d = -1; + int i; + + // Search the 6x6x6 cube. + for (i = 16; i < 232; ++i) + { + int n = i - 16; + int cr = cube[n / 36 % 6]; + int cg = cube[n / 6 % 6]; + int cb = cube[n % 6]; + long d = (cr - r) * (cr - r) + (cg - g) * (cg - g) + (cb - b) * (cb - b); + if (best_d < 0 || d < best_d) + { + best_d = d; + best = i; + } + } + // Search the grayscale ramp. + for (i = 232; i < 256; ++i) + { + int v = 8 + (i - 232) * 10; + long d = (v - r) * (v - r) + (v - g) * (v - g) + (v - b) * (v - b); + if (d < best_d) + { + best_d = d; + best = i; + } + } + return best; +} + +/* + * Resolve a color to an RGB triple. Prefer the gui RGB value (set by + * guifg/guibg) when valid, otherwise convert the cterm 256-color index. + * Returns true on success; false when no color is defined. + * cterm_c is 1-based (0 means "no color"). + */ + static bool +resolve_color_to_rgb(int cterm_c, guicolor_T rgb UNUSED, int *r, int *g, int *b) +{ +#ifdef FEAT_TERMGUICOLORS + if (!COLOR_INVALID(rgb)) + { + *r = (rgb >> 16) & 0xFF; + *g = (rgb >> 8) & 0xFF; + *b = rgb & 0xFF; + return true; + } +#endif + if (cterm_c > 0) + { + cterm_idx_to_rgb(cterm_c - 1, r, g, b); + return true; + } + return false; +} + +/* + * Blend two colors expressed as (cterm 256 index, gui RGB) pairs and + * return the nearest 1-based cterm 256-color index. Prefers the gui + * RGB so highlight definitions like "guibg=#2D2A3D" without ctermbg + * still produce a meaningful blend in 256-color terminals. + * + * "default_rgb" is used as the underlying color when neither under_c + * nor under_rgb is set; pass 0xFFFFFF for fg-style blends (so text + * fades toward white) or 0x000000 for bg-style blends (so the popup + * fades toward terminal default dark). + * + * Requires t_colors >= 256; otherwise returns popup_c unchanged. + */ + static int +blend_cterm_colors(int popup_c, guicolor_T popup_rgb, + int under_c, guicolor_T under_rgb, + int default_rgb, + int blend_val) +{ + int pr, pg, pb, ur, ug, ub, r, g, b; + + if (t_colors < 256) + return popup_c; + if (!resolve_color_to_rgb(popup_c, popup_rgb, &pr, &pg, &pb)) + return under_c; + if (blend_val <= 0) + return rgb_to_cterm_idx(pr, pg, pb) + 1; + if (!resolve_color_to_rgb(under_c, under_rgb, &ur, &ug, &ub)) + { + ur = (default_rgb >> 16) & 0xFF; + ug = (default_rgb >> 8) & 0xFF; + ub = default_rgb & 0xFF; + } + if (blend_val >= 100) + return rgb_to_cterm_idx(ur, ug, ub) + 1; + r = pr + (ur - pr) * blend_val / 100; + g = pg + (ug - pg) * blend_val / 100; + b = pb + (ub - pb) * blend_val / 100; + return rgb_to_cterm_idx(r, g, b) + 1; +} + +#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) /* * Blend two RGB colors based on blend value (0-100). * blend: 0=use popup color, 100=use background color @@ -3163,7 +3308,9 @@ blend_colors(guicolor_T popup_color, guicolor_T bg_color, int blend_val) { int r1, g1, b1, r2, g2, b2, r, g, b; - if (popup_color == INVALCOLOR) + // CTERMCOLOR is a sentinel meaning "use the cterm color"; for blending + // it has no real RGB so treat it like INVALCOLOR. + if (COLOR_INVALID(popup_color)) return INVALCOLOR; // Fully transparent: use underlying color as-is. @@ -3174,7 +3321,7 @@ blend_colors(guicolor_T popup_color, guicolor_T bg_color, int blend_val) g1 = (popup_color >> 8) & 0xFF; b1 = popup_color & 0xFF; - if (bg_color == INVALCOLOR) + if (COLOR_INVALID(bg_color)) { // Background color unknown: fade popup color to black as blend increases // This makes background text more visible at high blend values @@ -3253,11 +3400,13 @@ hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg UNUSED) else { // blend_fg=FALSE: popup text is opaque. Replace the - // underlying cell's attribute flags and fg with the - // popup's, so the underlying syntax highlighting does - // not bleed through. + // underlying cell's attribute flags, fg and special + // color with the popup's, so the underlying syntax + // highlighting and any decoration (textprop undercurl, + // ...) do not bleed through. new_en.ae_attr = popup_aep->ae_attr; new_en.ae_u.gui.fg_color = popup_aep->ae_u.gui.fg_color; + new_en.ae_u.gui.sp_color = popup_aep->ae_u.gui.sp_color; } // Blend background color: blend popup bg toward underlying bg if (popup_aep->ae_u.gui.bg_color != INVALCOLOR) @@ -3301,18 +3450,60 @@ hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg UNUSED) if (!blend_fg) { // blend_fg=FALSE: popup text is opaque. Replace the - // underlying cell's attribute flags and fg with the - // popup's, so the underlying syntax highlighting does - // not bleed through. + // underlying cell's attribute flags, fg and underline + // color with the popup's, so the underlying syntax + // highlighting and any decoration (textprop undercurl, + // ...) do not bleed through. When the popup has no fg + // (e.g. "guifg=NONE") fall back to Normal's fg so the + // text is still readable instead of taking on whatever + // the underlying cell happened to have. new_en.ae_attr = popup_aep->ae_attr; - new_en.ae_u.cterm.fg_color = popup_aep->ae_u.cterm.fg_color; + if (popup_aep->ae_u.cterm.fg_color > 0) + new_en.ae_u.cterm.fg_color = + popup_aep->ae_u.cterm.fg_color; + else if (cterm_normal_fg_color > 0) + new_en.ae_u.cterm.fg_color = cterm_normal_fg_color; + else + new_en.ae_u.cterm.fg_color = 16; // white-ish + new_en.ae_u.cterm.ul_color = popup_aep->ae_u.cterm.ul_color; +#ifdef FEAT_TERMGUICOLORS + new_en.ae_u.cterm.ul_rgb = popup_aep->ae_u.cterm.ul_rgb; +#endif + } + else + { + // blend_fg=TRUE: fade underlying fg toward popup bg in + // the 256-color palette. Used when the popup is over a + // cell rendered with cterm colors (no termguicolors RGB). + int under_fg = (char_aep != NULL) + ? char_aep->ae_u.cterm.fg_color : 0; + guicolor_T under_fg_rgb = INVALCOLOR; + guicolor_T popup_bg_rgb = INVALCOLOR; +#ifdef FEAT_TERMGUICOLORS + if (char_aep != NULL) + under_fg_rgb = char_aep->ae_u.cterm.fg_rgb; + popup_bg_rgb = popup_aep->ae_u.cterm.bg_rgb; +#endif + new_en.ae_u.cterm.fg_color = blend_cterm_colors( + popup_aep->ae_u.cterm.bg_color, popup_bg_rgb, + under_fg, under_fg_rgb, 0xFFFFFF, blend); + } + // Approximate cterm bg by blending with the underlying bg + // in the 256-color palette and mapping to the nearest entry. + { + int under_bg = (char_aep != NULL) + ? char_aep->ae_u.cterm.bg_color : 0; + guicolor_T under_bg_rgb = INVALCOLOR; + guicolor_T popup_bg_rgb = INVALCOLOR; +#ifdef FEAT_TERMGUICOLORS + if (char_aep != NULL) + under_bg_rgb = char_aep->ae_u.cterm.bg_rgb; + popup_bg_rgb = popup_aep->ae_u.cterm.bg_rgb; +#endif + new_en.ae_u.cterm.bg_color = blend_cterm_colors( + popup_aep->ae_u.cterm.bg_color, popup_bg_rgb, + under_bg, under_bg_rgb, 0x000000, blend); } - else if (popup_aep->ae_u.cterm.fg_color > 0) - // Blend foreground color - new_en.ae_u.cterm.fg_color = popup_aep->ae_u.cterm.fg_color; - // Use popup background color (cterm colors don't support blending) - if (popup_aep->ae_u.cterm.bg_color > 0) - new_en.ae_u.cterm.bg_color = popup_aep->ae_u.cterm.bg_color; #ifdef FEAT_TERMGUICOLORS // Blend RGB colors for termguicolors mode. // Fall back to cterm color converted to RGB when @@ -3336,24 +3527,38 @@ hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg UNUSED) if (popup_bg != INVALCOLOR) { int base_fg = 0xFFFFFF; + // CTERMCOLOR is a sentinel meaning "use the cterm + // color"; treat it as no underlying color so it is + // not blended in as a real near-white pixel. if (char_aep != NULL - && char_aep->ae_u.cterm.fg_rgb != INVALCOLOR) + && !COLOR_INVALID(char_aep->ae_u.cterm.fg_rgb)) base_fg = char_aep->ae_u.cterm.fg_rgb; new_en.ae_u.cterm.fg_rgb = blend_colors( base_fg, popup_bg, blend); } } else + { // blend_fg=FALSE: popup text is opaque. Replace fg - // with popup's (even INVALCOLOR) so the underlying - // syntax highlighting fg does not bleed. ae_attr - // was already set above for this branch. - new_en.ae_u.cterm.fg_rgb = popup_fg; + // with popup's so the underlying syntax highlighting + // fg does not bleed. ae_attr was already set above + // for this branch. When the popup has no fg fall + // back to Normal's fg, then to white, so the text + // stays readable instead of rendering as default + // (which can be black on dark themes). + if (!COLOR_INVALID(popup_fg)) + new_en.ae_u.cterm.fg_rgb = popup_fg; + else if (!COLOR_INVALID(cterm_normal_fg_gui_color)) + new_en.ae_u.cterm.fg_rgb = cterm_normal_fg_gui_color; + else + new_en.ae_u.cterm.fg_rgb = 0xFFFFFF; + } if (popup_bg != INVALCOLOR) { // Blend popup bg toward underlying bg guicolor_T underlying_bg = INVALCOLOR; - if (char_aep != NULL) + if (char_aep != NULL + && !COLOR_INVALID(char_aep->ae_u.cterm.bg_rgb)) underlying_bg = char_aep->ae_u.cterm.bg_rgb; new_en.ae_u.cterm.bg_rgb = blend_colors( popup_bg, underlying_bg, blend); @@ -3463,21 +3668,48 @@ hl_pum_blend_attr(int char_attr, int popup_attr, int blend UNUSED) popup_aep = syn_cterm_attr2entry(popup_attr); if (popup_aep != NULL) { - // Blend cterm fg: use popup bg (hides text when opaque) - if (popup_aep->ae_u.cterm.fg_color > 0) - new_en.ae_u.cterm.fg_color = - popup_aep->ae_u.cterm.fg_color; - // Use popup cterm bg. - if (popup_aep->ae_u.cterm.bg_color > 0) - new_en.ae_u.cterm.bg_color = - popup_aep->ae_u.cterm.bg_color; + // Blend cterm fg: pum_bg toward underlying_fg in the + // 256-color palette (mirrors the fg_rgb blend below). + { + int under_fg = (char_aep != NULL) + ? char_aep->ae_u.cterm.fg_color : 0; + guicolor_T under_fg_rgb = INVALCOLOR; + guicolor_T popup_bg_rgb = INVALCOLOR; +#ifdef FEAT_TERMGUICOLORS + if (char_aep != NULL) + under_fg_rgb = char_aep->ae_u.cterm.fg_rgb; + popup_bg_rgb = popup_aep->ae_u.cterm.bg_rgb; +#endif + new_en.ae_u.cterm.fg_color = blend_cterm_colors( + popup_aep->ae_u.cterm.bg_color, popup_bg_rgb, + under_fg, under_fg_rgb, 0xFFFFFF, blend); + } + // Approximate cterm bg by blending with the underlying bg + // in the 256-color palette and mapping to the nearest entry. + { + int under_bg = (char_aep != NULL) + ? char_aep->ae_u.cterm.bg_color : 0; + guicolor_T under_bg_rgb = INVALCOLOR; + guicolor_T popup_bg_rgb = INVALCOLOR; +#ifdef FEAT_TERMGUICOLORS + if (char_aep != NULL) + under_bg_rgb = char_aep->ae_u.cterm.bg_rgb; + popup_bg_rgb = popup_aep->ae_u.cterm.bg_rgb; +#endif + new_en.ae_u.cterm.bg_color = blend_cterm_colors( + popup_aep->ae_u.cterm.bg_color, popup_bg_rgb, + under_bg, under_bg_rgb, 0x000000, blend); + } #ifdef FEAT_TERMGUICOLORS // Blend fg_rgb: pum_bg toward underlying_fg. + // CTERMCOLOR is a sentinel meaning "use the cterm color"; + // treat it as no underlying color so it is not blended in + // as a real near-white pixel. if (popup_aep->ae_u.cterm.bg_rgb != INVALCOLOR) { int base_fg = 0xFFFFFF; if (char_aep != NULL - && char_aep->ae_u.cterm.fg_rgb != INVALCOLOR) + && !COLOR_INVALID(char_aep->ae_u.cterm.fg_rgb)) base_fg = char_aep->ae_u.cterm.fg_rgb; new_en.ae_u.cterm.fg_rgb = blend_colors( popup_aep->ae_u.cterm.bg_rgb, base_fg, blend); @@ -3486,7 +3718,8 @@ hl_pum_blend_attr(int char_attr, int popup_attr, int blend UNUSED) if (popup_aep->ae_u.cterm.bg_rgb != INVALCOLOR) { guicolor_T underlying_bg = INVALCOLOR; - if (char_aep != NULL) + if (char_aep != NULL + && !COLOR_INVALID(char_aep->ae_u.cterm.bg_rgb)) underlying_bg = char_aep->ae_u.cterm.bg_rgb; new_en.ae_u.cterm.bg_rgb = blend_colors( popup_aep->ae_u.cterm.bg_rgb, diff --git a/src/testdir/dumps/Test_popupwin_opacity_hl_80.dump b/src/testdir/dumps/Test_popupwin_opacity_hl_80.dump index 89700f9c40..d197c8dacf 100644 --- a/src/testdir/dumps/Test_popupwin_opacity_hl_80.dump +++ b/src/testdir/dumps/Test_popupwin_opacity_hl_80.dump @@ -1,7 +1,7 @@ >1+0&#ffffff0| @73 |2| @73 -|3| @7|f+0#ff404010#5fd7ff255|o@1| @1|b+0#0000000&|a|r| +0&#ffffff0@57 -|4| @7|b+0fd7ff255|a|z| @4| +0&#ffffff0@57 +|3| @7|f+0#ff404010#5fafd7255|o@1| +0#87d7ff255&@1|b+0#ffffff16&|a|r| +0#0000000#ffffff0@57 +|4| @7|b+0#ffffff16#5fafd7255|a|z| +0#87d7ff255&@4| +0#0000000#ffffff0@57 |5| @73 |6| @73 |7| @73 diff --git a/src/testdir/dumps/Test_popupwin_opacity_textprop_undercurl.dump b/src/testdir/dumps/Test_popupwin_opacity_textprop_undercurl.dump new file mode 100644 index 0000000000..2d29cfdeaf --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_opacity_textprop_undercurl.dump @@ -0,0 +1,8 @@ +>a+0&#ffffff0@2| |b@2| |c|P+0#e5e9f0255#03050b255|O|P|U|P|d+0#61646f255&|C+0#e5e9f0255&|O|N|T|E|N|T|f+0#61646f255&| |g@2| |h@2| @7| +0#0000000#ffffff0@10 +|i@2| |j@2| |k|k+0#61646f255#03050b255@1| |l@2| |m@2| |n@2| |o@2| |p@2| @7| +0#0000000#ffffff0@10 +|q@2| |r@2| |s|s+0#61646f255#03050b255@1| |m+0#e5e9f0255&|i|d@1|l|e|u+0#61646f255&| |v@2| |w@2| |x@2| @7| +0#0000000#ffffff0@10 +|y@2| |z@2| |1@2| |2@2| |3@2| |4@2| |5@2| |6@2| @18 +|~+0#0000ff255&| @48 +|~| @48 +|~| @48 +| +0#0000000&@31|1|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_popupwin_opacity_zero_01.dump b/src/testdir/dumps/Test_popupwin_opacity_zero_01.dump index 17aa447f86..1209af25c5 100644 --- a/src/testdir/dumps/Test_popupwin_opacity_zero_01.dump +++ b/src/testdir/dumps/Test_popupwin_opacity_zero_01.dump @@ -1,7 +1,7 @@ >b+0&#ffffff0|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 |b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 -|b|a|c|k|b|l|u|e|n|p|o|p|u|p|t| |h|e|r|e| @54 -|b|a|c|k|g|r|o|r|e|d| |p|o|p|u|p|h|e|r|e| @54 +|b|a|c|k|b+0#ffffff16#00005f255|l|u|e|n+0#8787d7255&|p+0#ffffff16&|o|p|u|p|t+0#0000000#ffffff0| |h|e|r|e| @54 +|b|a|c|k|g|r|o|r+0#ffffff16#000000255|e|d| +0#0000000#ffffff0|p+0#ffffff16#000000255|o|p|u|p|h+0#0000000#ffffff0|e|r|e| @54 |b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 |b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 |b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 diff --git a/src/testdir/dumps/Test_popupwin_opacity_zero_02.dump b/src/testdir/dumps/Test_popupwin_opacity_zero_02.dump index 03b9f4a59f..c724edb7fe 100644 --- a/src/testdir/dumps/Test_popupwin_opacity_zero_02.dump +++ b/src/testdir/dumps/Test_popupwin_opacity_zero_02.dump @@ -1,7 +1,7 @@ >b+0&#ffffff0|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 |b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 -|b|a|c|k|b|l|u|e|n|p|o|p|u|p|t| |h|e|r|e| @54 -|b|a|c|k|g|r|o|r|e|d| |p|o|p|u|p|h|e|r|e| @54 +|b|a|c|k|b+0#ffffff16#00005f255|l|u|e|n+0#8787d7255&|p+0#ffffff16&|o|p|u|p|t+0#0000000#ffffff0| |h|e|r|e| @54 +|b|a|c|k|g|r|o|r+0#ffffff16#000000255|e|d| +0#0000000#ffffff0|p+0#ffffff16#000000255|o|p|u|p|h+0#0000000#ffffff0|e|r|e| @54 |b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 |b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 |b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 diff --git a/src/testdir/dumps/Test_pumopt_opacity_50.dump b/src/testdir/dumps/Test_pumopt_opacity_50.dump index d9e44da0f7..4490b1ef1d 100644 --- a/src/testdir/dumps/Test_pumopt_opacity_50.dump +++ b/src/testdir/dumps/Test_pumopt_opacity_50.dump @@ -13,8 +13,8 @@ |B|A|C|K|G|R|O|U|N|D|B|A|C|K|G|R|O|U|N|D|B|A|C|K|G|R|O|U|N|D|B|A|C|K|G|R|O|U|N|D|B|A|C|K|G|R|O|U|N|D|B|A|C|K|G|R|O|U|N|D|B|A|C|K|G|R|O|U|N|D|B|A|C|K|G |R|O|U|N|D| @69 |h|e|l@1|o> @69 -|h+0#0000001#e0e0e08|e|l@1|o| @9| +0#4040ff13#ffffff0@59 -|h+0#0000001#ffd7ff255|e|l|p| @10| +0#4040ff13#ffffff0@59 +|h+0#0000001#5f5f5f255|e|l@1|o| +0#5f5fd7255&@9| +0#4040ff13#ffffff0@59 +|h+0#0000001#875f87255|e|l|p| +0#875fff255&@10| +0#4040ff13#ffffff0@59 |~| @73 |~| @73 |-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |2| +0#0000000&@33 diff --git a/src/testdir/dumps/Test_pumopt_opacity_text_attrs.dump b/src/testdir/dumps/Test_pumopt_opacity_text_attrs.dump index 93ea3a5a4a..0911a881c9 100644 --- a/src/testdir/dumps/Test_pumopt_opacity_text_attrs.dump +++ b/src/testdir/dumps/Test_pumopt_opacity_text_attrs.dump @@ -1,7 +1,7 @@ |ほ*0&#ffffff0|げ> +&@70 -|ほ*0#0000001#ffff4012|げ|ほ|げ|ほ|げ|漢*4&&| +&| +4#e000e06#ffffff0|テ*&|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 -|ふ*0#ffffff16#0000e05|が|漢|字|ほ|げ|漢*4&&| +&| +4#e000e06#ffffff0|テ*&|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 -|カ*0#ffffff16#0000e05|タ|カ|ナ|候|補|漢*4&&| +&| +4#e000e06#ffffff0|テ*&|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 +|ほ*0#0000001#875f00255|げ|ほ*0#ffd787255&|げ|ほ|げ|漢*4#ff875f255&| +&| +4#e000e06#ffffff0|テ*&|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 +|ふ*0#ffffff16#00005f255|が|漢|字|ほ*0#87afaf255&|げ|漢*4#875faf255&| +&| +4#e000e06#ffffff0|テ*&|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 +|カ*0#ffffff16#00005f255|タ|カ|ナ|候|補|漢*4#875faf255&| +&| +4#e000e06#ffffff0|テ*&|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 |ほ*&|げ|ほ|げ|ほ|げ|漢*4#e000e06&|字|テ|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 |ほ*&|げ|ほ|げ|ほ|げ|漢*4#e000e06&|字|テ|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 |ほ*&|げ|ほ|げ|ほ|げ|漢*4#e000e06&|字|テ|ス|ト|あ*0#0000000&|い|う|え|お|カ|タ|カ|ナ| +&@34 diff --git a/src/testdir/dumps/Test_pumopt_opacity_textprop_undercurl.dump b/src/testdir/dumps/Test_pumopt_opacity_textprop_undercurl.dump new file mode 100644 index 0000000000..8b9b0a0913 --- /dev/null +++ b/src/testdir/dumps/Test_pumopt_opacity_textprop_undercurl.dump @@ -0,0 +1,20 @@ +|p+0&#ffffff0|o|p|u|p|-|i|t|e|m|-|1> @62 +|p+0#000000255#5f5f5f255|o|p|u|p|-|i|t|e|m|-|1|d+0#dedede255&@2| +0#0000000#ffffff0|e@2| |f@2| |g@2| |h@2| @43 +|p+0#000000255#7f457f255|o|p|u|p|-|i|t|e|m|-|2|d+0#ffc5ff255&@2| +0#0000000#ffffff0|e@2| |f@2| |g@2| |h@2| @43 +|p+0#000000255#7f457f255|o|p|u|p|-|i|t|e|m|-|3|d+0#ffc5ff255&@2| +0#0000000#ffffff0|e@2| |f@2| |g@2| |h@2| @43 +|a@2| |b@2| |c@2| |d@2| |e@2| |f@2| |g@2| |h@2| @43 +|a@2| |b@2| |c@2| |d@2| |e@2| |f@2| |g@2| |h@2| @43 +|a@2| |b@2| |c@2| |d@2| |e@2| |f@2| |g@2| |h@2| @43 +|a@2| |b@2| |c@2| |d@2| |e@2| |f@2| |g@2| |h@2| @43 +|a@2| |b@2| |c@2| |d@2| |e@2| |f@2| |g@2| |h@2| @43 +|~+0#0000ff255&| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@44|1|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_pumopt_opacity_wide_bg.dump b/src/testdir/dumps/Test_pumopt_opacity_wide_bg.dump index ed52522f01..87c7bd6710 100644 --- a/src/testdir/dumps/Test_pumopt_opacity_wide_bg.dump +++ b/src/testdir/dumps/Test_pumopt_opacity_wide_bg.dump @@ -1,8 +1,8 @@ |ほ*0&#ffffff0|げ> +&@70 |╭+0#0000001#ffd7ff255|─@15|╮|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 -|│+0#0000001#ffd7ff255| +0&#e0e0e08|ほ*&|げ@1|ほ|げ|漢|字| +&|│+0&#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 -|│+0#0000001#ffd7ff255| |ふ*&|が|漢|字|げ|漢|字| +&|│|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 -|│+0#0000001#ffd7ff255| |カ*&|タ|カ|ナ|候|補|字| +&|│|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 +|│+0#0000001#ffd7ff255| +0f5f5f255|ほ*&|げ|げ*0#dadada255&|ほ|げ|漢|字| +&|│+0#0000001#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 +|│+0#0000001#ffd7ff255| +0ͫf87255|ふ*&|が|漢|字|げ*0#ffd7ff255&|漢|字| +&|│+0#0000001#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 +|│+0#0000001#ffd7ff255| +0ͫf87255|カ*&|タ|カ|ナ|候|補|字*0#ffd7ff255&| +&|│+0#0000001#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 |╰+0#0000001#ffd7ff255|─@15|╯|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 |ほ*&|げ|ほ|げ|ほ|げ|漢|字|テ|ス|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 |ほ*&|げ|ほ|げ|ほ|げ|漢|字|テ|ス|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 diff --git a/src/testdir/dumps/Test_pumopt_opacity_wide_bg_shifted.dump b/src/testdir/dumps/Test_pumopt_opacity_wide_bg_shifted.dump index c145bf0800..ef1b9acf82 100644 --- a/src/testdir/dumps/Test_pumopt_opacity_wide_bg_shifted.dump +++ b/src/testdir/dumps/Test_pumopt_opacity_wide_bg_shifted.dump @@ -1,8 +1,8 @@ |ほ*0&#ffffff0|げ> +&@70 |╭+0#0000001#ffd7ff255|─@15|╮|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 -|│+0#0000001#ffd7ff255| +0&#e0e0e08|ほ*&|げ| +&|げ*&|ほ|げ|漢|字|│+0&#ffd7ff255| +0#0000000#ffffff0|ス*&|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@33 -|│+0#0000001#ffd7ff255| |ふ*&|が|漢|字|げ|漢|字| +&|│|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 -|│+0#0000001#ffd7ff255| |カ*&|タ|カ|ナ|候|補| +&|字*&|│+&| +0#0000000#ffffff0|ス*&|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@33 +|│+0#0000001#ffd7ff255| +0f5f5f255|ほ*&|げ| +0#dadada255&|げ*&|ほ|げ|漢|字|│+0#0000001#ffd7ff255| +0#0000000#ffffff0|ス*&|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@33 +|│+0#0000001#ffd7ff255| +0ͫf87255|ふ*&|が|漢|字|げ*0#ffd7ff255&|漢|字| +&|│+0#0000001#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 +|│+0#0000001#ffd7ff255| +0ͫf87255|カ*&|タ|カ|ナ|候|補| +0#ffd7ff255&|字*&|│+0#0000001#ffd7ff255| +0#0000000#ffffff0|ス*&|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@33 |╰+0#0000001#ffd7ff255|─@15|╯|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 |a|ほ*&|げ|ほ|げ|ほ|げ|漢|字|テ|ス|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@33 |ほ*&|げ|ほ|げ|ほ|げ|漢|字|テ|ス|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34 diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim index 3362cd14f0..2ceaec945a 100644 --- a/src/testdir/test_popup.vim +++ b/src/testdir/test_popup.vim @@ -2474,6 +2474,40 @@ func Test_pumopt_opacity_wide_bg() call StopVimInTerminal(buf) endfunc +" hl_pum_blend_attr() treated the CTERMCOLOR sentinel as a real near-white +" color, leaking white bg onto textprop-covered cells under pum opacity. +" Triggered by a textprop hl that only sets guisp. +func Test_pumopt_opacity_textprop_undercurl() + CheckScreendump + let lines =<< trim END + set termguicolors + set t_Cs= t_Ce= + set pumopt=opacity:50 + set completeopt=menu + call setline(1, '') + for i in range(8) + call append(line('$'), 'aaa bbb ccc ddd eee fff ggg hhh') + endfor + hi MyError guisp=#ec7279 + call prop_type_add('mytype', #{highlight: 'MyError', combine: 1}) + for s:l in range(2, 8) + call prop_add(s:l, 5, #{type: 'mytype', length: 20}) + endfor + normal gg + inoremap call complete(col('.'), + \ ['popup-item-1', 'popup-item-2', 'popup-item-3']) + END + call writefile(lines, 'Xpumoptopacitytextprop', 'D') + let buf = RunVimInTerminal('-S Xpumoptopacitytextprop', {}) + call TermWait(buf) + call term_sendkeys(buf, "i\") + call TermWait(buf, 100) + call VerifyScreenDump(buf, 'Test_pumopt_opacity_textprop_undercurl', {}) + call term_sendkeys(buf, "\\u") + call TermWait(buf) + call StopVimInTerminal(buf) +endfunc + " Test pumopt opacity when every other background line is shifted by one " narrow cell, so the background's wide-character boundaries do not align " with the popup's wide-character grid. Exercises the blend path when: diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim index 1bc9344df3..d6aa634c72 100644 --- a/src/testdir/test_popupwin.vim +++ b/src/testdir/test_popupwin.vim @@ -5120,6 +5120,40 @@ func Test_popup_opacity_vsplit() call StopVimInTerminal(buf) endfunc +func Test_popup_opacity_textprop_undercurl() + CheckScreendump + + " hl_blend_attr() treated the CTERMCOLOR sentinel as a real near-white + " color, leaking white bg onto textprop-covered cells under an opacity + " popup. Triggered by a textprop hl that only sets guisp. + let lines =<< trim END + set termguicolors + set t_Cs= t_Ce= + call setline(1, ['aaa bbb ccc ddd eee fff ggg hhh', + \ 'iii jjj kkk lll mmm nnn ooo ppp', + \ 'qqq rrr sss ttt uuu vvv www xxx', + \ 'yyy zzz 111 222 333 444 555 666']) + hi MyError guisp=#ec7279 + call prop_type_add('mytype', #{highlight: 'MyError', combine: 1}) + for s:l in range(1, line('$')) + call prop_add(s:l, 5, #{type: 'mytype', length: 20}) + endfor + hi PanelBg guibg=#0b1021 guifg=#e5e9f0 + call popup_create(['POPUP CONTENT', ' ', ' middle '], #{ + \ line: 1, col: 10, + \ minwidth: 30, minheight: 3, + \ opacity: 35, + \ highlight: 'PanelBg', + \ zindex: 200, + \ }) + END + call writefile(lines, 'XtestPopupOpacityTextprop', 'D') + let buf = RunVimInTerminal('-S XtestPopupOpacityTextprop', #{rows: 8, cols: 50}) + call VerifyScreenDump(buf, 'Test_popupwin_opacity_textprop_undercurl', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_popup_close_b_nwindows() edit Xfoo setlocal bufhidden=wipe diff --git a/src/version.c b/src/version.c index d79705029e..d77ebcc1f4 100644 --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 427, /**/ 426, /**/