mirror of
https://github.com/tmux/tmux.git
synced 2026-08-28 17:41:35 +00:00
server-client, popup, screen-redraw: fix damage-system regressions
Fixes the four bugs caught by the regression tests added in aed1209c
(popup-drag-status-line.sh, popup-drag-wide-character.sh,
popup-drag-pane-prompt.sh, switch-client-redraw.sh), based on fixes
from Michael K. Darling (github.com/darlingm/tmux, pr5516-regression-
fixes), reviewed and adapted:
- server-client.c: server_client_set_session()'s check for whether the
client's window actually changed compared old->curw to s->curw, but
when old == s these read the same, already-updated field, so a
same-session window switch was never detected. Compare against the
client's own cached redraw scene instead (redraw_client_has_window(),
new in screen-redraw.c/tmux.h). Taken from darlingm as-is.
- popup.c: popup_damage() only translated a popup's client-coordinate
rectangle into window coordinates, so a popup dragged across the
status line never triggered a status-line redraw once it moved away -
status_redraw()'s own "skip if content unchanged" optimization
suppressed it, since only the popup moved, not the status content.
Now detects overlap with the status line and forces a redraw via the
existing (previously unused) CLIENT_REDRAWSTATUSALWAYS flag, and
properly clips the reported rectangle to the pane area for
status-at-top/bottom/off. Taken from darlingm as-is.
- screen-redraw.c: redraw_draw_damage_rect() clipped a span to a damage
rectangle's raw geometric edges, which have no idea what's in the
grid, so a clip edge could land mid-character and tear a wide
character in half. Added redraw_damage_grow_span_clip(): widen the
clip by one cell on each edge that isn't already at the span's own
boundary. Reimplemented simpler than darlingm's version (which walked
grid cells per span type via a switch and direct grid lookups) -
since no grid cell is ever wider than two columns, an unconditional
one-cell margin is always enough to pull a split character back in,
with no need to inspect grid content at all.
- screen-redraw.c: redraw_draw_damage_rect() also never re-overlaid a
pane's active in-pane prompt after drawing its underlying content, so
damage crossing a prompt row erased it until an unrelated redraw
restored it. Factored the existing full-redraw prompt-building code
into a shared redraw_make_pane_prompt() helper and added
redraw_damage_draw_pane_prompt(), which recomposes the prompt over
the drawn range. Taken from darlingm as-is.
All 9 regression tests in regress/ now pass. redraw_damage_grow_span_clip
was verified independently by disabling it and confirming
popup-drag-wide-character.sh reproduces its original failure.
Co-Authored-By: Michael K. Darling <darlingm@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
46
popup.c
46
popup.c
@@ -321,32 +321,50 @@ popup_resize_cb(__unused struct client *c, void *data)
|
||||
}
|
||||
|
||||
/*
|
||||
* Report damage on the current window for a popup's rectangle, given in raw
|
||||
* client/tty coordinates - translated into window coordinates by removing
|
||||
* any top status lines and adding the client's pan/window offset, the same
|
||||
* Report damage for a popup's rectangle, given in raw client/tty
|
||||
* coordinates. Status-line cells are outside the window scene - they have no
|
||||
* corresponding window content and redraw_damage_window() can't reach them -
|
||||
* so if the popup's rectangle overlaps the status line, force it to redraw
|
||||
* separately. The rest of the rectangle is clipped to the actual pane area
|
||||
* (above or below the status line, whichever side it's on) before being
|
||||
* translated into window coordinates and reported the normal way, the same
|
||||
* way mouse coordinates are translated elsewhere (e.g. cmd-join-pane.c,
|
||||
* cmd-split-window.c). redraw_damage_window() safely clips or drops
|
||||
* anything that ends up out of the window's own bounds (a popup can cover
|
||||
* the status line, which has no corresponding window content), so this
|
||||
* does not need to be exact for those edge cases.
|
||||
* cmd-split-window.c).
|
||||
*/
|
||||
static void
|
||||
popup_damage(struct client *c, u_int px, u_int py, u_int sx, u_int sy)
|
||||
{
|
||||
struct window *w;
|
||||
u_int ox, oy, osx, osy, wy;
|
||||
int statusat;
|
||||
u_int ox, oy, osx, osy, lines, top, bottom, y0, y1;
|
||||
|
||||
if (c->session == NULL)
|
||||
return;
|
||||
w = c->session->curw->window;
|
||||
|
||||
tty_window_offset(&c->tty, &ox, &oy, &osx, &osy);
|
||||
if (status_at_line(c) == 0 && py >= status_line_size(c))
|
||||
wy = py - status_line_size(c);
|
||||
else
|
||||
wy = py;
|
||||
lines = status_line_size(c);
|
||||
statusat = status_at_line(c);
|
||||
if (statusat >= 0 && py < (u_int)statusat + lines &&
|
||||
py + sy > (u_int)statusat)
|
||||
c->flags |= (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS);
|
||||
|
||||
redraw_damage_window(w, px + ox, wy + oy, sx, sy);
|
||||
if (statusat == 0) {
|
||||
top = lines;
|
||||
bottom = c->tty.sy;
|
||||
} else if (statusat > 0) {
|
||||
top = 0;
|
||||
bottom = statusat;
|
||||
} else {
|
||||
top = 0;
|
||||
bottom = c->tty.sy;
|
||||
}
|
||||
y0 = (py > top) ? py : top;
|
||||
y1 = (py + sy < bottom) ? py + sy : bottom;
|
||||
if (y0 >= y1)
|
||||
return;
|
||||
|
||||
tty_window_offset(&c->tty, &ox, &oy, &osx, &osy);
|
||||
redraw_damage_window(w, px + ox, y0 - top + oy, sx, y1 - y0);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
108
screen-redraw.c
108
screen-redraw.c
@@ -1073,6 +1073,13 @@ redraw_free_scene(struct redraw_scene *scene)
|
||||
free(scene);
|
||||
}
|
||||
|
||||
/* Does a client's cached scene show this window? */
|
||||
int
|
||||
redraw_client_has_window(struct client *c, struct window *w)
|
||||
{
|
||||
return (c->redraw_scene != NULL && c->redraw_scene->w == w);
|
||||
}
|
||||
|
||||
/* Mark a window's cached redraw scenes as out of date. */
|
||||
void
|
||||
redraw_invalidate_scene(struct window *w)
|
||||
@@ -1758,6 +1765,24 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
|
||||
dctx->flags |= REDRAW_ISOLATES;
|
||||
}
|
||||
|
||||
/* Build a pane prompt into a one-line screen. */
|
||||
static void
|
||||
redraw_make_pane_prompt(struct window_pane *wp, struct screen *screen)
|
||||
{
|
||||
struct screen_write_ctx ctx;
|
||||
struct prompt_draw_data pdd;
|
||||
|
||||
screen_init(screen, wp->sx, 1, 0);
|
||||
screen_write_start(&ctx, screen);
|
||||
pdd.ctx = &ctx;
|
||||
pdd.cursor_x = &wp->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = wp->sx;
|
||||
pdd.prompt_line = 0;
|
||||
prompt_draw(wp->prompt, &pdd);
|
||||
screen_write_stop(&ctx);
|
||||
}
|
||||
|
||||
/* Draw a pane's prompt over its content. */
|
||||
static void
|
||||
redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
@@ -1766,8 +1791,6 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
struct client *c = scene->c;
|
||||
struct tty *tty = &c->tty;
|
||||
struct screen screen;
|
||||
struct screen_write_ctx ctx;
|
||||
struct prompt_draw_data pdd;
|
||||
int ox = scene->ox, oy = scene->oy;
|
||||
int sx = scene->sx, sy = scene->sy;
|
||||
int line, cy, px, offset, width, wy;
|
||||
@@ -1800,16 +1823,7 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
if (px + width > sx)
|
||||
width = sx - px;
|
||||
|
||||
screen_init(&screen, wp->sx, 1, 0);
|
||||
screen_write_start(&ctx, &screen);
|
||||
pdd.ctx = &ctx;
|
||||
pdd.cursor_x = &wp->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = wp->sx;
|
||||
pdd.prompt_line = 0;
|
||||
prompt_draw(wp->prompt, &pdd);
|
||||
screen_write_stop(&ctx);
|
||||
|
||||
redraw_make_pane_prompt(wp, &screen);
|
||||
tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL);
|
||||
screen_free(&screen);
|
||||
}
|
||||
@@ -1841,7 +1855,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
redraw = status_prompt_redraw(c);
|
||||
else
|
||||
redraw = status_redraw(c);
|
||||
if (!redraw && !REDRAW_IS_ALL(flags)) {
|
||||
if (!redraw && (~c->flags & CLIENT_REDRAWSTATUSALWAYS) &&
|
||||
!REDRAW_IS_ALL(flags)) {
|
||||
flags &= ~REDRAW_STATUS;
|
||||
if (flags == 0)
|
||||
return;
|
||||
@@ -2014,7 +2029,7 @@ redraw_screen(struct client *c)
|
||||
} else {
|
||||
if (c->flags & CLIENT_REDRAWBORDERS)
|
||||
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
|
||||
if (c->flags & CLIENT_REDRAWSTATUS)
|
||||
if (c->flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))
|
||||
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
|
||||
if (c->flags & CLIENT_REDRAWOVERLAY)
|
||||
flags |= REDRAW_OVERLAY;
|
||||
@@ -2071,6 +2086,64 @@ redraw_damage_refresh_status(struct redraw_draw_ctx *dctx,
|
||||
wp->flags |= PANE_NEWSTATUS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Grow a clipped span range by one cell on either edge that isn't already at
|
||||
* the span's own boundary. A clip edge that lands mid-character (this is a
|
||||
* damage rectangle, so its edges are geometric and have no idea what's in
|
||||
* the grid) may be sitting on the second, padding half of a wide character
|
||||
* whose first half falls just outside the requested range - growing by one
|
||||
* cell is always enough to pull the whole character back in, since no grid
|
||||
* cell is ever wider than two columns, and clamping to the span's own x and
|
||||
* width keeps this from bleeding into a neighbouring span.
|
||||
*/
|
||||
static void
|
||||
redraw_damage_grow_span_clip(struct redraw_span *span, u_int *xp, u_int *endp)
|
||||
{
|
||||
if (*xp > span->x)
|
||||
(*xp)--;
|
||||
if (*endp < span->x + span->width)
|
||||
(*endp)++;
|
||||
}
|
||||
|
||||
/* Recompose a pane's prompt over a damaged section of its display row. */
|
||||
static void
|
||||
redraw_damage_draw_pane_prompt(struct redraw_draw_ctx *dctx,
|
||||
struct redraw_span *span, u_int y, u_int x, u_int n)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct window_pane *wp = span->data.p.wp;
|
||||
struct tty *tty = &scene->c->tty;
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *rr;
|
||||
struct screen screen;
|
||||
u_int i, px, width, prompt_y;
|
||||
|
||||
if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0)
|
||||
return;
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
prompt_y = 0;
|
||||
else
|
||||
prompt_y = wp->sy - 1;
|
||||
if (span->data.p.py != prompt_y)
|
||||
return;
|
||||
|
||||
redraw_make_pane_prompt(wp, &screen);
|
||||
r = tty_check_overlay_range(tty, x, y, n);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
rr = &r->ranges[i];
|
||||
if (rr->nx == 0)
|
||||
continue;
|
||||
px = span->data.p.px + (rr->px - span->x);
|
||||
if (px >= screen_size_x(&screen))
|
||||
continue;
|
||||
width = rr->nx;
|
||||
if (width > screen_size_x(&screen) - px)
|
||||
width = screen_size_x(&screen) - px;
|
||||
tty_draw_line(tty, &screen, px, 0, width, rr->px, y, NULL);
|
||||
}
|
||||
screen_free(&screen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compose exactly the cells within a damaged rectangle (already in this
|
||||
* client's own scene coordinates), rather than a whole pane. For each row
|
||||
@@ -2114,8 +2187,15 @@ redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
redraw_damage_refresh_status(dctx,
|
||||
span->data.st.wp);
|
||||
}
|
||||
redraw_damage_grow_span_clip(span, &clip_x,
|
||||
&clip_end);
|
||||
redraw_draw_span(dctx, span, cy, clip_x,
|
||||
clip_end - clip_x);
|
||||
if (type == REDRAW_SPAN_PANE) {
|
||||
redraw_damage_draw_pane_prompt(dctx,
|
||||
span, cy, clip_x,
|
||||
clip_end - clip_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,8 +488,17 @@ server_client_set_session(struct client *c, struct session *s)
|
||||
* just an active-pane change, already handled narrowly by
|
||||
* window_set_active_pane() and window_redraw_active_switch()
|
||||
* before this is reached.
|
||||
*
|
||||
* old and s may be the same session object, whose curw was
|
||||
* already updated to the new window before this function was
|
||||
* called - old->curw and s->curw would then read the same,
|
||||
* already-current value, so comparing them can never detect
|
||||
* a same-session window change. Compare against the client's
|
||||
* own cached scene instead, which only reflects what it has
|
||||
* actually drawn.
|
||||
*/
|
||||
if (old == NULL || old != s || old->curw != s->curw)
|
||||
if (old == NULL || old != s ||
|
||||
!redraw_client_has_window(c, s->curw->window))
|
||||
server_redraw_client(c);
|
||||
}
|
||||
|
||||
|
||||
1
tmux.h
1
tmux.h
@@ -3658,6 +3658,7 @@ void redraw_screen(struct client *);
|
||||
void redraw_pane(struct client *, struct window_pane *);
|
||||
void redraw_pane_scrollbar(struct client *, struct window_pane *);
|
||||
void redraw_free_scene(struct redraw_scene *);
|
||||
int redraw_client_has_window(struct client *, struct window *);
|
||||
void redraw_invalidate_scene(struct window *);
|
||||
void redraw_invalidate_all_scenes(void);
|
||||
void redraw_damage_window(struct window *, u_int, u_int, u_int, u_int);
|
||||
|
||||
Reference in New Issue
Block a user