From: Yasuhiro Matsumoto Date: Wed, 10 Jun 2026 20:24:48 +0000 (+0000) Subject: patch 9.2.0614: opacity popup leaves stale cells X-Git-Tag: v9.2.0614^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fab9ce9996f3d0b2528db010118d733b9dc6b5a8;p=thirdparty%2Fvim.git patch 9.2.0614: opacity popup leaves stale cells Problem: Background redraws under an opacity popup update ScreenLines[] but suppress terminal output, so the terminal no longer matches ScreenLines[] for those cells. Later draws skipped them as "unchanged", leaving parts of the old popup on screen after popup_settext() or popup_clear(). Solution: Track cells whose output was suppressed under an opacity popup and force their next output. fixes: #20459 closes: #20471 Signed-off-by: Yasuhiro Matsumoto Signed-off-by: Christian Brabandt --- diff --git a/src/screen.c b/src/screen.c index 97d2f189cb..7ade748b71 100644 --- a/src/screen.c +++ b/src/screen.c @@ -467,6 +467,56 @@ skip_for_popup(int row, int col) return FALSE; } +#ifdef FEAT_PROP_POPUP +// Cells where ScreenLines[] was updated but terminal output was suppressed +// because the cell was under an opacity popup. For these cells the terminal +// no longer matches ScreenLines[], so the "cell unchanged, skip output" +// optimization must not be applied until they are output again. +static char_u *suppressed_cells = NULL; +static int suppressed_rows = 0; +static int suppressed_cols = 0; + + static void +mark_suppressed_cell(int row, int col) +{ + if (suppressed_cells == NULL + || suppressed_rows != screen_Rows + || suppressed_cols != screen_Columns) + { + vim_free(suppressed_cells); + suppressed_cells = alloc_clear( + (size_t)screen_Rows * screen_Columns); + if (suppressed_cells == NULL) + { + suppressed_rows = 0; + suppressed_cols = 0; + return; + } + suppressed_rows = screen_Rows; + suppressed_cols = screen_Columns; + } + suppressed_cells[row * suppressed_cols + col] = TRUE; +} + + static void +unmark_suppressed_cell(int row, int col) +{ + if (suppressed_cells != NULL + && row >= 0 && row < suppressed_rows + && col >= 0 && col < suppressed_cols) + suppressed_cells[row * suppressed_cols + col] = FALSE; +} + + static int +is_suppressed_cell(int row, int col) +{ + return suppressed_cells != NULL + && row >= 0 && row < suppressed_rows + && col >= 0 && col < suppressed_cols + && suppressed_cells[row * suppressed_cols + col]; +} +#endif + #ifdef FEAT_PROP_POPUP /* * Cached attributes of the current opacity popup, computed once per @@ -696,6 +746,13 @@ screen_line( redraw_next = force || char_needs_redraw(off_from + char_cells, off_to + char_cells, endcol - col - char_cells); +#ifdef FEAT_PROP_POPUP + // A cell whose output was suppressed under an opacity popup does not + // match the terminal even when ScreenLines[] is unchanged. + if (!redraw_this && is_suppressed_cell(row, col + coloff)) + redraw_this = TRUE; +#endif + #ifdef FEAT_GUI # ifdef FEAT_GUI_MSWIN changed_this = changed_next; @@ -1079,7 +1136,11 @@ skip_opacity: // blank out the rest of the line while (col < clear_width && ScreenLines[off_to] == ' ' && ScreenAttrs[off_to] == 0 - && (!enc_utf8 || ScreenLinesUC[off_to] == 0)) + && (!enc_utf8 || ScreenLinesUC[off_to] == 0) +#ifdef FEAT_PROP_POPUP + && !is_suppressed_cell(row, col + coloff) +#endif + ) { ScreenCols[off_to] = (flags & SLF_INC_VCOL) ? ++last_vcol : last_vcol; @@ -1892,7 +1953,13 @@ screen_puts_len( || (ScreenLinesUC[off] != 0 && screen_comp_differs(off, u8cc)))) || ScreenAttrs[off] != cell_attr - || exmode_active; + || exmode_active +#ifdef FEAT_PROP_POPUP + // Output was suppressed under an opacity popup: the terminal + // does not match ScreenLines[] even when unchanged. + || is_suppressed_cell(row, col) +#endif + ; if ((need_redraw || force_redraw_this) && !skip_for_popup(row, col)) { @@ -2450,6 +2517,12 @@ screen_char(unsigned off, int row, int col) ScreenLinesUC[off2] = 0; screen_char(off2, row, col + 1); } + mark_suppressed_cell(row, col); + if (enc_utf8 && ScreenLinesUC[off] != 0 + && utf_char2cells(ScreenLinesUC[off]) == 2 + && col + 1 < screen_Columns + && popup_is_under_opacity(row, col + 1)) + mark_suppressed_cell(row, col + 1); screen_cur_col = 9999; return; } @@ -2458,6 +2531,8 @@ screen_char(unsigned off, int row, int col) && col + 1 < screen_Columns && popup_is_under_opacity(row, col + 1)) { + mark_suppressed_cell(row, col); + mark_suppressed_cell(row, col + 1); screen_cur_col = 9999; return; } @@ -2491,6 +2566,14 @@ screen_char(unsigned off, int row, int col) windgoto(row, col); +#ifdef FEAT_PROP_POPUP + // The cell is output below, the terminal matches ScreenLines again. + unmark_suppressed_cell(row, col); + if (enc_utf8 && ScreenLinesUC[off] != 0 + && utf_char2cells(ScreenLinesUC[off]) == 2) + unmark_suppressed_cell(row, col + 1); +#endif + if (screen_attr != attr) screen_start_highlight(attr); @@ -2574,6 +2657,8 @@ screen_char_2(unsigned off, int row, int col) // If under a higher-zindex opacity popup, suppress output. if (popup_is_under_opacity(row, col)) { + mark_suppressed_cell(row, col); + mark_suppressed_cell(row, col + 1); screen_cur_col = 9999; return; } @@ -2583,6 +2668,9 @@ screen_char_2(unsigned off, int row, int col) // second byte directly. screen_char(off, row, col); out_char(ScreenLines[off + 1]); +#ifdef FEAT_PROP_POPUP + unmark_suppressed_cell(row, col + 1); +#endif ++screen_cur_col; } @@ -2770,11 +2858,21 @@ screen_fill( // skip blanks (used often, keep it fast!) if (enc_utf8) while (off < end_off && ScreenLines[off] == ' ' - && ScreenAttrs[off] == 0 && ScreenLinesUC[off] == 0) + && ScreenAttrs[off] == 0 && ScreenLinesUC[off] == 0 +#ifdef FEAT_PROP_POPUP + && !is_suppressed_cell(row, + (int)(off - LineOffset[row])) +#endif + ) ++off; else while (off < end_off && ScreenLines[off] == ' ' - && ScreenAttrs[off] == 0) + && ScreenAttrs[off] == 0 +#ifdef FEAT_PROP_POPUP + && !is_suppressed_cell(row, + (int)(off - LineOffset[row])) +#endif + ) ++off; if (off < end_off) // something to be cleared { @@ -2787,6 +2885,10 @@ screen_fill( while (col--) // clear chars in ScreenLines { space_to_screenline(off, 0); +#ifdef FEAT_PROP_POPUP + // T_CE cleared the terminal cell, it matches again. + unmark_suppressed_cell(row, (int)(off - LineOffset[row])); +#endif ++off; } } @@ -2804,6 +2906,12 @@ screen_fill( || must_redraw == UPD_CLEAR // screen clear pending #if defined(FEAT_GUI) || defined(UNIX) || force_next +#endif +#ifdef FEAT_PROP_POPUP + // Output was suppressed under an opacity popup: the + // terminal does not match ScreenLines[] even when + // unchanged. + || is_suppressed_cell(row, col) #endif ) // Skip if under a(nother) popup. @@ -3448,6 +3556,9 @@ free_screenlines(void) VIM_CLEAR(popup_mask); VIM_CLEAR(popup_mask_next); VIM_CLEAR(popup_transparent); + VIM_CLEAR(suppressed_cells); + suppressed_rows = 0; + suppressed_cols = 0; #endif } @@ -3512,6 +3623,12 @@ screenclear2(int doclear) did_clear = TRUE; clear_cmdline = FALSE; mode_displayed = FALSE; +#ifdef FEAT_PROP_POPUP + // The display was cleared, the terminal matches ScreenLines again. + if (suppressed_cells != NULL) + vim_memset(suppressed_cells, 0, + (size_t)suppressed_rows * suppressed_cols); +#endif } else { diff --git a/src/testdir/dumps/Test_popupwin_opacity_settext_1.dump b/src/testdir/dumps/Test_popupwin_opacity_settext_1.dump new file mode 100644 index 0000000000..56ec5dfd8d --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_opacity_settext_1.dump @@ -0,0 +1,12 @@ +>s+0&#ffffff0|o|m|e| |t|e|x|t| |h|e|r|e| @60 +@1|╔+0#0000001#ffffff255|═@2|╗| +0#0000000#ffffff0@68 +@1|║+0#0000001#ffffff255|a|b|c|║| +0#0000000#ffffff0@68 +@1|║+0#0000001#ffffff255|A|B|C|║| +0#0000000#ffffff0@68 +@1|║+0#0000001#ffffff255|1|2|3|║| +0#0000000#ffffff0@68 +@1|║+0#0000001#ffffff255|4|5|6|║| +0#0000000#ffffff0@68 +|m|╚+0#0000001#ffffff255|═@2|╝|e+0#0000000#ffffff0|x|t| @65 +|~+0#4040ff13&| @73 +|~| @73 +|~| @73 +|~| @73 +| +0#0000000&@56|1|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_popupwin_opacity_settext_2.dump b/src/testdir/dumps/Test_popupwin_opacity_settext_2.dump new file mode 100644 index 0000000000..237ca18f96 --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_opacity_settext_2.dump @@ -0,0 +1,12 @@ +>s+0&#ffffff0|o|m|e| |t|e|x|t| |h|e|r|e| @60 +@1|╔+0#0000001#ffffff255|═@25|╗| +0#0000000#ffffff0@45 +@1|║+0#0000001#ffffff255|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|║| +0#0000000#ffffff0@45 +@1|║+0#0000001#ffffff255| +0#818181255&@25|║+0#0000001&| +0#0000000#ffffff0@45 +@1|║+0#0000001#ffffff255| +0#818181255&@25|║+0#0000001&| +0#0000000#ffffff0@45 +@1|║+0#0000001#ffffff255| +0#818181255&@25|║+0#0000001&| +0#0000000#ffffff0@45 +|m|║+0#0000001#ffffff255|r+0#818181255&|e| |t|e|x|t| @18|║+0#0000001&| +0#0000000#ffffff0@45 +|~+0#4040ff13&|║+0#0000001#ffffff255|.| +0#8787ff255&@24|║+0#0000001&| +0#4040ff13#ffffff0@45 +|~|╚+0#0000001#ffffff255|═@25|╝| +0#4040ff13#ffffff0@45 +|~| @73 +|~| @73 +| +0#0000000&@56|1|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_popupwin_opacity_settext_3.dump b/src/testdir/dumps/Test_popupwin_opacity_settext_3.dump new file mode 100644 index 0000000000..419898c8dd --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_opacity_settext_3.dump @@ -0,0 +1,12 @@ +>s+0&#ffffff0|o|m|e| |t|e|x|t| |h|e|r|e| @60 +@75 +@75 +@75 +@75 +@75 +|m|o|r|e| |t|e|x|t| @65 +|~+0#4040ff13&| @73 +|~| @73 +|~| @73 +|~| @73 +|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|c|l|e|a|r|(|)| @37|1|,|1| @10|A|l@1| diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim index 02352f2d73..5156584e4f 100644 --- a/src/testdir/test_popupwin.vim +++ b/src/testdir/test_popupwin.vim @@ -5273,6 +5273,34 @@ func Test_popup_opacity_zero() call StopVimInTerminal(buf) endfunc +func Test_popup_opacity_settext_no_leftover() + CheckScreendump + + " Growing a no-highlight opacity popup with popup_settext() used to leave + " cells of the old, smaller popup on the screen: the background redraw + " under an opacity popup suppresses terminal output, so a later draw must + " not skip cells that look unchanged in ScreenLines. + let lines =<< trim END + call setline(1, ['some text here', '', '', '', '', '', 'more text']) + let g:winid = popup_create(['abc', 'ABC', '123', '456'], + \ #{line: 2, col: 2, border: [], highlight: 'None', opacity: 50}) + END + call writefile(lines, 'XtestPopupOpacitySettext', 'D') + let buf = RunVimInTerminal('-S XtestPopupOpacitySettext', #{rows: 12}) + call VerifyScreenDump(buf, 'Test_popupwin_opacity_settext_1', {}) + + " Replace with larger content: no cells of the small popup may remain. + call term_sendkeys(buf, ":call popup_settext(g:winid," + \ .. " ['ABCDEFGHIJKLMNOPQRSTUVWXYZ', '', '', '', '', '.'])\") + call VerifyScreenDump(buf, 'Test_popupwin_opacity_settext_2', {}) + + " After closing the popup the screen must be fully restored. + call term_sendkeys(buf, ":call popup_clear()\") + call VerifyScreenDump(buf, 'Test_popupwin_opacity_settext_3', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_popup_opacity_terminal_no_freeze() CheckFeature terminal CheckUnix diff --git a/src/version.c b/src/version.c index f323818215..9b9abccd7b 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 614, /**/ 613, /**/