From: Yasuhiro Matsumoto Date: Tue, 12 May 2026 17:47:41 +0000 (+0000) Subject: patch 9.2.0477: popup: leftover content after popup_free under layout change X-Git-Tag: v9.2.0477^0 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3a9e1bb7e245f862f506b02845fc6132013ef6e7;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/src/popupwin.c b/src/popupwin.c index 685c3cae7c..0767fa9138 100644 --- a/src/popupwin.c +++ b/src/popupwin.c @@ -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); diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim index 873840d8e8..56cce4fb4e 100644 --- a/src/testdir/test_popupwin.vim +++ b/src/testdir/test_popupwin.vim @@ -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 diff --git a/src/version.c b/src/version.c index 6a8735c6bf..d52a2c4680 100644 --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 477, /**/ 476, /**/