From d533d7c97ca3f769cd689faf7201a67017d458f0 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 9 Jun 2026 21:22:22 +0000 Subject: [PATCH 1/9] Add a context for cell/palette/hyperlinks when drawing to tty to avoid passing so much in parameters. --- cmd-display-panes.c | 9 +++-- cmd-kill-pane.c | 2 - menu.c | 6 +-- popup.c | 18 +++++---- screen-redraw.c | 31 +++++++------- screen-write.c | 15 ++++--- tmux.h | 27 ++++++++----- tty-draw.c | 36 ++++++++++------- tty.c | 99 ++++++++++++++++++++------------------------- window-copy.c | 7 +--- 10 files changed, 125 insertions(+), 125 deletions(-) diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 5a1782ff..7470587a 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -91,6 +91,7 @@ cmd_display_panes_draw_format(struct screen_redraw_ctx *ctx, const char *format; char *expanded; u_int i, px = ctx->ox + xoff; + struct tty_style_ctx style_ctx = { .defaults = gc }; format = options_get_string(s->options, "display-panes-format"); expanded = format_single(NULL, format, c, s, s->curw, wp); @@ -105,7 +106,7 @@ cmd_display_panes_draw_format(struct screen_redraw_ctx *ctx, for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; tty_draw_line(tty, &screen, ri->px - px, 0, ri->nx, - ri->px - ctx->ox, yoff, gc, NULL); + ri->px - ctx->ox, yoff, &style_ctx); } screen_free(&screen); } @@ -199,7 +200,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, llen = 0; if (sx < len * 6 || sy < 5) { - tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); + tty_attributes(tty, &fgc, NULL); if (sx >= len + llen + 1) { len += llen + 1; cx = xoff + px - len / 2; @@ -220,7 +221,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, px -= len * 3; py -= 2; - tty_attributes(tty, &bgc, &grid_default_cell, NULL, NULL); + tty_attributes(tty, &bgc, NULL); for (ptr = buf; *ptr != '\0'; ptr++) { if (*ptr < '0' || *ptr > '9') continue; @@ -242,7 +243,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, goto out; cmd_display_panes_draw_format(ctx, wp, xoff, yoff, sx, &fgc); if (llen != 0) { - tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); + tty_attributes(tty, &fgc, NULL); cx = xoff + sx / 2 + len * 3 - llen - 1; cy = yoff + py + 5; cmd_display_panes_put(ctx, wp, cx, cy, lbuf, llen); diff --git a/cmd-kill-pane.c b/cmd-kill-pane.c index ed833643..836551f0 100644 --- a/cmd-kill-pane.c +++ b/cmd-kill-pane.c @@ -50,8 +50,6 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); struct cmd_find_state *target = cmdq_get_target(item); - struct session *s = target->s; - struct winlink *wl = target->wl; struct window_pane *wp = target->wp; const char *filter = args_get(args, 'f'); diff --git a/menu.c b/menu.c index 4f122831..98d441fe 100644 --- a/menu.c +++ b/menu.c @@ -281,10 +281,8 @@ menu_draw_cb(struct client *c, void *data, &md->style_gc, &md->border_style_gc, &md->selected_style_gc); screen_write_stop(&ctx); - for (i = 0; i < screen_size_y(&md->s); i++) { - tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, - &grid_default_cell, NULL); - } + for (i = 0; i < screen_size_y(&md->s); i++) + tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, NULL); } void diff --git a/popup.c b/popup.c index 7c29af62..e2de2038 100644 --- a/popup.c +++ b/popup.c @@ -209,7 +209,8 @@ popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults); ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER; - ttyctx->palette = &pd->palette; + ttyctx->style_ctx.defaults = &ttyctx->defaults; + ttyctx->style_ctx.palette = &pd->palette; ttyctx->redraw_cb = popup_redraw_cb; ttyctx->set_client_cb = popup_set_client_cb; ttyctx->arg = pd; @@ -295,8 +296,8 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) struct screen s; struct screen_write_ctx ctx; u_int i, px = pd->px, py = pd->py; - struct colour_palette *palette = &pd->palette; struct grid_cell defaults; + struct tty_style_ctx style_ctx; popup_reapply_styles(pd); @@ -322,9 +323,12 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) memcpy(&defaults, &pd->defaults, sizeof defaults); if (defaults.fg == 8) - defaults.fg = palette->fg; + defaults.fg = pd->palette.fg; if (defaults.bg == 8) - defaults.bg = palette->bg; + defaults.bg = pd->palette.bg; + style_ctx.defaults = &defaults; + style_ctx.palette = &pd->palette; + style_ctx.hyperlinks = s.hyperlinks; if (pd->md != NULL) { c->overlay_check = menu_check_cb; @@ -333,10 +337,8 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) c->overlay_check = NULL; c->overlay_data = NULL; } - for (i = 0; i < pd->sy; i++) { - tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults, - palette); - } + for (i = 0; i < pd->sy; i++) + tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &style_ctx); screen_free(&s); if (pd->md != NULL) { c->overlay_check = NULL; diff --git a/screen-redraw.c b/screen-redraw.c index 862c9488..63b19e49 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -725,8 +725,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (ri->nx == 0) continue; tty_draw_line(tty, s, l + (ri->px - x), 0, ri->nx, - ri->px, yoff - ctx->oy, - &grid_default_cell, NULL); + ri->px, yoff - ctx->oy, NULL); } } tty_cursor(tty, 0, 0); @@ -1044,7 +1043,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) screen_redraw_draw_border_arrows(ctx, i, j, cell_type, wp, active, &gc); - tty_cell(tty, &gc, &grid_default_cell, NULL, NULL); + tty_cell(tty, &gc, NULL); if (isolates) tty_puts(tty, START_ISOLATE); } @@ -1104,10 +1103,8 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) y = 0; else y = c->tty.sy - ctx->statuslines; - for (i = 0; i < ctx->statuslines; i++) { - tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i, - &grid_default_cell, NULL); - } + for (i = 0; i < ctx->statuslines; i++) + tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i, NULL); } /* @@ -1302,6 +1299,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; + struct tty_style_ctx style_ctx; u_int j, k, woy, wx, wy, py, width; struct visible_ranges *r; struct visible_range *ri; @@ -1376,10 +1374,15 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) width = ctx->sx - wx; } + /* Set up the default style. */ + tty_default_colours(&defaults, wp); + style_ctx.defaults = &defaults; + style_ctx.palette = palette; + style_ctx.hyperlinks = s->hyperlinks; + /* Get visible ranges of line before we draw it. */ r = tty_check_overlay_range(tty, wx, wy, width); r = screen_redraw_get_visible_ranges(wp, wx, wy, width, r); - tty_default_colours(&defaults, wp); for (k = 0; k < r->used; k++) { ri = &r->ranges[k]; if (ri->nx == 0) @@ -1390,7 +1393,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) ri->px + (int)ctx->ox - wp->xoff, j, ri->nx, ri->px, py, ri->nx); tty_draw_line(tty, s, ri->px + (int)ctx->ox - wp->xoff, - j, ri->nx, ri->px, py, &defaults, palette); + j, ri->nx, ri->px, py, &style_ctx); } } } @@ -1563,16 +1566,14 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, if ((sb_pos == PANE_SCROLLBARS_LEFT && i >= sb_w && i < sb_w + sb_pad) || (sb_pos == PANE_SCROLLBARS_RIGHT && - i < sb_pad)) { - tty_cell(tty, &grid_default_cell, - &grid_default_cell, NULL, NULL); - } else { + i < sb_pad)) + tty_cell(tty, &grid_default_cell, NULL); + else { if (j >= slider_y && j < slider_y + slider_h) gcp = &slgc; else gcp = &gc; - tty_cell(tty, gcp, &grid_default_cell, NULL, - NULL); + tty_cell(tty, gcp, NULL); } } } diff --git a/screen-write.c b/screen-write.c index 9aa86c41..3feaca0b 100644 --- a/screen-write.c +++ b/screen-write.c @@ -219,7 +219,8 @@ static void screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, int is_sync, int check_obscured) { - struct screen *s = ctx->s; + struct screen *s = ctx->s; + struct colour_palette *palette = NULL; memset(ttyctx, 0, sizeof *ttyctx); @@ -236,19 +237,23 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, ttyctx->flags |= TTY_CTX_PANE_OBSCURED; memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults); + ttyctx->style_ctx.defaults = &ttyctx->defaults; + ttyctx->style_ctx.hyperlinks = ctx->s->hyperlinks; + if (ctx->init_ctx_cb != NULL) { ctx->init_ctx_cb(ctx, ttyctx); - if (ttyctx->palette != NULL) { + if (ttyctx->style_ctx.palette != NULL) { + palette = ttyctx->style_ctx.palette; if (ttyctx->defaults.fg == 8) - ttyctx->defaults.fg = ttyctx->palette->fg; + ttyctx->defaults.fg = palette->fg; if (ttyctx->defaults.bg == 8) - ttyctx->defaults.bg = ttyctx->palette->bg; + ttyctx->defaults.bg = palette->bg; } } else { ttyctx->redraw_cb = screen_write_redraw_cb; if (ctx->wp != NULL) { tty_default_colours(&ttyctx->defaults, ctx->wp); - ttyctx->palette = &ctx->wp->palette; + ttyctx->style_ctx.palette = &ctx->wp->palette; ttyctx->set_client_cb = screen_write_set_client_cb; ttyctx->arg = ctx->wp; } diff --git a/tmux.h b/tmux.h index e80047dc..63588167 100644 --- a/tmux.h +++ b/tmux.h @@ -1610,6 +1610,13 @@ struct tty_term { }; LIST_HEAD(tty_terms, tty_term); +/* Terminal style context. */ +struct tty_style_ctx { + const struct grid_cell *defaults; + struct colour_palette *palette; + struct hyperlinks *hyperlinks; +}; + /* Client terminal. */ struct tty { struct client *client; @@ -1753,7 +1760,7 @@ struct tty_ctx { /* The default colours and palette. */ struct grid_cell defaults; - struct colour_palette *palette; + struct tty_style_ctx style_ctx; /* Containing region (usually window) offset and size. */ u_int wox; @@ -2621,6 +2628,10 @@ void environ_push(struct environ *); void printflike(2, 3) environ_log(struct environ *, const char *, ...); struct environ *environ_for_session(struct session *, int); +/* tty-draw.c */ +void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, + u_int, u_int, const struct tty_style_ctx *); + /* tty.c */ void tty_create_log(void); int tty_window_bigger(struct tty *); @@ -2629,8 +2640,7 @@ void tty_update_window_offset(struct window *); void tty_update_client_offset(struct client *); void tty_raw(struct tty *, const char *); void tty_attributes(struct tty *, const struct grid_cell *, - const struct grid_cell *, struct colour_palette *, - struct hyperlinks *); + const struct tty_style_ctx *); void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); @@ -2649,8 +2659,7 @@ void tty_puts(struct tty *, const char *); void tty_putc(struct tty *, u_char); void tty_putn(struct tty *, const void *, size_t, u_int); void tty_cell(struct tty *, const struct grid_cell *, - const struct grid_cell *, struct colour_palette *, - struct hyperlinks *); + const struct tty_style_ctx *); int tty_init(struct tty *, struct client *); void tty_resize(struct tty *); void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); @@ -2662,17 +2671,13 @@ void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); void tty_set_path(struct tty *, const char *); void tty_set_progress_bar(struct tty *, struct progress_bar *); -void tty_default_attributes(struct tty *, const struct grid_cell *, - struct colour_palette *, u_int, struct hyperlinks *); +void tty_default_attributes(struct tty *, u_int, + const struct tty_style_ctx *); void tty_update_mode(struct tty *, int, struct screen *); const struct grid_cell *tty_check_codeset(struct tty *, const struct grid_cell *); struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, u_int); - -/* tty-draw.c */ -void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, - u_int, u_int, const struct grid_cell *, struct colour_palette *); void tty_sync_start(struct tty *); void tty_sync_end(struct tty *); int tty_open(struct tty *, char **); diff --git a/tty-draw.c b/tty-draw.c index b6178754..a2868aa4 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -22,6 +22,7 @@ #include "tmux.h" +/* Current state when drawing line. */ enum tty_draw_line_state { TTY_DRAW_LINE_FIRST, TTY_DRAW_LINE_FLUSH, @@ -91,8 +92,7 @@ tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx, /* Draw a line from screen to tty. */ void tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, - u_int atx, u_int aty, const struct grid_cell *defaults, - struct colour_palette *palette) + u_int atx, u_int aty, const struct tty_style_ctx *style_ctx) { struct grid *gd = s->grid; const struct grid_cell *gcp; @@ -104,6 +104,14 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, char buf[1000]; size_t len; enum tty_draw_line_state current_state, next_state; + struct tty_style_ctx default_style_ctx = { 0 }; + + + if (style_ctx == NULL) { + default_style_ctx.defaults = &grid_default_cell; + default_style_ctx.hyperlinks = s->hyperlinks; + style_ctx = &default_style_ctx; + } /* * py is the line in the screen to draw. px is the start x and nx is @@ -130,8 +138,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, else ex = screen_size_x(s); log_debug("%s: drawing %u-%u,%u (end %u) at %u,%u; defaults: fg=%d, " - "bg=%d", __func__, px, px + nx, py, ex, atx, aty, defaults->fg, - defaults->bg); + "bg=%d", __func__, px, px + nx, py, ex, atx, aty, + style_ctx->defaults->fg, style_ctx->defaults->bg); /* Turn off cursor while redrawing and reset region and margins. */ flags = (tty->flags & TTY_NOCURSOR); @@ -142,8 +150,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* Start with the default cell as the last cell. */ memcpy(&last, &grid_default_cell, sizeof last); - last.bg = defaults->bg; - tty_default_attributes(tty, defaults, palette, 8, s->hyperlinks); + last.bg = style_ctx->defaults->bg; + tty_default_attributes(tty, 8, style_ctx); /* * If there is padding at the start, we must have truncated a wide @@ -164,7 +172,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, break; } if (i == 0) - bg = defaults->bg; + bg = style_ctx->defaults->bg; else { bg = gc.bg; if (gc.flags & GRID_FLAG_SELECTED) { @@ -173,9 +181,9 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, bg = ngc.bg; } } - tty_attributes(tty, &last, defaults, palette, s->hyperlinks); + tty_attributes(tty, &last, style_ctx); log_debug("%s: clearing %u padding cells", __func__, cx); - tty_draw_line_clear(tty, atx, aty, cx, defaults, bg, 0); + tty_draw_line_clear(tty, atx, aty, cx, style_ctx->defaults, bg, 0); if (cx == ex) goto out; atx += cx; @@ -274,15 +282,14 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* If the state has changed, flush any collected data. */ if (next_state != current_state) { if (current_state == TTY_DRAW_LINE_EMPTY) { - tty_attributes(tty, &last, defaults, palette, - s->hyperlinks); + tty_attributes(tty, &last, style_ctx); tty_draw_line_clear(tty, atx + last_i, aty, - i - last_i, defaults, last.bg, wrapped); + i - last_i, style_ctx->defaults, last.bg, + wrapped); wrapped = 0; } else if (next_state != TTY_DRAW_LINE_SAME && len != 0) { - tty_attributes(tty, &last, defaults, palette, - s->hyperlinks); + tty_attributes(tty, &last, style_ctx); if (atx + i - width != 0 || !wrapped) tty_cursor(tty, atx + i - width, aty); if (~last.attr & GRID_ATTR_CHARSET) @@ -322,4 +329,3 @@ out: tty->flags = (tty->flags & ~TTY_NOCURSOR)|flags; tty_update_mode(tty, tty->mode, s); } - diff --git a/tty.c b/tty.c index 1d4e25df..ef4dcd25 100644 --- a/tty.c +++ b/tty.c @@ -79,6 +79,10 @@ static int tty_check_overlay(struct tty *, u_int, u_int); #define TTY_QUERY_TIMEOUT 5 #define TTY_REQUEST_LIMIT 30 +static struct tty_style_ctx tty_default_style_ctx = { + &grid_default_cell, NULL, NULL +}; + void tty_create_log(void) { @@ -1410,7 +1414,7 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) if (rr->nx == 0) continue; tty_draw_line(tty, s, rr->px - ctx->xoff, py, rr->nx, - rr->px, ctx->yoff + py, &ctx->defaults, ctx->palette); + rr->px, ctx->yoff + py, &ctx->style_ctx); } return; } @@ -1421,7 +1425,7 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) if (rr->nx == 0) continue; tty_draw_line(tty, s, i + rr->px - x, py, rr->nx, - rr->px, ry, &ctx->defaults, ctx->palette); + rr->px, ry, &ctx->style_ctx); } } } @@ -1578,8 +1582,7 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1601,8 +1604,7 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1612,8 +1614,7 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->n, ctx->bg); } @@ -1635,8 +1636,7 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1663,8 +1663,7 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1677,8 +1676,7 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg); } @@ -1688,8 +1686,7 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) { u_int nx = ctx->sx - ctx->ocx; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg); } @@ -1697,8 +1694,7 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg); } @@ -1724,8 +1720,7 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1756,8 +1751,7 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1797,8 +1791,7 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1838,8 +1831,7 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1858,8 +1850,7 @@ tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1883,8 +1874,7 @@ tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1908,8 +1898,7 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, - ctx->s->hyperlinks); + tty_default_attributes(tty, ctx->bg, &ctx->style_ctx); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1934,8 +1923,7 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette, - ctx->s->hyperlinks); + tty_attributes(tty, &grid_default_cell, &ctx->style_ctx); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1968,7 +1956,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) vis += r->ranges[i].nx; if (vis < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, - px, py, &ctx->defaults, ctx->palette); + px, py, &ctx->style_ctx); return; } } @@ -1983,8 +1971,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_invalidate(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, - ctx->s->hyperlinks); + tty_cell(tty, ctx->cell, &ctx->style_ctx); if (ctx->flags & TTY_CTX_CELL_INVALIDATE) tty_invalidate(tty); @@ -2020,8 +2007,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, - ctx->s->hyperlinks); + tty_attributes(tty, ctx->cell, &ctx->style_ctx); /* Get tty position from pane position for overlay check. */ px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2098,8 +2084,7 @@ tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) void tty_cell(struct tty *tty, const struct grid_cell *gc, - const struct grid_cell *defaults, struct colour_palette *palette, - struct hyperlinks *hl) + const struct tty_style_ctx *style_ctx) { const struct grid_cell *gcp; @@ -2119,7 +2104,7 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, /* Check the output codeset and apply attributes. */ gcp = tty_check_codeset(tty, gc); - tty_attributes(tty, gcp, defaults, palette, hl); + tty_attributes(tty, gcp, style_ctx); /* If it is a single character, write with putc to handle ACS. */ if (gcp->data.size == 1) { @@ -2464,19 +2449,22 @@ tty_hyperlink(struct tty *tty, const struct grid_cell *gc, void tty_attributes(struct tty *tty, const struct grid_cell *gc, - const struct grid_cell *defaults, struct colour_palette *palette, - struct hyperlinks *hl) + const struct tty_style_ctx *style_ctx) { struct grid_cell *tc = &tty->cell, gc2; int changed; + /* Use default style if not given. */ + if (style_ctx == NULL) + style_ctx = &tty_default_style_ctx; + /* Copy cell and update default colours. */ memcpy(&gc2, gc, sizeof gc2); if (~gc->flags & GRID_FLAG_NOPALETTE) { if (gc2.fg == 8) - gc2.fg = defaults->fg; + gc2.fg = style_ctx->defaults->fg; if (gc2.bg == 8) - gc2.bg = defaults->bg; + gc2.bg = style_ctx->defaults->bg; } /* Ignore cell if it is the same as the last one. */ @@ -2503,9 +2491,9 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, } /* Fix up the colours if necessary. */ - tty_check_fg(tty, palette, &gc2); - tty_check_bg(tty, palette, &gc2); - tty_check_us(tty, palette, &gc2); + tty_check_fg(tty, style_ctx->palette, &gc2); + tty_check_bg(tty, style_ctx->palette, &gc2); + tty_check_us(tty, style_ctx->palette, &gc2); /* * If any bits are being cleared or the underline colour is now default, @@ -2561,7 +2549,7 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, tty_putcode(tty, TTYC_SMACS); /* Set hyperlink if any. */ - tty_hyperlink(tty, gc, hl); + tty_hyperlink(tty, gc, style_ctx->hyperlinks); memcpy(&tty->last_cell, &gc2, sizeof tty->last_cell); } @@ -2583,8 +2571,9 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) */ if (COLOUR_DEFAULT(gc->fg) || COLOUR_DEFAULT(gc->bg)) { /* - * If don't have AX, send sgr0. This resets both colours to default. - * Otherwise, try to set the default colour only as needed. + * If don't have AX, send sgr0. This resets both colours to + * default. Otherwise, try to set the default colour only as + * needed. */ if (!tty_term_flag(tty->term, TTYC_AX)) tty_reset(tty); @@ -2950,14 +2939,14 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) } void -tty_default_attributes(struct tty *tty, const struct grid_cell *defaults, - struct colour_palette *palette, u_int bg, struct hyperlinks *hl) +tty_default_attributes(struct tty *tty, u_int bg, + const struct tty_style_ctx *style_ctx) { struct grid_cell gc; memcpy(&gc, &grid_default_cell, sizeof gc); gc.bg = bg; - tty_attributes(tty, &gc, defaults, palette, hl); + tty_attributes(tty, &gc, style_ctx); } static void diff --git a/window-copy.c b/window-copy.c index 39ae1881..bdb81c44 100644 --- a/window-copy.c +++ b/window-copy.c @@ -567,13 +567,8 @@ window_copy_add(struct window_pane *wp, int parse, const char *fmt, ...) static void window_copy_init_ctx_cb(__unused struct screen_write_ctx *ctx, - struct tty_ctx *ttyctx) + __unused struct tty_ctx *ttyctx) { - memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults); - ttyctx->palette = NULL; - ttyctx->redraw_cb = NULL; - ttyctx->set_client_cb = NULL; - ttyctx->arg = NULL; } void From 5c0db5a293c054c1188d4304a9484e5c6d833e83 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 9 Jun 2026 21:31:16 +0000 Subject: [PATCH 2/9] Preserve the original text in the first line for display-panes. --- cmd-display-panes.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 7470587a..054c3c35 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -84,20 +84,21 @@ cmd_display_panes_draw_format(struct screen_redraw_ctx *ctx, struct client *c = ctx->c; struct tty *tty = &c->tty; struct session *s = c->session; - struct screen screen; + struct screen *sc = wp->screen, screen; struct screen_write_ctx sctx; struct visible_ranges *r; struct visible_range *ri; const char *format; char *expanded; u_int i, px = ctx->ox + xoff; - struct tty_style_ctx style_ctx = { .defaults = gc }; format = options_get_string(s->options, "display-panes-format"); expanded = format_single(NULL, format, c, s, s->curw, wp); screen_init(&screen, sx, 1, 0); screen_write_start(&sctx, &screen); + screen_write_fast_copy(&sctx, sc, 0, sc->grid->hsize, sx, 1); + screen_write_cursormove(&sctx, 0, 0, 0); format_draw(&sctx, gc, sx, expanded, NULL, 0); screen_write_stop(&sctx); free(expanded); @@ -106,7 +107,7 @@ cmd_display_panes_draw_format(struct screen_redraw_ctx *ctx, for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; tty_draw_line(tty, &screen, ri->px - px, 0, ri->nx, - ri->px - ctx->ox, yoff, &style_ctx); + ri->px - ctx->ox, yoff, NULL); } screen_free(&screen); } From f415a70c0be40fd6f8ae7b26523a92f8f3b6ca88 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 10 Jun 2026 14:17:10 +0100 Subject: [PATCH 3/9] Merge two SIXEL blocks. --- tmux.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tmux.h b/tmux.h index 9f979c1c..998afe01 100644 --- a/tmux.h +++ b/tmux.h @@ -2673,10 +2673,6 @@ struct environ *environ_for_session(struct session *, int); void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, u_int, u_int, const struct tty_style_ctx *); -#ifdef ENABLE_SIXEL -void tty_draw_images(struct client *, struct window_pane *, struct screen *); -#endif - /* tty.c */ void tty_create_log(void); int tty_window_bigger(struct tty *); @@ -2754,6 +2750,7 @@ void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); #ifdef ENABLE_SIXEL void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); +void tty_draw_images(struct client *, struct window_pane *, struct screen *); #endif void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); void tty_default_colours(struct grid_cell *, struct window_pane *); From c6c8f77bc03e48eae5b4c9b8bcbd9477d769a19a Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Jun 2026 14:06:45 +0000 Subject: [PATCH 4/9] While grouped sessions are being killed, it can leave session which are not really useful as targets (no active pane or current window) in the tree. Make cmd_find_best_session skip these. GitHub issue 5167. --- cmd-find.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmd-find.c b/cmd-find.c index 761f133f..88a276b3 100644 --- a/cmd-find.c +++ b/cmd-find.c @@ -148,6 +148,18 @@ cmd_find_session_better(struct session *s, struct session *than, int flags) return (timercmp(&s->activity_time, &than->activity_time, >)); } +/* Can this session be usefully targeted? */ +static int +cmd_find_session_valid(struct session *s) +{ + if (!session_alive(s) || + s->curw == NULL || + s->curw->window == NULL || + s->curw->window->active == NULL) + return (0); + return (1); +} + /* Find best session from a list, or all if list is NULL. */ static struct session * cmd_find_best_session(struct session **slist, u_int ssize, int flags) @@ -160,11 +172,15 @@ cmd_find_best_session(struct session **slist, u_int ssize, int flags) s = NULL; if (slist != NULL) { for (i = 0; i < ssize; i++) { + if (!cmd_find_session_valid(slist[i])) + continue; if (cmd_find_session_better(slist[i], s, flags)) s = slist[i]; } } else { RB_FOREACH(s_loop, sessions, &sessions) { + if (!cmd_find_session_valid(s_loop)) + continue; if (cmd_find_session_better(s_loop, s, flags)) s = s_loop; } From 8cb4aabb8bd2a699b08aca119ebe83e4c2f3798a Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Jun 2026 14:29:08 +0000 Subject: [PATCH 5/9] Replace refresh-from-pane in copy mode with a way to automatically update as pane content changes. This is toggled by pressing r. GitHub issue 5165 from Barrett Ruth. --- grid.c | 11 +- key-bindings.c | 4 +- tmux.1 | 15 ++- tmux.h | 5 + window-copy.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 284 insertions(+), 19 deletions(-) diff --git a/grid.c b/grid.c index 36c99184..52da173a 100644 --- a/grid.c +++ b/grid.c @@ -292,7 +292,7 @@ grid_free_line(struct grid *gd, u_int py) } /* Free several lines. */ -static void +void grid_free_lines(struct grid *gd, u_int py, u_int ny) { u_int yy; @@ -320,6 +320,10 @@ grid_create(u_int sx, u_int sy, u_int hlimit) gd->hsize = 0; gd->hlimit = hlimit; + gd->scroll_added = 0; + gd->scroll_collected = 0; + gd->scroll_generation = 0; + if (gd->sy != 0) gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata); else @@ -405,6 +409,7 @@ grid_collect_history(struct grid *gd, int all) grid_trim_history(gd, ny); gd->hsize -= ny; + gd->scroll_collected += ny; if (gd->hscrolled > gd->hsize) gd->hscrolled = gd->hsize; } @@ -442,6 +447,7 @@ grid_scroll_history(struct grid *gd, u_int bg) grid_compact_line(&gd->linedata[gd->hsize]); gd->linedata[gd->hsize].time = current_time; gd->hsize++; + gd->scroll_added++; } /* Clear the history. */ @@ -452,6 +458,7 @@ grid_clear_history(struct grid *gd) gd->hscrolled = 0; gd->hsize = 0; + gd->scroll_generation++; gd->linedata = xreallocarray(gd->linedata, gd->sy, sizeof *gd->linedata); @@ -489,6 +496,7 @@ grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg) /* Move the history offset down over the line. */ gd->hscrolled++; gd->hsize++; + gd->scroll_added++; } /* Expand line to fit to cell. */ @@ -1510,6 +1518,7 @@ grid_reflow(struct grid *gd, u_int sx) free(gd->linedata); gd->linedata = target->linedata; free(target); + gd->scroll_generation++; } /* Convert to position based on wrapped lines. */ diff --git a/key-bindings.c b/key-bindings.c index 8b97c5f0..f6fe6066 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -530,7 +530,7 @@ key_bindings_init(void) "bind -Tcopy-mode g { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }", "bind -Tcopy-mode n { send -X search-again }", "bind -Tcopy-mode q { send -X cancel }", - "bind -Tcopy-mode r { send -X refresh-from-pane }", + "bind -Tcopy-mode r { send -X refresh-toggle }", "bind -Tcopy-mode t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", "bind -Tcopy-mode Home { send -X start-of-line }", "bind -Tcopy-mode End { send -X end-of-line }", @@ -638,7 +638,7 @@ key_bindings_init(void) "bind -Tcopy-mode-vi n { send -X search-again }", "bind -Tcopy-mode-vi o { send -X other-end }", "bind -Tcopy-mode-vi q { send -X cancel }", - "bind -Tcopy-mode-vi r { send -X refresh-from-pane }", + "bind -Tcopy-mode-vi r { send -X refresh-toggle }", "bind -Tcopy-mode-vi t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", "bind -Tcopy-mode-vi v { send -X rectangle-toggle }", "bind -Tcopy-mode-vi w { send -X next-word }", diff --git a/tmux.1 b/tmux.1 index f6b24fde..d50c47f9 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2293,11 +2293,22 @@ Toggle rectangle selection mode. .Xc Cycles the current line between centre, top, and bottom. .It Xo -.Ic refresh\-from\-pane +.Ic refresh\-on +.Xc +Turn on automatic refresh of the content from the pane, so that new output +appears while in copy mode. +Automatic refresh is off by default; it will scroll only while the cursor is at +the bottom and is paused while a selection is in progress. +.It Xo +.Ic refresh\-off +.Xc +Turn off automatic refresh of the content from the pane. +.It Xo +.Ic refresh\-toggle (vi: r) (emacs: r) .Xc -Refresh the content from the pane. +Toggle automatic refresh of the content from the pane. .It Xo .Ic scroll\-bottom .Xc diff --git a/tmux.h b/tmux.h index 63588167..0c3d52af 100644 --- a/tmux.h +++ b/tmux.h @@ -862,6 +862,10 @@ struct grid { u_int hsize; u_int hlimit; + u_int scroll_added; + u_int scroll_collected; + u_int scroll_generation; + struct grid_line *linedata; }; @@ -3168,6 +3172,7 @@ int grid_cells_look_equal(const struct grid_cell *, const struct grid_cell *); struct grid *grid_create(u_int, u_int, u_int); void grid_destroy(struct grid *); +void grid_free_lines(struct grid *, u_int, u_int); int grid_compare(struct grid *, struct grid *); void grid_collect_history(struct grid *, int); void grid_remove_history(struct grid *, u_int ); diff --git a/window-copy.c b/window-copy.c index bdb81c44..558bd76c 100644 --- a/window-copy.c +++ b/window-copy.c @@ -51,6 +51,11 @@ static void window_copy_redraw_selection(struct window_mode_entry *, u_int); static void window_copy_redraw_lines(struct window_mode_entry *, u_int, u_int); static void window_copy_redraw_screen(struct window_mode_entry *); +static void window_copy_do_refresh(struct window_mode_entry *, int); +static void window_copy_refresh_timer(int, short, void *); +static void window_copy_refresh_arm(struct window_mode_entry *); +static void window_copy_refresh_start(struct window_mode_entry *); +static void window_copy_refresh_stop(struct window_mode_entry *); static void window_copy_style_changed(struct window_mode_entry *); static int window_copy_line_number_mode(struct window_mode_entry *); static int window_copy_line_number_is_absolute(struct window_mode_entry *); @@ -255,6 +260,10 @@ struct window_copy_mode_data { int backing_written; /* backing display started */ struct input_ctx *ictx; + u_int sync_added; /* snapshot of backing grid counters */ + u_int sync_collected; + u_int sync_generation; + int viewmode; /* view mode entered */ u_int oy; /* number of lines scrolled up */ @@ -338,6 +347,10 @@ struct window_copy_mode_data { struct event dragtimer; #define WINDOW_COPY_DRAG_REPEAT_TIME 50000 + + struct event refresh_timer; +#define WINDOW_COPY_REFRESH_INTERVAL 50000 + int refresh_active; }; static void @@ -424,6 +437,116 @@ window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx, return (dst); } +/* + * Snapshot the source grid's monotonic scroll counters so the next incremental + * sync can tell how much history was added or collected since this point. + */ +static void +window_copy_sync_snapshot(struct window_copy_mode_data *data, struct grid *src) +{ + data->sync_added = src->scroll_added; + data->sync_collected = src->scroll_collected; + data->sync_generation = src->scroll_generation; +} + +/* + * Reconcile the backing screen with the live pane grid in place, copying only + * the history that scrolled in or was collected since the last snapshot rather + * than cloning the whole scrollback. The result is identical to a fresh + * window_copy_clone_screen, so the caller repositions and redraws the same way + * for both paths. Returns 1 on success, or 0 if the caller must fall back to a + * full clone (different source pane, geometry or generation change, or counter + * deltas that do not add up). + */ +static int +window_copy_sync_backing(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + struct window_pane *wp = wme->swp; + struct screen *src = &wp->base; + struct screen *dst = data->backing; + struct grid *sg = src->grid; + struct grid *dg = dst->grid; + u_int sy = sg->sy; + u_int old_hsize = dg->hsize; + u_int new_hsize = sg->hsize; + u_int added, collected, kept; + + /* + * Only a pane's own live grid is tracked incrementally. A different + * source pane (copy-mode -s) goes through clone_screen, which also + * trims trailing blank lines that this path does not. + */ + if (data->viewmode || wme->swp != wme->wp) + return (0); + + /* Indices only line up at the same size and generation. */ + if (sg->sx != dg->sx || sg->sy != dg->sy || + sg->scroll_generation != data->sync_generation) + return (0); + + added = sg->scroll_added - data->sync_added; + collected = sg->scroll_collected - data->sync_collected; + + /* + * Reject anything that does not balance: counter wrap, a history-limit + * change that collected past the snapshot, or arithmetic that does not + * reproduce the new history size. + */ + if (added > (u_int)INT_MAX || collected > (u_int)INT_MAX || + collected > old_hsize || old_hsize + added < collected || + old_hsize + added - collected != new_hsize) + return (0); + + kept = old_hsize - collected; + + if (added == 0 && collected == 0) { + /* History is unchanged; only the viewport can have mutated. */ + grid_duplicate_lines(dg, dg->hsize, sg, sg->hsize, sy); + } else { + /* Drop the oldest lines and shift the rest down. */ + if (collected > 0) { + grid_free_lines(dg, 0, collected); + memmove(&dg->linedata[0], &dg->linedata[collected], + (old_hsize + sy - collected) * sizeof *dg->linedata); + memset(&dg->linedata[old_hsize + sy - collected], 0, + collected * sizeof *dg->linedata); + } + + /* Resize linedata to the new history plus viewport. */ + if (new_hsize + sy != old_hsize + sy - collected) { + dg->linedata = xreallocarray(dg->linedata, + new_hsize + sy, sizeof *dg->linedata); + memset(&dg->linedata[old_hsize + sy - collected], 0, + (new_hsize - kept) * sizeof *dg->linedata); + } + + /* + * Set hsize before copying so grid_duplicate_lines does not + * clamp the count to the old, smaller grid size. + */ + dg->hsize = new_hsize; + + /* Copy the newly scrolled history, then refresh the viewport. */ + if (added > 0) + grid_duplicate_lines(dg, kept, sg, kept, added); + grid_duplicate_lines(dg, new_hsize, sg, new_hsize, sy); + } + + dg->hscrolled = sg->hscrolled; + + /* Match clone_screen's backing cursor placement. */ + if (src->cy > dg->sy - 1) { + dst->cx = 0; + dst->cy = dg->sy - 1; + } else { + dst->cx = src->cx; + dst->cy = src->cy; + } + + return (1); +} + static struct window_copy_mode_data * window_copy_common_init(struct window_mode_entry *wme) { @@ -458,6 +581,7 @@ window_copy_common_init(struct window_mode_entry *wme) data->modekeys = options_get_number(wp->window->options, "mode-keys"); evtimer_set(&data->dragtimer, window_copy_scroll_timer, wme); + evtimer_set(&data->refresh_timer, window_copy_refresh_timer, wme); return (data); } @@ -475,6 +599,7 @@ window_copy_init(struct window_mode_entry *wme, data = window_copy_common_init(wme); data->backing = window_copy_clone_screen(base, &data->screen, &cx, &cy, wme->swp != wme->wp); + window_copy_sync_snapshot(data, base->grid); data->cx = cx; if (cy < screen_hsize(data->backing)) { @@ -541,6 +666,7 @@ window_copy_free(struct window_mode_entry *wme) struct window_copy_mode_data *data = wme->data; evtimer_del(&data->dragtimer); + evtimer_del(&data->refresh_timer); free(data->searchmark); free(data->searchstr); @@ -2781,34 +2907,136 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) return (action); } -static enum window_copy_cmd_action -window_copy_cmd_refresh_from_pane(struct window_copy_cmd_state *cs) +/* + * Reconcile the backing screen with the live pane, incrementally if possible + * and otherwise by recloning, then reposition the view. When following, jump + * to the bottom so new output stays visible; otherwise keep the same lines on + * screen. Driven by the automatic refresh timer. + */ +static void +window_copy_do_refresh(struct window_mode_entry *wme, int follow) { - struct window_mode_entry *wme = cs->wme; struct window_pane *wp = wme->swp; struct window_copy_mode_data *data = wme->data; u_int oy_from_top; - if (data->viewmode) - return (WINDOW_COPY_CMD_NOTHING); if (data->oy > screen_hsize(data->backing)) data->oy = screen_hsize(data->backing); oy_from_top = screen_hsize(data->backing) - data->oy; - screen_free(data->backing); - free(data->backing); - data->backing = window_copy_clone_screen(&wp->base, &data->screen, NULL, - NULL, wme->swp != wme->wp); + if (!window_copy_sync_backing(wme)) { + screen_free(data->backing); + free(data->backing); + data->backing = window_copy_clone_screen(&wp->base, + &data->screen, NULL, NULL, wme->swp != wme->wp); + } - if (oy_from_top <= screen_hsize(data->backing)) + if (follow) { + data->cy = screen_size_y(&data->screen) - 1; + data->cx = window_copy_cursor_limit(wme, + screen_hsize(data->backing) + data->cy, 0); + data->oy = 0; + } else if (oy_from_top <= screen_hsize(data->backing)) data->oy = screen_hsize(data->backing) - oy_from_top; else { data->cy = 0; data->oy = screen_hsize(data->backing); } + window_copy_sync_snapshot(data, wp->base.grid); window_copy_size_changed(wme); - return (WINDOW_COPY_CMD_REDRAW); +} + +static void +window_copy_refresh_arm(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + struct timeval tv = { + .tv_sec = WINDOW_COPY_REFRESH_INTERVAL / 1000000, + .tv_usec = WINDOW_COPY_REFRESH_INTERVAL % 1000000 + }; + + if (data->refresh_active) + evtimer_add(&data->refresh_timer, &tv); +} + +static void +window_copy_refresh_timer(__unused int fd, __unused short events, void *arg) +{ + struct window_mode_entry *wme = arg; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; + int follow; + + if (TAILQ_FIRST(&wp->modes) != wme || !data->refresh_active) + return; + + /* + * Skip the refresh while a selection is being made, otherwise it would + * move; only follow new output if the cursor is still at the bottom. + */ + if ((wp->flags & PANE_UNSEENCHANGES) && data->screen.sel == NULL && + data->cursordrag == CURSORDRAG_NONE) { + follow = (data->oy == 0 && + data->cy == screen_size_y(&data->screen) - 1); + window_copy_do_refresh(wme, follow); + window_copy_redraw_screen(wme); + /* The timer runs outside key handling, so force a repaint. */ + wp->flags |= PANE_REDRAW; + wp->flags &= ~PANE_UNSEENCHANGES; + } + + window_copy_refresh_arm(wme); +} + +static void +window_copy_refresh_start(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + + /* + * Do not refresh a view of another pane (copy-mode -s): the source may + * disappear and changes are not tracked on this pane. + */ + if (data->viewmode || wme->swp != wme->wp || data->refresh_active) + return; + data->refresh_active = 1; + window_copy_refresh_arm(wme); +} + +static void +window_copy_refresh_stop(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + + data->refresh_active = 0; + evtimer_del(&data->refresh_timer); +} + +static enum window_copy_cmd_action +window_copy_cmd_refresh_on(struct window_copy_cmd_state *cs) +{ + window_copy_refresh_start(cs->wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_refresh_off(struct window_copy_cmd_state *cs) +{ + window_copy_refresh_stop(cs->wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_refresh_toggle(struct window_copy_cmd_state *cs) +{ + struct window_copy_mode_data *data = cs->wme->data; + + if (data->refresh_active) + window_copy_refresh_stop(cs->wme); + else + window_copy_refresh_start(cs->wme); + return (WINDOW_COPY_CMD_NOTHING); } static enum window_copy_cmd_action @@ -3275,11 +3503,23 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_rectangle_toggle }, - { .command = "refresh-from-pane", + { .command = "refresh-on", .args = { "", 0, 0, NULL }, .flags = WINDOW_COPY_CMD_FLAG_READONLY, - .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, - .f = window_copy_cmd_refresh_from_pane + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_refresh_on + }, + { .command = "refresh-off", + .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_refresh_off + }, + { .command = "refresh-toggle", + .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_refresh_toggle }, { .command = "scroll-bottom", .args = { "", 0, 0, NULL }, From c019b3dad95fc9179898117595c3dd7fde3dd745 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Jun 2026 14:51:20 +0000 Subject: [PATCH 6/9] Use correct size when calculating status line right trim. GitHub issue 5098 from Aung Myo Kyaw. --- screen-redraw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen-redraw.c b/screen-redraw.c index 63b19e49..dd62971a 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -713,7 +713,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) /* Right not visible. */ l = 0; x = xoff - ctx->ox; - width = size - x; + width = ctx->sx - x; } r = tty_check_overlay_range(tty, x, yoff, width); From 2719549cf28b0521daafa41c23614d0768192339 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Jun 2026 16:03:14 +0000 Subject: [PATCH 7/9] Rename new-pane -B to -W since -B is best kept to match -B to display-popup. --- cmd-split-window.c | 16 ++++++++-------- server.c | 2 +- tmux.1 | 8 ++++---- tmux.h | 4 ++-- window.c | 10 +++++----- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 98a4072d..4c6a12da 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bBc:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vx:X:y:Y:Z", 0, -1, NULL }, - .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " + .args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, + .usage = "[-bdefhIklPvWZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " "[-R inactive-border-style] [-T title] [-x width] [-y height] " @@ -57,8 +57,8 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bBc:de:EfF:hIkl:m:p:PR:s:S:t:T:vZ", 0, -1, NULL }, - .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " + .args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL }, + .usage = "[-bdefhIklPvWZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " "[-R inactive-border-style] [-T title] " CMD_TARGET_PANE_USAGE " " @@ -246,13 +246,13 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (input) return (CMD_RETURN_WAIT); - if (args_has(args, 'B')) { + if (args_has(args, 'W')) { /* - * With -B, block this command queue item until the pane's - * command exits; window_pane_block_finish will be called to + * With -W, block this command queue item until the pane's + * command exits; window_pane_wait_finish will be called to * continue it. */ - new_wp->block_item = item; + new_wp->wait_item = item; return (CMD_RETURN_WAIT); } return (CMD_RETURN_NORMAL); diff --git a/server.c b/server.c index ff4d165a..e83b3230 100644 --- a/server.c +++ b/server.c @@ -491,7 +491,7 @@ server_child_exited(pid_t pid, int status) log_debug("%%%u exited", wp->id); wp->flags |= PANE_EXITED; - window_pane_block_finish(wp); + window_pane_wait_finish(wp); if (window_pane_destroy_ready(wp)) server_destroy_pane(wp, 1); diff --git a/tmux.1 b/tmux.1 index d50c47f9..6251ed6c 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3412,7 +3412,7 @@ but a different format may be specified with .Fl F . .Tg newp .It Xo Ic new\-pane -.Op Fl bBdefhIkPvZ +.Op Fl bdefhIkPvWZ .Op Fl c Ar start\-directory .Op Fl e Ar environment .Op Fl F Ar format @@ -3488,13 +3488,13 @@ but also sets the option for this pane to .Ar message . .Pp -.Fl B -blocks until +.Fl W +Waits until .Ar shell\-command exits, then returns its exit status. For example: .Bd -literal -offset indent -$ tmux new-pane -B 'vi afile' +$ tmux new-pane -W 'vi afile' $ echo $? 0 .Ed diff --git a/tmux.h b/tmux.h index 0c3d52af..736617b6 100644 --- a/tmux.h +++ b/tmux.h @@ -1266,7 +1266,7 @@ struct window_pane { char tty[TTY_NAME_MAX]; int status; struct timeval dead_time; - struct cmdq_item *block_item; /* new-pane -B: waiting for pane exit */ + struct cmdq_item *wait_item; /* new-pane -W: waiting for pane exit */ int fd; struct bufferevent *event; @@ -3383,7 +3383,7 @@ struct window *window_find_by_id(u_int); void window_update_activity(struct window *); struct window *window_create(u_int, u_int, u_int, u_int); void window_pane_set_event(struct window_pane *); -void window_pane_block_finish(struct window_pane *); +void window_pane_wait_finish(struct window_pane *); struct window_pane *window_get_active_at(struct window *, u_int, u_int); struct window_pane *window_find_string(struct window *, const char *); int window_has_floating_panes(struct window *); diff --git a/window.c b/window.c index 914c34d7..352ebf75 100644 --- a/window.c +++ b/window.c @@ -388,7 +388,7 @@ window_pane_destroy_ready(struct window_pane *wp) * If a command queue item is blocked on this pane, wait for the * child's exit status before destroying it. */ - if (wp->block_item != NULL && (~wp->flags & PANE_STATUSREADY)) + if (wp->wait_item != NULL && (~wp->flags & PANE_STATUSREADY)) return (0); return (1); } @@ -1086,15 +1086,15 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) } void -window_pane_block_finish(struct window_pane *wp) +window_pane_wait_finish(struct window_pane *wp) { - struct cmdq_item *item = wp->block_item; + struct cmdq_item *item = wp->wait_item; struct client *c; int retval = 0; if (item == NULL) return; - wp->block_item = NULL; + wp->wait_item = NULL; if (wp->flags & PANE_STATUSREADY) { if (WIFEXITED(wp->status)) @@ -1115,7 +1115,7 @@ window_pane_destroy(struct window_pane *wp) struct window_pane_resize *r; struct window_pane_resize *r1; - window_pane_block_finish(wp); + window_pane_wait_finish(wp); window_pane_reset_mode_all(wp); free(wp->searchstr); From 4702e58ee5b546a0dc472f28c719129551004ef8 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Jun 2026 16:08:03 +0000 Subject: [PATCH 8/9] Fix text to refer to status-keys not mode-keys, GitHub issue 5191. --- options-table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options-table.c b/options-table.c index 81f28be6..90c8fb57 100644 --- a/options-table.c +++ b/options-table.c @@ -739,7 +739,7 @@ const struct options_table_entry options_table[] = { .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the command prompt when in command mode, if " - "'mode-keys' is set to 'vi'." + "'status-keys' is set to 'vi'." }, { .name = "message-format", From 7ec94fda08fd05545d4b6c8c6f5b4a7ce0f303d7 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Jun 2026 18:32:33 +0000 Subject: [PATCH 9/9] Make list-keys only use a message if -1 is given, otherwise behave like other commands (stdout or mode). --- cmd-list-keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-list-keys.c b/cmd-list-keys.c index d8065259..c20768f3 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -237,7 +237,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) cmd_list_keys_format_add_key_binding(ft, l[i], prefix); line = format_expand(ft, template); - if ((single && tc != NULL) || n == 1) + if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL)) status_message_set(tc, -1, 1, 0, 0, "%s", line); else if (*line != '\0') cmdq_print(item, "%s", line);