From bbd4768bb62354796bbb5fb7ab978c436f808559 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 8 Jun 2026 21:08:37 +0100 Subject: [PATCH 01/60] 3.7-rc version. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 093d855bb..09451ed2f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], next-3.7) +AC_INIT([tmux], 3.7-rc) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) From 8771b6051f35d6fcd1a001d6b999e32901826c29 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Jun 2026 23:06:21 +0000 Subject: [PATCH 02/60] Fix mouse events on tiled pane status line - when panes share a border, prefer the pane for which the border is the status line. With Dane Jensen. --- server-client.c | 8 ++------ window.c | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/server-client.c b/server-client.c index 25719f366..20536804e 100644 --- a/server-client.c +++ b/server-client.c @@ -695,12 +695,8 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, bdr_bottom = fwp->yoff + fwp->sy; if (py == bdr_bottom) break; - if (window_pane_is_floating(wp)) { - /* Floating pane, check top border. */ - bdr_top = fwp->yoff - 1; - if (py == bdr_top) - break; - } + if (py == bdr_top) + break; } } if (fwp != NULL) diff --git a/window.c b/window.c index 0d86ac508..314415889 100644 --- a/window.c +++ b/window.c @@ -628,12 +628,32 @@ window_get_active_at(struct window *w, u_int x, u_int y) pane_status = options_get_number(w->options, "pane-border-status"); + if (pane_status == PANE_STATUS_TOP) { + /* + * Prefer a pane's top border status line over the pane above's + * bottom border. + */ + TAILQ_FOREACH(wp, &w->z_index, zentry) { + if (!window_pane_visible(wp) || window_pane_is_floating(wp)) + continue; + + window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); + if ((int)x < xoff || x > xoff + sx) + continue; + if ((int)y == yoff - 1) + return (wp); + } + } + TAILQ_FOREACH(wp, &w->z_index, zentry) { if (!window_pane_visible(wp)) continue; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); if (!window_pane_is_floating(wp)) { - /* Tiled - to and including bottom or right border. */ + /* + * Tiled - to and including the right border, excluding + * the bottom border. + */ if ((int)x < xoff || x > xoff + sx) continue; if (pane_status == PANE_STATUS_TOP) { From aa1f0653e902e98b394b41f05860387c8b709818 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 9 Jun 2026 09:47:18 +0100 Subject: [PATCH 03/60] Bump version to 3.7-rc2. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 09451ed2f..63541c2df 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], 3.7-rc) +AC_INIT([tmux], 3.7-rc2) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) From cc87db74c78c28a6f9c6619ceae3ae97a0b95c53 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 9 Jun 2026 11:49:36 +0000 Subject: [PATCH 04/60] Two fixes for RI codepoints. Firstly, do not combine more than two of them - previously we were ending up with four codepoints in one cell which tmux believed to be width 2, but terminals considered width 4. Secondly, invalidate cursor position before redrawing the cell when the second codepoint is received, terminals vary in how they manage backspace and cursor movement across these characters, so it is better to use absolute rather than relative positioning. GitHub issue 4853. --- tty.c | 2 ++ utf8-combined.c | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tty.c b/tty.c index f201ba1d1..5fd29130b 100644 --- a/tty.c +++ b/tty.c @@ -2051,6 +2051,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); + if (ctx->flags & TTY_CTX_CELL_INVALIDATE) + tty_invalidate(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, diff --git a/utf8-combined.c b/utf8-combined.c index 65ecf9cdc..923342b43 100644 --- a/utf8-combined.c +++ b/utf8-combined.c @@ -82,6 +82,23 @@ utf8_is_hangul_filler(const struct utf8_data *ud) return (memcmp(ud->data, "\343\205\244", 3) == 0); } +/* Count regional indicator characters. */ +static u_int +utf8_regional_count(const struct utf8_data *ud) +{ + u_int count = 0, i; + + for (i = 0; i + 4 <= ud->size; i++) { + if (ud->data[i] == 0xf0 && + ud->data[i + 1] == 0x9f && + ud->data[i + 2] == 0x87 && + ud->data[i + 3] >= 0xa6 && + ud->data[i + 3] <= 0xbf) + count++; + } + return (count); +} + /* Should these two characters combine? */ int utf8_should_combine(const struct utf8_data *with, const struct utf8_data *add) @@ -94,8 +111,13 @@ utf8_should_combine(const struct utf8_data *with, const struct utf8_data *add) return (0); /* Regional indicators. */ - if ((a >= 0x1F1E6 && a <= 0x1F1FF) && (w >= 0x1F1E6 && w <= 0x1F1FF)) + if ((a >= 0x1F1E6 && a <= 0x1F1FF) && (w >= 0x1F1E6 && w <= 0x1F1FF)) { + if (utf8_regional_count(with) != 1) + return (0); + if (utf8_regional_count(add) != 1) + return (0); return (1); + } /* Emoji skin tone modifiers. */ switch (a) { From dabaae1534f206dd8513429b882481e6221a6afd Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 11 Jun 2026 23:01:31 +0000 Subject: [PATCH 05/60] Make buffer creation time sort oldest first like it used to, but change windows to match sessions and panes as newest first. --- sort.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sort.c b/sort.c index fda6a90ba..212da22ea 100644 --- a/sort.c +++ b/sort.c @@ -65,7 +65,12 @@ sort_buffer_cmp(const void *a0, const void *b0) result = strcmp(pa->name, pb->name); break; case SORT_CREATION: - result = pa->order - pb->order; + if (pa->order > pb->order) + result = -1; + else if (pa->order < pb->order) + result = 1; + else + result = 0; break; case SORT_SIZE: result = pa->size - pb->size; @@ -251,11 +256,11 @@ sort_winlink_cmp(const void *a0, const void *b0) break; case SORT_CREATION: if (timercmp(&wa->creation_time, &wb->creation_time, >)) { - result = -1; + result = 1; break; } if (timercmp(&wa->creation_time, &wb->creation_time, <)) { - result = 1; + result = -1; break; } break; From 83e45343875bb39c5854abf2c88856a10375c7c2 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 14 Jun 2026 20:37:57 +0000 Subject: [PATCH 06/60] Skip floating panes when working out the top or bottom cell. Fixes missing bottom status pane status line when floating panes exist. --- layout.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/layout.c b/layout.c index 28c66f1eb..da98b590e 100644 --- a/layout.c +++ b/layout.c @@ -292,15 +292,22 @@ layout_fix_offsets(struct window *w) static int layout_cell_is_top(struct window *w, struct layout_cell *lc) { - struct layout_cell *next; + struct layout_cell *next, *edge; while (lc != w->layout_root) { next = lc->parent; if (next == NULL) return (0); - if (next->type == LAYOUT_TOPBOTTOM && - lc != TAILQ_FIRST(&next->cells)) - return (0); + if (next->type == LAYOUT_TOPBOTTOM) { + edge = TAILQ_FIRST(&next->cells); + while (edge != NULL) { + if (~edge->flags & LAYOUT_CELL_FLOATING) + break; + edge = TAILQ_NEXT(edge, entry); + } + if (lc != edge) + return (0); + } lc = next; } return (1); @@ -310,15 +317,22 @@ layout_cell_is_top(struct window *w, struct layout_cell *lc) static int layout_cell_is_bottom(struct window *w, struct layout_cell *lc) { - struct layout_cell *next; + struct layout_cell *next, *edge; while (lc != w->layout_root) { next = lc->parent; if (next == NULL) return (0); - if (next->type == LAYOUT_TOPBOTTOM && - lc != TAILQ_LAST(&next->cells, layout_cells)) - return (0); + if (next->type == LAYOUT_TOPBOTTOM) { + edge = TAILQ_LAST(&next->cells, layout_cells); + while (edge != NULL) { + if (~edge->flags & LAYOUT_CELL_FLOATING) + break; + edge = TAILQ_PREV(edge, layout_cells, entry); + } + if (lc != edge) + return (0); + } lc = next; } return (1); From 78afc24736483941d27288708a051b275596d253 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 14 Jun 2026 20:53:20 +0000 Subject: [PATCH 07/60] Fix various errors in redrawing: - Fix the active pane colour when only two panes and scrollbars enabled. - Clip left and right scrollbars the same for floating panes. - Do not subtract scrollbar width twice when working out width of status line. - Check if a character is inside a visible range correctly (do not include the next position outside the range). --- screen-redraw.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 159c86f8b..ca6cf0cd1 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -211,7 +211,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, } /* Top/bottom borders. */ - if (vsplit && pane_status == PANE_STATUS_OFF && sb_w == 0) { + if (vsplit && pane_status == PANE_STATUS_OFF) { if (wp->yoff == 0 && py == sy && px <= sx / 2) return (SCREEN_REDRAW_BORDER_BOTTOM); if (wp->yoff != 0 && py == wp->yoff - 1 && px > sx / 2) @@ -220,9 +220,10 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if (sb_pos == PANE_SCROLLBARS_LEFT) { if ((wp->xoff - sb_w == 0 || px >= wp->xoff - sb_w) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { - if (wp->yoff != 0 && py == wp->yoff - 1) + if (pane_status != PANE_STATUS_BOTTOM && + wp->yoff != 0 && py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); - if (py == ey) + if (pane_status != PANE_STATUS_TOP && py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ @@ -617,7 +618,7 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, width = 0; else width = wp->sx + sb_w - 2; - max_width = (int)w->sx - (wp->xoff + 2) - sb_w; + max_width = (int)w->sx - (wp->xoff + 2); if (max_width < 0) max_width = 0; if (width > (u_int)max_width) @@ -701,7 +702,7 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) width = size; } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) { /* Both left and right not visible. */ - l = ctx->ox; + l = ctx->ox - xoff; x = 0; width = ctx->sx; } else if (xoff < ctx->ox) { @@ -1124,7 +1125,7 @@ screen_redraw_is_visible(struct visible_ranges *r, u_int px) return (1); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; - if (ri->nx != 0 && px >= ri->px && px <= ri->px + ri->nx) + if (ri->nx != 0 && px >= ri->px && px < ri->px + ri->nx) return (1); } return (0); @@ -1146,11 +1147,13 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, u_int lb, rb, tb, bb; u_int i, s; - if (px + width <= 0 || py < 0) + if (py < 0 || width == 0) goto empty; if (px < 0) { + if ((u_int)-px >= width) + goto empty; + width -= (u_int)-px; px = 0; - width += px; } if (base_wp == NULL) { @@ -1197,7 +1200,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, (u_int)py < tb || (u_int)py > bb) continue; - if (!window_pane_is_floating(wp) && (u_int)py == bb) + if (!window_pane_is_floating(wp) && + ((u_int)py == tb || (u_int)py == bb)) continue; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; From f98aaf544d42ed34e068f9aa67ad71aab1866d3f Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 07:40:45 +0000 Subject: [PATCH 08/60] When redrawing a whole pane line, fall into tty_draw_line for any characters that are not plain ASCII. Fixes redraw issues with partial tabs and other wide characters when a pane is partly outside the window. --- screen-write.c | 61 ++++++++++++++++++++++++++++++++++++++++---------- tmux.h | 1 + tty.c | 20 +++++++++++++++++ 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/screen-write.c b/screen-write.c index ceec45a34..cee1afdbc 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1122,6 +1122,25 @@ screen_write_backspace(struct screen_write_ctx *ctx) screen_write_set_cursor(ctx, cx, cy); } +/* Is this cell a single ASCII character? */ +static int +screen_write_cell_is_single(const struct grid_cell *gc) +{ + if (gc->data.width != 1) + return (0); + if (gc->data.size != 1) + return (0); + if (*gc->data.data < 0x20 || *gc->data.data == 0x7f) + return (0); + if (gc->flags & GRID_FLAG_CLEARED) + return (0); + if (gc->flags & GRID_FLAG_PADDING) + return (0); + if (gc->flags & GRID_FLAG_TAB) + return (0); + return (1); +} + /* Redraw all visible cells on a line. */ static void screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, @@ -1130,11 +1149,14 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, struct window_pane *wp = ctx->wp; struct screen *s = ctx->s; struct grid_cell gc, ngc; - u_int sx = screen_size_x(s), cx, i, n; + u_int sx = screen_size_x(s), cx, i; int xoff = wp->xoff, yoff = wp->yoff; struct visible_ranges *r; struct visible_range *ri; + if (s->mode & MODE_SYNC) + return; + r = screen_redraw_get_visible_ranges(wp, xoff, yoff + yy, sx, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; @@ -1142,19 +1164,34 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, continue; cx = ri->px - xoff; - for (n = 0; n < ri->nx && cx < sx; n++, cx++) { - grid_view_get_cell(s->grid, cx, yy, &gc); - if (~gc.flags & GRID_FLAG_SELECTED) - ttyctx->cell = &gc; - else { - screen_select_cell(s, &ngc, &gc); - ttyctx->cell = &ngc; - } + if (cx >= sx) + continue; + if (cx + ri->nx > sx) + ttyctx->n = sx - cx; + else + ttyctx->n = ri->nx; + if (ttyctx->n == 0) + continue; + ttyctx->ocx = cx; + ttyctx->ocy = yy; - ttyctx->ocx = cx; - ttyctx->ocy = yy; - tty_write(tty_cmd_cell, ttyctx); + if (ttyctx->n != 1) { + tty_write(tty_cmd_redrawline, ttyctx); + continue; } + + grid_view_get_cell(s->grid, cx, yy, &gc); + if (!screen_write_cell_is_single(&gc)) { + tty_write(tty_cmd_redrawline, ttyctx); + continue; + } + if (~gc.flags & GRID_FLAG_SELECTED) + ttyctx->cell = &gc; + else { + screen_select_cell(s, &ngc, &gc); + ttyctx->cell = &ngc; + } + tty_write(tty_cmd_cell, ttyctx); } } diff --git a/tmux.h b/tmux.h index 870e778be..c7a7405c7 100644 --- a/tmux.h +++ b/tmux.h @@ -2727,6 +2727,7 @@ void tty_write(void (*)(struct tty *, const struct tty_ctx *), void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); void tty_cmd_cell(struct tty *, const struct tty_ctx *); void tty_cmd_cells(struct tty *, const struct tty_ctx *); +void tty_cmd_redrawline(struct tty *, const struct tty_ctx *); void tty_cmd_clearendofline(struct tty *, const struct tty_ctx *); void tty_cmd_clearendofscreen(struct tty *, const struct tty_ctx *); void tty_cmd_clearline(struct tty *, const struct tty_ctx *); diff --git a/tty.c b/tty.c index 5fd29130b..82cb602d2 100644 --- a/tty.c +++ b/tty.c @@ -1431,6 +1431,26 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) } } +void +tty_cmd_redrawline(struct tty *tty, const struct tty_ctx *ctx) +{ + u_int i, x, rx, ry, j; + struct visible_ranges *r; + struct visible_range *rr; + + if (tty_clamp_line(tty, ctx, ctx->ocx, ctx->ocy, ctx->n, + &i, &x, &rx, &ry)) { + r = tty_check_overlay_range(tty, x, ry, rx); + for (j = 0; j < r->used; j++) { + rr = &r->ranges[j]; + if (rr->nx == 0) + continue; + tty_draw_line(tty, ctx->s, i + rr->px - x, + ctx->ocy, rr->nx, rr->px, ry, &ctx->style_ctx); + } + } +} + /* Check if character needs to be mapped for codeset. */ const struct grid_cell * tty_check_codeset(struct tty *tty, const struct grid_cell *gc) From 9cf8de099ca9b03206cfd04cca94c2673d8c4569 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 14:56:30 +0000 Subject: [PATCH 09/60] Convert cursor position back to pane coordinates for tty_cmd_cell. --- screen-write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen-write.c b/screen-write.c index cee1afdbc..c711e7b24 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2643,7 +2643,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (ri->nx == 0) continue; for (n = 0; n < ri->nx; n++) { - ttyctx.ocx = ri->px + n; + ttyctx.ocx = (int)ri->px - xoff + (int)n; tty_write(tty_cmd_cell, &ttyctx); } } From bae5c14941095865b37ac42622296a03549b3db8 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 12:07:49 +0000 Subject: [PATCH 10/60] Tidy up screen_redraw_get_visible_ranges by using a couple of temporaries for start and end of range. --- screen-redraw.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index ca6cf0cd1..d7e81a866 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1144,7 +1144,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, struct visible_range *ri; static struct visible_ranges sr = { NULL, 0, 0 }; int found_self, sb, sb_w, sb_pos; - u_int lb, rb, tb, bb; + int lb, rb, tb, bb, sx, ex; u_int i, s; if (py < 0 || width == 0) @@ -1197,11 +1197,10 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, bb = wp->yoff + wp->sy; if (!found_self || !window_pane_visible(wp) || - (u_int)py < tb || - (u_int)py > bb) + py < tb || + py > bb) continue; - if (!window_pane_is_floating(wp) && - ((u_int)py == tb || (u_int)py == bb)) + if (!window_pane_is_floating(wp) && (py == tb || py == bb)) continue; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; @@ -1225,34 +1224,29 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, rb = wp->xoff + wp->sx; else /* PANE_SCROLLBARS_RIGHT or none. */ rb = wp->xoff + wp->sx + sb_w; - if (rb > w->sx) + if (rb > (int)w->sx) rb = w->sx - 1; - if (lb > ri->px && - lb < ri->px + ri->nx && - rb >= ri->px + ri->nx) - { + sx = ri->px; + ex = sx + ri->nx - 1; + if (lb > sx && lb <= ex && rb > ex) { /* * If the left edge of floating pane falls * inside this range and right edge covers up * to right of range, then shrink left edge of * range. */ - ri->nx = lb - ri->px; - } - else if (rb >= ri->px && - rb < ri->px + ri->nx && - lb <= ri->px) { + ri->nx = lb - sx; + } else if (rb >= sx && rb <= ex && lb <= sx) { /* * Else if the right edge of floating pane falls * inside of this range and left edge covers * the left of range, then move px forward to * right edge of pane. */ - ri->nx = ri->nx - (rb + 1 - ri->px); - ri->px = ri->px + (rb + 1 - ri->px); - } - else if (lb > ri->px && rb < ri->px + ri->nx) { + ri->nx = ex - rb; + ri->px = rb + 1; + } else if (lb > sx && rb <= ex) { /* * Else if pane fully inside range then split * into 2 ranges. @@ -1264,11 +1258,11 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, } ri = &r->ranges[i]; r->ranges[i + 1].px = rb + 1; - r->ranges[i + 1].nx = ri->px + ri->nx - (rb + 1); + r->ranges[i + 1].nx = ex - rb; /* ri->px was copied, unchanged. */ - ri->nx = lb - ri->px; + ri->nx = lb - sx; r->used++; - } else if (lb <= ri->px && rb >= ri->px + ri->nx) { + } else if (lb <= sx && rb > ex) { /* * If floating pane completely covers this range * then delete it (make it 0 length). From 2d9afa63485911f56f869516079da0ee0f404e3b Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 11:45:51 +0000 Subject: [PATCH 11/60] Use correct x position when drawing clipped line. --- tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tty.c b/tty.c index 82cb602d2..4db26287f 100644 --- a/tty.c +++ b/tty.c @@ -1445,7 +1445,7 @@ tty_cmd_redrawline(struct tty *tty, const struct tty_ctx *ctx) rr = &r->ranges[j]; if (rr->nx == 0) continue; - tty_draw_line(tty, ctx->s, i + rr->px - x, + tty_draw_line(tty, ctx->s, ctx->ocx + i + rr->px - x, ctx->ocy, rr->nx, rr->px, ry, &ctx->style_ctx); } } From 2c4df765c56a052288d92b5f50f8580d0ee29ec0 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 16 Jun 2026 08:53:14 +0000 Subject: [PATCH 12/60] Fix missing border when drawing floating panes. From Michael Grant. --- screen-redraw.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index d7e81a866..17085a411 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -127,7 +127,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; int hsplit = 0, vsplit = 0, pane_status = ctx->pane_status; int pane_scrollbars = ctx->pane_scrollbars, sb_w = 0; - int sb_pos, sx = wp->sx, sy = wp->sy; + int sb_pos, sx = wp->sx, sy = wp->sy, left, right; enum layout_type split_type; if (pane_scrollbars != 0) @@ -145,20 +145,19 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, /* Floating pane borders. */ if (window_pane_is_floating(wp)) { + left = wp->xoff - 1; + right = wp->xoff + sx; + if (sb_pos == PANE_SCROLLBARS_LEFT) + left -= sb_w; + else + right += sb_w; if (py >= wp->yoff - 1 && py <= wp->yoff + sy) { - if (sb_pos == PANE_SCROLLBARS_LEFT) { - if (px == wp->xoff - 1 - sb_w) - return (SCREEN_REDRAW_BORDER_LEFT); - if (px == wp->xoff + sx) - return (SCREEN_REDRAW_BORDER_RIGHT); - } else { /* PANE_SCROLLBARS_RIGHT or none. */ - if (px == wp->xoff - 1) - return (SCREEN_REDRAW_BORDER_LEFT); - if (px == wp->xoff + sx + sb_w) - return (SCREEN_REDRAW_BORDER_RIGHT); - } + if (px == left) + return (SCREEN_REDRAW_BORDER_LEFT); + if (px == right) + return (SCREEN_REDRAW_BORDER_RIGHT); } - if (px >= wp->xoff && px <= wp->xoff + sx) { + if (px > left && px <= right) { if (py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); if (py == wp->yoff + sy) From baac29debc5b7fd3273e0a87874f187c15f89791 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 16 Jun 2026 09:48:04 +0100 Subject: [PATCH 13/60] Prefer libtinfow if it is available, GitHub issue 5224 from Lars Wendler. --- configure.ac | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/configure.ac b/configure.ac index 63541c2df..67d16fdf0 100644 --- a/configure.ac +++ b/configure.ac @@ -297,24 +297,24 @@ fi # Look for ncurses or curses. Try pkg-config first then directly for the # library. PKG_CHECK_MODULES( - LIBTINFO, - tinfo, + LIBTINFOW, + tinfow, [ - AM_CPPFLAGS="$LIBTINFO_CFLAGS $AM_CPPFLAGS" - CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS" - LIBS="$LIBTINFO_LIBS $LIBS" + AM_CPPFLAGS="$LIBTINFOW_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBTINFOW_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBTINFOW_LIBS $LIBS" found_ncurses=yes ], found_ncurses=no ) if test "x$found_ncurses" = xno; then PKG_CHECK_MODULES( - LIBNCURSES, - ncurses, + LIBTINFO, + tinfo, [ - AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS" - CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS" - LIBS="$LIBNCURSES_LIBS $LIBS" + AM_CPPFLAGS="$LIBTINFO_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBTINFO_LIBS $LIBS" found_ncurses=yes ], found_ncurses=no @@ -333,10 +333,23 @@ if test "x$found_ncurses" = xno; then found_ncurses=no ) fi +if test "x$found_ncurses" = xno; then + PKG_CHECK_MODULES( + LIBNCURSES, + ncurses, + [ + AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBNCURSES_LIBS $LIBS" + found_ncurses=yes + ], + found_ncurses=no + ) +fi if test "x$found_ncurses" = xno; then AC_SEARCH_LIBS( setupterm, - [tinfo terminfo ncurses ncursesw], + [tinfow tinfo terminfo ncursesw ncurses], found_ncurses=yes, found_ncurses=no ) From 2f28d9a1685a72fee3cce72d043c767429292c9d Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 16 Jun 2026 08:57:07 +0000 Subject: [PATCH 14/60] Fix resizing floating pane with a left scrollbar, from Michael Grant. --- cmd-resize-pane.c | 24 ++++++++++++++++++------ server-client.c | 22 ++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index bae6a691b..d1a8f6723 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -181,6 +181,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) struct window_pane *wp; struct layout_cell *lc; int y, ly, x, lx, sx, sy, new_sx, new_sy; + int scrollbars, sb_pos, left, right; int new_xoff, new_yoff, resizes = 0; wp = cmd_mouse_pane(m, NULL, &wl); @@ -192,6 +193,17 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) lc = wp->layout_cell; sx = wp->sx; sy = wp->sy; + scrollbars = options_get_number(w->options, "pane-scrollbars"); + sb_pos = options_get_number(w->options, "pane-scrollbars-position"); + left = wp->xoff - 1; + right = wp->xoff + sx; + if (window_pane_show_scrollbar(wp, scrollbars) && + sb_pos == PANE_SCROLLBARS_LEFT) { + left -= wp->scrollbar_style.width + wp->scrollbar_style.pad; + } else if (window_pane_show_scrollbar(wp, scrollbars) && + sb_pos == PANE_SCROLLBARS_RIGHT) { + right += wp->scrollbar_style.width + wp->scrollbar_style.pad; + } y = m->y + m->oy; x = m->x + m->ox; if (m->statusat == 0 && y >= (int)m->statuslines) @@ -204,7 +216,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) else if (m->statusat > 0 && ly >= m->statusat) ly = m->statusat - 1; - if ((lx == wp->xoff - 1 || lx == wp->xoff) && ly == wp->yoff - 1) { + if ((lx == left || lx == left + 1) && ly == wp->yoff - 1) { /* Top left corner. */ new_sx = lc->sx + (lx - x); if (new_sx < PANE_MINIMUM) @@ -216,7 +228,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) new_yoff = y + 1; layout_set_size(lc, new_sx, new_sy, new_xoff, new_yoff); resizes++; - } else if ((lx == wp->xoff + sx + 1 || lx == wp->xoff + sx) && + } else if ((lx == right + 1 || lx == right) && ly == wp->yoff - 1) { /* Top right corner. */ new_sx = x - lc->xoff; @@ -228,7 +240,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) new_yoff = y + 1; layout_set_size(lc, new_sx, new_sy, lc->xoff, new_yoff); resizes++; - } else if ((lx == wp->xoff - 1 || lx == wp->xoff) && + } else if ((lx == left || lx == left + 1) && ly == wp->yoff + sy) { /* Bottom left corner. */ new_sx = lc->sx + (lx - x); @@ -240,7 +252,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) new_xoff = x + 1; layout_set_size(lc, new_sx, new_sy, new_xoff, lc->yoff); resizes++; - } else if ((lx == wp->xoff + sx + 1 || lx == wp->xoff + sx) && + } else if ((lx == right + 1 || lx == right) && ly == wp->yoff + sy) { /* Bottom right corner. */ new_sx = x - lc->xoff; @@ -251,14 +263,14 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) new_sy = PANE_MINIMUM; layout_set_size(lc, new_sx, new_sy, lc->xoff, lc->yoff); resizes++; - } else if (lx == wp->xoff + sx + 1) { + } else if (lx == right) { /* Right border. */ new_sx = x - lc->xoff; if (new_sx < PANE_MINIMUM) return; layout_set_size(lc, new_sx, lc->sy, lc->xoff, lc->yoff); resizes++; - } else if (lx == wp->xoff - 1) { + } else if (lx == left) { /* Left border. */ new_sx = lc->sx + (lx - x); if (new_sx < PANE_MINIMUM) diff --git a/server-client.c b/server-client.c index 20536804e..cf1e824c7 100644 --- a/server-client.c +++ b/server-client.c @@ -627,6 +627,9 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, pane_status_line = wp->yoff + wp->sy; else pane_status_line = -1; /* not used */ + bdr_left = wp->xoff - 1; + if (sb_pos == PANE_SCROLLBARS_LEFT) + bdr_left -= sb_pad + sb_w; /* Check if point is within the pane or scrollbar. */ if (((pane_status != PANE_STATUS_OFF && @@ -656,7 +659,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, } else /* py > sl_bottom */ return (KEYC_MOUSE_LOCATION_SCROLLBAR_DOWN); } else if (window_pane_is_floating(wp) && - (px == wp->xoff - 1 || + (px == bdr_left || py == wp->yoff - 1 || py == wp->yoff + (int)wp->sy)) { /* Floating pane left, bottom or top border. */ @@ -671,11 +674,20 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, if ((w->flags & WINDOW_ZOOMED) && (~fwp->flags & PANE_ZOOMED)) continue; + if (window_pane_show_scrollbar(fwp, sb)) { + sb_w = fwp->scrollbar_style.width; + sb_pad = fwp->scrollbar_style.pad; + } else { + sb_w = 0; + sb_pad = 0; + } bdr_top = fwp->yoff - 1; bdr_bottom = fwp->yoff + fwp->sy; - if (sb_pos == PANE_SCROLLBARS_LEFT) + bdr_left = fwp->xoff - 1; + if (sb_pos == PANE_SCROLLBARS_LEFT) { + bdr_left -= sb_pad + sb_w; bdr_right = fwp->xoff + fwp->sx; - else { + } else { /* PANE_SCROLLBARS_RIGHT or none. */ bdr_right = fwp->xoff + fwp->sx + sb_pad + sb_w; } @@ -685,13 +697,11 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, break; if (window_pane_is_floating(wp)) { /* Floating pane, check left border. */ - bdr_left = fwp->xoff - 1; if (px == bdr_left) break; } } - if (px >= fwp->xoff - 1 && - px <= fwp->xoff + (int)fwp->sx) { + if (px >= bdr_left && px <= fwp->xoff + (int)fwp->sx) { bdr_bottom = fwp->yoff + fwp->sy; if (py == bdr_bottom) break; From 1596f9c9f58c0e67ca626be9d76363a4f0c58f37 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 16 Jun 2026 10:22:51 +0100 Subject: [PATCH 15/60] Bump version again. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 67d16fdf0..0cf37da5b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], 3.7-rc2) +AC_INIT([tmux], 3.7-rc3) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) From c809aea278ca1b2bc4827e616024997f579f05f1 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 16 Jun 2026 10:24:06 +0100 Subject: [PATCH 16/60] Fix a merge problem. --- tty.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tty.c b/tty.c index 4db26287f..05919ba1b 100644 --- a/tty.c +++ b/tty.c @@ -1446,7 +1446,8 @@ tty_cmd_redrawline(struct tty *tty, const struct tty_ctx *ctx) if (rr->nx == 0) continue; tty_draw_line(tty, ctx->s, ctx->ocx + i + rr->px - x, - ctx->ocy, rr->nx, rr->px, ry, &ctx->style_ctx); + ctx->ocy, rr->nx, rr->px, ry, &ctx->defaults, + ctx->palette); } } } From 762e503978cfa8db9b0459453a58ef42145a208a Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 16 Jun 2026 09:28:17 +0000 Subject: [PATCH 17/60] Allow rectangle selection to extend past end of current line to behave the same as vi with virtualedit=block set. From Mark Kelly in GitHub issue 5227. --- window-copy.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/window-copy.c b/window-copy.c index 39ae18816..916d12eff 100644 --- a/window-copy.c +++ b/window-copy.c @@ -5172,8 +5172,12 @@ window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy) u_int maxx; int allow_onemore; - allow_onemore = (data->screen.sel != NULL && data->rectflag); - if (cy < screen_size_y(s)) { + /* + * Allow rectangle selection to extend past end of current line to + * behave the same as vi with virtualedit=block set. + */ + if (!data->rectflag && cy < screen_size_y(s)) { + allow_onemore = (data->screen.sel != NULL && data->rectflag); py = screen_hsize(data->backing) + cy - data->oy; maxx = window_copy_cursor_limit(wme, py, allow_onemore); if (cx > maxx) From 64e83caf04944f9a45113c32ecaff588a951fb99 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Jun 2026 21:41:39 +0000 Subject: [PATCH 18/60] Be more strict about what names and titles we allow and reject them immediately when possible, but allow # again for those directly set by commands (but not escape sequences). From Barrett Ruth in GitHub issue 5175. --- cmd-break-pane.c | 23 +++++++++++------- cmd-new-session.c | 58 ++++++++++++++++++++++++++------------------ cmd-new-window.c | 33 +++++++++++++++++++------ cmd-rename-session.c | 6 ++--- cmd-rename-window.c | 14 ++++++++--- cmd-select-pane.c | 2 +- cmd-split-window.c | 5 ++-- input.c | 10 ++++---- names.c | 4 +-- paste.c | 2 -- popup.c | 2 +- screen.c | 14 ++++++++--- session.c | 1 - spawn.c | 16 ++++-------- tmux.c | 19 +++++++++++++++ tmux.h | 13 +++++++--- window.c | 6 ++--- 17 files changed, 146 insertions(+), 82 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 4be989c3e..0d8d83fd9 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -58,9 +58,14 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) struct session *dst_s = target->s; struct window_pane *wp = source->wp; struct window *w = wl->window; - char *name, *cause, *cp; + char *newname, *cause, *cp; int idx = target->idx, before; - const char *template; + const char *template, *name = args_get(args, 'n'); + + if (name != NULL && !check_name(name, WINDOW_NAME_FORBID)) { + cmdq_error(item, "invalid window name: %s", name); + return (CMD_RETURN_ERROR); + } before = args_has(args, 'b'); if (args_has(args, 'a') || before) { @@ -80,8 +85,8 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) free(cause); return (CMD_RETURN_ERROR); } - if (args_has(args, 'n')) { - window_set_name(w, args_get(args, 'n')); + if (name != NULL) { + window_set_name(w, name, WINDOW_NAME_FORBID); options_set_number(w->options, "automatic-rename", 0); } server_unlink_window(src_s, wl); @@ -109,12 +114,12 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) w->active = wp; w->latest = tc; - if (!args_has(args, 'n')) { - name = default_window_name(w); - window_set_name(w, name); - free(name); + if (name != NULL) { + newname = default_window_name(w); + window_set_name(w, newname, WINDOW_NAME_FORBID); + free(newname); } else { - window_set_name(w, args_get(args, 'n')); + window_set_name(w, name, 0); options_set_number(w->options, "automatic-rename", 0); } diff --git a/cmd-new-session.c b/cmd-new-session.c index a397ccf9d..dbee4e559 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -77,8 +77,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) struct termios tio, *tiop; struct session_group *sg = NULL; const char *errstr, *template, *group, *tmp; - char *cause, *cwd = NULL, *cp, *newname = NULL; - char *name, *prefix = NULL; + char *cause, *cwd = NULL, *cp, *ename; + char *wname = NULL, *sname = NULL, *prefix = NULL; int detached, already_attached, is_control = 0; u_int sx, sy, dsx, dsy, count = args_count(args); struct spawn_context sc = { 0 }; @@ -99,20 +99,29 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } - tmp = args_get(args, 's'); - if (tmp != NULL) { - name = format_single(item, tmp, c, NULL, NULL, NULL); - newname = clean_name(name, "#:."); - if (newname == NULL) { - cmdq_error(item, "invalid session: %s", name); - free(name); + if ((tmp = args_get(args, 'n')) != NULL) { + ename = format_single(item, tmp, c, NULL, NULL, NULL); + if (!check_name(ename, WINDOW_NAME_FORBID)) { + cmdq_error(item, "invalid window name: %s", ename); + free(ename); return (CMD_RETURN_ERROR); } - free(name); + wname = clean_name(ename, WINDOW_NAME_FORBID); + free(ename); + } + if ((tmp = args_get(args, 's')) != NULL) { + ename = format_single(item, tmp, c, NULL, NULL, NULL); + if (!check_name(ename, SESSION_NAME_FORBID)) { + cmdq_error(item, "invalid session name: %s", ename); + free(ename); + goto fail; + } + sname = clean_name(ename, SESSION_NAME_FORBID); + free(ename); } if (args_has(args, 'A')) { - if (newname != NULL) - as = session_find(newname); + if (sname != NULL) + as = session_find(sname); else as = target->s; if (as != NULL) { @@ -120,12 +129,13 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) args_has(args, 'D'), args_has(args, 'X'), 0, args_get(args, 'c'), args_has(args, 'E'), args_get(args, 'f')); - free(newname); + free(wname); + free(sname); return (retval); } } - if (newname != NULL && session_find(newname) != NULL) { - cmdq_error(item, "duplicate session: %s", newname); + if (sname != NULL && session_find(sname) != NULL) { + cmdq_error(item, "duplicate session: %s", sname); goto fail; } @@ -142,12 +152,12 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) else if (groupwith != NULL) prefix = xstrdup(groupwith->name); else { - prefix = clean_name(group, "#:."); - if (prefix == NULL) { - cmdq_error(item, "invalid session group: %s", - group); + if (!check_name(group, SESSION_NAME_FORBID)) { + cmdq_error(item, + "invalid session group name: %s", group); goto fail; } + prefix = clean_name(group, SESSION_NAME_FORBID); } } @@ -276,7 +286,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) environ_put(env, av->string, 0); av = args_next_value(av); } - s = session_create(prefix, newname, cwd, env, oo, tiop); + s = session_create(prefix, sname, cwd, env, oo, tiop); /* Spawn the initial window. */ sc.item = item; @@ -284,7 +294,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (!detached) sc.tc = c; - sc.name = args_get(args, 'n'); + sc.name = wname; args_to_vector(args, &sc.argc, &sc.argv); sc.idx = -1; @@ -357,7 +367,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (sc.argv != NULL) cmd_free_argv(sc.argc, sc.argv); free(cwd); - free(newname); + free(wname); + free(sname); free(prefix); return (CMD_RETURN_NORMAL); @@ -365,7 +376,8 @@ fail: if (sc.argv != NULL) cmd_free_argv(sc.argc, sc.argv); free(cwd); - free(newname); + free(wname); + free(sname); free(prefix); return (CMD_RETURN_ERROR); } diff --git a/cmd-new-window.c b/cmd-new-window.c index dd64baab0..c7a203e2b 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -61,7 +61,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) struct session *s = target->s; struct winlink *wl = target->wl, *new_wl = NULL; int idx = target->idx, before; - char *cause = NULL, *cp, *expanded; + char *cause = NULL, *cp, *expanded, *wname; const char *template, *name; struct cmd_find_state fs; struct args_value *av; @@ -71,8 +71,18 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) * name already exists, select it. */ name = args_get(args, 'n'); - if (args_has(args, 'S') && name != NULL && target->idx == -1) { + if (name != NULL) { expanded = format_single(item, name, c, s, NULL, NULL); + if (!check_name(expanded, WINDOW_NAME_FORBID)) { + cmdq_error(item, "invalid window name: %s", expanded); + free(expanded); + return (CMD_RETURN_ERROR); + } + wname = clean_name(expanded, WINDOW_NAME_FORBID); + free(expanded); + } + if (args_has(args, 'S') && wname != NULL && target->idx == -1) { + expanded = format_single(item, wname, c, s, NULL, NULL); RB_FOREACH(wl, winlinks, &s->windows) { if (strcmp(wl->window->name, expanded) != 0) continue; @@ -80,12 +90,14 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) new_wl = wl; continue; } - cmdq_error(item, "multiple windows named %s", name); + cmdq_error(item, "multiple windows named %s", wname); + free(wname); free(expanded); return (CMD_RETURN_ERROR); } free(expanded); if (new_wl != NULL) { + free(wname); if (args_has(args, 'd')) return (CMD_RETURN_NORMAL); if (session_set_current(s, new_wl) == 0) @@ -108,7 +120,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) sc.s = s; sc.tc = tc; - sc.name = args_get(args, 'n'); + sc.name = wname; args_to_vector(args, &sc.argc, &sc.argv); sc.environ = environ_create(); @@ -130,10 +142,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) if ((new_wl = spawn_window(&sc, &cause)) == NULL) { cmdq_error(item, "create window failed: %s", cause); free(cause); - if (sc.argv != NULL) - cmd_free_argv(sc.argc, sc.argv); - environ_free(sc.environ); - return (CMD_RETURN_ERROR); + goto fail; } if (!args_has(args, 'd') || new_wl == s->curw) { cmd_find_from_winlink(current, new_wl, 0); @@ -156,5 +165,13 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) if (sc.argv != NULL) cmd_free_argv(sc.argc, sc.argv); environ_free(sc.environ); + free(wname); return (CMD_RETURN_NORMAL); + +fail: + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + free(wname); + return (CMD_RETURN_ERROR); } diff --git a/cmd-rename-session.c b/cmd-rename-session.c index 37b261006..ca6a2eacb 100644 --- a/cmd-rename-session.c +++ b/cmd-rename-session.c @@ -52,12 +52,12 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item) char *newname, *tmp; tmp = format_single_from_target(item, args_string(args, 0)); - newname = clean_name(tmp, "#:."); - if (newname == NULL) { - cmdq_error(item, "invalid session: %s", tmp); + if (!check_name(tmp, SESSION_NAME_FORBID)) { + cmdq_error(item, "invalid session name: %s", tmp); free(tmp); return (CMD_RETURN_ERROR); } + newname = clean_name(tmp, SESSION_NAME_FORBID); free(tmp); if (strcmp(newname, s->name) == 0) { free(newname); diff --git a/cmd-rename-window.c b/cmd-rename-window.c index 472b571ba..8428e23f3 100644 --- a/cmd-rename-window.c +++ b/cmd-rename-window.c @@ -48,15 +48,21 @@ cmd_rename_window_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 winlink *wl = target->wl; - char *newname; + char *name; - newname = format_single_from_target(item, args_string(args, 0)); - window_set_name(wl->window, newname); + name = format_single_from_target(item, args_string(args, 0)); + if (!check_name(name, WINDOW_NAME_FORBID)) { + cmdq_error(item, "invalid window name: %s", name); + free(name); + return (CMD_RETURN_ERROR); + } + + window_set_name(wl->window, name, WINDOW_NAME_FORBID); options_set_number(wl->window->options, "automatic-rename", 0); + free(name); server_redraw_window_borders(wl->window); server_status_window(wl->window); - free(newname); return (CMD_RETURN_NORMAL); } diff --git a/cmd-select-pane.c b/cmd-select-pane.c index e7fee1d63..f3c2cc124 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -217,7 +217,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'T')) { title = format_single_from_target(item, args_get(args, 'T')); - if (screen_set_title(&wp->base, title)) { + if (screen_set_title(&wp->base, title, 0)) { notify_pane("pane-title-changed", wp); server_redraw_window_borders(wp->window); server_status_window(wp->window); diff --git a/cmd-split-window.c b/cmd-split-window.c index 97fa9ae34..2e17c2636 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -191,8 +191,9 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) options_set_number(new_wp->options, "remain-on-exit", 3); if (args_has(args, 'm')) options_set_string(new_wp->options, - "remain-on-exit-format", - 0, "%s", args_get(args, 'm')); + "remain-on-exit-format", 0, "%s", + args_get(args, 'm')); + } } if (input) { diff --git a/input.c b/input.c index 6c9acd07a..4710d08a2 100644 --- a/input.c +++ b/input.c @@ -2701,7 +2701,7 @@ input_exit_osc(struct input_ctx *ictx) case 2: if (wp != NULL && options_get_number(wp->options, "allow-set-title") && - screen_set_title(sctx->s, p)) { + screen_set_title(sctx->s, p, 1)) { notify_pane("pane-title-changed", wp); server_redraw_window_borders(wp->window); server_status_window(wp->window); @@ -2711,7 +2711,7 @@ input_exit_osc(struct input_ctx *ictx) input_osc_4(ictx, p); break; case 7: - if (screen_set_path(sctx->s, p) && wp != NULL) { + if (wp != NULL && screen_set_path(sctx->s, p, 1)) { server_redraw_window_borders(wp->window); server_status_window(wp->window); } @@ -2779,7 +2779,7 @@ input_exit_apc(struct input_ctx *ictx) if (wp != NULL && options_get_number(wp->options, "allow-set-title") && - screen_set_title(sctx->s, ictx->input_buf)) { + screen_set_title(sctx->s, ictx->input_buf, 1)) { notify_pane("pane-title-changed", wp); server_redraw_window_borders(wp->window); server_status_window(wp->window); @@ -2822,10 +2822,10 @@ input_exit_rename(struct input_ctx *ictx) if (o != NULL) options_remove_or_default(o, -1, NULL); if (!options_get_number(w->options, "automatic-rename")) - window_set_name(w, ""); + window_set_name(w, "", WINDOW_NAME_FORBID_EXT); } else { options_set_number(w->options, "automatic-rename", 0); - window_set_name(w, ictx->input_buf); + window_set_name(w, ictx->input_buf, WINDOW_NAME_FORBID_EXT); } server_redraw_window_borders(w); server_status_window(w); diff --git a/names.c b/names.c index 8a04df73f..756af5b07 100644 --- a/names.c +++ b/names.c @@ -95,7 +95,7 @@ check_window_name(struct window *w) name = format_window_name(w); if (strcmp(name, w->name) != 0) { log_debug("@%u new name %s (was %s)", w->id, name, w->name); - window_set_name(w, name); + window_set_name(w, name, WINDOW_NAME_FORBID_EXT); server_redraw_window_borders(w); server_status_window(w); } else @@ -166,7 +166,7 @@ parse_window_name(const char *in) if (*name == '/') name = basename(name); - name = clean_name(name, "#"); + name = clean_name(name, WINDOW_NAME_FORBID); free(copy); if (name == NULL) return (xstrdup("")); diff --git a/paste.c b/paste.c index 6461d9ba2..081e3dbeb 100644 --- a/paste.c +++ b/paste.c @@ -176,7 +176,6 @@ paste_add(const char *prefix, char *data, size_t size) } pb = xmalloc(sizeof *pb); - pb->name = NULL; do { free(pb->name); @@ -296,7 +295,6 @@ paste_set(char *data, size_t size, const char *name, char **cause) } pb = xmalloc(sizeof *pb); - pb->name = newname; pb->data = data; diff --git a/popup.c b/popup.c index cb8dbf060..908b2a726 100644 --- a/popup.c +++ b/popup.c @@ -435,7 +435,7 @@ popup_make_pane(struct popup_data *pd, enum layout_type type) pd->job = NULL; } - screen_set_title(&pd->s, new_wp->base.title); + screen_set_title(&pd->s, new_wp->base.title, 0); screen_free(&new_wp->base); memcpy(&new_wp->base, &pd->s, sizeof wp->base); screen_resize(&new_wp->base, new_wp->sx, new_wp->sy, 1); diff --git a/screen.c b/screen.c index 2c78c5ac9..418af8071 100644 --- a/screen.c +++ b/screen.c @@ -245,11 +245,14 @@ screen_set_cursor_colour(struct screen *s, int colour) /* Set screen title. */ int -screen_set_title(struct screen *s, const char *title) +screen_set_title(struct screen *s, const char *title, int untrusted) { char *new_title; - new_title = clean_name(title, "#"); + if (untrusted) + new_title = clean_name(title, "#"); + else + new_title = clean_name(title, ""); if (new_title == NULL) return (0); free(s->title); @@ -259,11 +262,14 @@ screen_set_title(struct screen *s, const char *title) /* Set screen path. */ int -screen_set_path(struct screen *s, const char *path) +screen_set_path(struct screen *s, const char *path, int untrusted) { char *new_path; - new_path = clean_name(path, "#"); + if (untrusted) + new_path = clean_name(path, "#"); + else + new_path = clean_name(path, ""); if (new_path == NULL) return (0); free(s->path); diff --git a/session.c b/session.c index 1e558977a..445897e2a 100644 --- a/session.c +++ b/session.c @@ -116,7 +116,6 @@ session_create(const char *prefix, const char *name, const char *cwd, s = xcalloc(1, sizeof *s); s->references = 1; s->flags = 0; - s->cwd = xstrdup(cwd); TAILQ_INIT(&s->lastw); diff --git a/spawn.c b/spawn.c index b0a4f7dfb..ddeca0d34 100644 --- a/spawn.c +++ b/spawn.c @@ -74,15 +74,12 @@ spawn_log(const char *from, struct spawn_context *sc) struct winlink * spawn_window(struct spawn_context *sc, char **cause) { - struct cmdq_item *item = sc->item; - struct client *c = cmdq_get_client(item); struct session *s = sc->s; struct window *w; struct window_pane *wp; struct winlink *wl; int idx = sc->idx; u_int sx, sy, xpixel, ypixel; - char *name; spawn_log(__func__, sc); @@ -180,15 +177,12 @@ spawn_window(struct spawn_context *sc, char **cause) /* Set the name of the new window. */ if (~sc->flags & SPAWN_RESPAWN) { free(w->name); - if (sc->name != NULL) { - name = format_single(item, sc->name, c, s, NULL, NULL); - w->name = clean_name(name, "#"); - free(name); - if (w->name == NULL) - w->name = xstrdup(""); - options_set_number(w->options, "automatic-rename", 0); - } else + if (sc->name == NULL || *sc->name == '\0') w->name = default_window_name(w); + else { + w->name = xstrdup(sc->name); + options_set_number(w->options, "automatic-rename", 0); + } } /* Switch to the new window if required. */ diff --git a/tmux.c b/tmux.c index 251b03368..c06a24350 100644 --- a/tmux.c +++ b/tmux.c @@ -298,6 +298,25 @@ clean_name(const char *name, const char* forbid) return (new_name); } +/* + * Check a name given by a command: reject it if it is empty, not valid UTF-8, + * or contains a forbidden character. Other characters that clean_name would + * change (for example with utf8_stravis) are allowed and fixed silently. + */ +int +check_name(const char *name, const char *forbid) +{ + const char *cp; + + if (*name == '\0' || !utf8_isvalid(name)) + return (0); + for (cp = name; *cp != '\0'; cp++) { + if (strchr(forbid, *cp) != NULL) + return (0); + } + return (1); +} + const char * sig2name(int signo) { diff --git a/tmux.h b/tmux.h index c7a7405c7..56dd2e537 100644 --- a/tmux.h +++ b/tmux.h @@ -96,6 +96,12 @@ struct winlink; #define TMUX_LOCK_CMD "lock -np" #endif +/* Forbidden characters in names. */ +#define WINDOW_NAME_FORBID ":." +#define WINDOW_NAME_FORBID_EXT ":.#" +#define SESSION_NAME_FORBID ":." +#define SESSION_NAME_FORBID_EXT ":.#" + /* Minimum layout cell size, NOT including border lines. */ #define PANE_MINIMUM 1 @@ -2404,6 +2410,7 @@ void setblocking(int, int); char *shell_argv0(const char *, int); uint64_t get_timer(void); char *clean_name(const char *, const char *); +int check_name(const char *, const char *); const char *sig2name(int); const char *find_cwd(void); const char *find_home(void); @@ -3375,8 +3382,8 @@ void screen_reset_hyperlinks(struct screen *); void screen_set_default_cursor(struct screen *, struct options *); void screen_set_cursor_style(u_int, enum screen_cursor_style *, int *); void screen_set_cursor_colour(struct screen *, int); -int screen_set_title(struct screen *, const char *); -int screen_set_path(struct screen *, const char *); +int screen_set_title(struct screen *, const char *, int); +int screen_set_path(struct screen *, const char *, int); void screen_push_title(struct screen *); void screen_pop_title(struct screen *); void screen_set_progress_bar(struct screen *, enum progress_bar_state, int); @@ -3480,7 +3487,7 @@ void window_pane_stack_push(struct window_panes *, struct window_pane *); void window_pane_stack_remove(struct window_panes *, struct window_pane *); -void window_set_name(struct window *, const char *); +void window_set_name(struct window *, const char *, const char *); void window_add_ref(struct window *, const char *); void window_remove_ref(struct window *, const char *); void winlink_clear_flags(struct winlink *); diff --git a/window.c b/window.c index 314415889..89c28b8e6 100644 --- a/window.c +++ b/window.c @@ -401,11 +401,11 @@ window_remove_ref(struct window *w, const char *from) } void -window_set_name(struct window *w, const char *new_name) +window_set_name(struct window *w, const char *new_name, const char *forbid) { char *name; - name = clean_name(new_name, "#"); + name = clean_name(new_name, forbid); if (name != NULL) { free(w->name); w->name = name; @@ -1079,7 +1079,7 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) style_ranges_init(&wp->border_status_line.ranges); if (gethostname(host, sizeof host) == 0) - screen_set_title(&wp->base, host); + screen_set_title(&wp->base, host, 0); return (wp); } From 3b8e25fef4523b196a97185db6d836179c1e6aeb Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 16 Jun 2026 09:00:25 +0000 Subject: [PATCH 19/60] Initialize wname so it we are not freeing garbage if it is not used. --- cmd-new-window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-new-window.c b/cmd-new-window.c index c7a203e2b..c65745a26 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -61,7 +61,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) struct session *s = target->s; struct winlink *wl = target->wl, *new_wl = NULL; int idx = target->idx, before; - char *cause = NULL, *cp, *expanded, *wname; + char *cause = NULL, *cp, *expanded, *wname = NULL; const char *template, *name; struct cmd_find_state fs; struct args_value *av; From e7f414416f52e8c90e10ee34cf113222e832766e Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Jun 2026 10:17:08 +0000 Subject: [PATCH 20/60] Set check callback for menus so they aren't overwritten by sync. --- menu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/menu.c b/menu.c index 4f1228319..31f4529ec 100644 --- a/menu.c +++ b/menu.c @@ -638,7 +638,7 @@ menu_display(struct menu *menu, int flags, int starting_choice, style, selected_style, border_style, fs, cb, data); if (md == NULL) return (-1); - server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb, - menu_key_cb, menu_free_cb, menu_resize_cb, md); + server_client_set_overlay(c, 0, menu_check_cb, menu_mode_cb, + menu_draw_cb, menu_key_cb, menu_free_cb, menu_resize_cb, md); return (0); } From 4fc4644652793be253fcb401452164141e311d13 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 26 Jun 2026 18:50:42 +0100 Subject: [PATCH 21/60] Tweak. --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index cb92e2ff0..8d3c244b8 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ CHANGES FROM 3.6b TO 3.7 the same escape sequence support). Floating panes are created with the new-pane command, bound to * by default. + This is an early release of this feature and they are relatively limited. Currently floating panes can only be moved and resized using the mouse. The default second status line (if status-format is set to 2) has changed to show a list of panes. Many obvious features are not yet available for floating From 4eafb27b897444e1f25a8a7051bb683d5b8554cd Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 26 Jun 2026 18:50:58 +0100 Subject: [PATCH 22/60] tmux 3.7. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0cf37da5b..5396cb6c6 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], 3.7-rc3) +AC_INIT([tmux], 3.7) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) From 81f88f8517c9fc5371b56cf117530c6b477c96ac Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 26 Jun 2026 18:55:59 +0100 Subject: [PATCH 23/60] Add a missing {. --- cmd-split-window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 2e17c2636..5c4946637 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -189,7 +189,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } if (args_has(args, 'k') || args_has(args, 'm')) { options_set_number(new_wp->options, "remain-on-exit", 3); - if (args_has(args, 'm')) + if (args_has(args, 'm')) { options_set_string(new_wp->options, "remain-on-exit-format", 0, "%s", args_get(args, 'm')); From 84291b021f19de442f14d2e9378794a9b94ef21d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sat, 27 Jun 2026 10:58:39 +0100 Subject: [PATCH 24/60] Fix check so as to not use NULL name. --- CHANGES | 4 ++++ cmd-break-pane.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8d3c244b8..d3e43b1e7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +CHANGES FROM 3.7 to 3.7a + +* Fix crash in break-pane when no name is provided. + CHANGES FROM 3.6b TO 3.7 * Add floating panes. These are panes which sit above the layout ("tiled diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 0d8d83fd9..da15e8b5f 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -114,7 +114,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) w->active = wp; w->latest = tc; - if (name != NULL) { + if (name == NULL) { newname = default_window_name(w); window_set_name(w, newname, WINDOW_NAME_FORBID); free(newname); From 6c2ef7568195577f2864cdaa0c1f2eaa6436343d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 28 Jun 2026 16:54:51 +0100 Subject: [PATCH 25/60] Bump version. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5396cb6c6..57dc391a6 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], 3.7) +AC_INIT([tmux], 3.7a) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) From 4e612612dc06d973baa56796f653985e9f7d5dad Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 28 Jun 2026 15:53:18 +0000 Subject: [PATCH 26/60] Only forbid #( in names and titles (styles are #[ and are useful). --- tmux.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tmux.c b/tmux.c index c06a24350..c2557226d 100644 --- a/tmux.c +++ b/tmux.c @@ -290,7 +290,10 @@ clean_name(const char *name, const char* forbid) return (NULL); copy = xstrdup(name); for (cp = copy; *cp != '\0'; cp++) { - if (strchr(forbid, *cp) != NULL) + if (*cp == '#' && strchr(forbid, '#') != NULL) { + if (cp[1] == '(') + *cp = '_'; + } else if (strchr(forbid, *cp) != NULL) *cp = '_'; } utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); From 132a63d1da0e056d3b182eeabe3042b390f331f8 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 29 Jun 2026 16:20:45 +0000 Subject: [PATCH 27/60] Allow empty window and session names. --- tmux.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tmux.c b/tmux.c index c2557226d..912cd0d1c 100644 --- a/tmux.c +++ b/tmux.c @@ -286,7 +286,7 @@ clean_name(const char *name, const char* forbid) { char *copy, *cp, *new_name; - if (*name == '\0' || !utf8_isvalid(name)) + if (!utf8_isvalid(name)) return (NULL); copy = xstrdup(name); for (cp = copy; *cp != '\0'; cp++) { @@ -311,7 +311,7 @@ check_name(const char *name, const char *forbid) { const char *cp; - if (*name == '\0' || !utf8_isvalid(name)) + if (!utf8_isvalid(name)) return (0); for (cp = name; *cp != '\0'; cp++) { if (strchr(forbid, *cp) != NULL) From 166267c87a6c99639642e04314a9f9e0889f6222 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 29 Jun 2026 18:17:28 +0000 Subject: [PATCH 28/60] Allow :. in names again, forbidding them is overly pernickety. Only forbid invalid UTF-8 and #(. --- cmd-break-pane.c | 6 +++--- cmd-new-session.c | 12 ++++++------ cmd-new-window.c | 4 ++-- cmd-rename-session.c | 4 ++-- cmd-rename-window.c | 4 ++-- input.c | 14 ++++++-------- names.c | 4 ++-- paste.c | 4 ++-- screen.c | 10 ++-------- tmux.c | 20 +++----------------- tmux.h | 12 +++--------- window.c | 4 ++-- 12 files changed, 35 insertions(+), 63 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index da15e8b5f..39f530f6d 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -62,7 +62,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) int idx = target->idx, before; const char *template, *name = args_get(args, 'n'); - if (name != NULL && !check_name(name, WINDOW_NAME_FORBID)) { + if (name != NULL && !check_name(name)) { cmdq_error(item, "invalid window name: %s", name); return (CMD_RETURN_ERROR); } @@ -86,7 +86,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } if (name != NULL) { - window_set_name(w, name, WINDOW_NAME_FORBID); + window_set_name(w, name, 0); options_set_number(w->options, "automatic-rename", 0); } server_unlink_window(src_s, wl); @@ -116,7 +116,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) if (name == NULL) { newname = default_window_name(w); - window_set_name(w, newname, WINDOW_NAME_FORBID); + window_set_name(w, newname, 0); free(newname); } else { window_set_name(w, name, 0); diff --git a/cmd-new-session.c b/cmd-new-session.c index dbee4e559..ba02c72d4 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -101,22 +101,22 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if ((tmp = args_get(args, 'n')) != NULL) { ename = format_single(item, tmp, c, NULL, NULL, NULL); - if (!check_name(ename, WINDOW_NAME_FORBID)) { + if (!check_name(ename)) { cmdq_error(item, "invalid window name: %s", ename); free(ename); return (CMD_RETURN_ERROR); } - wname = clean_name(ename, WINDOW_NAME_FORBID); + wname = clean_name(ename, 0); free(ename); } if ((tmp = args_get(args, 's')) != NULL) { ename = format_single(item, tmp, c, NULL, NULL, NULL); - if (!check_name(ename, SESSION_NAME_FORBID)) { + if (!check_name(ename)) { cmdq_error(item, "invalid session name: %s", ename); free(ename); goto fail; } - sname = clean_name(ename, SESSION_NAME_FORBID); + sname = clean_name(ename, 0); free(ename); } if (args_has(args, 'A')) { @@ -152,12 +152,12 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) else if (groupwith != NULL) prefix = xstrdup(groupwith->name); else { - if (!check_name(group, SESSION_NAME_FORBID)) { + if (!check_name(group)) { cmdq_error(item, "invalid session group name: %s", group); goto fail; } - prefix = clean_name(group, SESSION_NAME_FORBID); + prefix = clean_name(group, 0); } } diff --git a/cmd-new-window.c b/cmd-new-window.c index c65745a26..bb6113fd6 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -73,12 +73,12 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) name = args_get(args, 'n'); if (name != NULL) { expanded = format_single(item, name, c, s, NULL, NULL); - if (!check_name(expanded, WINDOW_NAME_FORBID)) { + if (!check_name(expanded)) { cmdq_error(item, "invalid window name: %s", expanded); free(expanded); return (CMD_RETURN_ERROR); } - wname = clean_name(expanded, WINDOW_NAME_FORBID); + wname = clean_name(expanded, 0); free(expanded); } if (args_has(args, 'S') && wname != NULL && target->idx == -1) { diff --git a/cmd-rename-session.c b/cmd-rename-session.c index ca6a2eacb..b399483b8 100644 --- a/cmd-rename-session.c +++ b/cmd-rename-session.c @@ -52,12 +52,12 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item) char *newname, *tmp; tmp = format_single_from_target(item, args_string(args, 0)); - if (!check_name(tmp, SESSION_NAME_FORBID)) { + if (!check_name(tmp)) { cmdq_error(item, "invalid session name: %s", tmp); free(tmp); return (CMD_RETURN_ERROR); } - newname = clean_name(tmp, SESSION_NAME_FORBID); + newname = clean_name(tmp, 0); free(tmp); if (strcmp(newname, s->name) == 0) { free(newname); diff --git a/cmd-rename-window.c b/cmd-rename-window.c index 8428e23f3..8ab9b658a 100644 --- a/cmd-rename-window.c +++ b/cmd-rename-window.c @@ -51,13 +51,13 @@ cmd_rename_window_exec(struct cmd *self, struct cmdq_item *item) char *name; name = format_single_from_target(item, args_string(args, 0)); - if (!check_name(name, WINDOW_NAME_FORBID)) { + if (!check_name(name)) { cmdq_error(item, "invalid window name: %s", name); free(name); return (CMD_RETURN_ERROR); } - window_set_name(wl->window, name, WINDOW_NAME_FORBID); + window_set_name(wl->window, name, 0); options_set_number(wl->window->options, "automatic-rename", 0); free(name); diff --git a/input.c b/input.c index 4710d08a2..788663e8f 100644 --- a/input.c +++ b/input.c @@ -1953,16 +1953,14 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) case 2004: screen_write_mode_clear(sctx, MODE_BRACKETPASTE); break; + case 2026: + screen_write_stop_sync(ictx->wp); + break; case 2031: screen_write_mode_clear(sctx, MODE_THEME_UPDATES); if (ictx->wp != NULL) ictx->wp->flags &= ~PANE_THEMECHANGED; break; - case 2026: /* synchronized output */ - screen_write_stop_sync(ictx->wp); - if (ictx->wp != NULL) - ictx->wp->flags |= PANE_REDRAW; - break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -2065,7 +2063,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) ictx->wp->flags &= ~PANE_THEMECHANGED; } break; - case 2026: /* synchronized output */ + case 2026: screen_write_start_sync(ictx->wp); break; default: @@ -2822,10 +2820,10 @@ input_exit_rename(struct input_ctx *ictx) if (o != NULL) options_remove_or_default(o, -1, NULL); if (!options_get_number(w->options, "automatic-rename")) - window_set_name(w, "", WINDOW_NAME_FORBID_EXT); + window_set_name(w, "", 1); } else { options_set_number(w->options, "automatic-rename", 0); - window_set_name(w, ictx->input_buf, WINDOW_NAME_FORBID_EXT); + window_set_name(w, ictx->input_buf, 1); } server_redraw_window_borders(w); server_status_window(w); diff --git a/names.c b/names.c index 756af5b07..cee310366 100644 --- a/names.c +++ b/names.c @@ -95,7 +95,7 @@ check_window_name(struct window *w) name = format_window_name(w); if (strcmp(name, w->name) != 0) { log_debug("@%u new name %s (was %s)", w->id, name, w->name); - window_set_name(w, name, WINDOW_NAME_FORBID_EXT); + window_set_name(w, name, 1); server_redraw_window_borders(w); server_status_window(w); } else @@ -166,7 +166,7 @@ parse_window_name(const char *in) if (*name == '/') name = basename(name); - name = clean_name(name, WINDOW_NAME_FORBID); + name = clean_name(name, 0); free(copy); if (name == NULL) return (xstrdup("")); diff --git a/paste.c b/paste.c index 081e3dbeb..5ee80afd1 100644 --- a/paste.c +++ b/paste.c @@ -219,7 +219,7 @@ paste_rename(const char *oldname, const char *newname, char **cause) return (-1); } - name = clean_name(newname, ""); + name = clean_name(newname, 0); if (name == NULL) { if (cause != NULL) xasprintf(cause, "invalid buffer name: %s", newname); @@ -287,7 +287,7 @@ paste_set(char *data, size_t size, const char *name, char **cause) return (-1); } - newname = clean_name(name, ""); + newname = clean_name(name, 0); if (newname == NULL) { if (cause != NULL) xasprintf(cause, "invalid buffer name: %s", name); diff --git a/screen.c b/screen.c index 418af8071..b1472765d 100644 --- a/screen.c +++ b/screen.c @@ -249,10 +249,7 @@ screen_set_title(struct screen *s, const char *title, int untrusted) { char *new_title; - if (untrusted) - new_title = clean_name(title, "#"); - else - new_title = clean_name(title, ""); + new_title = clean_name(title, untrusted); if (new_title == NULL) return (0); free(s->title); @@ -266,10 +263,7 @@ screen_set_path(struct screen *s, const char *path, int untrusted) { char *new_path; - if (untrusted) - new_path = clean_name(path, "#"); - else - new_path = clean_name(path, ""); + new_path = clean_name(path, untrusted); if (new_path == NULL) return (0); free(s->path); diff --git a/tmux.c b/tmux.c index 912cd0d1c..132c39217 100644 --- a/tmux.c +++ b/tmux.c @@ -282,7 +282,7 @@ get_timer(void) } char * -clean_name(const char *name, const char* forbid) +clean_name(const char *name, int untrusted) { char *copy, *cp, *new_name; @@ -290,10 +290,7 @@ clean_name(const char *name, const char* forbid) return (NULL); copy = xstrdup(name); for (cp = copy; *cp != '\0'; cp++) { - if (*cp == '#' && strchr(forbid, '#') != NULL) { - if (cp[1] == '(') - *cp = '_'; - } else if (strchr(forbid, *cp) != NULL) + if (untrusted && cp[0] == '#' && cp[1] == '(') *cp = '_'; } utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); @@ -301,22 +298,11 @@ clean_name(const char *name, const char* forbid) return (new_name); } -/* - * Check a name given by a command: reject it if it is empty, not valid UTF-8, - * or contains a forbidden character. Other characters that clean_name would - * change (for example with utf8_stravis) are allowed and fixed silently. - */ int -check_name(const char *name, const char *forbid) +check_name(const char *name) { - const char *cp; - if (!utf8_isvalid(name)) return (0); - for (cp = name; *cp != '\0'; cp++) { - if (strchr(forbid, *cp) != NULL) - return (0); - } return (1); } diff --git a/tmux.h b/tmux.h index 56dd2e537..0fbc4424e 100644 --- a/tmux.h +++ b/tmux.h @@ -96,12 +96,6 @@ struct winlink; #define TMUX_LOCK_CMD "lock -np" #endif -/* Forbidden characters in names. */ -#define WINDOW_NAME_FORBID ":." -#define WINDOW_NAME_FORBID_EXT ":.#" -#define SESSION_NAME_FORBID ":." -#define SESSION_NAME_FORBID_EXT ":.#" - /* Minimum layout cell size, NOT including border lines. */ #define PANE_MINIMUM 1 @@ -2409,8 +2403,8 @@ int checkshell(const char *); void setblocking(int, int); char *shell_argv0(const char *, int); uint64_t get_timer(void); -char *clean_name(const char *, const char *); -int check_name(const char *, const char *); +char *clean_name(const char *, int); +int check_name(const char *); const char *sig2name(int); const char *find_cwd(void); const char *find_home(void); @@ -3487,7 +3481,7 @@ void window_pane_stack_push(struct window_panes *, struct window_pane *); void window_pane_stack_remove(struct window_panes *, struct window_pane *); -void window_set_name(struct window *, const char *, const char *); +void window_set_name(struct window *, const char *, int); void window_add_ref(struct window *, const char *); void window_remove_ref(struct window *, const char *); void winlink_clear_flags(struct winlink *); diff --git a/window.c b/window.c index 89c28b8e6..45d8d2f68 100644 --- a/window.c +++ b/window.c @@ -401,11 +401,11 @@ window_remove_ref(struct window *w, const char *from) } void -window_set_name(struct window *w, const char *new_name, const char *forbid) +window_set_name(struct window *w, const char *new_name, int untrusted) { char *name; - name = clean_name(new_name, forbid); + name = clean_name(new_name, untrusted); if (name != NULL) { free(w->name); w->name = name; From dbe50934b1cef66417de663f82bdda9728a8d0a6 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 30 Jun 2026 07:41:02 +0100 Subject: [PATCH 29/60] Add caching of scrollbar options to 3.7a branch. --- cmd-resize-pane.c | 12 +++---- layout.c | 19 ++++------- options.c | 7 +++- screen-redraw.c | 85 ++++++++++++++++++++++------------------------- server-client.c | 23 ++++++------- tmux.h | 5 ++- window.c | 20 +++++------ 7 files changed, 80 insertions(+), 91 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index d1a8f6723..8ad2655d2 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -181,7 +181,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) struct window_pane *wp; struct layout_cell *lc; int y, ly, x, lx, sx, sy, new_sx, new_sy; - int scrollbars, sb_pos, left, right; + int left, right; int new_xoff, new_yoff, resizes = 0; wp = cmd_mouse_pane(m, NULL, &wl); @@ -193,15 +193,13 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) lc = wp->layout_cell; sx = wp->sx; sy = wp->sy; - scrollbars = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); left = wp->xoff - 1; right = wp->xoff + sx; - if (window_pane_show_scrollbar(wp, scrollbars) && - sb_pos == PANE_SCROLLBARS_LEFT) { + if (window_pane_show_scrollbar(wp) && + w->sb_pos == PANE_SCROLLBARS_LEFT) { left -= wp->scrollbar_style.width + wp->scrollbar_style.pad; - } else if (window_pane_show_scrollbar(wp, scrollbars) && - sb_pos == PANE_SCROLLBARS_RIGHT) { + } else if (window_pane_show_scrollbar(wp) && + w->sb_pos == PANE_SCROLLBARS_RIGHT) { right += wp->scrollbar_style.width + wp->scrollbar_style.pad; } diff --git a/layout.c b/layout.c index da98b590e..70226d6af 100644 --- a/layout.c +++ b/layout.c @@ -359,12 +359,10 @@ layout_fix_panes(struct window *w, struct window_pane *skip) { struct window_pane *wp; struct layout_cell *lc; - int status, scrollbars, sb_pos, sb_w, sb_pad; + int status, sb_w, sb_pad; u_int sx, sy; status = options_get_number(w->options, "pane-border-status"); - scrollbars = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); TAILQ_FOREACH(wp, &w->panes, entry) { if ((lc = wp->layout_cell) == NULL || wp == skip) @@ -382,14 +380,14 @@ layout_fix_panes(struct window *w, struct window_pane *skip) sy--; } - if (window_pane_show_scrollbar(wp, scrollbars)) { + if (window_pane_show_scrollbar(wp)) { sb_w = wp->scrollbar_style.width; sb_pad = wp->scrollbar_style.pad; if (sb_w < 1) sb_w = 1; if (sb_pad < 0) sb_pad = 0; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { if ((int)sx - sb_w < PANE_MINIMUM) { wp->xoff = wp->xoff + (int)sx - PANE_MINIMUM; @@ -398,7 +396,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip) sx = sx - sb_w - sb_pad; wp->xoff = wp->xoff + sb_w + sb_pad; } - } else /* sb_pos == PANE_SCROLLBARS_RIGHT */ + } else /* w->sb_pos == PANE_SCROLLBARS_RIGHT */ if ((int)sx - sb_w - sb_pad < PANE_MINIMUM) sx = PANE_MINIMUM; else @@ -438,16 +436,15 @@ layout_resize_check(struct window *w, struct layout_cell *lc, struct layout_cell *lcchild; struct style *sb_style = &w->active->scrollbar_style; u_int available, minimum; - int status, scrollbars; + int status; status = options_get_number(w->options, "pane-border-status"); - scrollbars = options_get_number(w->options, "pane-scrollbars"); if (lc->type == LAYOUT_WINDOWPANE) { /* Space available in this cell only. */ if (type == LAYOUT_LEFTRIGHT) { available = lc->sx; - if (scrollbars) + if (w->sb != PANE_SCROLLBARS_OFF) minimum = PANE_MINIMUM + sb_style->width + sb_style->pad; else @@ -1003,7 +1000,6 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, u_int sx, sy, xoff, yoff, size1, size2, minimum; u_int new_size, saved_size, resize_first = 0; int full_size = (flags & SPAWN_FULLSIZE), status; - int scrollbars; /* * If full_size is specified, add a new cell at the top of the window @@ -1014,7 +1010,6 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, else lc = wp->layout_cell; status = options_get_number(wp->window->options, "pane-border-status"); - scrollbars = options_get_number(wp->window->options, "pane-scrollbars"); /* Copy the old cell size. */ sx = lc->sx; @@ -1025,7 +1020,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, /* Check there is enough space for the two new panes. */ switch (type) { case LAYOUT_LEFTRIGHT: - if (scrollbars) { + if (wp->window->sb != PANE_SCROLLBARS_OFF) { minimum = PANE_MINIMUM * 2 + sb_style->width + sb_style->pad; } else diff --git a/options.c b/options.c index a847ad9c1..e38056c34 100644 --- a/options.c +++ b/options.c @@ -1237,8 +1237,13 @@ options_push_changes(const char *name) if (strcmp(name, "pane-border-status") == 0 || strcmp(name, "pane-scrollbars") == 0 || strcmp(name, "pane-scrollbars-position") == 0) { - RB_FOREACH(w, windows, &windows) + RB_FOREACH(w, windows, &windows) { + w->sb = options_get_number(w->options, + "pane-scrollbars"); + w->sb_pos = options_get_number(w->options, + "pane-scrollbars-position"); layout_fix_panes(w, NULL); + } } if (strcmp(name, "pane-scrollbars-style") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) { diff --git a/screen-redraw.c b/screen-redraw.c index 17085a411..e1d8c6289 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -123,31 +123,28 @@ static enum screen_redraw_border_type screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, int px, int py) { - struct options *oo = wp->window->options; + struct window *w = wp->window; + struct options *oo = w->options; int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; int hsplit = 0, vsplit = 0, pane_status = ctx->pane_status; - int pane_scrollbars = ctx->pane_scrollbars, sb_w = 0; - int sb_pos, sx = wp->sx, sy = wp->sy, left, right; + int sb_w = 0; + int sx = wp->sx, sy = wp->sy, left, right; enum layout_type split_type; - if (pane_scrollbars != 0) - sb_pos = ctx->pane_scrollbars_pos; - else - sb_pos = 0; - /* Inside pane. */ if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey) return (SCREEN_REDRAW_INSIDE); /* Are scrollbars enabled? */ - if (window_pane_show_scrollbar(wp, pane_scrollbars)) + if (window_pane_show_scrollbar(wp)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; /* Floating pane borders. */ if (window_pane_is_floating(wp)) { left = wp->xoff - 1; right = wp->xoff + sx; - if (sb_pos == PANE_SCROLLBARS_LEFT) + if (w->sb != PANE_SCROLLBARS_OFF && + w->sb_pos == PANE_SCROLLBARS_LEFT) left -= sb_w; else right += sb_w; @@ -182,7 +179,8 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, * active window's border when there are two panes. */ if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb != PANE_SCROLLBARS_OFF && + w->sb_pos == PANE_SCROLLBARS_LEFT) { if (wp->xoff - sb_w == 0 && px == sx + sb_w) { if (!hsplit || (hsplit && py <= sy / 2)) return (SCREEN_REDRAW_BORDER_RIGHT); @@ -194,7 +192,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if (px == wp->xoff + sx + sb_w - 1) return (SCREEN_REDRAW_BORDER_RIGHT); } - } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled */ + } else { /* w->sb_pos == PANE_SCROLLBARS_RIGHT or disabled */ if (wp->xoff == 0 && px == sx + sb_w) { if (!hsplit || (hsplit && py <= sy / 2)) return (SCREEN_REDRAW_BORDER_RIGHT); @@ -216,7 +214,8 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if (wp->yoff != 0 && py == wp->yoff - 1 && px > sx / 2) return (SCREEN_REDRAW_BORDER_TOP); } else { - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb != PANE_SCROLLBARS_OFF && + w->sb_pos == PANE_SCROLLBARS_LEFT) { if ((wp->xoff - sb_w == 0 || px >= wp->xoff - sb_w) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { if (pane_status != PANE_STATUS_BOTTOM && @@ -225,7 +224,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if (pane_status != PANE_STATUS_TOP && py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } - } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ + } else { /* w->sb_pos == PANE_SCROLLBARS_RIGHT */ if ((wp->xoff == 0 || px >= wp->xoff) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { if (pane_status != PANE_STATUS_BOTTOM && @@ -278,17 +277,15 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, struct client *c = ctx->c; struct window *w = c->session->curw->window; struct window_pane *wp2; - int sx = w->sx, sy = w->sy, sb_w, sb_pos, n; + int sx = w->sx, sy = w->sy, sb_w, n; - if (ctx->pane_scrollbars != 0) - sb_pos = ctx->pane_scrollbars_pos; - else - sb_pos = 0; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; /* For floating panes, only check the pane itself. */ if (window_pane_is_floating(wp)) { - n = screen_redraw_cell_border1(ctx, sb_pos, sb_w, wp, px, py); + n = screen_redraw_cell_border1(ctx, + w->sb != PANE_SCROLLBARS_OFF ? w->sb_pos : 0, + sb_w, wp, px, py); if (n == -1) return (0); return (n); @@ -311,7 +308,9 @@ screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, TAILQ_FOREACH(wp2, &w->z_index, zentry) { if (!window_pane_visible(wp2) || window_pane_is_floating(wp2)) continue; - n = screen_redraw_cell_border1(ctx, sb_pos, sb_w, wp2, px, py); + n = screen_redraw_cell_border1(ctx, + w->sb != PANE_SCROLLBARS_OFF ? w->sb_pos : 0, + sb_w, wp2, px, py); if (n != -1) return (n); } @@ -435,9 +434,9 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py, struct window_pane *wp, *start; int sx = w->sx, sy = w->sy; int pane_status = ctx->pane_status; - int border, pane_scrollbars = ctx->pane_scrollbars; + int border; int pane_status_line, tiled_only = 0, left, right; - int sb_pos = ctx->pane_scrollbars_pos, sb_w; + int sb_w; *wpp = NULL; @@ -452,7 +451,8 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py, continue; } sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb != PANE_SCROLLBARS_OFF && + w->sb_pos == PANE_SCROLLBARS_LEFT) { if ((px >= wp->xoff - 1 - sb_w && px <= wp->xoff + (int)wp->sx) && (py >= wp->yoff - 1 && @@ -492,7 +492,8 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py, *wpp = wp; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb != PANE_SCROLLBARS_OFF && + w->sb_pos == PANE_SCROLLBARS_LEFT) { if ((px < wp->xoff - 1 - sb_w || px > wp->xoff + (int)wp->sx) && (py < wp->yoff - 1 || @@ -524,7 +525,7 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py, } /* Check if CELL_SCROLLBAR. */ - if (window_pane_show_scrollbar(wp, pane_scrollbars)) { + if (window_pane_show_scrollbar(wp)) { /* * Check if py could lie within a scrollbar. If the * pane is at the top then py == 0 to sy; if the pane @@ -536,10 +537,10 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py, (py >= wp->yoff && py < wp->yoff + (int)wp->sy)) { /* Check if px lies within a scrollbar. */ - if ((sb_pos == PANE_SCROLLBARS_RIGHT && + if ((w->sb_pos == PANE_SCROLLBARS_RIGHT && (px >= wp->xoff + (int)wp->sx && px < wp->xoff + (int)wp->sx + sb_w)) || - (sb_pos == PANE_SCROLLBARS_LEFT && + (w->sb_pos == PANE_SCROLLBARS_LEFT && (px >= wp->xoff - sb_w && px < wp->xoff))) return (CELL_SCROLLBAR); @@ -593,13 +594,12 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, struct style_line_entry *sle = &wp->border_status_line; char *expanded; int pane_status = rctx->pane_status, sb_w = 0; - int pane_scrollbars = rctx->pane_scrollbars; int max_width; u_int width, i, cell_type, px, py; struct screen_write_ctx ctx; struct screen old; - if (window_pane_show_scrollbar(wp, pane_scrollbars)) + if (window_pane_show_scrollbar(wp)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); @@ -791,10 +791,6 @@ screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx) ctx->pane_status = options_get_number(wo, "pane-border-status"); ctx->pane_lines = options_get_number(wo, "pane-border-lines"); - ctx->pane_scrollbars = options_get_number(wo, "pane-scrollbars"); - ctx->pane_scrollbars_pos = options_get_number(wo, - "pane-scrollbars-position"); - tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy); log_debug("%s: %s @%u ox=%u oy=%u sx=%u sy=%u %u/%d", __func__, c->name, @@ -863,7 +859,7 @@ screen_redraw_pane(struct client *c, struct window_pane *wp, if (!redraw_scrollbar_only) screen_redraw_draw_pane(&ctx, wp); - if (window_pane_show_scrollbar(wp, ctx.pane_scrollbars)) + if (window_pane_show_scrollbar(wp)) screen_redraw_draw_pane_scrollbar(&ctx, wp); tty_reset(&c->tty); @@ -1142,7 +1138,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, struct window *w; struct visible_range *ri; static struct visible_ranges sr = { NULL, 0, 0 }; - int found_self, sb, sb_w, sb_pos; + int found_self, sb_w; int lb, rb, tb, bb, sx, ex; u_int i, s; @@ -1182,9 +1178,6 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, r->used = 1; } - sb = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); - found_self = 0; TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { if (wp == base_wp) { @@ -1203,12 +1196,12 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, continue; sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; - if (!window_pane_show_scrollbar(wp, sb)) - sb_w = sb_pos = 0; + if (!window_pane_show_scrollbar(wp)) + sb_w = 0; for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { if (wp->xoff > sb_w) lb = wp->xoff - 1 - sb_w; else @@ -1219,7 +1212,7 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px, else lb = 0; } - if (sb_pos == PANE_SCROLLBARS_LEFT) + if (w->sb_pos == PANE_SCROLLBARS_LEFT) rb = wp->xoff + wp->sx; else /* PANE_SCROLLBARS_RIGHT or none. */ rb = wp->xoff + wp->sx + sb_w; @@ -1407,7 +1400,7 @@ screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *ctx) log_debug("%s: %s @%u", __func__, c->name, w->id); TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_show_scrollbar(wp, ctx->pane_scrollbars) && + if (window_pane_show_scrollbar(wp) && window_pane_visible(wp)) screen_redraw_draw_pane_scrollbar(ctx, wp); } @@ -1420,8 +1413,8 @@ screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, { struct screen *s = wp->screen; double percent_view; - u_int sb = ctx->pane_scrollbars, total_height, sb_h = wp->sy; - u_int sb_pos = ctx->pane_scrollbars_pos, slider_h, slider_y; + u_int sb = wp->window->sb, total_height, sb_h = wp->sy; + u_int sb_pos = wp->window->sb_pos, slider_h, slider_y; int sb_w = wp->scrollbar_style.width; int sb_pad = wp->scrollbar_style.pad; int cm_y, cm_size, xoff = wp->xoff; diff --git a/server-client.c b/server-client.c index cf1e824c7..52886f014 100644 --- a/server-client.c +++ b/server-client.c @@ -603,17 +603,14 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, u_int *sl_mpos) { struct window *w = wp->window; - struct options *wo = w->options; struct window_pane *fwp; - int pane_status, sb, sb_pos, sb_w, sb_pad; + int pane_status, sb_w, sb_pad; int pane_status_line, sl_top, sl_bottom; int bdr_bottom, bdr_top, bdr_left, bdr_right; - sb = options_get_number(wo, "pane-scrollbars"); - sb_pos = options_get_number(wo, "pane-scrollbars-position"); - pane_status = options_get_number(wo, "pane-border-status"); + pane_status = options_get_number(w->options, "pane-border-status"); - if (window_pane_show_scrollbar(wp, sb)) { + if (window_pane_show_scrollbar(wp)) { sb_w = wp->scrollbar_style.width; sb_pad = wp->scrollbar_style.pad; } else { @@ -628,7 +625,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, else pane_status_line = -1; /* not used */ bdr_left = wp->xoff - 1; - if (sb_pos == PANE_SCROLLBARS_LEFT) + if (w->sb_pos == PANE_SCROLLBARS_LEFT) bdr_left -= sb_pad + sb_w; /* Check if point is within the pane or scrollbar. */ @@ -636,15 +633,15 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, py != pane_status_line && py != wp->yoff + (int)wp->sy) || (wp->yoff == 0 && py < (int)wp->sy) || (py >= wp->yoff && py < wp->yoff + (int)wp->sy)) && - ((sb_pos == PANE_SCROLLBARS_RIGHT && + ((w->sb_pos == PANE_SCROLLBARS_RIGHT && px < wp->xoff + (int)wp->sx + sb_pad + sb_w) || - (sb_pos == PANE_SCROLLBARS_LEFT && + (w->sb_pos == PANE_SCROLLBARS_LEFT && px < wp->xoff + (int)wp->sx - sb_pad - sb_w))) { /* Check if in the scrollbar. */ - if ((sb_pos == PANE_SCROLLBARS_RIGHT && + if ((w->sb_pos == PANE_SCROLLBARS_RIGHT && (px >= wp->xoff + (int)wp->sx + sb_pad && px < wp->xoff + (int)wp->sx + sb_pad + sb_w)) || - (sb_pos == PANE_SCROLLBARS_LEFT && + (w->sb_pos == PANE_SCROLLBARS_LEFT && (px >= wp->xoff - sb_pad - sb_w && px < wp->xoff - sb_pad))) { /* Check where inside the scrollbar. */ @@ -674,7 +671,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, if ((w->flags & WINDOW_ZOOMED) && (~fwp->flags & PANE_ZOOMED)) continue; - if (window_pane_show_scrollbar(fwp, sb)) { + if (window_pane_show_scrollbar(fwp)) { sb_w = fwp->scrollbar_style.width; sb_pad = fwp->scrollbar_style.pad; } else { @@ -684,7 +681,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py, bdr_top = fwp->yoff - 1; bdr_bottom = fwp->yoff + fwp->sy; bdr_left = fwp->xoff - 1; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { bdr_left -= sb_pad + sb_w; bdr_right = fwp->xoff + fwp->sx; } else { diff --git a/tmux.h b/tmux.h index 0fbc4424e..5dc86b2b1 100644 --- a/tmux.h +++ b/tmux.h @@ -1390,6 +1390,9 @@ struct window { u_int last_new_pane_x; u_int last_new_pane_y; + int sb; + int sb_pos; + struct utf8_data *fill_character; int flags; #define WINDOW_BELL 0x1 @@ -3495,7 +3498,7 @@ void window_pane_update_used_data(struct window_pane *, void window_set_fill_character(struct window *); void window_pane_default_cursor(struct window_pane *); int window_pane_mode(struct window_pane *); -int window_pane_show_scrollbar(struct window_pane *, int); +int window_pane_show_scrollbar(struct window_pane *); int window_pane_get_bg(struct window_pane *); int window_pane_get_fg(struct window_pane *); int window_pane_get_fg_control_client(struct window_pane *); diff --git a/window.c b/window.c index 45d8d2f68..00ed1a079 100644 --- a/window.c +++ b/window.c @@ -1471,20 +1471,16 @@ window_pane_full_size_offset(struct window_pane *wp, int *xoff, int *yoff, u_int *sx, u_int *sy) { struct window *w = wp->window; - int pane_scrollbars; - u_int sb_w, sb_pos; + u_int sb_w; - pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); - sb_pos = options_get_number(w->options, "pane-scrollbars-position"); - - if (window_pane_show_scrollbar(wp, pane_scrollbars)) + if (window_pane_show_scrollbar(wp)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; else sb_w = 0; - if (sb_pos == PANE_SCROLLBARS_LEFT) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) { *xoff = wp->xoff - sb_w; *sx = wp->sx + sb_w; - } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ + } else { /* w->sb_pos == PANE_SCROLLBARS_RIGHT */ *xoff = wp->xoff; *sx = wp->sx + sb_w; } @@ -1899,12 +1895,14 @@ window_pane_mode(struct window_pane *wp) /* Return 1 if scrollbar is or should be displayed. */ int -window_pane_show_scrollbar(struct window_pane *wp, int sb_option) +window_pane_show_scrollbar(struct window_pane *wp) { + struct window *w = wp->window; + if (SCREEN_IS_ALTERNATE(&wp->base)) return (0); - if (sb_option == PANE_SCROLLBARS_ALWAYS || - (sb_option == PANE_SCROLLBARS_MODAL && + if (w->sb == PANE_SCROLLBARS_ALWAYS || + (w->sb == PANE_SCROLLBARS_MODAL && window_pane_mode(wp) != WINDOW_PANE_NO_MODE)) return (1); return (0); From 78a2145a47441cc2fb6d0283a1f4d5e12e4a6556 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 30 Jun 2026 07:42:25 +0100 Subject: [PATCH 30/60] Update CHANGES. --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index d3e43b1e7..172a8d533 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,11 @@ CHANGES FROM 3.7 to 3.7a * Fix crash in break-pane when no name is provided. +* Scrollbar options are now cached rather than being looked up for every redraw + (issue 5298). + +* Only forbid #( in names, allow #[, empty names, : and . + CHANGES FROM 3.6b TO 3.7 * Add floating panes. These are panes which sit above the layout ("tiled From a97f643bd3b82a713476d3eea56b7fdf0cff5e2d Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 30 Jun 2026 06:44:46 +0000 Subject: [PATCH 31/60] Remove C-r from help since it does not in fact exist. --- mode-tree.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/mode-tree.c b/mode-tree.c index fae0f3d8a..3a126129c 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -206,8 +206,6 @@ static const char* mode_tree_help_start[] = { "#[fg=themelightgrey]" " C-s #[#{E:tree-mode-border-style},acs]x#[default] Search forward", "#[fg=themelightgrey]" - " C-r #[#{E:tree-mode-border-style},acs]x#[default] Search backward", - "#[fg=themelightgrey]" " n #[#{E:tree-mode-border-style},acs]x#[default] Repeat search forward", "#[fg=themelightgrey]" " N #[#{E:tree-mode-border-style},acs]x#[default] Repeat search backward", From 0e418b62d259ce8da8970f75732cc6632ee4c3a0 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 30 Jun 2026 07:44:51 +0100 Subject: [PATCH 32/60] Remove C-r from help. --- mode-tree.c | 1 - 1 file changed, 1 deletion(-) diff --git a/mode-tree.c b/mode-tree.c index 825354970..fa868ef19 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -148,7 +148,6 @@ static const char* mode_tree_help_start[] = { "\r\033[1m T \033[0m\016x\017 \033[0mUntag all %1s\n", "\r\033[1m C-t \033[0m\016x\017 \033[0mTag all %1s\n", "\r\033[1m C-s \033[0m\016x\017 \033[0mSearch forward\n", - "\r\033[1m C-r \033[0m\016x\017 \033[0mSearch backward\n", "\r\033[1m n \033[0m\016x\017 \033[0mRepeat search forward\n", "\r\033[1m N \033[0m\016x\017 \033[0mRepeat search backward\n", "\r\033[1m f \033[0m\016x\017 \033[0mFilter %1s\n", From abefc3f7054c4330018bc928c3595841042021a7 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 30 Jun 2026 09:20:43 +0000 Subject: [PATCH 33/60] Redraw when sync stops again (accidentally turned off), from Japin Li in GitHub issue 5304. --- screen-write.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/screen-write.c b/screen-write.c index 31d177ef0..4a3a11c83 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1011,6 +1011,8 @@ screen_write_stop_sync(struct window_pane *wp) evtimer_del(&wp->sync_timer); wp->base.mode &= ~MODE_SYNC; + wp->flags |= PANE_REDRAW; + log_debug("%s: %%%u stopped sync mode", __func__, wp->id); } From f8674cc993dd09cb355fe920eb95973198a3b254 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 30 Jun 2026 10:07:05 +0000 Subject: [PATCH 34/60] Add default features for Ghostty, GitHub issue 5305 from Glib Shpychka. --- tty-features.c | 12 ++++++++++++ tty-keys.c | 2 ++ 2 files changed, 14 insertions(+) diff --git a/tty-features.c b/tty-features.c index b321af279..28b6d0f64 100644 --- a/tty-features.c +++ b/tty-features.c @@ -573,6 +573,18 @@ tty_default_features(int *feat, const char *name, u_int version) "hyperlinks," "usstyle" }, + { .name = "ghostty", + .features = TTY_FEATURES_BASE_MODERN_XTERM "," + "ccolour," + "cstyle," + "extkeys," + "focus," + "hyperlinks," + "osc7," + "sync," + "usstyle," + "progressbar" + }, { .name = "XTerm", /* * xterm also supports DECSLRM and DECFRA, but they can be diff --git a/tty-keys.c b/tty-keys.c index d3a93f10f..f48fb1b71 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1663,6 +1663,8 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, tty_default_features(features, "foot", 0); else if (strncmp(tmp, "WezTerm ", 7) == 0) tty_default_features(features, "WezTerm", 0); + else if (strncmp(tmp, "ghostty ", 8) == 0) + tty_default_features(features, "ghostty", 0); log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf); free(c->term_type); From eda7e563d01ca4e1c5154fb16a35a19e0df56f91 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 30 Jun 2026 20:27:36 +0000 Subject: [PATCH 35/60] Change a malloc to calloc. --- grid.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/grid.c b/grid.c index 44efa574d..3b1b96a38 100644 --- a/grid.c +++ b/grid.c @@ -308,27 +308,16 @@ grid_create(u_int sx, u_int sy, u_int hlimit) { struct grid *gd; - gd = xmalloc(sizeof *gd); + gd = xcalloc(1, sizeof *gd); gd->sx = sx; gd->sy = sy; if (hlimit != 0) gd->flags = GRID_HISTORY; - else - gd->flags = 0; - - gd->hscrolled = 0; - 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 - gd->linedata = NULL; return (gd); } From 928c7a97715b384c1fc039f7ad032cbc6ee0f1c0 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 30 Jun 2026 21:28:28 +0100 Subject: [PATCH 36/60] More checks for macOS. --- grid.c | 35 +++++++++++++++++++++++++++++++++++ screen.c | 2 +- tmux.h | 1 + 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/grid.c b/grid.c index 28680cbdd..da5c69177 100644 --- a/grid.c +++ b/grid.c @@ -74,11 +74,45 @@ grid_check_lines(struct grid *gd) } } } + +void +grid_check_is_clear(struct grid *gd) +{ + struct grid_line *gl; + u_int yy, ny; + + assert(gd != NULL); + + if (gd->sy == 0) { + assert(gd->linedata == NULL); + return; + } + + assert(gd->linedata != NULL); + + ny = gd->hsize + gd->sy; + for (yy = 0; yy < ny; yy++) { + gl = &gd->linedata[yy]; + + assert(gl->celldata == NULL); + assert(gl->cellused == 0); + assert(gl->cellsize == 0); + assert(gl->extddata == NULL); + assert(gl->extdsize == 0); + assert(gl->flags == 0); + assert(gl->time == 0); + } +} #else static void grid_check_lines(__unused struct grid *gd) { } + +void +grid_check_is_clear(__unused struct grid *gd) +{ +} #endif /* Store cell in entry. */ @@ -363,6 +397,7 @@ grid_create(u_int sx, u_int sy, u_int hlimit) else gd->linedata = NULL; + grid_check_is_clear(gd); return (gd); } diff --git a/screen.c b/screen.c index 8e513758e..f3fdfcbd4 100644 --- a/screen.c +++ b/screen.c @@ -123,7 +123,7 @@ screen_reinit(struct screen *s) s->saved_cy = UINT_MAX; screen_reset_tabs(s); - + grid_check_is_clear(s->grid); grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8); screen_clear_selection(s); diff --git a/tmux.h b/tmux.h index 863a41eca..175c9b78e 100644 --- a/tmux.h +++ b/tmux.h @@ -3323,6 +3323,7 @@ bitstr_t *fuzzy_match(const char *, const char *, u_int, u_int *); /* grid.c */ extern const struct grid_cell grid_default_cell; +void grid_check_is_clear(struct grid *); void grid_empty_line(struct grid *, u_int, u_int); void grid_set_tab(struct grid_cell *, u_int); int grid_cells_equal(const struct grid_cell *, const struct grid_cell *); From 74a5069c0a25a5207ceca6b22dc6c9b135bf118a Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 30 Jun 2026 22:44:21 +0000 Subject: [PATCH 37/60] Fix some indentation. --- server-client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server-client.c b/server-client.c index 3dc294c99..e0a9d08b8 100644 --- a/server-client.c +++ b/server-client.c @@ -2093,10 +2093,10 @@ server_client_reset_state(struct client *c) mode |= MODE_MOUSE_ALL; } } - if (options_get_number(oo, "focus-follows-mouse") || - w->sb == PANE_SCROLLBARS_MODAL || - w->sb == PANE_SCROLLBARS_AUTOHIDE) - mode |= MODE_MOUSE_ALL; + if (options_get_number(oo, "focus-follows-mouse") || + w->sb == PANE_SCROLLBARS_MODAL || + w->sb == PANE_SCROLLBARS_AUTOHIDE) + mode |= MODE_MOUSE_ALL; else if (~mode & MODE_MOUSE_ALL) mode |= MODE_MOUSE_BUTTON; } From 1e9a16a748776cec6980f5d575d3235e2caf11f7 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 30 Jun 2026 22:48:12 +0000 Subject: [PATCH 38/60] And some other indentation. --- server-client.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/server-client.c b/server-client.c index e0a9d08b8..c25c7ca19 100644 --- a/server-client.c +++ b/server-client.c @@ -2052,19 +2052,18 @@ server_client_reset_state(struct client *c) if (!window_position_is_visible(r, cx)) cursor = 0; - if (window_pane_scrollbar_overlay_visible(wp)) { - sb_w = wp->scrollbar_style.width; - if (sb_w > wp->sx) - sb_w = wp->sx; - if (sb_w != 0 && - w->sb_pos == - PANE_SCROLLBARS_LEFT) { - if (s->cx < sb_w) + if (window_pane_scrollbar_overlay_visible(wp)) { + sb_w = wp->scrollbar_style.width; + if (sb_w > wp->sx) + sb_w = wp->sx; + if (sb_w != 0 && + w->sb_pos == PANE_SCROLLBARS_LEFT) { + if (s->cx < sb_w) + cursor = 0; + } else if (sb_w != 0 && + s->cx >= wp->sx - sb_w) cursor = 0; - } else if (sb_w != 0 && - s->cx >= wp->sx - sb_w) - cursor = 0; - } + } if (status_at_line(c) == 0) cy += status_line_size(c); From e69950a9ca773f3183749f3ccd6ee588880040d8 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jul 2026 06:17:58 +0000 Subject: [PATCH 39/60] Add overline for ghostty also, GitHub issue 5309. --- tty-features.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tty-features.c b/tty-features.c index 28b6d0f64..ea6769dc6 100644 --- a/tty-features.c +++ b/tty-features.c @@ -579,6 +579,7 @@ tty_default_features(int *feat, const char *name, u_int version) "cstyle," "extkeys," "focus," + "overline," "hyperlinks," "osc7," "sync," From 724f85d275845c81df53676da627ec90c7c89b01 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jul 2026 07:48:48 +0000 Subject: [PATCH 40/60] Do not draw to tty if PANE_REDRAW has been set since it is just going to be replaced. --- screen-write.c | 56 +++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/screen-write.c b/screen-write.c index 4a3a11c83..1c32ff5b4 100644 --- a/screen-write.c +++ b/screen-write.c @@ -214,6 +214,20 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx) return (0); } +/* Should we draw to TTY for this screen? */ +static int +screen_write_should_draw(struct screen_write_ctx *ctx) +{ + struct window_pane *wp = ctx->wp; + struct screen *s = ctx->s; + + if (s->mode & MODE_SYNC) + return (0); + if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP))) + return (0); + return (1); +} + /* Set up context for TTY command. */ static void screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, @@ -1004,7 +1018,7 @@ screen_write_start_sync(struct window_pane *wp) void screen_write_stop_sync(struct window_pane *wp) { - if (wp == NULL) + if (wp == NULL || (~wp->base.mode & MODE_SYNC)) return; if (event_initialized(&wp->sync_timer)) @@ -1166,7 +1180,7 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, struct visible_ranges *r; struct visible_range *ri; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; r = window_visible_ranges(wp, xoff, yoff + yy, sx, NULL); @@ -1244,7 +1258,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) screen_write_initctx(ctx, &ttyctx, 1, 1); - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_alignmenttest, &ttyctx); @@ -1280,7 +1294,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_insertcharacter, &ttyctx); @@ -1316,7 +1330,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_deletecharacter, &ttyctx); @@ -1352,7 +1366,7 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_clearcharacter, &ttyctx); @@ -1388,7 +1402,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_insertline, &ttyctx); @@ -1415,7 +1429,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_insertline, &ttyctx); @@ -1451,7 +1465,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_deleteline, &ttyctx); @@ -1478,7 +1492,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_deleteline, &ttyctx); @@ -1605,7 +1619,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_reverseindex, &ttyctx); @@ -1717,7 +1731,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = lines; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_scrolldown, &ttyctx); @@ -1765,7 +1779,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1)); screen_write_collect_flush(ctx, 0, __func__); - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearendofscreen, &ttyctx); @@ -1835,7 +1849,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, s->cy); screen_write_collect_flush(ctx, 0, __func__); - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearstartofscreen, &ttyctx); @@ -1903,7 +1917,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, sy); - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearscreen, &ttyctx); @@ -2221,7 +2235,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen_write_citem *ci, *tmp; struct screen_write_cline *cl; - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) goto discard; if (ctx->scrolled != 0) { @@ -2555,12 +2569,12 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (s->mode & MODE_INSERT) { screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = width; - if (~s->mode & MODE_SYNC) + if (screen_write_should_draw(ctx)) tty_write(tty_cmd_insertcharacter, &ttyctx); } /* If not writing, done now. */ - if (skip || s->mode & MODE_SYNC) + if (skip || !screen_write_should_draw(ctx)) return; /* Do a full line redraw if needed. */ @@ -2580,7 +2594,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) for (i = 0, vis = 0; i < r->used; i++) vis += r->ranges[i].nx; if (vis >= width) { - if (~s->mode & MODE_SYNC) + if (screen_write_should_draw(ctx)) tty_write(tty_cmd_cell, &ttyctx); return; } @@ -2590,7 +2604,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) * spaces in the visible regions. */ utf8_set(&tmp_gc.data, ' '); - if (s->mode & MODE_SYNC) + if (!screen_write_should_draw(ctx)) return; for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; @@ -2733,7 +2747,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) ttyctx.cell = &last; if (force_wide) ttyctx.flags |= TTY_CTX_CELL_INVALIDATE; - if (~s->mode & MODE_SYNC) + if (screen_write_should_draw(ctx)) tty_write(tty_cmd_cell, &ttyctx); screen_write_set_cursor(ctx, cx, cy); From e9396e59d90d727284b4661971f1c2ac9c245fd2 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jul 2026 13:12:17 +0000 Subject: [PATCH 41/60] Add a couple of format c/f and c/b to emit colours. --- cmd-list-keys.c | 2 +- colour.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ format.c | 39 ++++++++++++++++++++++++++++++------- tmux.1 | 6 ++++++ tmux.h | 1 + 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/cmd-list-keys.c b/cmd-list-keys.c index abb51b2c8..7aa38c61e 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -232,7 +232,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) n = 1; ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); - format_defaults(ft, NULL, NULL, NULL, NULL); + format_defaults(ft, tc, NULL, NULL, NULL); format_add(ft, "notes_only", "%d", notes_only); format_add(ft, "key_has_repeat", "%d", key_bindings_has_repeat(l, n)); format_add(ft, "key_string_width", "%u", cmd_list_keys_get_width(l, n)); diff --git a/colour.c b/colour.c index 0db080fc4..b37882a88 100644 --- a/colour.c +++ b/colour.c @@ -290,6 +290,58 @@ colour_tostring(int c) return ("invalid"); } +/* Convert colour to an SGR escape sequence. */ +const char * +colour_toescape(struct client *c, int colour, int bg) +{ + static char s[32]; + u_char r, g, b; + int n, flags = (TERM_256COLOURS|TERM_RGBCOLOURS); + u_int o = (bg ? 40 : 30); + + if (c != NULL && (c->tty.flags & TTY_OPENED) && c->tty.term != NULL) + flags = c->tty.term->flags; + + if (colour & COLOUR_FLAG_THEME) { + n = colour & 0xff; + if (c != NULL && (u_int)n < COLOUR_THEME_COUNT) + colour = c->theme_colours[n]; + else + colour = colour_theme_terminal_colour(n); + } + + if (colour == 8 || colour == 9) { + xsnprintf(s, sizeof s, "\033[%dm", o + 9); + return (s); + } + + if ((~flags & TERM_RGBCOLOURS) & (colour & COLOUR_FLAG_RGB)) { + colour_split_rgb(colour, &r, &g, &b); + colour = colour_find_rgb(r, g, b); + } + if ((~flags & TERM_256COLOURS) & (colour & COLOUR_FLAG_256)) + colour = colour_256to16(colour); + + if (colour & COLOUR_FLAG_RGB) { + colour_split_rgb(colour, &r, &g, &b); + xsnprintf(s, sizeof s, "\033[%d;2;%u;%u;%um", o + 8, r, g, b); + return (s); + } + if (colour & COLOUR_FLAG_256) { + xsnprintf(s, sizeof s, "\033[%d;5;%um", o + 8, colour & 0xff); + return (s); + } + if (colour >= 0 && colour <= 7) { + xsnprintf(s, sizeof s, "\033[%dm", colour + o); + return (s); + } + if (colour >= 90 && colour <= 97) { + xsnprintf(s, sizeof s, "\033[%dm", colour + o - 30); + return (s); + } + return (NULL); +} + /* Convert background colour to theme. */ enum client_theme colour_totheme(int c) diff --git a/format.c b/format.c index 8af01342d..4055efa5d 100644 --- a/format.c +++ b/format.c @@ -121,6 +121,8 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_CLIENT_TERMCAP 0x1000000 #define FORMAT_CLIENT_TERMFEAT 0x2000000 #define FORMAT_CLIENT_ENVIRON 0x4000000 +#define FORMAT_COLOUR_ESC_FG 0x8000000 +#define FORMAT_COLOUR_ESC_BG 0x10000000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -4454,7 +4456,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, break; /* Check single character modifiers with no arguments. */ - if (strchr("labcdnwETSWPL!<>", cp[0]) != NULL && + if (strchr("labdnwETSWPL!<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -4476,7 +4478,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, } /* Now try single character with arguments. */ - if (strchr("ImCLNPSst=pReqW", cp[0]) == NULL) + if (strchr("ImCLNPSst=pReqWc", cp[0]) == NULL) break; c = cp[0]; @@ -5254,6 +5256,12 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, break; case 'c': modifiers |= FORMAT_COLOUR; + if (fm->argc < 1) + break; + if (strchr(fm->argv[0], 'f') != NULL) + modifiers |= FORMAT_COLOUR_ESC_FG; + if (strchr(fm->argv[0], 'b') != NULL) + modifiers |= FORMAT_COLOUR_ESC_BG; break; case 'd': modifiers |= FORMAT_DIRNAME; @@ -5452,11 +5460,28 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, /* Is this a colour? */ if (modifiers & FORMAT_COLOUR) { new = format_expand1(es, copy); - c = colour_fromstring(new); - if (c == -1 || (c = colour_force_rgb(c)) == -1) - value = xstrdup(""); - else - xasprintf(&value, "%06x", c & 0xffffff); + if (modifiers & (FORMAT_COLOUR_ESC_FG|FORMAT_COLOUR_ESC_BG)) { + if (strcasecmp(new, "none") == 0) + value = xstrdup("\033[0m"); + else if ((c = colour_fromstring(new)) == -1) + value = xstrdup(""); + else { + if (modifiers & FORMAT_COLOUR_ESC_BG) + cp = colour_toescape(ft->c, c, 1); + else + cp = colour_toescape(ft->c, c, 0); + if (cp == NULL) + value = xstrdup(""); + else + value = xstrdup(cp); + } + } else { + c = colour_fromstring(new); + if (c == -1 || (c = colour_force_rgb(c)) == -1) + value = xstrdup(""); + else + xasprintf(&value, "%06x", c & 0xffffff); + } free(new); goto done; } diff --git a/tmux.1 b/tmux.1 index 2e4d2bceb..da766ade9 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6694,6 +6694,12 @@ results in replaces a .Nm colour by its six-digit hexadecimal RGB value. +If an argument of +.Ql f +or +.Ql b +is given, it will instead produce the SGR escape sequence to set the foreground +or background colour respectively. .Pp A limit may be placed on the length of the resultant string by prefixing it by an diff --git a/tmux.h b/tmux.h index 61a2cbf91..ea3dc54ee 100644 --- a/tmux.h +++ b/tmux.h @@ -3254,6 +3254,7 @@ void colour_split_rgb(int, u_char *, u_char *, u_char *); int colour_force_rgb(int); int colour_dim(int, u_int); const char *colour_tostring(int); +const char *colour_toescape(struct client *, int, int); enum client_theme colour_totheme(int); int colour_fromstring(const char *); const char *colour_theme_option(u_int, enum client_theme); From 565db46a1947b829af00bb2ff4acbc0b8c189fcd Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jul 2026 15:44:13 +0000 Subject: [PATCH 42/60] Instead of fully redrawing the pane when sync ends, only redraw lines that have been changed. --- screen-redraw.c | 2 + screen-write.c | 186 +++++++++++++++++++++++++++++++++++------------- tmux.h | 4 ++ window.c | 1 + 4 files changed, 142 insertions(+), 51 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 1f9beb576..9e389be91 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1655,12 +1655,14 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) if (wp != NULL) { if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); + screen_write_clear_dirty(wp); } else { TAILQ_FOREACH(loop, &scene->w->panes, entry) { if (!window_pane_is_visible(loop)) continue; if (loop->base.mode & MODE_SYNC) screen_write_stop_sync(loop); + screen_write_clear_dirty(loop); } } } diff --git a/screen-write.c b/screen-write.c index 1c32ff5b4..b725af6e5 100644 --- a/screen-write.c +++ b/screen-write.c @@ -38,6 +38,7 @@ static int screen_write_overwrite(struct screen_write_ctx *, struct grid_cell *, u_int); static int screen_write_combine(struct screen_write_ctx *, const struct grid_cell *); +static void screen_write_flush_dirty(struct window_pane *); struct screen_write_citem { u_int x; @@ -214,20 +215,48 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx) return (0); } -/* Should we draw to TTY for this screen? */ +/* Should we draw to the TTY? */ static int -screen_write_should_draw(struct screen_write_ctx *ctx) +screen_write_should_draw_lines(struct screen_write_ctx *ctx, u_int y, u_int ny) { struct window_pane *wp = ctx->wp; struct screen *s = ctx->s; + u_int sy = screen_size_y(s); + bitstr_t *bs; - if (s->mode & MODE_SYNC) - return (0); if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP))) return (0); + if (s->mode & MODE_SYNC) { + if (wp != NULL && y < sy && ny != 0) { + bs = wp->sync_dirty; + if (ny > sy - y) + ny = sy - y; + if (bs == NULL || wp->sync_dirty_size != sy) { + if (bs != NULL && wp->sync_dirty_size != sy) { + y = 0; + ny = sy; + } + free(bs); + + bs = wp->sync_dirty = bit_alloc(sy); + if (bs == NULL) + fatal("bit_alloc failed"); + wp->sync_dirty_size = sy; + } + bit_nset(bs, y, y + ny - 1); + } + return (0); + } return (1); } +/* Should we draw this line to the TTY? */ +static int +screen_write_should_draw_line(struct screen_write_ctx *ctx, u_int y) +{ + return (screen_write_should_draw_lines(ctx, y, 1)); +} + /* Set up context for TTY command. */ static void screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, @@ -993,7 +1022,7 @@ screen_write_sync_callback(__unused int fd, __unused short events, void *arg) if (wp->base.mode & MODE_SYNC) { wp->base.mode &= ~MODE_SYNC; - wp->flags |= PANE_REDRAW; + screen_write_flush_dirty(wp); } } @@ -1025,7 +1054,7 @@ screen_write_stop_sync(struct window_pane *wp) evtimer_del(&wp->sync_timer); wp->base.mode &= ~MODE_SYNC; - wp->flags |= PANE_REDRAW; + screen_write_flush_dirty(wp); log_debug("%s: %%%u stopped sync mode", __func__, wp->id); } @@ -1180,9 +1209,6 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, struct visible_ranges *r; struct visible_range *ri; - if (!screen_write_should_draw(ctx)) - return; - r = window_visible_ranges(wp, xoff, yoff + yy, sx, NULL); for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; @@ -1221,6 +1247,44 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, } } +/* Redraw dirty lines. */ +static void +screen_write_flush_dirty(struct window_pane *wp) +{ + struct screen_write_ctx ctx; + struct tty_ctx ttyctx; + struct screen *s = &wp->base; + u_int y, sy = screen_size_y(s), lines = 0; + + if (wp->sync_dirty == NULL) + return; + + screen_write_start_pane(&ctx, wp, s); + screen_write_initctx(&ctx, &ttyctx, 1, 1); + + for (y = 0; y < sy; y++) { + if (bit_test(wp->sync_dirty, y)) { + screen_write_redraw_line(&ctx, &ttyctx, y); + lines++; + } + } + log_debug("%s: %%%u had %u dirty lines", __func__, wp->id, lines); + + screen_write_stop(&ctx); + screen_write_clear_dirty(wp); +} + +/* Clear any dirty lines. */ +void +screen_write_clear_dirty(struct window_pane *wp) +{ + if (wp != NULL && wp->sync_dirty != NULL) { + free(wp->sync_dirty); + wp->sync_dirty = NULL; + wp->sync_dirty_size = 0; + } +} + /* Redraw all visible cells in a pane. */ static void screen_write_redraw_pane(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) @@ -1258,7 +1322,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) screen_write_initctx(ctx, &ttyctx, 1, 1); - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_lines(ctx, 0, screen_size_y(s))) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_alignmenttest, &ttyctx); @@ -1294,7 +1358,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_line(ctx, s->cy)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_insertcharacter, &ttyctx); @@ -1330,7 +1394,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_line(ctx, s->cy)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_deletecharacter, &ttyctx); @@ -1366,7 +1430,7 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = nx; - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_line(ctx, s->cy)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_clearcharacter, &ttyctx); @@ -1402,7 +1466,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_lines(ctx, s->cy, sy - s->cy)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_insertline, &ttyctx); @@ -1429,7 +1493,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_lines(ctx, s->cy, s->rlower + 1 - s->cy)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_insertline, &ttyctx); @@ -1446,7 +1510,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) struct screen *s = ctx->s; struct grid *gd = s->grid; struct tty_ctx ttyctx; - u_int sy = screen_size_y(s); + u_int sy = screen_size_y(s), ry; if (ny == 0) ny = 1; @@ -1465,7 +1529,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - if (!screen_write_should_draw(ctx)) + ry = s->rlower + 1 - s->rupper; + if (!screen_write_should_draw_lines(ctx, s->rupper, ry)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_deleteline, &ttyctx); @@ -1476,8 +1541,9 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) return; } - if (ny > s->rlower + 1 - s->cy) - ny = s->rlower + 1 - s->cy; + ry = s->rlower + 1 - s->cy; + if (ny > ry) + ny = ry; if (ny == 0) return; @@ -1492,7 +1558,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = ny; - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_lines(ctx, s->cy, ry)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_deleteline, &ttyctx); @@ -1611,24 +1677,29 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) { struct screen *s = ctx->s; struct tty_ctx ttyctx; + u_int ry; - if (s->cy == s->rupper) { - grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); - screen_write_collect_flush(ctx, 0, __func__); + if (s->cy != s->rupper) { + if (s->cy > 0) + screen_write_set_cursor(ctx, -1, s->cy - 1); + return; + } - screen_write_initctx(ctx, &ttyctx, 1, 1); - ttyctx.bg = bg; + grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); + screen_write_collect_flush(ctx, 0, __func__); - if (!screen_write_should_draw(ctx)) - return; - if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { - tty_write(tty_cmd_reverseindex, &ttyctx); - return; - } + screen_write_initctx(ctx, &ttyctx, 1, 1); + ttyctx.bg = bg; - screen_write_redraw_pane(ctx, &ttyctx); - } else if (s->cy > 0) - screen_write_set_cursor(ctx, -1, s->cy - 1); + ry = s->rlower + 1 - s->rupper; + if (!screen_write_should_draw_lines(ctx, s->rupper, ry)) + return; + if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { + tty_write(tty_cmd_reverseindex, &ttyctx); + return; + } + + screen_write_redraw_pane(ctx, &ttyctx); } /* Set scroll region. */ @@ -1675,12 +1746,15 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) ctx->bg = bg; } - if (s->cy == s->rlower) { - grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); - screen_write_collect_scroll(ctx, bg); - ctx->scrolled++; - } else if (s->cy < screen_size_y(s) - 1) - screen_write_set_cursor(ctx, -1, s->cy + 1); + if (s->cy != s->rlower) { + if (s->cy < screen_size_y(s) - 1) + screen_write_set_cursor(ctx, -1, s->cy + 1); + return; + } + + grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); + screen_write_collect_scroll(ctx, bg); + ctx->scrolled++; } /* Scroll up. */ @@ -1715,7 +1789,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) struct screen *s = ctx->s; struct grid *gd = s->grid; struct tty_ctx ttyctx; - u_int i; + u_int i, ry; screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; @@ -1731,7 +1805,8 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = lines; - if (!screen_write_should_draw(ctx)) + ry = s->rlower + 1 - s->rupper; + if (!screen_write_should_draw_lines(ctx, s->rupper, ry)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) { tty_write(tty_cmd_scrolldown, &ttyctx); @@ -1779,7 +1854,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1)); screen_write_collect_flush(ctx, 0, __func__); - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_lines(ctx, s->cy, sy - s->cy)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearendofscreen, &ttyctx); @@ -1849,7 +1924,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, s->cy); screen_write_collect_flush(ctx, 0, __func__); - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_lines(ctx, 0, s->cy + 1)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearstartofscreen, &ttyctx); @@ -1917,7 +1992,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) screen_write_collect_clear(ctx, 0, sy); - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_lines(ctx, s->cy, sy - s->cy)) return; if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) { tty_write(tty_cmd_clearscreen, &ttyctx); @@ -2231,12 +2306,21 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, const char *from) { struct screen *s = ctx->s; + struct window_pane *wp = ctx->wp; u_int y, cx, cy, items = 0; struct screen_write_citem *ci, *tmp; struct screen_write_cline *cl; - if (!screen_write_should_draw(ctx)) + if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP))) goto discard; + if (s->mode & MODE_SYNC) { + for (y = 0; y < screen_size_y(s); y++) { + cl = &s->write_list[y]; + if (!TAILQ_EMPTY(&cl->items)) + screen_write_should_draw_line(ctx, y); + } + goto discard; + } if (ctx->scrolled != 0) { if (!screen_write_collect_flush_scrolled(ctx)) @@ -2569,12 +2653,12 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (s->mode & MODE_INSERT) { screen_write_collect_flush(ctx, 0, __func__); ttyctx.n = width; - if (screen_write_should_draw(ctx)) + if (screen_write_should_draw_line(ctx, s->cy)) tty_write(tty_cmd_insertcharacter, &ttyctx); } /* If not writing, done now. */ - if (skip || !screen_write_should_draw(ctx)) + if (skip || !screen_write_should_draw_line(ctx, s->cy)) return; /* Do a full line redraw if needed. */ @@ -2594,7 +2678,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) for (i = 0, vis = 0; i < r->used; i++) vis += r->ranges[i].nx; if (vis >= width) { - if (screen_write_should_draw(ctx)) + if (screen_write_should_draw_line(ctx, s->cy)) tty_write(tty_cmd_cell, &ttyctx); return; } @@ -2604,7 +2688,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) * spaces in the visible regions. */ utf8_set(&tmp_gc.data, ' '); - if (!screen_write_should_draw(ctx)) + if (!screen_write_should_draw_line(ctx, s->cy)) return; for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; @@ -2747,7 +2831,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) ttyctx.cell = &last; if (force_wide) ttyctx.flags |= TTY_CTX_CELL_INVALIDATE; - if (screen_write_should_draw(ctx)) + if (screen_write_should_draw_line(ctx, cy)) tty_write(tty_cmd_cell, &ttyctx); screen_write_set_cursor(ctx, cx, cy); diff --git a/tmux.h b/tmux.h index ea3dc54ee..d4add2d92 100644 --- a/tmux.h +++ b/tmux.h @@ -1260,6 +1260,9 @@ struct window_pane { #define PANE_UNSEENCHANGES 0x4000 #define PANE_REDRAWSCROLLBAR 0x8000 + bitstr_t *sync_dirty; + u_int sync_dirty_size; + u_int sb_slider_y; u_int sb_slider_h; int sb_auto_visible; @@ -3396,6 +3399,7 @@ void screen_write_mode_set(struct screen_write_ctx *, int); void screen_write_mode_clear(struct screen_write_ctx *, int); void screen_write_start_sync(struct window_pane *); void screen_write_stop_sync(struct window_pane *); +void screen_write_clear_dirty(struct window_pane *); void screen_write_cursorup(struct screen_write_ctx *, u_int); void screen_write_cursordown(struct screen_write_ctx *, u_int); void screen_write_cursorright(struct screen_write_ctx *, u_int); diff --git a/window.c b/window.c index 1ee39ce3b..e7ceab3ff 100644 --- a/window.c +++ b/window.c @@ -1203,6 +1203,7 @@ window_pane_destroy(struct window_pane *wp) window_pane_clear_prompt(wp); window_pane_free_modes(wp); + screen_write_clear_dirty(wp); free(wp->searchstr); if (wp->fd != -1) { From 824ef37f5ff412bd2844fee896c167175ba13aad Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jul 2026 16:43:14 +0000 Subject: [PATCH 43/60] Change layout sets to build the layout using the new mechanics, from Dane Jensen. --- layout-custom.c | 6 +- layout-set.c | 314 ++++++++++++++++++++++-------------------------- layout.c | 37 +++--- spawn.c | 2 +- tmux.h | 5 +- window.c | 6 +- 6 files changed, 178 insertions(+), 192 deletions(-) diff --git a/layout-custom.c b/layout-custom.c index 0fc3f5d16..c5a348ac0 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -265,7 +265,7 @@ layout_parse(struct window *w, const char *layout, char **cause) window_resize(w, tiled_lc->sx, tiled_lc->sy, -1, -1); /* Destroy the old layout and swap to the new. */ - layout_free_cell(w->layout_root); + layout_free_cell(w->layout_root, 0); w->layout_root = tiled_lc; /* Assign the panes into the cells. */ @@ -291,7 +291,7 @@ layout_parse(struct window *w, const char *layout, char **cause) return (0); fail: - layout_free_cell(tiled_lc); + layout_free_cell(tiled_lc, 0); return (-1); } @@ -423,6 +423,6 @@ layout_construct(struct layout_cell *lcparent, const char **layout, return (0); fail: - layout_free_cell(*lc); + layout_free_cell(*lc, 0); return (-1); } diff --git a/layout-set.c b/layout-set.c index 157133904..cf78585c2 100644 --- a/layout-set.c +++ b/layout-set.c @@ -124,12 +124,12 @@ layout_set_previous(struct window *w) } static struct window_pane * -layout_first_tiled(struct window *w) +layout_set_first_tiled(struct window *w) { struct window_pane *wp; TAILQ_FOREACH(wp, &w->panes, entry) { - if (!window_pane_is_floating(wp)) + if (wp->layout_cell && layout_cell_is_tiled(wp->layout_cell)) return (wp); } return (NULL); @@ -139,19 +139,15 @@ static void layout_set_even(struct window *w, enum layout_type type) { struct window_pane *wp; - struct layout_cell *lc, *lcnew; + struct layout_cell *lcroot, *lcchild; u_int n, sx, sy; layout_print_cell(w->layout_root, __func__, 1); - /* Get number of panes. */ n = window_count_panes(w, 0); if (n <= 1) return; - /* Free the old root and construct a new. */ - layout_free(w); - lc = w->layout_root = layout_create_cell(NULL); if (type == LAYOUT_LEFTRIGHT) { sx = (n * (PANE_MINIMUM + 1)) - 1; if (sx < w->sx) @@ -163,30 +159,30 @@ layout_set_even(struct window *w, enum layout_type type) sy = w->sy; sx = w->sx; } - layout_set_size(lc, sx, sy, 0, 0); - layout_make_node(lc, type); - /* Build new leaf cells. */ + layout_free(w, 1); + lcroot = w->layout_root = layout_create_cell(NULL); + layout_set_size(lcroot, sx, sy, 0, 0); + layout_make_node(lcroot, type); + TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_is_floating(wp)) - continue; - lcnew = layout_create_cell(lc); - layout_make_leaf(lcnew, wp); - lcnew->sx = w->sx; - lcnew->sy = w->sy; - TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry); + lcchild = wp->layout_cell; + TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry); + lcchild->parent = lcroot; + if (layout_cell_is_tiled(lcchild)) { + lcchild->sx = w->sx; + lcchild->sy = w->sy; + } } - /* Spread out cells. */ - layout_spread_cell(w, lc); + layout_spread_cell(w, lcroot); - /* Fix cell offsets. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lc->sx, lc->sy, -1, -1); + window_resize(w, lcroot->sx, lcroot->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -206,15 +202,14 @@ layout_set_even_v(struct window *w) static void layout_set_main_h(struct window *w) { - struct window_pane *wp; - struct layout_cell *lc, *lcmain, *lcother, *lcchild; + struct window_pane *wp, *wpmain; + struct layout_cell *lcroot, *lcmain, *lcother, *lcchild; u_int n, mainh, otherh, sx, sy; char *cause; const char *s; layout_print_cell(w->layout_root, __func__, 1); - /* Get number of panes. */ n = window_count_panes(w, 0); if (n <= 1) return; @@ -255,52 +250,48 @@ layout_set_main_h(struct window *w) if (sx < w->sx) sx = w->sx; - /* Free old tree and create a new root. */ - layout_free(w); - lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, sx, mainh + otherh + 1, 0, 0); - layout_make_node(lc, LAYOUT_TOPBOTTOM); + layout_free(w, 1); + lcroot = w->layout_root = layout_create_cell(NULL); + layout_set_size(lcroot, sx, mainh + otherh + 1, 0, 0); + layout_make_node(lcroot, LAYOUT_TOPBOTTOM); - /* Create the main pane. */ - lcmain = layout_create_cell(lc); + wpmain = layout_set_first_tiled(w); + lcmain = wpmain->layout_cell; + lcmain->parent = lcroot; layout_set_size(lcmain, sx, mainh, 0, 0); - layout_make_leaf(lcmain, layout_first_tiled(w)); - TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); + TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry); - /* Create the other pane. */ - lcother = layout_create_cell(lc); - layout_set_size(lcother, sx, otherh, 0, 0); if (n == 1) { - wp = TAILQ_NEXT(layout_first_tiled(w), entry); - while (wp != NULL && window_pane_is_floating(wp)) + wp = TAILQ_NEXT(wpmain, entry); + while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell)) wp = TAILQ_NEXT(wp, entry); - layout_make_leaf(lcother, wp); - TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry); + wp->layout_cell->parent = lcroot; } else { + lcother = layout_create_cell(lcroot); + layout_set_size(lcother, sx, otherh, 0, 0); layout_make_node(lcother, LAYOUT_LEFTRIGHT); - TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + TAILQ_INSERT_TAIL(&lcroot->cells, lcother, entry); - /* Add the remaining panes as children. */ TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_is_floating(wp)) + if (wp == wpmain) continue; - if (wp == layout_first_tiled(w)) - continue; - lcchild = layout_create_cell(lcother); - layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0); - layout_make_leaf(lcchild, wp); + lcchild = wp->layout_cell; TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); + lcchild->parent = lcother; + if (layout_cell_is_tiled(lcchild)) + layout_set_size(lcchild, PANE_MINIMUM, otherh, + 0, 0); } layout_spread_cell(w, lcother); } - /* Fix cell offsets. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lc->sx, lc->sy, -1, -1); + window_resize(w, lcroot->sx, lcroot->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -308,15 +299,14 @@ layout_set_main_h(struct window *w) static void layout_set_main_h_mirrored(struct window *w) { - struct window_pane *wp; - struct layout_cell *lc, *lcmain, *lcother, *lcchild; + struct window_pane *wp, *wpmain; + struct layout_cell *lcroot, *lcmain, *lcother, *lcchild; u_int n, mainh, otherh, sx, sy; char *cause; const char *s; layout_print_cell(w->layout_root, __func__, 1); - /* Get number of panes. */ n = window_count_panes(w, 0); if (n <= 1) return; @@ -357,52 +347,48 @@ layout_set_main_h_mirrored(struct window *w) if (sx < w->sx) sx = w->sx; - /* Free old tree and create a new root. */ - layout_free(w); - lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, sx, mainh + otherh + 1, 0, 0); - layout_make_node(lc, LAYOUT_TOPBOTTOM); + layout_free(w, 1); + lcroot = w->layout_root = layout_create_cell(NULL); + layout_set_size(lcroot, sx, mainh + otherh + 1, 0, 0); + layout_make_node(lcroot, LAYOUT_TOPBOTTOM); + + wpmain = layout_set_first_tiled(w); + lcmain = wpmain->layout_cell; + lcmain->parent = lcroot; + layout_set_size(lcmain, sx, mainh, 0, 0); + TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry); - /* Create the other pane. */ - lcother = layout_create_cell(lc); - layout_set_size(lcother, sx, otherh, 0, 0); if (n == 1) { - wp = TAILQ_NEXT(layout_first_tiled(w), entry); - while (wp != NULL && window_pane_is_floating(wp)) + wp = TAILQ_NEXT(wpmain, entry); + while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell)) wp = TAILQ_NEXT(wp, entry); - layout_make_leaf(lcother, wp); - TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry); + wp->layout_cell->parent = lcroot; } else { + lcother = layout_create_cell(lcroot); + layout_set_size(lcother, sx, otherh, 0, 0); layout_make_node(lcother, LAYOUT_LEFTRIGHT); - TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + TAILQ_INSERT_HEAD(&lcroot->cells, lcother, entry); - /* Add the remaining panes as children. */ TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_is_floating(wp)) + if (wp == wpmain) continue; - if (wp == layout_first_tiled(w)) - continue; - lcchild = layout_create_cell(lcother); - layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0); - layout_make_leaf(lcchild, wp); + lcchild = wp->layout_cell; TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); + lcchild->parent = lcother; + if (layout_cell_is_tiled(lcchild)) + layout_set_size(lcchild, PANE_MINIMUM, otherh, + 0, 0); } layout_spread_cell(w, lcother); } - /* Create the main pane. */ - lcmain = layout_create_cell(lc); - layout_set_size(lcmain, sx, mainh, 0, 0); - layout_make_leaf(lcmain, layout_first_tiled(w)); - TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); - - /* Fix cell offsets. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lc->sx, lc->sy, -1, -1); + window_resize(w, lcroot->sx, lcroot->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -410,21 +396,20 @@ layout_set_main_h_mirrored(struct window *w) static void layout_set_main_v(struct window *w) { - struct window_pane *wp; - struct layout_cell *lc, *lcmain, *lcother, *lcchild; + struct window_pane *wp, *wpmain; + struct layout_cell *lcroot, *lcmain, *lcother, *lcchild; u_int n, mainw, otherw, sx, sy; char *cause; const char *s; layout_print_cell(w->layout_root, __func__, 1); - /* Get number of panes. */ n = window_count_panes(w, 0); if (n <= 1) return; n--; /* take off main pane */ - /* Find available width - take off one line for the border. */ + /* Find available width - take off one column for the border. */ sx = w->sx - 1; /* Get the main pane width. */ @@ -459,52 +444,48 @@ layout_set_main_v(struct window *w) if (sy < w->sy) sy = w->sy; - /* Free old tree and create a new root. */ - layout_free(w); - lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, mainw + otherw + 1, sy, 0, 0); - layout_make_node(lc, LAYOUT_LEFTRIGHT); + layout_free(w, 1); + lcroot = w->layout_root = layout_create_cell(NULL); + layout_set_size(lcroot, mainw + otherw + 1, sy, 0, 0); + layout_make_node(lcroot, LAYOUT_LEFTRIGHT); - /* Create the main pane. */ - lcmain = layout_create_cell(lc); + wpmain = layout_set_first_tiled(w); + lcmain = wpmain->layout_cell; + lcmain->parent = lcroot; layout_set_size(lcmain, mainw, sy, 0, 0); - layout_make_leaf(lcmain, layout_first_tiled(w)); - TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); + TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry); - /* Create the other pane. */ - lcother = layout_create_cell(lc); - layout_set_size(lcother, otherw, sy, 0, 0); if (n == 1) { - wp = TAILQ_NEXT(layout_first_tiled(w), entry); - while (wp != NULL && window_pane_is_floating(wp)) + wp = TAILQ_NEXT(wpmain, entry); + while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell)) wp = TAILQ_NEXT(wp, entry); - layout_make_leaf(lcother, wp); - TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry); + wp->layout_cell->parent = lcroot; } else { + lcother = layout_create_cell(lcroot); layout_make_node(lcother, LAYOUT_TOPBOTTOM); - TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + layout_set_size(lcother, otherw, sy, 0, 0); + TAILQ_INSERT_TAIL(&lcroot->cells, lcother, entry); - /* Add the remaining panes as children. */ TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_is_floating(wp)) + if (wp == wpmain) continue; - if (wp == layout_first_tiled(w)) - continue; - lcchild = layout_create_cell(lcother); - layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0); - layout_make_leaf(lcchild, wp); + lcchild = wp->layout_cell; TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); + lcchild->parent = lcother; + if (layout_cell_is_tiled(lcchild)) + layout_set_size(lcchild, otherw, PANE_MINIMUM, + 0, 0); } layout_spread_cell(w, lcother); } - /* Fix cell offsets. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lc->sx, lc->sy, -1, -1); + window_resize(w, lcroot->sx, lcroot->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -512,8 +493,8 @@ layout_set_main_v(struct window *w) static void layout_set_main_v_mirrored(struct window *w) { - struct window_pane *wp; - struct layout_cell *lc, *lcmain, *lcother, *lcchild; + struct window_pane *wp, *wpmain; + struct layout_cell *lcroot, *lcmain, *lcother, *lcchild; u_int n, mainw, otherw, sx, sy; char *cause; const char *s; @@ -526,7 +507,7 @@ layout_set_main_v_mirrored(struct window *w) return; n--; /* take off main pane */ - /* Find available width - take off one line for the border. */ + /* Find available width - take off one column for the border. */ sx = w->sx - 1; /* Get the main pane width. */ @@ -561,62 +542,58 @@ layout_set_main_v_mirrored(struct window *w) if (sy < w->sy) sy = w->sy; - /* Free old tree and create a new root. */ - layout_free(w); - lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, mainw + otherw + 1, sy, 0, 0); - layout_make_node(lc, LAYOUT_LEFTRIGHT); + layout_free(w, 1); + lcroot = w->layout_root = layout_create_cell(NULL); + layout_set_size(lcroot, mainw + otherw + 1, sy, 0, 0); + layout_make_node(lcroot, LAYOUT_LEFTRIGHT); + + wpmain = layout_set_first_tiled(w); + lcmain = wpmain->layout_cell; + lcmain->parent = lcroot; + layout_set_size(lcmain, mainw, sy, 0, 0); + TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry); - /* Create the other pane. */ - lcother = layout_create_cell(lc); - layout_set_size(lcother, otherw, sy, 0, 0); if (n == 1) { - wp = TAILQ_NEXT(layout_first_tiled(w), entry); - while (wp != NULL && window_pane_is_floating(wp)) + wp = TAILQ_NEXT(wpmain, entry); + while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell)) wp = TAILQ_NEXT(wp, entry); - layout_make_leaf(lcother, wp); - TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry); + wp->layout_cell->parent = lcroot; } else { + lcother = layout_create_cell(lcroot); layout_make_node(lcother, LAYOUT_TOPBOTTOM); - TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + layout_set_size(lcother, otherw, sy, 0, 0); + TAILQ_INSERT_HEAD(&lcroot->cells, lcother, entry); - /* Add the remaining panes as children. */ TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_is_floating(wp)) + if (wp == wpmain) continue; - if (wp == layout_first_tiled(w)) - continue; - lcchild = layout_create_cell(lcother); - layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0); - layout_make_leaf(lcchild, wp); + lcchild = wp->layout_cell; TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); + lcchild->parent = lcother; + if (layout_cell_is_tiled(lcchild)) + layout_set_size(lcchild, otherw, PANE_MINIMUM, + 0, 0); } layout_spread_cell(w, lcother); } - /* Create the main pane. */ - lcmain = layout_create_cell(lc); - layout_set_size(lcmain, mainw, sy, 0, 0); - layout_make_leaf(lcmain, layout_first_tiled(w)); - TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); - - /* Fix cell offsets. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lc->sx, lc->sy, -1, -1); + window_resize(w, lcroot->sx, lcroot->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } -void +static void layout_set_tiled(struct window *w) { struct options *oo = w->options; struct window_pane *wp; - struct layout_cell *lc, *lcrow, *lcchild; + struct layout_cell *lcroot, *lcrow, *lcchild; u_int n, width, height, used, sx, sy; u_int i, j, columns, rows, max_columns; @@ -647,56 +624,59 @@ layout_set_tiled(struct window *w) if (height < PANE_MINIMUM) height = PANE_MINIMUM; - /* Free old tree and create a new root. */ - layout_free(w); - lc = w->layout_root = layout_create_cell(NULL); sx = ((width + 1) * columns) - 1; if (sx < w->sx) sx = w->sx; sy = ((height + 1) * rows) - 1; if (sy < w->sy) sy = w->sy; - layout_set_size(lc, sx, sy, 0, 0); - layout_make_node(lc, LAYOUT_TOPBOTTOM); - /* Create a grid of the cells, skipping any floating panes. */ + layout_free(w, 1); + lcroot = w->layout_root = layout_create_cell(NULL); + layout_set_size(lcroot, sx, sy, 0, 0); + layout_make_node(lcroot, LAYOUT_TOPBOTTOM); + + /* Create a grid of the tiled cells. */ wp = TAILQ_FIRST(&w->panes); - while (wp != NULL && window_pane_is_floating(wp)) - wp = TAILQ_NEXT(wp, entry); for (j = 0; j < rows; j++) { + while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell)) + wp = TAILQ_NEXT(wp, entry); /* If this is the last cell, all done. */ if (wp == NULL) break; - /* Create the new row. */ - lcrow = layout_create_cell(lc); - layout_set_size(lcrow, w->sx, height, 0, 0); - TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry); + lcchild = wp->layout_cell; /* If only one column, just use the row directly. */ if (n - (j * columns) == 1 || columns == 1) { - layout_make_leaf(lcrow, wp); + lcchild->parent = lcroot; + TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry); + layout_set_size(lcchild, w->sx, height, 0, 0); wp = TAILQ_NEXT(wp, entry); - while (wp != NULL && window_pane_is_floating(wp)) - wp = TAILQ_NEXT(wp, entry); continue; } - /* Add in the columns. */ + /* Create the new row. */ + lcrow = layout_create_cell(lcroot); layout_make_node(lcrow, LAYOUT_LEFTRIGHT); + layout_set_size(lcrow, w->sx, height, 0, 0); + TAILQ_INSERT_TAIL(&lcroot->cells, lcrow, entry); + + /* Add in the columns. */ for (i = 0; i < columns; i++) { /* Create and add a pane cell. */ - lcchild = layout_create_cell(lcrow); - layout_set_size(lcchild, width, height, 0, 0); - layout_make_leaf(lcchild, wp); + lcchild->parent = lcrow; TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry); + layout_set_size(lcchild, width, height, 0, 0); /* Move to the next non-floating cell. */ wp = TAILQ_NEXT(wp, entry); - while (wp != NULL && window_pane_is_floating(wp)) + while (wp != NULL && + !layout_cell_is_tiled(wp->layout_cell)) wp = TAILQ_NEXT(wp, entry); if (wp == NULL) break; + lcchild = wp->layout_cell; } /* @@ -713,21 +693,19 @@ layout_set_tiled(struct window *w) w->sx - used); } - /* Adjust the last row height to fit if necessary. */ used = (rows * height) + rows - 1; if (w->sy > used) { - lcrow = TAILQ_LAST(&lc->cells, layout_cells); + lcrow = TAILQ_LAST(&lcroot->cells, layout_cells); layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM, w->sy - used); } - /* Fix cell offsets. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); - window_resize(w, lc->sx, lc->sy, -1, -1); + window_resize(w, lcroot->sx, lcroot->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } diff --git a/layout.c b/layout.c index c38197b44..2a605d6cc 100644 --- a/layout.c +++ b/layout.c @@ -88,20 +88,24 @@ layout_create_cell(struct layout_cell *lcparent) /* Free a layout cell. */ void -layout_free_cell(struct layout_cell *lc) +layout_free_cell(struct layout_cell *lc, int only_nodes) { - struct layout_cell *lcchild; + struct layout_cell *lcchild, *lcnext; - if (lc == NULL) + if (lc == NULL || (only_nodes && lc->type == LAYOUT_WINDOWPANE)) return; switch (lc->type) { case LAYOUT_LEFTRIGHT: case LAYOUT_TOPBOTTOM: - while (!TAILQ_EMPTY(&lc->cells)) { - lcchild = TAILQ_FIRST(&lc->cells); - TAILQ_REMOVE(&lc->cells, lcchild, entry); - layout_free_cell(lcchild); + lcchild = TAILQ_FIRST(&lc->cells); + while (lcchild != NULL) { + lcnext = TAILQ_NEXT(lcchild, entry); + if (!only_nodes || lcchild->type != LAYOUT_WINDOWPANE) { + TAILQ_REMOVE(&lc->cells, lcchild, entry); + layout_free_cell(lcchild, only_nodes); + } + lcchild = lcnext; } break; case LAYOUT_WINDOWPANE: @@ -255,7 +259,7 @@ layout_fix_zindexes(struct window *w, struct layout_cell *lc) } } -static int +int layout_cell_is_tiled(struct layout_cell *lc) { int is_leaf = lc->type == LAYOUT_WINDOWPANE; @@ -699,13 +703,13 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, if (lcparent == NULL) { if (lc->wp != NULL) *lcroot = NULL; - layout_free_cell(lc); + layout_free_cell(lc, 0); return; } if (!layout_cell_is_tiled(lc)) { TAILQ_REMOVE(&lcparent->cells, lc, entry); - layout_free_cell(lc); + layout_free_cell(lc, 0); goto out; } @@ -721,7 +725,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, /* Remove this from the parent's list. */ TAILQ_REMOVE(&lcparent->cells, lc, entry); - layout_free_cell(lc); + layout_free_cell(lc, 0); out: /* @@ -742,7 +746,7 @@ out: } else TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry); - layout_free_cell(lcparent); + layout_free_cell(lcparent, 0); } } @@ -760,9 +764,9 @@ layout_init(struct window *w, struct window_pane *wp) /* Free layout for pane. */ void -layout_free(struct window *w) +layout_free(struct window *w, int only_nodes) { - layout_free_cell(w->layout_root); + layout_free_cell(w->layout_root, only_nodes); } /* Resize the entire layout after window resize. */ @@ -1507,7 +1511,8 @@ layout_spread_cell(struct window *w, struct layout_cell *parent) number = 0; TAILQ_FOREACH (lc, &parent->cells, entry) - number++; + if (layout_cell_is_tiled(lc)) + number++; if (number <= 1) return (0); status = window_get_pane_status(w); @@ -1535,6 +1540,8 @@ layout_spread_cell(struct window *w, struct layout_cell *parent) changed = 0; TAILQ_FOREACH (lc, &parent->cells, entry) { + if (!layout_cell_is_tiled(lc)) + continue; change = 0; if (parent->type == LAYOUT_LEFTRIGHT) { change = each - (int)lc->sx; diff --git a/spawn.c b/spawn.c index 7fd61b5ca..4f7a9611e 100644 --- a/spawn.c +++ b/spawn.c @@ -106,7 +106,7 @@ spawn_window(struct spawn_context *sc, char **cause) sc->wp0 = TAILQ_FIRST(&w->panes); TAILQ_REMOVE(&w->panes, sc->wp0, entry); - layout_free(w); + layout_free(w, 0); window_destroy_panes(w); TAILQ_INSERT_HEAD(&w->panes, sc->wp0, entry); diff --git a/tmux.h b/tmux.h index d4add2d92..2fbe08790 100644 --- a/tmux.h +++ b/tmux.h @@ -3630,7 +3630,7 @@ struct visible_ranges *window_visible_ranges(struct window_pane *, int, int, /* layout.c */ u_int layout_count_cells(struct layout_cell *); struct layout_cell *layout_create_cell(struct layout_cell *); -void layout_free_cell(struct layout_cell *); +void layout_free_cell(struct layout_cell *, int); void layout_print_cell(struct layout_cell *, const char *, u_int); void layout_destroy_cell(struct window *, struct layout_cell *, struct layout_cell **); @@ -3641,6 +3641,7 @@ void layout_set_size(struct layout_cell *, u_int, u_int, int, int); void layout_make_leaf(struct layout_cell *, struct window_pane *); void layout_make_node(struct layout_cell *, enum layout_type); void layout_fix_zindexes(struct window *, struct layout_cell *); +int layout_cell_is_tiled(struct layout_cell *); void layout_fix_offsets(struct window *); void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, @@ -3649,7 +3650,7 @@ void layout_resize_set_size(struct window *, struct layout_cell *, enum layout_type, u_int); struct layout_cell *layout_cell_get_neighbour(struct layout_cell *); void layout_init(struct window *, struct window_pane *); -void layout_free(struct window *); +void layout_free(struct window *, int); void layout_resize(struct window *, u_int, u_int); void layout_resize_pane(struct window_pane *, enum layout_type, int, int); diff --git a/window.c b/window.c index e7ceab3ff..93cb1cc74 100644 --- a/window.c +++ b/window.c @@ -359,8 +359,8 @@ window_destroy(struct window *w) window_unzoom(w, 0); RB_REMOVE(windows, &windows, w); - layout_free_cell(w->layout_root); - layout_free_cell(w->saved_layout_root); + layout_free_cell(w->layout_root, 0); + layout_free_cell(w->saved_layout_root, 0); free(w->old_layout); window_destroy_panes(w); @@ -778,7 +778,7 @@ window_unzoom(struct window *w, int notify) return (-1); w->flags &= ~WINDOW_ZOOMED; - layout_free(w); + layout_free(w, 0); w->layout_root = w->saved_layout_root; w->saved_layout_root = NULL; From e802909de06012a4df6209d55e86487c56223163 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 17:49:40 +0100 Subject: [PATCH 44/60] Fix so that the end of a synchronized update again triggers a redraw. --- CHANGES | 6 +++++- configure.ac | 2 +- input.c | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 172a8d533..7b1a2a54d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,8 @@ -CHANGES FROM 3.7 to 3.7a +CHANGES FROM 3.7a TO 3.7b + +* Fix so that the end of a synchronized update again triggers a redraw. + +CHANGES FROM 3.7 TO 3.7a * Fix crash in break-pane when no name is provided. diff --git a/configure.ac b/configure.ac index 57dc391a6..a239c78d3 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], 3.7a) +AC_INIT([tmux], 3.7b) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) diff --git a/input.c b/input.c index 788663e8f..ae503e50c 100644 --- a/input.c +++ b/input.c @@ -1955,6 +1955,8 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) break; case 2026: screen_write_stop_sync(ictx->wp); + if (ictx->wp != NULL) + ictx->wp->flags |= PANE_REDRAW; break; case 2031: screen_write_mode_clear(sctx, MODE_THEME_UPDATES); From 5e32eddb8c9942de76569f731e55114a8f1ec1b9 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:00:15 +0100 Subject: [PATCH 45/60] Fix up merge. --- screen-write.c | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/screen-write.c b/screen-write.c index 901db2d5b..dbc051cfb 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1724,22 +1724,16 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int ry; -<<<<<<< HEAD - if (s->cy == s->rupper) { -#ifdef ENABLE_SIXEL - if (image_free_all(s) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; -#endif - - grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); - screen_write_collect_flush(ctx, 0, __func__); -======= if (s->cy != s->rupper) { if (s->cy > 0) screen_write_set_cursor(ctx, -1, s->cy - 1); return; } ->>>>>>> obsd-master + +#ifdef ENABLE_SIXEL + if (image_free_all(s) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); screen_write_collect_flush(ctx, 0, __func__); @@ -1805,32 +1799,24 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) ctx->bg = bg; } -<<<<<<< HEAD - if (s->cy == s->rlower) { -#ifdef ENABLE_SIXEL - if (rlower == screen_size_y(s) - 1) - redraw = image_scroll_up(s, 1); - else - redraw = image_check_line(s, rupper, rlower - rupper); - if (redraw && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; -#endif - grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); - screen_write_collect_scroll(ctx, bg); - ctx->scrolled++; - } else if (s->cy < screen_size_y(s) - 1) - screen_write_set_cursor(ctx, -1, s->cy + 1); -======= if (s->cy != s->rlower) { if (s->cy < screen_size_y(s) - 1) screen_write_set_cursor(ctx, -1, s->cy + 1); return; } +#ifdef ENABLE_SIXEL + if (rlower == screen_size_y(s) - 1) + redraw = image_scroll_up(s, 1); + else + redraw = image_check_line(s, rupper, rlower - rupper); + if (redraw && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); screen_write_collect_scroll(ctx, bg); ctx->scrolled++; ->>>>>>> obsd-master } /* Scroll up. */ From 81083569787ca8588cf89072800567509af62389 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:02:25 +0100 Subject: [PATCH 46/60] Update names test. --- regress/check-names.sh | 65 ++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/regress/check-names.sh b/regress/check-names.sh index 21e185ac1..886299f2d 100644 --- a/regress/check-names.sh +++ b/regress/check-names.sh @@ -35,68 +35,59 @@ $TMUX set-option -qg allow-set-title on || exit 1 $TMUX set-option -qg allow-rename on || exit 1 $TMUX set-option -qg automatic-rename off || exit 1 -# Commands reject ':' and '.' for sessions and windows, but allow '#'. -$TMUX rename-session 'session#ok' || fail "session name with # rejected" -must_equal "$($TMUX display-message -p '#{session_name}')" 'session#ok' -must_fail $TMUX rename-session 'session:bad' -must_fail $TMUX rename-session 'session.bad' +# Commands allow empty names, ':', '.', '#' and '#('. +$TMUX rename-session '' || fail "empty session name rejected" +must_equal "$($TMUX display-message -p '#{session_name}')" '' +$TMUX rename-session 'session:.##(ok)' || \ + fail "session name with : . or #( rejected" +must_equal "$($TMUX display-message -p '#{session_name}')" 'session:.#(ok)' -$TMUX rename-window 'window#ok' || fail "window name with # rejected" -must_equal "$($TMUX display-message -p '#{window_name}')" 'window#ok' -must_fail $TMUX rename-window 'window:bad' -must_fail $TMUX rename-window 'window.bad' +$TMUX rename-window '' || fail "empty window name rejected" +must_equal "$($TMUX display-message -p '#{window_name}')" '' +$TMUX rename-window 'window:.##(ok)' || \ + fail "window name with : . or #( rejected" +must_equal "$($TMUX display-message -p '#{window_name}')" 'window:.#(ok)' -$TMUX set-option -q @name 'format#ok' || exit 1 +$TMUX set-option -q @name 'format:.#(ok)' || exit 1 $TMUX rename-session '#{@name}' || fail "format in session name not expanded" -must_equal "$($TMUX display-message -p '#{session_name}')" 'format#ok' +must_equal "$($TMUX display-message -p '#{session_name}')" 'format:.#(ok)' $TMUX rename-window '#{@name}' || fail "format in window name not expanded" -must_equal "$($TMUX display-message -p '#{window_name}')" 'format#ok' -must_fail $TMUX rename-session '#{session_name}:bad' -must_fail $TMUX rename-window '#{window_name}.bad' +must_equal "$($TMUX display-message -p '#{window_name}')" 'format:.#(ok)' +$TMUX set-option -q @name 'format:.#(ok)' || exit 1 pid=$($TMUX display-message -p '#{pid}') created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \ - -s 'new-session#ok' -n 'new-window#ok') || \ - fail "new-session name with # rejected" + -s 'new-session:.##(ok)' -n 'new-window:.##(ok)') || \ + fail "new-session name with : . or #( rejected" created_session=${created%:*} created_window=${created#*:} must_equal "$($TMUX display-message -pt "$created_session" '#{session_name}')" \ - 'new-session#ok' + 'new-session:.#(ok)' must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \ - 'new-window#ok' + 'new-window:.#(ok)' $TMUX kill-session -t "$created_session" -must_fail $TMUX new-session -d -s 'new-session:bad' -must_fail $TMUX new-session -d -s 'new-session.bad' -must_fail $TMUX new-session -d -n 'new-window:bad' -must_fail $TMUX new-session -d -n 'new-window.bad' - created_window=$($TMUX new-window -dP -F '#{window_id}' \ - -n 'created-window#ok') || \ - fail "new-window name with # rejected" + -n 'created-window:.##(ok)') || \ + fail "new-window name with : . or #( rejected" must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \ - 'created-window#ok' -must_fail $TMUX new-window -d -n 'created-window:bad' -must_fail $TMUX new-window -d -n 'created-window.bad' + 'created-window:.#(ok)' created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \ - -s 'new-session-#{pid}' -n 'new-window-#{pid}') || \ + -s 'new-session-#{pid}:.##(ok)' -n 'new-window-#{pid}:.##(ok)') || \ fail "format in new-session name not expanded" created_session=${created%:*} created_window=${created#*:} must_equal "$($TMUX display-message -pt "$created_session" '#{session_name}')" \ - "new-session-$pid" + "new-session-$pid:.#(ok)" must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \ - "new-window-$pid" + "new-window-$pid:.#(ok)" $TMUX kill-session -t "$created_session" created_window=$($TMUX new-window -dP -F '#{window_id}' -n '#{@name}') || \ fail "format in new-window name not expanded" must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \ - 'format#ok' -must_fail $TMUX new-session -d -s 'new-session-#{pid}:bad' -must_fail $TMUX new-session -d -n 'new-window-#{pid}.bad' -must_fail $TMUX new-window -d -n '#{window_name}:bad' + 'format:.#(ok)' # Invalid UTF-8 is never allowed for command names. invalid=$(printf '\302') @@ -124,9 +115,9 @@ must_equal "$($TMUX list-buffers -F '#{buffer_name}')" 'buffer#:.ok' # Window names from escape sequences allow '#' except in '#('. $TMUX send-keys "printf '\\033kescape#:.ok\\033\\\\'" Enter || exit 1 sleep 1 -must_equal "$($TMUX display-message -p '#{window_name}')" 'escape#__ok' +must_equal "$($TMUX display-message -p '#{window_name}')" 'escape#:.ok' -# Titles from escape sequences reject only '#'. +# Titles from escape sequences allow '#' except in '#('. $TMUX send-keys "printf '\\033]2;escape#:.ok\\007'" Enter || exit 1 sleep 1 must_equal "$($TMUX display-message -p '#{pane_title}')" 'escape#:.ok' From bd5d7a8a7af7c1a917432f686f1186b65158c18b Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:15:40 +0100 Subject: [PATCH 47/60] Update prompt test. --- regress/prompt-keys.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/regress/prompt-keys.sh b/regress/prompt-keys.sh index 7fa7a673b..8c17581b1 100644 --- a/regress/prompt-keys.sh +++ b/regress/prompt-keys.sh @@ -131,11 +131,14 @@ $IN send-keys -l "Z" || exit 1 settle search_is "hello Z" "C-w did not kill a word" -# C-a then C-k kills the whole line. +# C-a then C-k kills the whole line. The mode prompt no longer fills the rest +# of the row, so insert a marker to distinguish prompt input from tree content +# that may remain visible after the prompt. $IN send-keys C-a || exit 1 $IN send-keys C-k || exit 1 +$IN send-keys -l "X" || exit 1 settle -search_row | grep -q '(search) [^ ]' && fail "C-a C-k did not clear the line" +search_is "X" "C-a C-k did not clear the line" # --- 3. Editing kept the prompt open the whole time. --- in_tree_mode || fail "editing keys closed the mode" From fada35478b5daaa3bb50d64a1f38e128c99c4d2b Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:30:55 +0100 Subject: [PATCH 48/60] Colours changed in tests. --- .../floating-over-scrollbar.result | 12 +++++----- .../marked-pane-lr.result | 24 +++++++++---------- .../marked-pane-status.result | 24 +++++++++---------- .../marked-pane-tb.result | 2 +- .../marked-pane-three.result | 24 +++++++++---------- .../scrollbar-floating.result | 12 +++++----- .../scrollbar-split-left.result | 14 +++++------ .../scrollbar-split-right.result | 14 +++++------ .../window-style-active.result | 10 ++++---- 9 files changed, 68 insertions(+), 68 deletions(-) diff --git a/regress/screen-redraw-results/floating-over-scrollbar.result b/regress/screen-redraw-results/floating-over-scrollbar.result index 5e85eff82..d618c0a60 100644 --- a/regress/screen-redraw-results/floating-over-scrollbar.result +++ b/regress/screen-redraw-results/floating-over-scrollbar.result @@ -1,12 +1,12 @@ base      - ┌──────────────┐  - │OVERSB  │  - │  │  - │  │  - │  │  - └──────────────┘  + ┌──────────────┐  + │OVERSB  │  + │  │  + │  │  + │  │  + └──────────────┘        diff --git a/regress/screen-redraw-results/marked-pane-lr.result b/regress/screen-redraw-results/marked-pane-lr.result index 4f3d1af11..53c9aff44 100644 --- a/regress/screen-redraw-results/marked-pane-lr.result +++ b/regress/screen-redraw-results/marked-pane-lr.result @@ -1,12 +1,12 @@ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ diff --git a/regress/screen-redraw-results/marked-pane-status.result b/regress/screen-redraw-results/marked-pane-status.result index ff99a0bc2..502f4cc70 100644 --- a/regress/screen-redraw-results/marked-pane-status.result +++ b/regress/screen-redraw-results/marked-pane-status.result @@ -1,12 +1,12 @@ -── 0:left ──────────┬── 1:right ──────── - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ +── 0:left ──────────┬── 1:right ──────── + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ diff --git a/regress/screen-redraw-results/marked-pane-tb.result b/regress/screen-redraw-results/marked-pane-tb.result index 23fe88924..c16b2a177 100644 --- a/regress/screen-redraw-results/marked-pane-tb.result +++ b/regress/screen-redraw-results/marked-pane-tb.result @@ -4,7 +4,7 @@ -──────────────────────────────────────── +──────────────────────────────────────── diff --git a/regress/screen-redraw-results/marked-pane-three.result b/regress/screen-redraw-results/marked-pane-three.result index f7b776713..e199f5415 100644 --- a/regress/screen-redraw-results/marked-pane-three.result +++ b/regress/screen-redraw-results/marked-pane-three.result @@ -1,12 +1,12 @@ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ diff --git a/regress/screen-redraw-results/scrollbar-floating.result b/regress/screen-redraw-results/scrollbar-floating.result index 51542ad0d..81c9dfbae 100644 --- a/regress/screen-redraw-results/scrollbar-floating.result +++ b/regress/screen-redraw-results/scrollbar-floating.result @@ -1,12 +1,12 @@ SB00 abcdefghij  SB01 abcdefghij  SB02 abcdefghij  -SB03 abc┌──────────────────┐  -SB04 abc│FLOAT02 abcdef  │  -SB05 abc│FLOAT03 abcdef  │  -SB06 abc│FLOAT04 abcdef  │  -SB07 abc│  │  -SB08 abc└──────────────────┘  +SB03 abc┌──────────────────┐  +SB04 abc│FLOAT02 abcdef  │  +SB05 abc│FLOAT03 abcdef  │  +SB06 abc│FLOAT04 abcdef  │  +SB07 abc│  │  +SB08 abc└──────────────────┘  SB09 abcdefghij  SB10 abcdefghij    diff --git a/regress/screen-redraw-results/scrollbar-split-left.result b/regress/screen-redraw-results/scrollbar-split-left.result index 9d602a4d6..5ac964ab2 100644 --- a/regress/screen-redraw-results/scrollbar-split-left.result +++ b/regress/screen-redraw-results/scrollbar-split-left.result @@ -1,10 +1,10 @@ - SB00 abcdefghij │ SBL00 abcdefghij - SB01 abcdefghij │ SBL01 abcdefghij - SB02 abcdefghij │ SBL02 abcdefghij - SB03 abcdefghij │ SBL03 abcdefghij - SB04 abcdefghij │ SBL04 abcdefghij - SB05 abcdefghij │ SBL05 abcdefghij - SB06 abcdefghij │ SBL06 abcdefghij + SB00 abcdefghij │ SBL00 abcdefghij + SB01 abcdefghij │ SBL01 abcdefghij + SB02 abcdefghij │ SBL02 abcdefghij + SB03 abcdefghij │ SBL03 abcdefghij + SB04 abcdefghij │ SBL04 abcdefghij + SB05 abcdefghij │ SBL05 abcdefghij + SB06 abcdefghij │ SBL06 abcdefghij  SB07 abcdefghij │ SBL07 abcdefghij  SB08 abcdefghij │ SBL08 abcdefghij  SB09 abcdefghij │ SBL09 abcdefghij diff --git a/regress/screen-redraw-results/scrollbar-split-right.result b/regress/screen-redraw-results/scrollbar-split-right.result index ab1d465ef..7946089cd 100644 --- a/regress/screen-redraw-results/scrollbar-split-right.result +++ b/regress/screen-redraw-results/scrollbar-split-right.result @@ -1,10 +1,10 @@ -SB00 abcdefghij  │SBR00 abcdefghij  -SB01 abcdefghij  │SBR01 abcdefghij  -SB02 abcdefghij  │SBR02 abcdefghij  -SB03 abcdefghij  │SBR03 abcdefghij  -SB04 abcdefghij  │SBR04 abcdefghij  -SB05 abcdefghij  │SBR05 abcdefghij  -SB06 abcdefghij  │SBR06 abcdefghij  +SB00 abcdefghij  │SBR00 abcdefghij  +SB01 abcdefghij  │SBR01 abcdefghij  +SB02 abcdefghij  │SBR02 abcdefghij  +SB03 abcdefghij  │SBR03 abcdefghij  +SB04 abcdefghij  │SBR04 abcdefghij  +SB05 abcdefghij  │SBR05 abcdefghij  +SB06 abcdefghij  │SBR06 abcdefghij  SB07 abcdefghij  │SBR07 abcdefghij  SB08 abcdefghij  │SBR08 abcdefghij  SB09 abcdefghij  │SBR09 abcdefghij  diff --git a/regress/screen-redraw-results/window-style-active.result b/regress/screen-redraw-results/window-style-active.result index 6210820aa..aa110d5ed 100644 --- a/regress/screen-redraw-results/window-style-active.result +++ b/regress/screen-redraw-results/window-style-active.result @@ -1,8 +1,8 @@ -STYLE00 abcdefghij │STYLE00 abcdefghij -STYLE01 abcdefghij │STYLE01 abcdefghij -STYLE02 abcdefghij │STYLE02 abcdefghij -STYLE03 abcdefghij │STYLE03 abcdefghij -STYLE04 abcdefghij │STYLE04 abcdefghij +STYLE00 abcdefghij │STYLE00 abcdefghij +STYLE01 abcdefghij │STYLE01 abcdefghij +STYLE02 abcdefghij │STYLE02 abcdefghij +STYLE03 abcdefghij │STYLE03 abcdefghij +STYLE04 abcdefghij │STYLE04 abcdefghij STYLE05 abcdefghij │STYLE05 abcdefghij STYLE06 abcdefghij │STYLE06 abcdefghij  │ From 6f588402c34471b0dea0c0894a2caca275f92f17 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:43:01 +0100 Subject: [PATCH 49/60] Remove a char which differs between platforms. --- regress/UTF-8-test.txt | Bin 22781 -> 22698 bytes regress/utf8-test.result | 1 - 2 files changed, 1 deletion(-) diff --git a/regress/UTF-8-test.txt b/regress/UTF-8-test.txt index a5b5d50e6b61eb9a3b751b3954f83e61bb59db9b..ab230089d62ebccf1bd8bfee570be773a8bb7bdd 100644 GIT binary patch delta 18 acmeynk#W^V#tod}lP`z_Y+fTC=m-E%RR^m8 delta 63 zcmZ3rk@4?F#tod}lMjfpD4HrLgz6d?7#bQF7#JwnDx{exSQ;o;fRU|&(w7O73k0<$ Qd9zFounX86DIVwu07=FX`Tzg` diff --git a/regress/utf8-test.result b/regress/utf8-test.result index e700cb17f..67ed5cb52 100644 --- a/regress/utf8-test.result +++ b/regress/utf8-test.result @@ -90,7 +90,6 @@ You should see the Greek word 'kosme': "κόσμε" 2.3.2 U-0000E000 = ee 80 80 = "" | 2.3.3 U-0000FFFD = ef bf bd = "�" | 2.3.4 U-0010FFFF = f4 8f bf bf = "􏿿" | -2.3.5 U-00110000 = f4 90 80 80 = "�" | | 3 Malformed sequences | | From 85fc0ea57c029ef396b2714cc51ed27c299cc410 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:43:59 +0100 Subject: [PATCH 50/60] Add a test for regress. --- .github/workflows/regress.yml | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/regress.yml diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml new file mode 100644 index 000000000..10ed437f8 --- /dev/null +++ b/.github/workflows/regress.yml @@ -0,0 +1,45 @@ +name: 'Run Tests' + +on: + workflow_dispatch: + schedule: + - cron: '33 3 * * *' + +permissions: + contents: read + +concurrency: + group: tmux-tests + cancel-in-progress: true + +jobs: + regress: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + autoconf \ + automake \ + bison \ + build-essential \ + libevent-dev \ + libncurses-dev \ + pkg-config + + - name: build + run: | + sh autogen.sh + ./configure + make -j"$(nproc)" + + - name: test + run: | + cd regress + make From 35f429cb6d530a4fc5205bd8c928e56864fac108 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:52:47 +0100 Subject: [PATCH 51/60] macOS too. --- .github/workflows/regress.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 10ed437f8..096873446 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -14,14 +14,27 @@ concurrency: jobs: regress: - runs-on: ubuntu-latest - timeout-minutes: 30 + name: ${{ matrix.name }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 45 + + strategy: + fail-fast: false + matrix: + include: + - name: ubuntu-24.04-x64 + runner: ubuntu-24.04 + - name: ubuntu-24.04-arm64 + runner: ubuntu-24.04-arm + - name: macos-26-arm64 + runner: macos-26 steps: - name: checkout uses: actions/checkout@v4 - name: dependencies + if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y \ @@ -33,11 +46,22 @@ jobs: libncurses-dev \ pkg-config + - name: dependencies + if: runner.os == 'macOS' + run: | + brew install \ + autoconf \ + automake \ + bison \ + libevent \ + ncurses \ + pkg-config + - name: build run: | sh autogen.sh ./configure - make -j"$(nproc)" + make -j"$(getconf _NPROCESSORS_ONLN)" - name: test run: | From c41e98be3ccc9707bbbbee6e0a88bc41db3d0bb4 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:53:16 +0100 Subject: [PATCH 52/60] yaml is poo. --- .github/workflows/regress.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 096873446..4ca9e26f8 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -50,12 +50,12 @@ jobs: if: runner.os == 'macOS' run: | brew install \ - autoconf \ - automake \ - bison \ - libevent \ - ncurses \ - pkg-config + autoconf \ + automake \ + bison \ + libevent \ + ncurses \ + pkg-config - name: build run: | From 5b37471db77d84f67a18be333d6cb26077d0c37f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:53:37 +0100 Subject: [PATCH 53/60] Spaces. --- .github/workflows/regress.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 4ca9e26f8..b9fcaf6bc 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -50,12 +50,12 @@ jobs: if: runner.os == 'macOS' run: | brew install \ - autoconf \ - automake \ - bison \ - libevent \ - ncurses \ - pkg-config + autoconf \ + automake \ + bison \ + libevent \ + ncurses \ + pkg-config - name: build run: | From 728ad24d4a8b8e3bad7473d494ef3aed78c86e41 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:55:28 +0100 Subject: [PATCH 54/60] utf8proc. --- .github/workflows/regress.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index b9fcaf6bc..46b45c6a3 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -55,12 +55,13 @@ jobs: bison \ libevent \ ncurses \ + utf8proc \ pkg-config - name: build run: | sh autogen.sh - ./configure + ./configure --enable-utf8proc make -j"$(getconf _NPROCESSORS_ONLN)" - name: test From d9d12c5c4381e27905876dbbd39e5df05dbf385f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 18:59:34 +0100 Subject: [PATCH 55/60] More tweaks. --- .github/workflows/regress.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 46b45c6a3..952a77179 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -24,10 +24,13 @@ jobs: include: - name: ubuntu-24.04-x64 runner: ubuntu-24.04 + make: make - name: ubuntu-24.04-arm64 runner: ubuntu-24.04-arm + make: make - name: macos-26-arm64 runner: macos-26 + make: gmake steps: - name: checkout @@ -44,6 +47,7 @@ jobs: build-essential \ libevent-dev \ libncurses-dev \ + libutf8proc-dev pkg-config - name: dependencies @@ -54,6 +58,7 @@ jobs: automake \ bison \ libevent \ + make \ ncurses \ utf8proc \ pkg-config @@ -62,9 +67,9 @@ jobs: run: | sh autogen.sh ./configure --enable-utf8proc - make -j"$(getconf _NPROCESSORS_ONLN)" + ${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)" - name: test run: | cd regress - make + ${{ matrix.make }} From 9fb67a2cdda83914a1c168e3b6b3628eafbac9ce Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 19:00:37 +0100 Subject: [PATCH 56/60] Missing \. --- .github/workflows/regress.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 952a77179..a49ac3f23 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -47,7 +47,7 @@ jobs: build-essential \ libevent-dev \ libncurses-dev \ - libutf8proc-dev + libutf8proc-dev \ pkg-config - name: dependencies From 3d58e04c93c17af60c6852531aeb6d85a5975d09 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 1 Jul 2026 19:05:24 +0100 Subject: [PATCH 57/60] no point in having stuff i cannot test --- .github/workflows/regress.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index a49ac3f23..883ee720e 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -25,12 +25,12 @@ jobs: - name: ubuntu-24.04-x64 runner: ubuntu-24.04 make: make - - name: ubuntu-24.04-arm64 - runner: ubuntu-24.04-arm - make: make - - name: macos-26-arm64 - runner: macos-26 - make: gmake + # - name: ubuntu-24.04-arm64 + # runner: ubuntu-24.04-arm + # make: make + # - name: macos-26-arm64 + # runner: macos-26 + # make: gmake steps: - name: checkout From 735bbf9da75cb0d7545750f4028c26420507bb07 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jul 2026 20:18:05 +0000 Subject: [PATCH 58/60] Disable time expansion if expanding an inner format or more %s are needed. --- format.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/format.c b/format.c index 4055efa5d..c0a8c1e92 100644 --- a/format.c +++ b/format.c @@ -4434,6 +4434,15 @@ format_build_modifiers(struct format_expand_state *es, const char **s, struct format_modifier *list = NULL; char c, last[] = "X;:", **argv, *value; int argc; + struct format_expand_state next; + + /* + * Expand modifier arguments without time expansion so that any strftime + * specifiers are preserved for modifiers such as t/f to interpret; the + * final value is expanded with time later where appropriate. + */ + format_copy_state(&next, es, 0); + next.flags &= ~FORMAT_EXPAND_TIME; /* * Modifiers are a ; separated list of the forms: @@ -4499,7 +4508,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, argv = xcalloc(1, sizeof *argv); value = format_unescape(es, cp + 1, end - (cp + 1)); - argv[0] = format_expand1(es, value); + argv[0] = format_expand1(&next, value); free(value); argc = 1; @@ -4523,7 +4532,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, argv = xreallocarray(argv, argc + 1, sizeof *argv); value = format_unescape(es, cp, end - cp); - argv[argc++] = format_expand1(es, value); + argv[argc++] = format_expand1(&next, value); free(value); cp = end; From 8dc9e24c74103dea70a033ba28c8658b28aa76db Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jul 2026 20:26:39 +0000 Subject: [PATCH 59/60] Revert previous, it is not the right change. --- format.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/format.c b/format.c index c0a8c1e92..4055efa5d 100644 --- a/format.c +++ b/format.c @@ -4434,15 +4434,6 @@ format_build_modifiers(struct format_expand_state *es, const char **s, struct format_modifier *list = NULL; char c, last[] = "X;:", **argv, *value; int argc; - struct format_expand_state next; - - /* - * Expand modifier arguments without time expansion so that any strftime - * specifiers are preserved for modifiers such as t/f to interpret; the - * final value is expanded with time later where appropriate. - */ - format_copy_state(&next, es, 0); - next.flags &= ~FORMAT_EXPAND_TIME; /* * Modifiers are a ; separated list of the forms: @@ -4508,7 +4499,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, argv = xcalloc(1, sizeof *argv); value = format_unescape(es, cp + 1, end - (cp + 1)); - argv[0] = format_expand1(&next, value); + argv[0] = format_expand1(es, value); free(value); argc = 1; @@ -4532,7 +4523,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, argv = xreallocarray(argv, argc + 1, sizeof *argv); value = format_unescape(es, cp, end - cp); - argv[argc++] = format_expand1(&next, value); + argv[argc++] = format_expand1(es, value); free(value); cp = end; From 3ef51a469cc5738feca8a1e17740748171af6b6f Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jul 2026 20:53:38 +0000 Subject: [PATCH 60/60] move-pane needs -D. --- cmd-join-pane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 54b565b0f..6cb37d7eb 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -53,7 +53,7 @@ const struct cmd_entry cmd_move_pane_entry = { .name = "move-pane", .alias = "movep", - .args = { "bdfhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL }, + .args = { "bdD::fhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL }, .usage = "[-bdfhMv] [-D lines] [-l size] [-L columns] [-P position] " "[-R columns] " CMD_SRCDST_PANE_USAGE " [-U lines] " "[-X x-position] [-Y y-position] [-z z-index]",