patch 9.2.0477: popup: leftover content after popup_free under layout change

Problem:  popup_mask still marks the freed popup's cells as covered
          until may_update_popup_mask() runs inside the next
          update_screen.  Any screen_fill / screen_puts called in
          between (for example msg_clr_eos triggered by a status message
          from :copen) hits skip_for_popup() and silently drops writes
          to those cells, so the popup's chars survive on screen until
          those cells happen to be redrawn for another reason.
Solution: Add popup_clear_mask_for() and call it from popup_hide() and
          popup_free() when the popup was visible, so the upcoming
          writes take effect immediately (Yasuhiro Matsumoto)

Note: The test is limited to MS-Windows because the original report
      (#20178) was reproduced there and the redraw timing required to
      surface the bug differs on other platforms.

fixes:  #20178
closes: #20188

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yasuhiro Matsumoto
2026-05-12 17:47:41 +00:00
committed by Christian Brabandt
parent 38237411e4
commit 3a9e1bb7e2
3 changed files with 85 additions and 0 deletions
+35
View File
@@ -3522,10 +3522,38 @@ f_popup_close(typval_T *argvars, typval_T *rettv UNUSED)
popup_close_and_callback(wp, &argvars[1]);
}
/*
* Clear popup_mask entries for the cells covered by "wp" so that
* screen_fill / screen_puts calls made before the next update_screen()
* (e.g. msg_clr_eos triggered by a status message) are not silently
* dropped by skip_for_popup(). Without this the popup's chars survive
* on screen until may_update_popup_mask() runs and the affected cells
* happen to be redrawn.
*/
static void
popup_clear_mask_for(win_T *wp)
{
int r, c;
int row_start, col_start, row_end, col_end;
if (popup_mask == NULL || !popup_visible)
return;
row_start = MAX(wp->w_winrow, 0);
col_start = MAX(wp->w_wincol, 0);
row_end = MIN(wp->w_winrow + popup_height(wp), (int)screen_Rows);
col_end = MIN(wp->w_wincol + popup_width(wp), (int)screen_Columns);
for (r = row_start; r < row_end; ++r)
for (c = col_start; c < col_end; ++c)
popup_mask[r * screen_Columns + c] = 0;
}
void
popup_hide(win_T *wp)
{
popup_area_T old_area;
int was_visible = (wp->w_popup_flags & POPF_HIDDEN) == 0;
#ifdef FEAT_TERMINAL
if (error_if_term_popup_window())
@@ -3541,6 +3569,9 @@ popup_hide(win_T *wp)
if (wp->w_winrow + popup_height(wp) >= cmdline_row)
clear_cmdline = TRUE;
if (was_visible)
popup_clear_mask_for(wp);
if (old_area.active)
popup_redraw_exposed_area(&old_area);
else
@@ -3725,6 +3756,7 @@ f_popup_setbuf(typval_T *argvars, typval_T *rettv UNUSED)
popup_free(win_T *wp)
{
popup_area_T old_area;
int was_visible = (wp->w_popup_flags & POPF_HIDDEN) == 0;
popup_save_area(wp, &old_area);
@@ -3733,6 +3765,9 @@ popup_free(win_T *wp)
if (wp->w_winrow + popup_height(wp) >= cmdline_row)
clear_cmdline = TRUE;
if (was_visible)
popup_clear_mask_for(wp);
popup_redraw_exposed_area(&old_area);
win_free_popup(wp);
+48
View File
@@ -5408,4 +5408,52 @@ func Test_popupwin_close_status_redraw()
call StopVimInTerminal(buf)
endfunc
func Test_popupwin_close_copen_redraw()
CheckMSWindows
CheckFeature quickfix
func! s:OpenPopup()
call popup_create(repeat(['ZZZZZZZZZ'], 10), #{
\ pos: 'botright',
\ col: &columns,
\ line: &lines,
\ filter: function('s:PopupFilter'),
\ })
endfunc
func! s:PopupFilter(winid, key)
if a:key ==# 'q'
call popup_close(a:winid)
copen
endif
return 1
endfunc
enew!
call setline(1, range(1, 30))
call setqflist(map(range(1, 20),
\ {_, v -> {'bufnr': bufnr('%'), 'lnum': v, 'col': 1, 'text': 'item ' .. v}}))
call s:OpenPopup()
redraw
call feedkeys('q', 'xt')
redraw
cclose
redraw
call s:OpenPopup()
redraw
call feedkeys('q', 'xt')
redraw
for row in range(&lines - 9, &lines)
let line = join(map(range(1, &columns), 'screenstring(row, v:val)'), '')
call assert_notmatch('Z', line)
endfor
cclose
bwipe!
delfunc s:OpenPopup
delfunc s:PopupFilter
endfunc
" vim: shiftwidth=2 sts=2
+2
View File
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
477,
/**/
476,
/**/