patch 9.2.0189: MS-Windows: opacity popups flicker during redraw in the console

Commit: 
https://github.com/vim/vim/commit/019c53b37f1b2cf16825cc6f1fa38c25a6f7a9bd
Author: Yasuhiro Matsumoto <[email protected]>
Date:   Tue Mar 17 20:51:22 2026 +0000

    patch 9.2.0189: MS-Windows: opacity popups flicker during redraw in the 
console
    
    Problem:  When using transparent popups in the Win32 console, redrawing
              background windows causes flickering. This happens because
              the background is drawn opaquely before the popup blends
              and draws on top.
    Solution: Implement a Z-index mask  to suppress screen_char() output for
              cells covered by an opacity popup. Disable the Clear-to-EOL
              (T_CE) optimization for lines overlapping these popups to
              prevent accidental erasure (Yasuhiro Matsumoto).
    
    closes: #19697
    Signed-off-by: Yasuhiro Matsumoto <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/popupwin.c b/src/popupwin.c
index 23588c7ca..fe06777f7 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -4256,6 +4256,41 @@ popup_need_position_adjust(win_T *wp)
     return wp->w_cursor.lnum != wp->w_popup_last_curline;
 }
 
+// Cached array with max zindex of opacity popups covering each cell.
+// Allocated in may_update_popup_mask() when opacity popups exist.
+static short *opacity_zindex = NULL;
+static int    opacity_zindex_rows = 0;
+static int    opacity_zindex_cols = 0;
+
+/*
+ * Mark cells covered by opacity popup "wp" in opacity_zindex[].
+ * Stores the maximum zindex so that lower popups can be suppressed too.
+ */
+    static void
+popup_mark_opacity_zindex(win_T *wp)
+{
+    int            width;
+    int            height;
+    int            r, c;
+
+    if (!(wp->w_popup_flags & POPF_OPACITY) || wp->w_popup_blend <= 0
+           || (wp->w_popup_flags & POPF_HIDDEN))
+       return;
+
+    width = popup_width(wp);
+    height = popup_height(wp);
+    for (r = wp->w_winrow;
+                      r < wp->w_winrow + height && r < screen_Rows; ++r)
+       for (c = wp->w_wincol;
+                c < wp->w_wincol + width - wp->w_popup_leftoff
+                                               && c < screen_Columns; ++c)
+       {
+           int off = r * screen_Columns + c;
+           if (wp->w_zindex > opacity_zindex[off])
+               opacity_zindex[off] = wp->w_zindex;
+       }
+}
+
 /*
  * Force background windows to redraw rows under an opacity popup.
  */
@@ -4278,16 +4313,55 @@ redraw_win_under_opacity_popup(win_T *wp)
        win_T       *twp;
 
        twp = mouse_find_win(&line_cp, &col_cp, IGNORE_POPUP);
-       if (twp != NULL && line_cp < twp->w_height)
+       if (twp != NULL)
        {
-           linenr_T lnum;
+           if (line_cp < twp->w_height)
+           {
+               linenr_T lnum;
 
-           (void)mouse_comp_pos(twp, &line_cp, &col_cp, &lnum, NULL);
-           redrawWinline(twp, lnum);
+               (void)mouse_comp_pos(twp, &line_cp, &col_cp, &lnum, NULL);
+               redrawWinline(twp, lnum);
+           }
+           else if (line_cp == twp->w_height)
+               // Status bar line: mark for redraw to prevent
+               // opacity blend accumulation.
+               twp->w_redr_status = TRUE;
        }
     }
 }
 
