]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0452: screen.c popup opacity blend logic is duplicated v9.2.0452
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Thu, 7 May 2026 19:40:16 +0000 (19:40 +0000)
committerChristian Brabandt <cb@256bit.org>
Thu, 7 May 2026 19:40:16 +0000 (19:40 +0000)
Problem:  screen_line() has four near-identical blocks computing
          the popup_attr, the combined attr, the blend value and
          the underlying base attr in sequence when handling popups
          with opacity.  The duplication makes the function long
          and hard to follow, and changes have to be applied to all
          four sites.
Solution: Extract the shared computation into popup_blend_with_base()
          and popup_base_attr_or() helpers, and cache per-popup
          attrs once via popup_opacity_T.  No behavior change
          (Yasuhiro Matsumoto).

closes: #20154

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/screen.c
src/version.c

index da4d6e2783a0a20036d17bd034489cc65528101f..ec2babfef6b25d3ccc01ac1f1d79b6e1155221e1 100644 (file)
@@ -468,6 +468,52 @@ skip_for_popup(int row, int col)
 }
 
 #ifdef FEAT_PROP_POPUP
+/*
+ * Cached attributes of the current opacity popup, computed once per
+ * screen_line() to avoid recomputing them inside the per-cell loop.
+ */
+typedef struct {
+    int                popup_attr;     // get_win_attr(screen_opacity_popup)
+    int                blend;          // screen_opacity_popup->w_popup_blend
+} popup_opacity_T;
+
+    static inline void
+popup_opacity_init(popup_opacity_T *po)
+{
+    po->popup_attr = get_win_attr(screen_opacity_popup);
+    po->blend = screen_opacity_popup->w_popup_blend;
+}
+
+/*
+ * Read the underlying base screen attribute at (row, col), falling back
+ * to "fallback" when the position is outside base_screenattrs.
+ */
+    static inline int
+popup_base_attr_or(int row, int col, int fallback)
+{
+    int                base = fallback;
+
+    popup_get_base_screen_cell(row, col, NULL, &base, NULL);
+    return base;
+}
+
+/*
+ * Blend "char_attr" through the cached opacity popup over "base_attr".
+ * "blend_fg" is TRUE when both fg and bg should blend (space cells), FALSE
+ * when only bg should blend so the popup fg is preserved (text cells).
+ */
+    static inline int
+popup_blend_with_base(
+       const popup_opacity_T   *po,
+       int                     char_attr,
+       int                     base_attr,
+       int                     blend_fg)
+{
+    int                combined = hl_combine_attr(po->popup_attr, char_attr);
+
+    return hl_blend_attr(base_attr, combined, po->blend, blend_fg);
+}
+
 /*
  * For a double-wide character at a popup boundary with opacity:0
  * (blend==100), the two cells may have different underlying attrs.
@@ -558,6 +604,14 @@ screen_line(
                                        // 2: occupies two display cells
     bool           override_success =
        push_highlight_overrides(wp->w_hl, wp->w_hl_len);
+#ifdef FEAT_PROP_POPUP
+    popup_opacity_T po;                        // cached when drawing an opacity popup
+    int                    drawing_opacity_popup =
+                               screen_opacity_popup != NULL
+                               && (flags & SLF_POPUP);
+    if (drawing_opacity_popup)
+       popup_opacity_init(&po);
+#endif
 
     // Check for illegal row and col, just in case.
     if (row >= Rows)
@@ -674,128 +728,100 @@ screen_line(
        // For popup with opacity windows: if drawing a space, show the
        // underlying character with the popup's attributes blended in.
        int opacity_blank = FALSE;
-       // Check if the popup is drawing a space and the background is the
-       // second cell of a wide character. Skip drawing to preserve the
-       // wide character that was drawn in the previous cell.
-       if (screen_opacity_popup != NULL
-               && (flags & SLF_POPUP)
-               && ScreenLines[off_from] == ' '
-               && (!enc_utf8 || ScreenLinesUC[off_from] == 0)
-               && ScreenLines[off_to] == 0
-               && (!enc_utf8 || ScreenLinesUC[off_to] == 0)
-               && off_to > 0
-               && enc_utf8 && ScreenLinesUC[off_to - 1] != 0
-               && utf_char2cells(ScreenLinesUC[off_to - 1]) == 2)
-       {
-           // This is the second cell of a wide character. Don't overwrite it.
-           opacity_blank = TRUE;
-           redraw_this = FALSE;
-       }
-       else if (screen_opacity_popup != NULL
-               && (flags & SLF_POPUP)
+       // Popup with opacity: when drawing a space, blend the popup attrs
+       // onto whatever is underneath instead of overwriting it.
+       if (drawing_opacity_popup
                && ScreenLines[off_from] == ' '
-               && (!enc_utf8 || ScreenLinesUC[off_from] == 0)
-               && (ScreenLines[off_to] != 0
-                   || (enc_utf8 && ScreenLinesUC[off_to] != 0)))
+               && (!enc_utf8 || ScreenLinesUC[off_from] == 0))
        {
-           int bg_char_cells = 1;
-           if (enc_utf8 && ScreenLinesUC[off_to] != 0)
-               bg_char_cells = utf_char2cells(ScreenLinesUC[off_to]);
-
-           // For wide background character, check if the next popup cell
-           // is also a space.  If not, the wide char would be partially
-           // covered by a popup character, so don't show it.
-           if (bg_char_cells == 2)
+           int popup_src_attr = ScreenAttrs[off_from];
+           int bg_is_empty = ScreenLines[off_to] == 0
+                               && (!enc_utf8 || ScreenLinesUC[off_to] == 0);
+
+           // Case A: target cell is the second half of a wide char from a
+           // previous draw.  Don't overwrite it.
+           if (bg_is_empty
+                   && off_to > 0
+                   && enc_utf8 && ScreenLinesUC[off_to - 1] != 0
+                   && utf_char2cells(ScreenLinesUC[off_to - 1]) == 2)
            {
-               if (col + 1 >= endcol || off_from + 1 >= max_off_from
-                                                  || off_to + 1 >= max_off_to)
+               opacity_blank = TRUE;
+               redraw_this = FALSE;
+           }
+           // Case B: target cell holds a real character — blend popup attrs
+           // over it, preserving the underlying glyph.
+           else if (ScreenLines[off_to] != 0
+                   || (enc_utf8 && ScreenLinesUC[off_to] != 0))
+           {
+               int bg_char_cells = (enc_utf8 && ScreenLinesUC[off_to] != 0)
+                               ? utf_char2cells(ScreenLinesUC[off_to]) : 1;
+
+               if (bg_char_cells == 2)
                {
-                   // Wide char doesn't fit at the edge.  Replace with a
-                   // blended space so opacity is still applied.
-                   int char_attr = ScreenAttrs[off_from];
-                   int popup_attr = get_win_attr(screen_opacity_popup);
-                   int combined = hl_combine_attr(popup_attr, char_attr);
-                   int blend = screen_opacity_popup->w_popup_blend;
-                   ScreenLines[off_to] = ' ';
-                   if (enc_utf8)
-                       ScreenLinesUC[off_to] = 0;
-                   int base_attr = ScreenAttrs[off_to];
-                   popup_get_base_screen_cell(row, col + coloff,
-                                               NULL, &base_attr, NULL);
-                   ScreenAttrs[off_to] = hl_blend_attr(base_attr,
-                                                   combined, blend, TRUE);
-                   screen_char(off_to, row, col + coloff);
-                   opacity_blank = TRUE;
-                   redraw_this = FALSE;
-                   goto skip_opacity;
+                   if (col + 1 >= endcol || off_from + 1 >= max_off_from
+                                                      || off_to + 1 >= max_off_to)
+                   {
+                       // Wide char doesn't fit at the edge.  Replace with a
+                       // blended space so opacity is still applied.
+                       int base_attr;
+                       ScreenLines[off_to] = ' ';
+                       if (enc_utf8)
+                           ScreenLinesUC[off_to] = 0;
+                       base_attr = popup_base_attr_or(row, col + coloff,
+                                                            ScreenAttrs[off_to]);
+                       ScreenAttrs[off_to] = popup_blend_with_base(&po,
+                                           popup_src_attr, base_attr, TRUE);
+                       screen_char(off_to, row, col + coloff);
+                       opacity_blank = TRUE;
+                       redraw_this = FALSE;
+                       goto skip_opacity;
+                   }
+                   // Wide bg char must be followed by a popup space, otherwise
+                   // a popup glyph would corrupt its right half.
+                   if (!(ScreenLines[off_from + 1] == ' '
+                           && (!enc_utf8 || ScreenLinesUC[off_from + 1] == 0)))
+                       goto skip_opacity;
                }
-               int next_off_from = off_from + 1;
-               if (!(ScreenLines[next_off_from] == ' '
-                       && (!enc_utf8 || ScreenLinesUC[next_off_from] == 0)))
+
+               // Keep the underlying character and blend the popup attrs over
+               // it.  blend_fg=TRUE because we're drawing a space, so the
+               // foreground (the underlying glyph) should also blend.
+               opacity_blank = TRUE;
+               int base_attr = popup_base_attr_or(row, col + coloff,
+                                                            ScreenAttrs[off_to]);
+               ScreenAttrs[off_to] = popup_blend_with_base(&po,
+                                           popup_src_attr, base_attr, TRUE);
+               screen_char(off_to, row, col + coloff);
+               // For wide bg char also blend the second cell; its base may be
+               // outside the popup area.
+               if (bg_char_cells == 2)
                {
-                   // Next cell is not a space, don't show the wide char.
-                   goto skip_opacity;
+                   int base_attr2 = popup_base_attr_or(row, col + coloff + 1,
+                                                        ScreenAttrs[off_to + 1]);
+                   ScreenAttrs[off_to + 1] = popup_blend_with_base(&po,
+                                           popup_src_attr, base_attr2, TRUE);
+                   if (po.blend == 100)
+                       resolve_wide_char_opacity_attrs(row,
+                               col + coloff, col + coloff + 1,
+                               &ScreenAttrs[off_to], &ScreenAttrs[off_to + 1]);
                }
+               redraw_this = FALSE;
            }
-
-           opacity_blank = TRUE;
-           // Keep the underlying character and blend its foreground color
-           // from popup background color to original color.
-           // Combine the popup window color with the character's own
-           // attribute (e.g. match highlight) so that its background
-           // color is preserved on blank cells.
-           int char_attr = ScreenAttrs[off_from];
-           int popup_attr = get_win_attr(screen_opacity_popup);
-           int combined = hl_combine_attr(popup_attr, char_attr);
-           int blend = screen_opacity_popup->w_popup_blend;
-           int base_attr = ScreenAttrs[off_to];
-           popup_get_base_screen_cell(row, col + coloff,
-                                           NULL, &base_attr, NULL);
-           ScreenAttrs[off_to] = hl_blend_attr(base_attr,
-                                           combined, blend, TRUE);
-           screen_char(off_to, row, col + coloff);
-           // For wide background character, also update the second cell
-           // with its own base attr (it may be outside the popup area).
-           if (bg_char_cells == 2)
+           // Case C: popup space overlaps the second half of a destroyed wide
+           // character.  Blend so the cell matches its neighbors instead of
+           // appearing as a solid-colored gap.
+           else if (bg_is_empty)
            {
-               int base_attr2 = ScreenAttrs[off_to + 1];
-               popup_get_base_screen_cell(row, col + coloff + 1,
-                                               NULL, &base_attr2, NULL);
-               ScreenAttrs[off_to + 1] = hl_blend_attr(base_attr2,
-                                               combined, blend, TRUE);
-               if (blend == 100)
-                   resolve_wide_char_opacity_attrs(row,
-                           col + coloff, col + coloff + 1,
-                           &ScreenAttrs[off_to], &ScreenAttrs[off_to + 1]);
+               int base_attr;
+               ScreenLines[off_to] = ' ';
+               base_attr = popup_base_attr_or(row, col + coloff,
+                                                            ScreenAttrs[off_to]);
+               ScreenAttrs[off_to] = popup_blend_with_base(&po,
+                                           popup_src_attr, base_attr, TRUE);
+               screen_char(off_to, row, col + coloff);
+               opacity_blank = TRUE;
+               redraw_this = FALSE;
            }
-           redraw_this = FALSE;
-       }
-       // When a popup space overlaps the second half of a destroyed wide
-       // character (e.g., the first half was overwritten by popup content),
-       // the underlying cell has ScreenLines == 0 and no valid wide char
-       // at the previous cell.  Apply opacity blending so that the cell
-       // matches surrounding opacity-blended cells instead of appearing
-       // as a solid-colored gap.
-       else if (screen_opacity_popup != NULL
-               && (flags & SLF_POPUP)
-               && ScreenLines[off_from] == ' '
-               && (!enc_utf8 || ScreenLinesUC[off_from] == 0)
-               && ScreenLines[off_to] == 0
-               && (!enc_utf8 || ScreenLinesUC[off_to] == 0))
-       {
-           int char_attr = ScreenAttrs[off_from];
-           int popup_attr = get_win_attr(screen_opacity_popup);
-           int combined = hl_combine_attr(popup_attr, char_attr);
-           int blend = screen_opacity_popup->w_popup_blend;
-           ScreenLines[off_to] = ' ';
-           int base_attr = ScreenAttrs[off_to];
-           popup_get_base_screen_cell(row, col + coloff,
-                                           NULL, &base_attr, NULL);
-           ScreenAttrs[off_to] = hl_blend_attr(base_attr,
-                                           combined, blend, TRUE);
-           screen_char(off_to, row, col + coloff);
-           opacity_blank = TRUE;
-           redraw_this = FALSE;
        }
 skip_opacity:
 #endif
@@ -947,49 +973,37 @@ skip_opacity:
 
            ScreenAttrs[off_to] = ScreenAttrs[off_from];
 #ifdef FEAT_PROP_POPUP
-           // For popup with opacity text: blend background with underlying.
-           if (screen_opacity_popup != NULL
-                   && (flags & SLF_POPUP)
-                   && screen_opacity_popup->w_popup_blend > 0)
+           // For popup with opacity text: blend popup attrs over the underlying
+           // base.  blend_fg=FALSE so the popup foreground (the real glyph) is
+           // preserved while only the background blends.
+           if (drawing_opacity_popup && po.blend > 0)
            {
-               int char_attr = ScreenAttrs[off_from];
-               int popup_attr = get_win_attr(screen_opacity_popup);
-               int blend = screen_opacity_popup->w_popup_blend;
-               int combined = hl_combine_attr(popup_attr, char_attr);
-               int underlying_attr = 0;
-
-               popup_get_base_screen_cell(row, col + coloff,
-                                               NULL, &underlying_attr, NULL);
-
-               // For double-wide characters, a terminal cannot render
-               // different background colors for the left and right
-               // halves.  When one half is over a lower opacity popup
-               // and the other is not, use the non-popup side's
-               // underlying attr for both to avoid color leaking.
+               int popup_src_attr = ScreenAttrs[off_from];
+               int scol1 = col + coloff;
+               int base1 = popup_base_attr_or(row, scol1, 0);
+
                if (char_cells == 2)
                {
-                   int underlying_attr2 = 0;
-                   int scol1 = col + coloff;
+                   int base2 = popup_base_attr_or(row, scol1 + 1, 0);
+
+                   // Terminals can't render different bg colors for the two
+                   // halves of a wide char.  If one half is over a lower
+                   // opacity popup and the other isn't, use the non-popup
+                   // side for both halves to avoid color leaking.
                    int over1 = popup_is_over_opacity(row, scol1);
                    int over2 = popup_is_over_opacity(row, scol1 + 1);
-
-                   popup_get_base_screen_cell(row, scol1 + 1,
-                                               NULL, &underlying_attr2, NULL);
                    if (over1 != over2)
                    {
-                       // One half is over a lower popup, the other is
-                       // not.  Use the non-popup side for both.
                        if (over1)
-                           underlying_attr = underlying_attr2;
+                           base1 = base2;
                        else
-                           underlying_attr2 = underlying_attr;
+                           base2 = base1;
                    }
-                   ScreenAttrs[off_to + 1] = hl_blend_attr(
-                                       underlying_attr2, combined, blend,
-                                       FALSE);
+                   ScreenAttrs[off_to + 1] = popup_blend_with_base(&po,
+                                           popup_src_attr, base2, FALSE);
                }
-               ScreenAttrs[off_to] = hl_blend_attr(underlying_attr,
-                                               combined, blend, FALSE);
+               ScreenAttrs[off_to] = popup_blend_with_base(&po,
+                                           popup_src_attr, base1, FALSE);
            }
            else
 #endif
index 8ab3ed1d49b5fcfdc21dcfc900333c790a2cc27c..9d5136df606707f95778e25ce20cdd2d84a7750b 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    452,
 /**/
     451,
 /**/