]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0477: popup: leftover content after popup_free under layout change v9.2.0477
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Tue, 12 May 2026 17:47:41 +0000 (17:47 +0000)
committerChristian Brabandt <cb@256bit.org>
Tue, 12 May 2026 17:47:41 +0000 (17:47 +0000)
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>
src/popupwin.c
src/testdir/test_popupwin.vim
src/version.c

index 685c3cae7c81cd8a9782d71fbbbb2c029eb5cc94..0767fa9138a83e1c421f379e3b4983ee9b41fff4 100644 (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);
index 873840d8e80bae9f64bb2277f6f1d6e30d218f56..56cce4fb4ee932a9271152b2187212c8d702179d 100644 (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
index 6a8735c6bfd5a8647dc3bba4fd2144f51ce49e4d..d52a2c468087e4c9e557133d3d904fb22c437465 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    477,
 /**/
     476,
 /**/