+
+/*
+ * Return TRUE if cell (row, col) is covered by a higher-zindex opacity popup.
+ */
+    int
+popup_is_under_opacity(int row, int col)
+{
+    if (opacity_zindex == NULL
+           || row < 0 || row >= opacity_zindex_rows
+           || col < 0 || col >= opacity_zindex_cols)
+       return FALSE;
+    return opacity_zindex[row * opacity_zindex_cols + col] > screen_zindex;
+}
+
+/*
+ * Return TRUE if any cell in row "row" from "start_col" to "end_col"
+ * (exclusive) is covered by a higher-zindex opacity popup.
+ */
+    int
+popup_is_under_opacity_range(int row, int start_col, int end_col)
+{
+    int col;
+
+    if (opacity_zindex == NULL
+           || row < 0 || row >= opacity_zindex_rows)
+       return FALSE;
+    for (col = start_col; col < end_col && col < opacity_zindex_cols; ++col)
+       if (opacity_zindex[row * opacity_zindex_cols + col] > screen_zindex)
+           return TRUE;
+    return FALSE;
+}
+
 /*
  * Update "popup_mask" if needed.
  * Also recomputes the popup size and positions.
@@ -4330,10 +4404,58 @@ may_update_popup_mask(int type)
     // wouldn't normally be redrawn.  Without this, ScreenAttrs retains
     // blended values from the previous cycle, causing blend accumulation.
     // This must run every cycle, not just when popup_mask_refresh is set.
-    FOR_ALL_POPUPWINS(wp)
-       redraw_win_under_opacity_popup(wp);
-    FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
-       redraw_win_under_opacity_popup(wp);
+    //
+    // Also build the opacity_zindex array used by screen_char() to suppress
+    // output for cells under opacity popups during background draw.
+    {
+       int has_opacity = FALSE;
+
+       FOR_ALL_POPUPWINS(wp)
+       {
+           redraw_win_under_opacity_popup(wp);
+           if ((wp->w_popup_flags & POPF_OPACITY)
+                   && wp->w_popup_blend > 0
+                   && !(wp->w_popup_flags & POPF_HIDDEN))
+               has_opacity = TRUE;
+       }
+       FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
+       {
+           redraw_win_under_opacity_popup(wp);
+           if ((wp->w_popup_flags & POPF_OPACITY)
+                   && wp->w_popup_blend > 0
+                   && !(wp->w_popup_flags & POPF_HIDDEN))
+               has_opacity = TRUE;
+       }
+
+       if (!has_opacity)
+       {
+           VIM_CLEAR(opacity_zindex);
+           opacity_zindex_rows = 0;
+           opacity_zindex_cols = 0;
+       }
+       else
+       {
+           if (opacity_zindex_rows != screen_Rows
+                   || opacity_zindex_cols != screen_Columns)
+           {
+               vim_free(opacity_zindex);
+               opacity_zindex = LALLOC_MULT(short,
+                                       screen_Rows * screen_Columns);
+               opacity_zindex_rows = screen_Rows;
+               opacity_zindex_cols = screen_Columns;
+           }
+           if (opacity_zindex != NULL)
+           {
+               vim_memset(opacity_zindex, 0,
+                   (size_t)screen_Rows * screen_Columns * sizeof(short));
+
+               FOR_ALL_POPUPWINS(wp)
+                   popup_mark_opacity_zindex(wp);
+               FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
+                   popup_mark_opacity_zindex(wp);
+           }
+       }
+    }
 
     if (!popup_mask_refresh)
        return;
diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro
index ee8efbb14..d6ac2b72a 100644
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -52,6 +52,8 @@ win_T *find_next_popup(int lowest, int handled_flag);
 int popup_do_filter(int c);
 int popup_no_mapping(void);
 void popup_check_cursor_pos(void);
+int popup_is_under_opacity(int row, int col);
+int popup_is_under_opacity_range(int row, int start_col, int end_col);
 void may_update_popup_mask(int type);
 void may_update_popup_position(void);
 int popup_get_base_screen_cell(int row, int col, schar_T *linep, int *attrp, 
u8char_T *ucp);
diff --git a/src/screen.c b/src/screen.c
index acef4d38b..48261009f 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -600,7 +600,13 @@ screen_line(
     {
        ScreenLines[off_to - 1] = ' ';
        ScreenLinesUC[off_to - 1] = 0;
-       screen_char(off_to - 1, row, col + coloff - 1);
+       // Skip screen output when drawing an opacity popup: the
+       // background draw already output this cell, and outputting
+       // a space here would briefly erase it causing flicker.
+# ifdef FEAT_PROP_POPUP
+       if (screen_opacity_popup == NULL)
+# endif
+           screen_char(off_to - 1, row, col + coloff - 1);
     }
 #endif
 
@@ -1004,7 +1010,13 @@ skip_opacity:
        ScreenLines[off_to] = ' ';
        if (enc_utf8)
            ScreenLinesUC[off_to] = 0;
-       screen_char(off_to, row, col + coloff);
+       // Skip screen output when drawing an opacity popup: the
+       // background already has this cell, outputting a space here
+       // would briefly erase it causing flicker.
+#ifdef FEAT_PROP_POPUP
+       if (screen_opacity_popup == NULL)
+#endif
+           screen_char(off_to, row, col + coloff);
     }
 
     if (clear_width > 0
@@ -2224,6 +2236,23 @@ screen_char(unsigned off, int row, int col)
     if (row >= screen_Rows || col >= screen_Columns)
        return;
 
+#ifdef FEAT_PROP_POPUP
+    // If this cell is under a higher-zindex opacity popup, suppress
+    // output to prevent flicker.  The higher popup's redraw will
+    // output the final blended result.
+    // Also suppress if this is a wide character whose second cell
+    // is under an opacity popup.
+    if (popup_is_under_opacity(row, col)
+           || (enc_utf8 && ScreenLinesUC[off] != 0
+               && utf_char2cells(ScreenLinesUC[off]) == 2
+               && col + 1 < screen_Columns
+               && popup_is_under_opacity(row, col + 1)))
+    {
+       screen_cur_col = 9999;
+       return;
+    }
+#endif
+
     // Outputting a character in the last cell on the screen may scroll the
     // screen up.  Only do it when the "xn" termcap property is set, otherwise
     // mark the character invalid (update it when scrolled up).
@@ -2331,6 +2360,15 @@ screen_char_2(unsigned off, int row, int col)
        return;
     }
 
+#ifdef FEAT_PROP_POPUP
+    // If under a higher-zindex opacity popup, suppress output.
+    if (popup_is_under_opacity(row, col))
+    {
+       screen_cur_col = 9999;
+       return;
+    }
+#endif
+
     // Output the first byte normally (positions the cursor), then write the
     // second byte directly.
     screen_char(off, row, col);
@@ -2498,7 +2536,15 @@ screen_fill(
                && (attr == 0
                    || (norm_term
                        && attr <= HL_ALL
-                       && ((attr & ~(HL_BOLD | HL_ITALIC)) == 0))))
+                       && ((attr & ~(HL_BOLD | HL_ITALIC)) == 0)))
+#ifdef FEAT_PROP_POPUP
+               // Do not use T_CE optimization if any cell in the
+               // range is under an opacity popup.  The clear-to-eol
+               // command would erase the popup area on screen.
+               && !popup_is_under_opacity_range(row,
+                                               start_col, end_col)
+#endif
+               )
        {
            /*
             * check if we really need to clear something
@@ -3461,6 +3507,17 @@ windgoto(int row, int col)
                        break;
                    }
            }
+#ifdef FEAT_PROP_POPUP
+           // Don't output characters over opacity popup cells, it
+           // would show unblended background values.
+           if (cost < 999)
+               for (i = wouldbe_col; i < col; ++i)
+                   if (popup_is_under_opacity(row, i))
+                   {
+                       cost = 999;
+                       break;
+                   }
+#endif
        }
 
        /*
diff --git a/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump 
b/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
index 9ec6d5ba6..be464f772 100644
--- a/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
+++ b/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
@@ -1,8 +1,8 @@
 >い*0&#ffffff0|え|ー@15|い|!+&| |1| @3
 |い*&|え|ー@15|い|!+&| |2| @3
 |い*&|え|ー@15|い|!+&| |3| @3
-|い*&|え*0#ffffff16#e000002|ー@6| |ー*0#0000000#ffffff0@7|い|!+&| |4| @3
-|い*&| +0#ffffff16#e000002|カ*&|ラ|フ|ル|な| +&|ー*&@1| |ー*0#0000000#ffffff0@7|い|!+&| 
|5| @3
+|い*&|え*0#ffffff16#e000002|ー@6| +&| +0#0000000#ffffff0|ー*&@7|い|!+&| |4| @3
+|い*&| +0#ffffff16#e000002|カ*&|ラ|フ|ル|な| +&|ー*&@1| +&| 
+0#0000000#ffffff0|ー*&@7|い|!+&| |5| @3
 |い*&| +0#ffffff16#e000002|ポ*&|ッ|プ|ア|ッ|プ|で|─+&|╮| 
+0#0000000#ffffff0|ー*&@7|い|!+&| |6| @3
 |い*&| +0#ffffff16#e000002|最*&|上|川| +&|ぼ*&|赤|い|な|│+&| 
+0#0000000#ffffff0|ー*&@7|い|!+&| |7| @3
 |い*&| +0#ffffff16#e000002|│|あ*&|い|う|え|お|ー@1|│+&| 
+0#0000000#ffffff0|ー*&@7|い|!+&| |8| @3
diff --git a/src/version.c b/src/version.c
index 295f8ed0b..d7c480a67 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    189,
 /**/
     188,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1w2bWG-00AU3b-07%40256bit.org.

Raspunde prin e-mail lui