From 69ee9cf7f4b8bdb984eca8b52dafa15af8cd6a3c Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Fri, 7 Aug 2026 16:13:07 +0100 Subject: [PATCH] =?UTF-8?q?Ordinary=20text=20writes=20replaced=20the=20ent?= =?UTF-8?q?ire=20grid=20cell,=20dropping=20GRID=5FFLAG=5FIMAGE=20and=20rel?= =?UTF-8?q?easing=20the=20mage=20reference.=20Deferred=20pane=20redraws=20?= =?UTF-8?q?explain=20the=20intermittent=20=E2=80=9Cimage=20never=20appears?= =?UTF-8?q?=E2=80=9D=20behavior.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed: - Preserves image metadata while updating the text underlay in the collected ASCII path: grid.c:779 - Handles the single-cell/non-ASCII path and schedules an image redraw: screen-write.c:2631 - Uses the requested if/else for new_id. - Adds a deterministic regression reproducing point (2): regress/image-support.sh:240 This matches Kitty’s requirement that normal text operations must not affect graphics; graphics require explicit deletion. Kitty graphics protocol --- grid.c | 28 +++++++++++++++++++++++----- regress/image-support.sh | 28 ++++++++++++++++++++++++++-- screen-write.c | 18 ++++++++++++++++++ 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/grid.c b/grid.c index 9a425d522..fe26a7888 100644 --- a/grid.c +++ b/grid.c @@ -782,6 +782,10 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, struct grid_line *gl; struct grid_cell_entry *gce; struct grid_extd_entry *gee; + const struct grid_cell *new_gc; +#ifdef ENABLE_IMAGES + struct grid_cell old_gc, image_gc; +#endif u_int i; if (grid_check_y(gd, __func__, py) != 0) @@ -795,11 +799,25 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, for (i = 0; i < slen; i++) { gce = &gl->celldata[px + i]; + new_gc = gc; #ifdef ENABLE_IMAGES { u_int old_id = grid_entry_image(gl, gce); - u_int new_id = (gc->flags & GRID_FLAG_IMAGE) ? - gc->image_id : 0; + u_int new_id; + + if (old_id != 0 && (~gc->flags & GRID_FLAG_IMAGE)) { + grid_get_cell1(gl, px + i, &old_gc); + memcpy(&image_gc, gc, sizeof image_gc); + image_gc.flags |= GRID_FLAG_IMAGE; + image_gc.image_id = old_gc.image_id; + image_gc.image_x = old_gc.image_x; + image_gc.image_y = old_gc.image_y; + new_gc = &image_gc; + } + if (new_gc->flags & GRID_FLAG_IMAGE) + new_id = new_gc->image_id; + else + new_id = 0; if (old_id != new_id) { if (old_id != 0) @@ -809,11 +827,11 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, } } #endif - if (grid_need_extended_cell(gce, gc)) { - gee = grid_extended_cell(gl, gce, gc); + if (grid_need_extended_cell(gce, new_gc)) { + gee = grid_extended_cell(gl, gce, new_gc); gee->data = utf8_build_one(s[i]); } else - grid_store_cell(gce, gc, s[i]); + grid_store_cell(gce, new_gc, s[i]); } } diff --git a/regress/image-support.sh b/regress/image-support.sh index aa19fa791..f71286790 100755 --- a/regress/image-support.sh +++ b/regress/image-support.sh @@ -36,10 +36,12 @@ $TMUX send-keys -X history-top || exit 1 $TMUX capture-pane -p >$TMP || exit 1 $TMUX send-keys -X cancel || exit 1 -# Ordinary text overwrites marker cells through the normal grid write path. +# Ordinary text updates the image underlay through the normal grid write path. +# Deleting the image reveals the newly written text. $TMUX new-window -d " - printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2;/wAA/wD/AP8AAP///////w==\033\\' + printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,i=8;/wAA/wD/AP8AAP///////w==\033\\' printf '\033[HXY' + printf '\033_Ga=d,d=i,q=2,i=8\033\\' sleep 10" sleep 1 [ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1 @@ -235,6 +237,28 @@ $TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1 sleep 1 [ "$($TMUX2 capture-pane -pS0 -E0)" = "XY" ] || exit 1 +# Text written over a Kitty placement updates its underlay without deleting +# the image. Deleting the placement afterwards reveals the updated text. +TEXT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' " + printf 'test\r' + printf '\033_Ga=T,q=2,C=1,f=32,s=2,v=2,c=2,r=2,i=14,p=7;/wAA/wD/AP8AAP///////w==\033\\' + printf '\r' + printf 'test\n' + printf 'test\n' + $TEST_TMUX wait-for image-text-delete-$$ + printf '\033_Ga=d,d=i,q=2,i=14,p=7\033\\' + sleep 10") || exit 1 +$TMUX2 select-window -t"$TEXT_WINDOW" || exit 1 +sleep 1 +$TMUX capture-pane -pS0 -E1 >$TMP || exit 1 +[ "$(sed -n 1p $TMP)" = ".*st" ] || exit 1 +[ "$(sed -n 2p $TMP)" = " @st" ] || exit 1 +$TMUX2 wait-for -S image-text-delete-$$ || exit 1 +sleep 1 +$TMUX capture-pane -pS0 -E1 >$TMP || exit 1 +[ "$(sed -n 1p $TMP)" = "test" ] || exit 1 +[ "$(sed -n 2p $TMP)" = "test" ] || exit 1 + # A weighted median at the maximum channel level must still leave colours on # both sides of the split. This skewed black, grey and white image used to stop # palette generation after one colour instead of producing three. diff --git a/screen-write.c b/screen-write.c index cf7707f17..e12d3cc83 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2666,6 +2666,9 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) struct grid_line *gl; struct grid_cell_entry *gce; struct grid_cell tmp_gc, now_gc; +#ifdef ENABLE_IMAGES + struct grid_cell image_gc; +#endif struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); u_int width = ud->width, xx, not_wrap, i, n, vis; @@ -2710,6 +2713,21 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) return; screen_write_initctx(ctx, &ttyctx, 0, 0); +#ifdef ENABLE_IMAGES + /* Update the text underlay without removing an image placement. */ + grid_view_get_cell(gd, s->cx, s->cy, &now_gc); + if ((now_gc.flags & GRID_FLAG_IMAGE) && + (~gc->flags & GRID_FLAG_IMAGE)) { + image_redraw_area(ctx, s->cx, s->cy, width, 1); + memcpy(&image_gc, gc, sizeof image_gc); + image_gc.flags |= GRID_FLAG_IMAGE; + image_gc.image_id = now_gc.image_id; + image_gc.image_x = now_gc.image_x; + image_gc.image_y = now_gc.image_y; + gc = &image_gc; + } +#endif + /* Handle overwriting of UTF-8 characters. */ gl = grid_get_line(s->grid, s->grid->hsize + s->cy); if (gl->flags & GRID_LINE_EXTENDED) {