patch 9.2.1074: popup: image stays on the screen in another tab page

Problem:  The image of a tab-local popup window stays on the screen
          after switching to another tab page, while the border and the
          background of the popup are gone.  The image backends keep an
          image until it is removed: a kitty placement and a GTK4
          texture until they are deleted, GDI and cairo pixels until the
          area is repainted, sixel pixels until the screen is cleared.
Solution: Remove the images of the popups local to a tab page when the
          tab page is left, they are emitted again when it is entered.
          (Hirohito Higashi).

related: #21234
closes:  #21273

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Hirohito HigashiandClaude Fable 5.1 authored and Christian Brabandt committed 2026-09-11 21:15:43 +00:00
1 parent 737b761ee6
commit 3362c8c89f
5 files changed
+150

No files matched your search

+37
View File
@@ -4645,6 +4645,43 @@ popup_close(int id, int force)
return FAIL;
}
#ifdef FEAT_IMAGE
/*
* Remove the images of the popups local to tabpage "tp", it is about to be
* left. The popups are not drawn in another tabpage, their images stay until
* they are removed. They are emitted again when the tabpage is entered.
*/
void
popup_leave_tabpage(tabpage_T *tp)
{
win_T *wp;
FOR_ALL_POPUPWINS_IN_TAB(tp, wp)
{
if ((wp->w_popup_flags & POPF_HIDDEN) || wp->w_popup_image_data == NULL)
continue;
# ifdef FEAT_IMAGE_KITTY
popup_image_clear_kitty(wp, false);
# endif
# ifdef FEAT_IMAGE_GDK
if (gui.in_use)
gui_gtk4_remove_image(wp);
# endif
# ifdef POPUP_IMAGE_CLEAR_GUI
popup_image_clear_gui(wp);
# endif
# ifdef FEAT_IMAGE_SIXEL
// Sixel pixels only go away with a clear of the screen.
# ifdef FEAT_GUI
if (!gui.in_use)
# endif
if (popup_image_backend() == IMAGE_BACKEND_SIXEL)
redraw_later_clear();
# endif
}
}
#endif
/*
* Close a popup window with Window-id "id" in tabpage "tp".
*/
+1
View File
@@ -39,6 +39,7 @@ void f_popup_settext(typval_T *argvars, typval_T *rettv);
void f_popup_setbuf(typval_T *argvars, typval_T *rettv);
int error_if_popup_window(int also_with_term);
int popup_close(int id, int force);
void popup_leave_tabpage(tabpage_T *tp);
int popup_close_tabpage(tabpage_T *tp, int id, int force);
void close_all_popups(int force);
void f_popup_move(typval_T *argvars, typval_T *rettv);
+107
View File
@@ -6310,6 +6310,113 @@ func Test_popup_image_clipwindow_scroll()
call prop_type_delete('imgclipprop')
endfunc
" The kitty and sixel image backends cannot be tested in a terminal window:
" it does not show the escape sequences Vim writes and does not answer the
" probe for the kitty graphics protocol. Run Vim on a pty as a job instead
" and look at what it writes there.
" The sequences of the kitty graphics protocol Vim writes, "a=" is the action.
" See https://sw.kovidgoyal.net/kitty/graphics-protocol/
" a=q: does the terminal support the protocol?
let s:kitty_probe = "\<Esc>_Gi=31"
" What a terminal that supports the protocol answers, followed by the reply to
" the DA1 request that ends the probe.
let s:kitty_probe_answer = "\<Esc>_Gi=31;OK\<Esc>\\\<Esc>[?62;4c"
" a=t: transmit the pixels of an image.
let s:kitty_transmit = "\<Esc>_Ga=t,"
" a=p: place a transmitted image on the screen.
let s:kitty_place = "\<Esc>_Ga=p,"
" a=d,d=i: delete the placement of an image, the terminal keeps the pixels.
let s:kitty_delete = "\<Esc>_Ga=d,d=i,"
" The start of a sixel image, a DCS with the "q" command.
let s:sixel_start_pat = "\<Esc>P[0-9;]*q"
" Clear the screen, the only way to remove sixel pixels on some terminals.
let s:clear_screen = "\<Esc>[2J"
" The script for the Vim on the pty: create a popup with an image.
let s:image_popup_script =<< trim END
let img = repeat([0xff, 0, 0], 16 * 32)->list2blob()
call popup_create('', #{image: #{data: img, width: 16, height: 32}})
redraw
END
" Collect what the Vim on the pty writes in s:pty_out. With "kitty" the probe
" is answered like a terminal that supports the protocol, otherwise Vim gets
" no answer and uses sixel.
func s:PtyOutput(job, msg, kitty)
let s:pty_out ..= a:msg
if a:kitty && !s:pty_answered && stridx(s:pty_out, s:kitty_probe) >= 0
call ch_sendraw(a:job, s:kitty_probe_answer)
let s:pty_answered = 1
endif
endfunc
" Start Vim on a pty with the script written to XpopupImageTab.
func s:StartVimWithImageOnPty(kitty)
let s:pty_out = ''
let s:pty_answered = 0
return job_start(GetVimCommandCleanTerm() .. ' -S XpopupImageTab', #{
\ pty: 1,
\ out_mode: 'raw',
\ env: #{TERM: 'xterm', LINES: '24', COLUMNS: '80'},
\ out_cb: {job, msg -> s:PtyOutput(job, msg, a:kitty)},
\ })
endfunc
" Wait for the Vim on the pty to write "seq" after the first "start" bytes.
func s:WaitForPtyOutput(seq, start)
call WaitForAssert({-> assert_notequal(-1,
\ stridx(s:pty_out, a:seq, a:start))})
endfunc
func Test_popup_image_kitty_leave_tabpage()
CheckUnix
CheckFeature job
CheckFeature image_kitty
call writefile(s:image_popup_script, 'XpopupImageTab', 'D')
let job = s:StartVimWithImageOnPty(1)
try
call s:WaitForPtyOutput(s:kitty_transmit, 0)
call s:WaitForPtyOutput(s:kitty_place, 0)
" Leaving the tab page deletes the placement.
let start = len(s:pty_out)
call ch_sendraw(job, ":tabedit\<CR>")
call s:WaitForPtyOutput(s:kitty_delete, start)
" Entering the tab page places the image again without transmitting it.
let start = len(s:pty_out)
call ch_sendraw(job, ":tabnext\<CR>")
call s:WaitForPtyOutput(s:kitty_place, start)
call assert_equal(-1, stridx(s:pty_out, s:kitty_transmit, start))
finally
call job_stop(job, 'kill')
call WaitForAssert({-> assert_equal('dead', job_status(job))})
endtry
endfunc
func Test_popup_image_sixel_leave_tabpage()
CheckUnix
CheckFeature job
CheckFeature image_sixel
call writefile(s:image_popup_script, 'XpopupImageTab', 'D')
let job = s:StartVimWithImageOnPty(0)
try
call WaitForAssert({-> assert_match(s:sixel_start_pat, s:pty_out)})
" Leaving the tab page clears the screen.
let start = len(s:pty_out)
call ch_sendraw(job, ":tabedit\<CR>")
call s:WaitForPtyOutput(s:clear_screen, start)
finally
call job_stop(job, 'kill')
call WaitForAssert({-> assert_equal('dead', job_status(job))})
endtry
endfunc
func Test_popupwin_textprop_redraw()
CheckScreendump
+2
View File
@@ -763,6 +763,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1074,
/**/
1073,
/**/
+3
View File
@@ -5116,6 +5116,9 @@ leave_tabpage(
return FAIL;
}
#ifdef FEAT_IMAGE
popup_leave_tabpage(tp);
#endif
reset_dragwin();
#if defined(FEAT_GUI)
// Remove the scrollbars. They may be added back later.