]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0427: popup: opacity blend may leaks white bg color v9.2.0427
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Fri, 1 May 2026 16:10:21 +0000 (16:10 +0000)
committerChristian Brabandt <cb@256bit.org>
Fri, 1 May 2026 16:22:32 +0000 (16:22 +0000)
Problem:  popup: opacity blend may leaks white bg color
Solution: Add cterm color blending for 256 color terminals, use
          COLOR_INVALID() macro to check for invalid color
          (Yasuhiro Matsumoto)

When a textprop highlight only set gui=undercurl/guisp (no fg/bg), the
CTERMCOLOR sentinel was treated by hl_blend_attr() as a real near-white
color, leaking white bg onto textprop-covered cells under an opacity
popup or pum.  Add a cterm color blending path that approximates blends
in the xterm 256-color palette using the gui RGB when available, so
opacity now has a visible effect even without 'termguicolors' (in
256-color terminals).  Below 256 colors the blend is skipped.

Also document the requirement (GUI, 'termguicolors', or 256-color
terminal) and update existing pumopt/popupwin opacity screendumps to
reflect the new blended output.

closes: #20095

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
15 files changed:
runtime/doc/options.txt
runtime/doc/popup.txt
src/highlight.c
src/testdir/dumps/Test_popupwin_opacity_hl_80.dump
src/testdir/dumps/Test_popupwin_opacity_textprop_undercurl.dump [new file with mode: 0644]
src/testdir/dumps/Test_popupwin_opacity_zero_01.dump
src/testdir/dumps/Test_popupwin_opacity_zero_02.dump
src/testdir/dumps/Test_pumopt_opacity_50.dump
src/testdir/dumps/Test_pumopt_opacity_text_attrs.dump
src/testdir/dumps/Test_pumopt_opacity_textprop_undercurl.dump [new file with mode: 0644]
src/testdir/dumps/Test_pumopt_opacity_wide_bg.dump
src/testdir/dumps/Test_pumopt_opacity_wide_bg_shifted.dump
src/testdir/test_popup.vim
src/testdir/test_popupwin.vim
src/version.c

index d9dbcd8336ac96e00c169617685432d3203f3b54..b0f3f1c72b0dc9436db18fb1349bd89942feeba5 100644 (file)
@@ -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
index 6b99ac47ee96fcf06617c5514bb11f427b1090b2..250127b9b62e86daee37aef0fe7f6e0846634d4c 100644 (file)
@@ -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
index 67b551c67e96a8409127eeecf873aaaef6257e46..b21e489e617054071eec018bb65eaf5832741b51 100644 (file)
@@ -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
+
+/*
+ * 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;
+    }
 }
-# endif
 
+/*
+ * 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,
index 89700f9c400fb49340b4e8d266938697935f876d..d197c8dacf9288ef5e0d9355dafbb9d7659babac 100644 (file)
@@ -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+0&#5fd7ff255|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 (file)
index 0000000..2d29cfd
--- /dev/null
@@ -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| 
index 17aa447f864ba993b6c17bcc67ca082c9abd4100..1209af25c5dea0baa11a14f149148b7cbd9b9138 100644 (file)
@@ -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
index 03b9f4a59ff30f595fe8c07574e7dda8054463a4..c724edb7fe0f8c02a913b7f6512448e728949d51 100644 (file)
@@ -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
index d9e44da0f7160e84c95920f704949476c8187d5e..4490b1ef1d0538503011f4f5d3eb5d101496867c 100644 (file)
@@ -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
index 93ea3a5a4a73f17689008009f15844ee79675096..0911a881c92a0b1497398679f742bf65ec770c73 100644 (file)
@@ -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 (file)
index 0000000..8b9b0a0
--- /dev/null
@@ -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| 
index ed52522f01f3fac0e419d43abfc31b3e3928813d..87c7bd6710b0e7ea9c8c6f4a926a24153e207a44 100644 (file)
@@ -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| +0&#5f5f5f255|ほ*&|げ|げ*0#dadada255&|ほ|げ|漢|字| +&|│+0#0000001#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
+|│+0#0000001#ffd7ff255| +0&#875f87255|ふ*&|が|漢|字|げ*0#ffd7ff255&|漢|字| +&|│+0#0000001#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
+|│+0#0000001#ffd7ff255| +0&#875f87255|カ*&|タ|カ|ナ|候|補|字*0#ffd7ff255&| +&|│+0#0000001#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
 |╰+0#0000001#ffd7ff255|─@15|╯|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
 |ほ*&|げ|ほ|げ|ほ|げ|漢|字|テ|ス|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
 |ほ*&|げ|ほ|げ|ほ|げ|漢|字|テ|ス|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
index c145bf0800a1ccc9f453c1aff2c710412ac9e872..ef1b9acf825effb842ff907352c81bffeef59982 100644 (file)
@@ -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| +0&#5f5f5f255|ほ*&|げ| +0#dadada255&|げ*&|ほ|げ|漢|字|│+0#0000001#ffd7ff255| +0#0000000#ffffff0|ス*&|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@33
+|│+0#0000001#ffd7ff255| +0&#875f87255|ふ*&|が|漢|字|げ*0#ffd7ff255&|漢|字| +&|│+0#0000001#ffd7ff255|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
+|│+0#0000001#ffd7ff255| +0&#875f87255|カ*&|タ|カ|ナ|候|補| +0#ffd7ff255&|字*&|│+0#0000001#ffd7ff255| +0#0000000#ffffff0|ス*&|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@33
 |╰+0#0000001#ffd7ff255|─@15|╯|ス*0#0000000#ffffff0|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
 |a|ほ*&|げ|ほ|げ|ほ|げ|漢|字|テ|ス|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@33
 |ほ*&|げ|ほ|げ|ほ|げ|漢|字|テ|ス|ト|あ|い|う|え|お|カ|タ|カ|ナ| +&@34
index 3362cd14f0b5a86fdd0c827ab099a2ad89e1ce41..2ceaec945a03fd05e3a4c80cf9875e6cc74327c6 100644 (file)
@@ -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 <F5> <Cmd>call complete(col('.'),
+          \ ['popup-item-1', 'popup-item-2', 'popup-item-3'])<CR>
+  END
+  call writefile(lines, 'Xpumoptopacitytextprop', 'D')
+  let buf = RunVimInTerminal('-S Xpumoptopacitytextprop', {})
+  call TermWait(buf)
+  call term_sendkeys(buf, "i\<F5>")
+  call TermWait(buf, 100)
+  call VerifyScreenDump(buf, 'Test_pumopt_opacity_textprop_undercurl', {})
+  call term_sendkeys(buf, "\<C-E>\<Esc>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:
index 1bc9344df3e447f5df1590cb5843dd2659a9b343..d6aa634c72c9f12b6c2ed4425c81a65fce2c9abd 100644 (file)
@@ -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
index d79705029ead25a27fb7ff9bf49e4ece5ccf68d2..d77ebcc1f49f159592213e5d34efabf70033b6e3 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    427,
 /**/
     426,
 /**/