From d7a2b52da8187204de8f8b3c18b770da5205b115 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 5 Jun 2026 08:04:03 +0000 Subject: [PATCH 01/27] Add a way to quote for command arguments and use for key in list-keys, GitHub issue 5153. --- cmd-list-keys.c | 2 +- format.c | 8 ++++++++ tmux.1 | 9 +++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 405c03c7..d8065259 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -35,7 +35,7 @@ "," \ "bind-key #{?key_has_repeat,#{?key_repeat,-r, },} " \ "-T #{p|#{key_table_width}:key_table} " \ - "#{p|#{key_string_width}:key_string} " \ + "#{p|#{key_string_width}:#{q|a:key_string}} " \ "#{key_command}}" static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *); diff --git a/format.c b/format.c index 75771630..7b184613 100644 --- a/format.c +++ b/format.c @@ -116,6 +116,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_NOT 0x80000 #define FORMAT_NOT_NOT 0x100000 #define FORMAT_REPEAT 0x200000 +#define FORMAT_QUOTE_ARGUMENTS 0x400000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -4164,6 +4165,11 @@ found: found = format_quote_style(saved); free(saved); } + if (modifiers & FORMAT_QUOTE_ARGUMENTS) { + saved = found; + found = args_escape(saved); + free(saved); + } return (found); } @@ -5137,6 +5143,8 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, else if (strchr(fm->argv[0], 'e') != NULL || strchr(fm->argv[0], 'h') != NULL) modifiers |= FORMAT_QUOTE_STYLE; + else if (strchr(fm->argv[0], 'a') != NULL) + modifiers |= FORMAT_QUOTE_ARGUMENTS; break; case 'E': modifiers |= FORMAT_EXPAND; diff --git a/tmux.1 b/tmux.1 index 9c9b38de..223580a1 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6286,12 +6286,17 @@ of the variable respectively. .Ql q:\& will escape .Xr sh 1 -special characters or with a +special characters; with a .Ql h suffix, escape hash characters (so .Ql # becomes -.Ql ## ) . +.Ql ## ) ; +or with +.Ql a +escape +.Nm +command arguments. .Ql E:\& will expand the format twice, for example .Ql #{E:status\-left} From 35709a30ee7ff18b7cb4092ec52d063be8724c51 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 6 Jun 2026 17:30:48 +0000 Subject: [PATCH 02/27] Change so that floating panes are always part of the root layout. From Dane Jensen. --- cmd-split-window.c | 2 +- layout.c | 141 +++++++++++++++++++++++++++++---------------- tmux.h | 10 ++-- 3 files changed, 96 insertions(+), 57 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 1d8fa08d..59239ee8 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -105,7 +105,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) flags |= SPAWN_EMPTY; if (is_floating) - lc = layout_get_floating_cell(item, args, w, wp, lc, &cause); + lc = layout_get_floating_cell(item, args, w, wp, &cause); else lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause); if (cause != NULL) { diff --git a/layout.c b/layout.c index 5cdb4134..c5b457ff 100644 --- a/layout.c +++ b/layout.c @@ -46,8 +46,6 @@ static int layout_set_size_check(struct window *, struct layout_cell *, enum layout_type, int); static void layout_resize_child_cells(struct window *, struct layout_cell *); -void layout_redistribute_cells(struct window *, struct layout_cell *, - enum layout_type); /* Create a new layout cell. */ struct layout_cell * @@ -252,6 +250,8 @@ layout_fix_offsets1(struct layout_cell *lc) if (lc->type == LAYOUT_LEFTRIGHT) { xoff = lc->xoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->flags & LAYOUT_CELL_FLOATING) + continue; lcchild->xoff = xoff; lcchild->yoff = lc->yoff; if (lcchild->type != LAYOUT_WINDOWPANE) @@ -261,6 +261,8 @@ layout_fix_offsets1(struct layout_cell *lc) } else { yoff = lc->yoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->flags & LAYOUT_CELL_FLOATING) + continue; lcchild->xoff = lc->xoff; lcchild->yoff = yoff; if (lcchild->type != LAYOUT_WINDOWPANE) @@ -276,6 +278,10 @@ layout_fix_offsets(struct window *w) { struct layout_cell *lc = w->layout_root; + /* Root consists of a single floating cell */ + if (lc->flags & LAYOUT_CELL_FLOATING) + return; + lc->xoff = 0; lc->yoff = 0; @@ -484,8 +490,11 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, /* Child cell runs in a different direction. */ if (lc->type != type) { - TAILQ_FOREACH(lcchild, &lc->cells, entry) + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->flags & LAYOUT_CELL_FLOATING) + continue; layout_resize_adjust(w, lcchild, type, change); + } return; } @@ -497,6 +506,8 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (change == 0) break; + if (lcchild->flags & LAYOUT_CELL_FLOATING) + continue; if (change > 0) { layout_resize_adjust(w, lcchild, type, 1); change--; @@ -515,30 +526,33 @@ void layout_destroy_cell(struct window *w, struct layout_cell *lc, struct layout_cell **lcroot) { - struct layout_cell *lcother, *lcparent; + struct layout_cell *lcother = NULL, *lcparent; - /* - * If no parent, this is either a floating pane or the last - * pane so window close is imminent and there is no need to - * resize anything. - */ + /* If no parent, this is the last pane in a window. */ lcparent = lc->parent; if (lcparent == NULL) { - if (lc->wp != NULL && !window_pane_is_floating(lc->wp)) + if (lc->wp != NULL) *lcroot = NULL; layout_free_cell(lc); return; } - /* Merge the space into the previous or next cell. */ - if (lc == TAILQ_FIRST(&lcparent->cells)) - lcother = TAILQ_NEXT(lc, entry); - else - lcother = TAILQ_PREV(lc, layout_cells, entry); - if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) - layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); - else if (lcother != NULL) - layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); + if (~lc->flags & LAYOUT_CELL_FLOATING) { + /* Merge the space into the previous or next cell. */ + if (lc == TAILQ_FIRST(&lcparent->cells)) + lcother = TAILQ_NEXT(lc, entry); + else + lcother = TAILQ_PREV(lc, layout_cells, entry); + } + if (lcother != NULL && (~lcother->flags & LAYOUT_CELL_FLOATING)) { + if (lcparent->type == LAYOUT_LEFTRIGHT) { + layout_resize_adjust(w, lcother, lcparent->type, + lc->sx + 1); + } else { + layout_resize_adjust(w, lcother, lcparent->type, + lc->sy + 1); + } + } /* Remove this from the parent's list. */ TAILQ_REMOVE(&lcparent->cells, lc, entry); @@ -554,8 +568,10 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, lc->parent = lcparent->parent; if (lc->parent == NULL) { - lc->xoff = 0; - lc->yoff = 0; + if (~lc->flags & LAYOUT_CELL_FLOATING) { + lc->xoff = 0; + lc->yoff = 0; + } *lcroot = lc; } else TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry); @@ -603,6 +619,8 @@ layout_resize(struct window *w, u_int sx, u_int sy) * out proportionately - this should leave the layout fitting the new * window size. */ + if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) + return; xchange = sx - lc->sx; xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); if (xchange < 0 && xchange < -xlimit) @@ -915,6 +933,8 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) count = 0; previous = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->flags & LAYOUT_CELL_FLOATING) + continue; count++; if (lc->type == LAYOUT_LEFTRIGHT) previous += lcchild->sx; @@ -933,6 +953,8 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) /* Resize children into the new size. */ idx = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->flags & LAYOUT_CELL_FLOATING) + continue; if (lc->type == LAYOUT_TOPBOTTOM) { lcchild->sx = lc->sx; lcchild->xoff = lc->xoff; @@ -941,9 +963,10 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) lc->type, lc->sx, count - idx, available); available -= (lcchild->sx + 1); } - if (lc->type == LAYOUT_LEFTRIGHT) + if (lc->type == LAYOUT_LEFTRIGHT) { lcchild->sy = lc->sy; - else { + lcchild->yoff = lc->yoff; + } else { lcchild->sy = layout_new_pane_size(w, previous, lcchild, lc->type, lc->sy, count - idx, available); available -= (lcchild->sy + 1); @@ -1132,6 +1155,39 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, return (lcnew); } +/* + * Creates a cell for a new floating pane. This must be followed by + * layout_assign_pane before much else happens! + */ +struct layout_cell * +layout_floating_pane(struct window *w, u_int sx, u_int sy, int ox, int oy) +{ + struct layout_cell *lc = w->layout_root, *lcnew, *lcparent; + + if (lc->type == LAYOUT_WINDOWPANE) { + /* + * Adding a pane to a root that doesn't have a container. Must + * create and insert a new root. + */ + lcparent = layout_create_cell(NULL); + layout_make_node(lcparent, LAYOUT_TOPBOTTOM); + layout_set_size(lcparent, w->sx, w->sy, 0, 0); + w->layout_root = lcparent; + + /* Insert the old cell. */ + lc->parent = lcparent; + TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry); + } else + lcparent = w->layout_root; + + lcnew = layout_create_cell(lcparent); + TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry); + lcnew->flags |= LAYOUT_CELL_FLOATING; + layout_set_size(lcnew, sx, sy, ox, oy); + + return (lcnew); +} + /* Destroy the cell associated with a pane. */ void layout_close_pane(struct window_pane *wp) @@ -1143,6 +1199,7 @@ layout_close_pane(struct window_pane *wp) /* Remove the cell. */ layout_destroy_cell(w, wp->layout_cell, &w->layout_root); + wp->layout_cell = NULL; /* Fix pane offsets and sizes. */ if (w->layout_root != NULL) { @@ -1299,49 +1356,37 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, /* Get a new floating cell. */ struct layout_cell * layout_get_floating_cell(struct cmdq_item *item, struct args *args, - struct window *w, __unused struct window_pane *wp, struct layout_cell *lc, - char **cause) + struct window *w, __unused struct window_pane *wp, char **cause) { - u_int sx, sy, ox, oy; - int new = 0; - - if (lc == NULL) { - lc = layout_create_cell(NULL); - new = 1; - } - - sx = lc->sx; sy = lc->sy; - ox = lc->xoff; oy = lc->yoff; + struct layout_cell *lcnew; + u_int sx = w->sx / 2, sy = w->sy / 4; + int ox = INT_MAX, oy = INT_MAX; if (args_has(args, 'x')) { sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, item, cause); if (*cause != NULL) - goto error; + return (NULL); } if (args_has(args, 'y')) { sy = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, item, cause); if (*cause != NULL) - goto error; + return (NULL); } if (args_has(args, 'X')) { ox = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, item, cause); if (*cause != NULL) - goto error; + return (NULL); } if (args_has(args, 'Y')) { oy = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, item, cause); if (*cause != NULL) - goto error; + return (NULL); } - if (sx == UINT_MAX) - sx = w->sx / 2; - if (sy == UINT_MAX) - sy = w->sy / 4; if (ox == INT_MAX) { if (w->last_new_pane_x == 0) ox = 4; @@ -1362,13 +1407,7 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, } w->last_new_pane_y = oy; } - layout_set_size(lc, sx, sy, ox, oy); - lc->flags |= LAYOUT_CELL_FLOATING; - return (lc); - -error: - if (new) - layout_destroy_cell(w, lc, &w->layout_root); - return (NULL); + lcnew = layout_floating_pane(w, sx, sy, ox, oy); + return (lcnew); } diff --git a/tmux.h b/tmux.h index 524748ae..7f0e438a 100644 --- a/tmux.h +++ b/tmux.h @@ -1235,7 +1235,7 @@ struct window_pane { #define PANE_FOCUSED 0x4 #define PANE_VISITED 0x8 #define PANE_ZOOMED 0x10 -/* unused 0x20 */ +/* 0x20 unused */ #define PANE_INPUTOFF 0x40 #define PANE_CHANGED 0x80 #define PANE_EXITED 0x100 @@ -1439,8 +1439,7 @@ TAILQ_HEAD(layout_cells, layout_cell); struct layout_cell { enum layout_type type; -/* unused 0x1 */ -#define LAYOUT_CELL_FLOATING 0x2 +#define LAYOUT_CELL_FLOATING 0x1 int flags; struct layout_cell *parent; @@ -3481,12 +3480,13 @@ void layout_assign_pane(struct layout_cell *, struct window_pane *, int); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, int, int); +struct layout_cell *layout_floating_pane(struct window *, u_int, u_int, int, + int); void layout_close_pane(struct window_pane *); int layout_spread_cell(struct window *, struct layout_cell *); void layout_spread_out(struct window_pane *); struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *, - struct window *, struct window_pane *, struct layout_cell *, - char **); + struct window *, struct window_pane *, char **); struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *, struct window *, struct window_pane *, int, char **); From 3a72f3beb838edcb910c94f19d3d8ce4a1df89d3 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 6 Jun 2026 17:35:30 +0000 Subject: [PATCH 03/27] Use correct X position for visible range checks in screen_write_fast_copy, GitHub issue 5164 from Barrett Ruth. --- screen-write.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/screen-write.c b/screen-write.c index 0db8dc29..47d6ebc9 100644 --- a/screen-write.c +++ b/screen-write.c @@ -624,21 +624,23 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, struct grid_line *gl, *sgl; struct grid_cell gc; u_int xx, yy, cx = s->cx, cy = s->cy; - int yoff = 0; + int xoff = 0, yoff = 0; struct visible_ranges *r; if (nx == 0 || ny == 0) return; - if (wp != NULL) + if (wp != NULL) { + xoff = wp->xoff; yoff = wp->yoff; + } for (yy = py; yy < py + ny; yy++) { if (yy >= gd->hsize + gd->sy) break; s->cx = cx; screen_write_initctx(ctx, &ttyctx, 0, 0); - r = screen_redraw_get_visible_ranges(wp, px, s->cy + yoff, nx, - NULL); + r = screen_redraw_get_visible_ranges(wp, xoff + s->cx, + s->cy + yoff, nx, NULL); for (xx = px; xx < px + nx; xx++) { gl = grid_get_line(gd, yy); sgl = grid_get_line(s->grid, s->cy); @@ -650,7 +652,7 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, break; grid_view_set_cell(s->grid, s->cx, s->cy, &gc); - if (!screen_redraw_is_visible(r, px)) + if (!screen_redraw_is_visible(r, xoff + s->cx)) break; ttyctx.cell = &gc; ttyctx.flags &= (TTY_CTX_OVERLAY_SYNC|TTY_CTX_SYNC); From 4063f968c620079f62892f8b76d53fdfd1af8c0d Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 08:22:58 +0000 Subject: [PATCH 04/27] Do not allow swapping floating panes for the moment. --- cmd-swap-pane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 24ed0b9d..48785c92 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -79,7 +79,7 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) if (src_wp == dst_wp) goto out; - if (window_pane_is_floating(src_wp) && + if (window_pane_is_floating(src_wp) || window_pane_is_floating(dst_wp)) { cmdq_error(item, "cannot swap floating panes"); return (CMD_RETURN_ERROR); From c9e22ab67bd7fe89ebb767215f2e671e72573e00 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 08:25:59 +0000 Subject: [PATCH 05/27] Enable floating panes. These are panes which sit above the layout ("tiled panes") like popups but unlike popups are not modal and behave like panes (so the same escape sequence support). Floating panes are created with the "new-pane" command, bound to * by default. 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. This is the first step and many obvious features are not yet complete (notably the ability to swap floating panes, resize them using resize-pane, change them between floating and tiles, and restore custom layouts with floating panes). Mostly written by Michael Grant with help from Dane Jensen. --- cmd-split-window.c | 2 +- key-bindings.c | 1 + tmux.1 | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 59239ee8..56616591 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -91,7 +91,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) u_int count = args_count(args); if (cmd_get_entry(self) == &cmd_new_pane_entry) - is_floating = 0; /* !args_has(args, 'L'); */ + is_floating = !args_has(args, 'L'); else is_floating = 0; input = (args_has(args, 'I') && count == 0); diff --git a/key-bindings.c b/key-bindings.c index 4e145a79..8b97c5f0 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -362,6 +362,7 @@ key_bindings_init(void) "bind -N 'Split window horizontally' % { split-window -h }", "bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }", "bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }", + "bind -N 'New floating pane' * { new-pane }", "bind -N 'Switch to previous client' ( { switch-client -p }", "bind -N 'Switch to next client' ) { switch-client -n }", "bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }", diff --git a/tmux.1 b/tmux.1 index 223580a1..c23930ce 100644 --- a/tmux.1 +++ b/tmux.1 @@ -296,6 +296,8 @@ Prompt for a window index to select. Switch the attached client to the previous session. .It \&) Switch the attached client to the next session. +.It * +Create a new floating pane. .It , Rename the current window. .It \- From 5744021ac96b14ec4dc92753973a1c34b8870649 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 09:54:25 +0000 Subject: [PATCH 06/27] Allow floating panes to be created partially off the window, based on a change from Michael Grant. --- cmd-join-pane.c | 2 +- cmd-split-window.c | 2 +- layout.c | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index f154a18b..f4fb2ab7 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -94,7 +94,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) lc = layout_get_tiled_cell(item, args, dst_w, dst_wp, flags, &cause); if (cause != NULL) { - cmdq_error(item, "%s", cause); + cmdq_error(item, "size or position %s", cause); free(cause); return (CMD_RETURN_ERROR); } diff --git a/cmd-split-window.c b/cmd-split-window.c index 56616591..1408cbe0 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -109,7 +109,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) else lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause); if (cause != NULL) { - cmdq_error(item, "%s", cause); + cmdq_error(item, "size or position %s", cause); free(cause); return (CMD_RETURN_ERROR); } diff --git a/layout.c b/layout.c index c5b457ff..28c66f1e 100644 --- a/layout.c +++ b/layout.c @@ -1359,30 +1359,30 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, struct window *w, __unused struct window_pane *wp, char **cause) { struct layout_cell *lcnew; - u_int sx = w->sx / 2, sy = w->sy / 4; + int sx = w->sx / 2, sy = w->sy / 4; int ox = INT_MAX, oy = INT_MAX; if (args_has(args, 'x')) { - sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, + sx = args_percentage_and_expand(args, 'x', 0, w->sx - 1, w->sx, item, cause); if (*cause != NULL) return (NULL); } if (args_has(args, 'y')) { - sy = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, + sy = args_percentage_and_expand(args, 'y', 0, w->sy - 1, w->sy, item, cause); if (*cause != NULL) return (NULL); } if (args_has(args, 'X')) { - ox = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, - item, cause); + ox = args_percentage_and_expand(args, 'X', -sx, w->sx, + w->sx, item, cause); if (*cause != NULL) return (NULL); } if (args_has(args, 'Y')) { - oy = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, - item, cause); + oy = args_percentage_and_expand(args, 'Y', -sy, w->sy, + w->sy, item, cause); if (*cause != NULL) return (NULL); } From 3d9e6bf454bc1904d94f26937fad6164571fd3a9 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 7 Jun 2026 12:00:57 +0100 Subject: [PATCH 07/27] Update CHANGES for 3.7. --- CHANGES | 326 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) diff --git a/CHANGES b/CHANGES index cf125fd9..7bbd74b6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,329 @@ +CHANGES FROM 3.6b TO 3.7 + +* Add floating panes. These are panes which sit above the layout ("tiled + panes") like popups but unlike popups are not modal and behave like panes (so + the same escape sequence support). Floating panes are created with the + new-pane command, bound to * by default. + + 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 + panes (notably the ability to swap floating panes, resize them using + resize-pane, change them between floating and tiles, and restore custom + layouts with floating panes). + + Mostly written by Michael Grant with help from Dane Jensen; testing and fixes + from others. + +* Allow run-shell arguments after a shell command to be expanded as #{1}, #{2} + and so on (from Rasmus Thystrup Karstensen in issue 5121). + +* Tighten up read-only checks on attach-session, detach-client and + switch-client so that a user should be able to only detach their own client + (reported by John Walker). + +* Increase escape delay if the buffer contains a partial paste end, fixes + issues with at least Windows Terminal (from jing dot empty at gmail.com issue + 5088). + +* When mode-keys is set to vi, do not allow the cursor to go into the invisible + extra cell to the right of the visible text; this is closer to what vi(1) + does (from Max Vim in issue 5070). + +* Add a five second limit on pasting for terminals which mysteriously lose the + end sequence if the paste is too big (that is, Terminal.app) (reported by + Garri Djavadyan in issue 4527). + +* Show file open errors more sensibly (reported by Meriel Luna Mittelbach in + issue 5081). + +* Update supported features list for Foot terminal (from Meriel Luna Mittelbach + in issue 5079). + +* Turn off the "is this a paste" guessing if the terminal supports bracket + pasting instead (issue 5031). + +* Check FIONREAD for all panes not just piped panes. + +* Add emacs-style recentre-top-bottom command to copy mode (issue 5053 from + sinyax75 at gmail dot com). + +* Allow the indicator in tree mode to be customized by two new options: + tree-mode-preview-format and tree-mode-preview-style. + +* Fix control client hang on exit after toggling no-output (issue 5049 from + Aaron Campbell). Also various other control mode fixes. + +* Add support for line numbers in copy mode. There is a new + copy-mode-line-numbers option which may be set to off, default (tmux's normal + line numbering where 0 is the top visible line), absolute (first line in + history is 1), relative (relative to the cursor) and hybrid (current line is + absolute, others relative). Also adds copy-mode-line-number-style and + copy-mode-current-line-number-style to set the style of the line numbers. + When copy mode is entered with the mouse, line numbers stay off. From Leo + Henon in issue 5025. + +* Make C-[ have the same bindings as Escape for terminals with extended + keys where they are different (issue 5035 from Eric Nicolas). + +* Sanitize paste buffer names in paste_set and paste_rename (issue 5032 from + Barrett Ruth). + +* Do not hang in run-shell when job_run fails (from Barrett Ruth in issue + 5037). + +* Add ability to forward progress bar to outside terminal (issue 4972 + from Eric Dorland). + +* Translate keypad keys to text in prompt input (from Barrett Ruth in issue + 4996). + +* Sanitize pane titles and window and session names more consistently and + strictly, prevents C0 characters and other invisible characters causing + problems (reported by Chris Monardo in issue 4999). + +* Make clock visible on terminals without colours (from Manuel Einfalt in issue + 5001). + +* Add detach to default session menu (suggested by Przemyslaw Sztoch). + +* Include window format variables for pane notifications (issue 5007 from Saul + Nogueras). + +* Limit precision to 100 for formats to stop silly formats from running out of + memory, reported by z1281552865 at gmail dot com. Also various other similar + changes, mostly found by OSS-Fuzz. + +* Add WAYLAND_DISPLAY to default update-environment, issue 4965 from wgh at + torlan dot ru. + +* Add -C flag to command-prompt to match display-message -C (do not freeze + panes) (from Barrett Ruth in issue 4978). + +* Cache user from getpwuid because it can be very expensive on some + platforms (from Ben Maurer in issue 4973). + +* Add remain-on-exit key to keep pane around until a key is pressed (from + Michael Grant). + +* Add some new mouse ranges called "control0" to "control9" and use to add some + mouse controls to the pane state line (from Dane Jensen with some bits from + Michael Grant). + +* Handle OSC 9;4 progress bar sequence and store in format variables (from Eric + Dorland in issue 4954). + +* Correctly size buffer used for parsing clipboard sequences (from Michal + Majchrowicz). + +* Limit MSG_COMMAND argument to between 0 and 1000 to prevent a misbehaving + client from crashing the server (from Michal Majchrowicz). + +* Reorganize host keys are represented internally so they can be built more + easily (from Dane Jensen in issue 4953). + +* Add new fuzzers for command parsing, formats and styles (from David + Korczynski in issue 4957). Also fix various issues shown from these. + +* Add bracket_paste_flag format flag (from George Nachman in issue 4951). + +* Use \- for hyphens in tmux.1 to cause newer groff versions to render them + correctly (from Keith Thompson in issue 4948). + +* Various minor code improvements and fixes from Pavel Lavrukhin (issue 4936 + and others). + +* Use window options for cursor-style to avoid crash when no pane (from Arden + Packeer in issue 4942). + +* Fix issue where popup window gets overwritten by background updates (from + Conor Taylor in issue 4920). + +* Protect against overflow when scrollbar is off screen (from san65384 at gmail + dot com in issue 4933). + +* Copy hyperlinks when redrawing popup so they do not vanish (from Antoine + Gaudreau Simard in issue 4925). + +* Work around systemd killing panes early during system shutdown by creating + dependencies from the panes to the service which started tmux (issue 4926 + from Dmitry Torokhov). + +* Allow codepoint-widths to accept ranges (from san65384 at gmail dot com in + issue 4930). + +* Add a short builtin help text for each mode accessible with C-h (based on + code from Patrick Motard in issue 4751). + +* Draw message as one format, allowing prompts and messages to occupy only a + portion of the status bar, overlaying the normal status content rather than + replacing the entire line. A new message-format option now controls the + entire message (like status-format). The message-style option now need to + include "fill" in order to cover the whole width (the default has + "fill=yellow"). From Conor Taylor in issue 4861. + +* Add next/previous variables for windows in W: loop (from Conor Taylor in + issue 4856). + +* Various bug and memory leak fixes from Renaud Allard (issue 4916). + +* Add pane_pipe_pid with pipe file descriptor. + +* Make -c work with new-session -A (from Jody Frankowski in issue 4906). + +* Allow copy mode to work for readonly clients, except for copy commands (from + Dane Jensen). + +* Pass paste buffer through vis(3) when pasting to prevent buffers containing + for example the bracket end sequence causing issues, a new -S flag disables + (reported by Mason Davis). + +* Add sorting (-O flag) and a custom format (-F) to list-keys (from Dane Jensen + in issue 4845). + +* Add scroll-exit-on, scroll-exit-off, scroll-exit-toggle commands to copy mode + (from xcdnlgd at hotmail dot com in issue 4884). + +* Respond to DECRQM 2026 (from David Turnbull in issue 4887) and various others + (from Ayman Bagabas in issue 5118). + +* Fix various memory leaks reported by Huihui Huang (issue 4872). + +* Pass which clipboard is set through to the terminal (from Axel Lindskog in + issue 4858). + +* Reuse extended entry when clearing RGB cell, to prevent memory growth when + cells are repeatedly cleared (from Michael K Darling in issue 4862). + +* Do not write before buffer when parsing empty clipboard or palette replies, + or try to allocate zero bytes with an empty clipboard sequence (reported by + DongHan Kim). + +* Various bug fixes and code improvements from Conor Taylor (issue 4848). + +* Clear search counts when clearing marks in case of repeated search (reported + by Daniel Pereira in issue 4817). + +* Make OSC 52 work in popups (from gogongxt at 163 dot com in issue 4797). + +* Refresh copy mode when style changes (from Josh Cooper in issue 4830). + +* Make sorting code common and add -O for sorting to the list commands (from + Dane Jensen in issue 4813). + +* Do not treat cells as empty unless the background colour stays the same, + fixes invisible clock in clock mode (reported by Theo Buehler). + +* When history-limit is changed, apply to existing panes, not just new + ones (issue 4705). + +* Reevaluate menu and popup styles on each draw to allow them to change when + options change (from Josh Cooper in issues 4828 and 4829). + +* Handle theme keys earlier so they are processed even if a popup is open (from + Josh Cooper in issue 4827). + +* Fix window-size=latest not resizing on switch-client in session groups (from + Ilya Grigoriev in issue 4818). + +* Add -e flag to command-prompt to close if empty (from Dane Jensen in issue + 4812). + +* Correctly draw indicators when pane-border-indicators is set to both + (reported by Ilya Grigoriev in issue 4780). + +* Remember last pane or type of location for double and triple clicks and + correctly handle it changes between first and second or second and third + (issue 4795 from Shaobo Song). + +* Add paste to the default pane menu (issue 4763). + +* Reduce request timeout to 500 milliseconds to match the extended escape-time, + and discard palette requests if receiving a reply for a different index. + +* Extend escape timeout if there are active forwarded requests not just + tmux's own requests (issue 4793). + +* Correct redrawing of wide characters when overwritten (reported by Jake + Stewart in issue 4737). + +* If cannot find a terminator for palette responses, treat as a partial key not + complete (issue 4749). + +* Do not send theme unless it has changed, and do not send immediately when + updates are enabled (issue 5787). + +* Do not use ;;s in list-keys output as it is confusing and cannot be + parsed on input (from Patrick Motard in issue 4750). + +* Redraw pane borders when entering or leaving alternate screen (from Mike + Jonkmans in issue 4788). + +* Add focus-follows-mouse option (from Barry Wasdell in issue 4771). + +* Add selection_mode format variable for copy mode (from Mike Jonkmans in issue + 4773). + +* Add prompt-command-cursor-style (from Joshua Cooper in issue 4765). + +* With status-keys vi, move the cursor left by one when pressing Escape to + enter command mode, like vi (issue 4767 from Joshua Cooper). + +* Add {current}/{active} for -t for current window or active pane (from Manuel + Einfalt, issue 4766). + +* Add support for applications to use synchronized output mode (DECSET 2026) to + prevent screen tearing during rapid updates (from Chris Lloyd in issue 4744). + +* Do not set a default prompt cursor colour because some terminals (urxvt, st) + do not support the reset sequence (issue 4759). + +* Add a scroll-to-mouse command for copy mode to scroll to the mouse position + and bind to the scrollbar, brings the scrollbar keys into line with the other + mouse keys (from Michael Grant in issue 4731). + +* Bump the maximum number of SIXEL images to 20. + +* Fix key code for M-BSpace (issue 4717). + +* Fix calculation of scaled SIXEL size (from nincsnevem662 at gmail dot com in + issue 4739). Also various other fixes and improvements for SIXEL. + +* Fix a race between fork and pane_current_path, most noticeable on systems + where starting processes is slow (issue 4719). + +* Fix mouse position calculation on scrollbar with pane status line at the top + (issue 4738 from Michael Grant). + +* Do not read outside buffer if format is a single #, and do not loop forever + if UTF-8 is unfinished in a format (reported by Giorgi Kobakhia in issue + 4735). + +* Add a missing skin tone character (from Jake Stewart in issue 4736). + +* Allow UTF-8 characters to be combined in either order (reported by Jake + Stewart in issue 4726). + +* Do not show scrollbar when entering copy mode from a pane in the alternate + screen (issue 4728 from Michael Grant). + +* Fix the size calculation for left-right windows used to spread out cells + horizontally evenly (from Michael Grant in issue 4724). + +* Add a get-clipboard option which when enabled (the default is off) and a + clipboard is requested from a pane, requests it from the terminal and + forwards to the requesting pane; also remove the now-redundant + forward-to-pane ability from "refresh-client -l" (issue 4275). + +* Fix the noattr attribute in styles, used by the default mode-style (issue + 4713). + +* Do not remove TERM for commands run from config file (regression reported by + Dennis Eriksen). + +* Add seconds options for clock mode (from augustus7613 dot mail at pm dot me, + issue 4697; later improved by Joao Pedro in issue 4760). + CHANGES FROM 3.6a TO 3.6b * Remove images from the correct list when they are removed while in the From 306ee0eddb7403ce6cbb475905951067ea6ac7f8 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 13:15:28 +0000 Subject: [PATCH 08/27] Add -E to split-window to explicitly make an empty pane rather than requiring an empty command. --- cmd-split-window.c | 16 +++++++++++----- tmux.1 | 15 ++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 1408cbe0..8cae6b6f 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -39,7 +39,7 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL }, + .args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " @@ -57,7 +57,7 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:fF:hIkl:m:p:PR:s:S:t:vZ", 0, -1, NULL }, + .args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:vZ", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " @@ -84,7 +84,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *wp = target->wp, *new_wp; struct layout_cell *lc = NULL; struct cmd_find_state fs; - int input, is_floating, flags = 0; + int input, empty, is_floating, flags = 0; const char *template, *style; char *cause = NULL, *cp; struct args_value *av; @@ -94,14 +94,20 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) is_floating = !args_has(args, 'L'); else is_floating = 0; - input = (args_has(args, 'I') && count == 0); flags = is_floating ? SPAWN_FLOATING : 0; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) flags |= SPAWN_FULLSIZE; - if (input || (count == 1 && *args_string(args, 0) == '\0')) + + input = args_has(args, 'I'); + empty = (count == 1 && *args_string(args, 0) == '\0'); + if (!empty && (input || args_has(args, 'E'))) { + cmdq_error(item, "command cannot be given for empty pane"); + return (CMD_RETURN_ERROR); + } + if (input || empty || args_has(args, 'E')) flags |= SPAWN_EMPTY; if (is_floating) diff --git a/tmux.1 b/tmux.1 index c23930ce..17149692 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3377,15 +3377,16 @@ but also sets the option for this pane to .Ar message . .Pp -An empty -.Ar shell\-command -(\[aq]\[aq]) will create a pane with no command running in it. +.Fl E , +or an empty +.Ar shell\-command , +(\[aq]\[aq]) will create an empty pane with no command running in it; +.Ic display-message +.Fl I +can write to an empty pane. The .Fl I -flag (if -.Ar shell\-command -is not specified or empty) -will create an empty pane and forward any output from stdin to it. +flag will create an empty pane and forward any output from stdin to it. For example: .Bd -literal -offset indent $ make 2>&1|tmux splitw \-dI & From ef8e9df02f666acac7934262587bc69cb83cc10b Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 13:29:16 +0000 Subject: [PATCH 09/27] Tweak previous. --- cmd-split-window.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index 8cae6b6f..39770606 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -102,12 +102,17 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) flags |= SPAWN_FULLSIZE; input = args_has(args, 'I'); - empty = (count == 1 && *args_string(args, 0) == '\0'); - if (!empty && (input || args_has(args, 'E'))) { + if (input) + empty = 1; + else + empty = args_has(args, 'E'); + if (empty && + count != 0 && + (count != 1 || *args_string(args, 0) != '\0')) { cmdq_error(item, "command cannot be given for empty pane"); return (CMD_RETURN_ERROR); } - if (input || empty || args_has(args, 'E')) + if (empty) flags |= SPAWN_EMPTY; if (is_floating) From 0256ee77c2d3e7a1a7aec0890e8951fadb0835df Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 13:37:39 +0000 Subject: [PATCH 10/27] Add some additional environment variables needed for Wayland, from shbernal dot 01 at gmail dot com. --- options-table.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/options-table.c b/options-table.c index 0332fa07..42e835b7 100644 --- a/options-table.c +++ b/options-table.c @@ -1029,7 +1029,8 @@ const struct options_table_entry options_table[] = { .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "DISPLAY KRB5CCNAME MSYSTEM SSH_ASKPASS SSH_AUTH_SOCK " "SSH_AGENT_PID SSH_CONNECTION WAYLAND_DISPLAY " - "WINDOWID XAUTHORITY", + "WINDOWID XAUTHORITY XDG_CURRENT_DESKTOP " + "XDG_SESSION_DESKTOP XDG_SESSION_TYPE", .text = "List of environment variables to update in the session " "environment when a client is attached." }, From 343ee5ece1581a6d0e408d635004ecb485765d9e Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 13:51:42 +0000 Subject: [PATCH 11/27] Add -g flag to kill-session to kill all sessions in a group, GitHub issue 5157 from github at jiku dot jp. --- cmd-kill-session.c | 11 +++++++++-- tmux.1 | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd-kill-session.c b/cmd-kill-session.c index 19a8d495..7d3ae984 100644 --- a/cmd-kill-session.c +++ b/cmd-kill-session.c @@ -33,8 +33,8 @@ const struct cmd_entry cmd_kill_session_entry = { .name = "kill-session", .alias = NULL, - .args = { "aCt:", 0, 0, NULL }, - .usage = "[-aC] " CMD_TARGET_SESSION_USAGE, + .args = { "aCgt:", 0, 0, NULL }, + .usage = "[-aCg] " CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -48,6 +48,7 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); struct cmd_find_state *target = cmdq_get_target(item); struct session *s = target->s, *sloop, *stmp; + struct session_group *sg; struct winlink *wl; if (args_has(args, 'C')) { @@ -63,6 +64,12 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item) session_destroy(sloop, 1, __func__); } } + } else if (args_has(args, 'g') && + (sg = session_group_contains(s)) != NULL) { + TAILQ_FOREACH_SAFE(sloop, &sg->sessions, gentry, stmp) { + server_destroy_session(sloop); + session_destroy(sloop, 1, __func__); + } } else { server_destroy_session(s); session_destroy(s, 1, __func__); diff --git a/tmux.1 b/tmux.1 index 17149692..166fb680 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1202,7 +1202,7 @@ Kill the .Nm server and clients and destroy all sessions. .It Xo Ic kill\-session -.Op Fl aC +.Op Fl aCg .Op Fl t Ar target\-session .Xc Destroy the given session, closing any windows linked to it and no other @@ -1210,6 +1210,10 @@ sessions, and detaching all clients attached to it. If .Fl a is given, all sessions but the specified one is killed. +If +.Fl g +is given and the session is in a session group, all sessions in the group are +killed. The .Fl C flag clears alerts (bell, activity, or silence) in all windows linked to the From 3acd247f5d0c4372c001f5c023d0edc1ad4cb322 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 14:11:20 +0000 Subject: [PATCH 12/27] Redraw entire session when making a new pane. --- 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 39770606..aa90256d 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -221,7 +221,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) window_pop_zoom(wp->window); server_redraw_window(wp->window); } - server_status_session(s); + server_redraw_session(s); if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) From 0ec6870902e04c1970ec8d41d71a463e4e5d945d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 7 Jun 2026 15:24:53 +0100 Subject: [PATCH 13/27] Add to CHANGES. --- CHANGES | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 7bbd74b6..cb92e2ff 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,9 @@ CHANGES FROM 3.6b TO 3.7 * Allow run-shell arguments after a shell command to be expanded as #{1}, #{2} and so on (from Rasmus Thystrup Karstensen in issue 5121). +* Add -g to kill-session to kill all sessions in a session group (issue 5157 + from github at jiku dot jp). + * Tighten up read-only checks on attach-session, detach-client and switch-client so that a user should be able to only detach their own client (reported by John Walker). @@ -94,8 +97,8 @@ CHANGES FROM 3.6b TO 3.7 memory, reported by z1281552865 at gmail dot com. Also various other similar changes, mostly found by OSS-Fuzz. -* Add WAYLAND_DISPLAY to default update-environment, issue 4965 from wgh at - torlan dot ru. +* Add WAYLAND_DISPLAY to default update-environment (issue 4965). Also some + additional XDG_* variables (issue 5169). * Add -C flag to command-prompt to match display-message -C (do not freeze panes) (from Barrett Ruth in issue 4978). From 42f3e7f7ce3b8ef480cc168eb7177f6282c1e626 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 14:41:27 +0000 Subject: [PATCH 14/27] Clear entire lines when removing from history or freeing. --- grid.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/grid.c b/grid.c index 0d1db844..20deb936 100644 --- a/grid.c +++ b/grid.c @@ -285,9 +285,8 @@ static void grid_free_line(struct grid *gd, u_int py) { free(gd->linedata[py].celldata); - gd->linedata[py].celldata = NULL; free(gd->linedata[py].extddata); - gd->linedata[py].extddata = NULL; + memset(&gd->linedata[py], 0, sizeof gd->linedata[py]); } /* Free several lines. */ @@ -332,9 +331,7 @@ void grid_destroy(struct grid *gd) { grid_free_lines(gd, 0, gd->hsize + gd->sy); - free(gd->linedata); - free(gd); } @@ -414,12 +411,14 @@ grid_collect_history(struct grid *gd, int all) void grid_remove_history(struct grid *gd, u_int ny) { - u_int yy; + u_int yy, start; if (ny > gd->hsize) return; + start = gd->hsize + gd->sy - ny; for (yy = 0; yy < ny; yy++) - grid_free_line(gd, gd->hsize + gd->sy - 1 - yy); + grid_free_line(gd, start + yy); + memset(&gd->linedata[start], 0, ny * sizeof *gd->linedata); gd->hsize -= ny; } From e0ebae448192dfca959057697e83b3d29a03269e Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 7 Jun 2026 16:11:38 +0100 Subject: [PATCH 15/27] Fix a couple of tests and add -f/dev/null to them all. --- regress/am-terminal.sh | 4 ++-- regress/border-arrows.sh | 4 ++-- regress/capture-pane-hyperlink.sh | 2 +- regress/capture-pane-sgr0.sh | 2 +- regress/combine-test.sh | 2 +- regress/command-order.sh | 2 +- regress/conf-syntax.sh | 2 +- regress/control-client-sanity.sh | 2 +- regress/control-client-size.sh | 2 +- regress/copy-mode-test-emacs.sh | 2 +- regress/cursor-test1.sh | 2 +- regress/cursor-test2.sh | 2 +- regress/cursor-test3.sh | 2 +- regress/cursor-test4.sh | 2 +- regress/decrqm-sync.sh | 29 +++++++++++++--------------- regress/format-strings.sh | 2 +- regress/has-session-return.sh | 2 +- regress/if-shell-TERM.sh | 2 +- regress/if-shell-error.sh | 2 +- regress/if-shell-nested.sh | 2 +- regress/input-keys.sh | 2 +- regress/kill-session-process-exit.sh | 2 +- regress/new-session-base-index.sh | 2 +- regress/new-session-command.sh | 2 +- regress/new-session-environment.sh | 4 ++-- regress/new-session-no-client.sh | 2 +- regress/new-session-size.sh | 2 +- regress/new-window-command.sh | 2 +- regress/osc-11colours.sh | 2 +- regress/run-shell-output.sh | 2 +- regress/session-group-resize.sh | 2 +- regress/style-trim.sh | 4 ++-- regress/tty-keys.sh | 4 ++-- regress/utf8-test.sh | 2 +- 34 files changed, 51 insertions(+), 54 deletions(-) diff --git a/regress/am-terminal.sh b/regress/am-terminal.sh index 94033468..bf1cabfe 100644 --- a/regress/am-terminal.sh +++ b/regress/am-terminal.sh @@ -4,9 +4,9 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null -TMUX2="$TEST_TMUX -Ltest2" +TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" $TMUX2 kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/border-arrows.sh b/regress/border-arrows.sh index 7e7bffd0..ea571b05 100644 --- a/regress/border-arrows.sh +++ b/regress/border-arrows.sh @@ -11,9 +11,9 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null -TMUX_OUTER="$TEST_TMUX -Ltest2" +TMUX_OUTER="$TEST_TMUX -Ltest2 -f/dev/null" $TMUX_OUTER kill-server 2>/dev/null trap "$TMUX kill-server 2>/dev/null; $TMUX_OUTER kill-server 2>/dev/null" 0 1 15 diff --git a/regress/capture-pane-hyperlink.sh b/regress/capture-pane-hyperlink.sh index bcd053b4..80092e7f 100644 --- a/regress/capture-pane-hyperlink.sh +++ b/regress/capture-pane-hyperlink.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" TMP=$(mktemp) TMP2=$(mktemp) trap "rm -f $TMP $TMP2" 0 1 15 diff --git a/regress/capture-pane-sgr0.sh b/regress/capture-pane-sgr0.sh index 5d21ccc4..7c9c3231 100644 --- a/regress/capture-pane-sgr0.sh +++ b/regress/capture-pane-sgr0.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/combine-test.sh b/regress/combine-test.sh index 14fa709b..a4d4f368 100644 --- a/regress/combine-test.sh +++ b/regress/combine-test.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/command-order.sh b/regress/command-order.sh index 04046f0d..775768a6 100644 --- a/regress/command-order.sh +++ b/regress/command-order.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/conf-syntax.sh b/regress/conf-syntax.sh index 96d35140..49b9710b 100644 --- a/regress/conf-syntax.sh +++ b/regress/conf-syntax.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null for i in conf/*.conf; do diff --git a/regress/control-client-sanity.sh b/regress/control-client-sanity.sh index 48ffd0ee..f6c36e1b 100644 --- a/regress/control-client-sanity.sh +++ b/regress/control-client-sanity.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/control-client-size.sh b/regress/control-client-size.sh index dc275e52..7d5e1dc2 100644 --- a/regress/control-client-size.sh +++ b/regress/control-client-size.sh @@ -8,7 +8,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/copy-mode-test-emacs.sh b/regress/copy-mode-test-emacs.sh index ad863030..a5b69c67 100644 --- a/regress/copy-mode-test-emacs.sh +++ b/regress/copy-mode-test-emacs.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -f/dev/null -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null $TMUX new -d -x40 -y10 \ diff --git a/regress/cursor-test1.sh b/regress/cursor-test1.sh index 2dc20539..73a336be 100644 --- a/regress/cursor-test1.sh +++ b/regress/cursor-test1.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -f/dev/null -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/cursor-test2.sh b/regress/cursor-test2.sh index 9791f567..3de80fda 100644 --- a/regress/cursor-test2.sh +++ b/regress/cursor-test2.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/cursor-test3.sh b/regress/cursor-test3.sh index 8bb4bd6f..e4881c79 100644 --- a/regress/cursor-test3.sh +++ b/regress/cursor-test3.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/cursor-test4.sh b/regress/cursor-test4.sh index 2bf1bb0e..8f4dfa5c 100644 --- a/regress/cursor-test4.sh +++ b/regress/cursor-test4.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/decrqm-sync.sh b/regress/decrqm-sync.sh index bca57dca..7f8c3f61 100644 --- a/regress/decrqm-sync.sh +++ b/regress/decrqm-sync.sh @@ -9,16 +9,12 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null sleep 1 TMP=$(mktemp) -TMP2=$(mktemp) -TMP3=$(mktemp) -TMP4=$(mktemp) -TMP5=$(mktemp) -trap "rm -f $TMP $TMP2 $TMP3 $TMP4 $TMP5; $TMUX kill-server 2>/dev/null" 0 1 15 +trap "rm -f $TMP; $TMUX kill-server 2>/dev/null" 0 1 15 $TMUX -f/dev/null new -d -x80 -y24 || exit 1 sleep 1 @@ -35,13 +31,14 @@ query_decrpm() { _outfile=$1 _mode=$2 _setup=$3 + _n=$4 $TMUX respawnw -k -t:0 -- sh -c " exec 2>/dev/null stty raw -echo ${_setup:+printf '$_setup'; sleep 0.2} printf '\033[%s\$p' "$_mode" - dd bs=1 count=11 2>/dev/null | cat -v > $_outfile + dd bs=1 count=$_n 2>/dev/null | cat -v > $_outfile sleep 0.2 " || exit 1 sleep 2 @@ -50,7 +47,7 @@ query_decrpm() { # ------------------------------------------------------------------ # Test 1: mode 2026 should be reset by default (Ps=2) # ------------------------------------------------------------------ -query_decrpm "$TMP" "?2026" +query_decrpm "$TMP" "?2026" '' 11 actual=$(cat "$TMP") expected='^[[?2026;2$y' @@ -67,9 +64,9 @@ fi # ------------------------------------------------------------------ # Test 2: set mode 2026 (SM ?2026), then query (expect Ps=1) # ------------------------------------------------------------------ -query_decrpm "$TMP2" "?2026" '\033[?2026h' +query_decrpm "$TMP" "?2026" '\033[?2026h' 11 -actual=$(cat "$TMP2") +actual=$(cat "$TMP") expected='^[[?2026;1$y' if [ "$actual" = "$expected" ]; then @@ -84,9 +81,9 @@ fi # ------------------------------------------------------------------ # Test 3: mode 25 should return current value # ------------------------------------------------------------------ -query_decrpm "$TMP3" "?25" '\033[?25l' +query_decrpm "$TMP" "?25" '\033[?25l' 9 -actual=$(cat "$TMP3") +actual=$(cat "$TMP") expected='^[[?25;2$y' if [ "$actual" = "$expected" ]; then @@ -101,9 +98,9 @@ fi # ------------------------------------------------------------------ # Test 4: mode ?9999 should return not recognized # ------------------------------------------------------------------ -query_decrpm "$TMP4" "?9999" '\033[?9999h' +query_decrpm "$TMP" "?9999" '\033[?9999h' 11 -actual=$(cat "$TMP4") +actual=$(cat "$TMP") expected='^[[?9999;0$y' if [ "$actual" = "$expected" ]; then @@ -122,9 +119,9 @@ exit $exit_status # ------------------------------------------------------------------ # Test 5: mode 4 is reset by default # ------------------------------------------------------------------ -query_decrpm "$TMP5" "4" '\033[4h' +query_decrpm "$TMP" "4" '\033[4h' 11 -actual=$(cat "$TMP5") +actual=$(cat "$TMP") expected='^[[4;1$y' if [ "$actual" = "$expected" ]; then diff --git a/regress/format-strings.sh b/regress/format-strings.sh index e9bf8b0f..8d192ed2 100644 --- a/regress/format-strings.sh +++ b/regress/format-strings.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" # test_format $format $expected_result test_format() diff --git a/regress/has-session-return.sh b/regress/has-session-return.sh index 5cc30f05..a7d76ad8 100644 --- a/regress/has-session-return.sh +++ b/regress/has-session-return.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null $TMUX -f/dev/null has -tfoo /dev/null && exit 1 diff --git a/regress/if-shell-TERM.sh b/regress/if-shell-TERM.sh index 5967a58f..21846fd1 100644 --- a/regress/if-shell-TERM.sh +++ b/regress/if-shell-TERM.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/if-shell-error.sh b/regress/if-shell-error.sh index 271e160b..2eab8f7c 100644 --- a/regress/if-shell-error.sh +++ b/regress/if-shell-error.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/if-shell-nested.sh b/regress/if-shell-nested.sh index 976c6738..434775c4 100644 --- a/regress/if-shell-nested.sh +++ b/regress/if-shell-nested.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/input-keys.sh b/regress/input-keys.sh index 96d2d5e8..e2fc41f5 100644 --- a/regress/input-keys.sh +++ b/regress/input-keys.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null sleep 1 $TMUX -f/dev/null new -x20 -y2 -d \; set -g escape-time 0 || exit 1 diff --git a/regress/kill-session-process-exit.sh b/regress/kill-session-process-exit.sh index 82cd7412..04617ca2 100644 --- a/regress/kill-session-process-exit.sh +++ b/regress/kill-session-process-exit.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null sleep 1 diff --git a/regress/new-session-base-index.sh b/regress/new-session-base-index.sh index 337037c3..bb6dd594 100644 --- a/regress/new-session-base-index.sh +++ b/regress/new-session-base-index.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/new-session-command.sh b/regress/new-session-command.sh index 8dec322a..b2fc91db 100644 --- a/regress/new-session-command.sh +++ b/regress/new-session-command.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/new-session-environment.sh b/regress/new-session-environment.sh index 84062aba..e5404e67 100644 --- a/regress/new-session-environment.sh +++ b/regress/new-session-environment.sh @@ -5,14 +5,14 @@ PATH=/bin:/usr/bin [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TERM=$($TMUX start \; show -gv default-terminal) TMP=$(mktemp) OUT=$(mktemp) SCRIPT=$(mktemp) -#trap "rm -f $TMP $OUT $SCRIPT" 0 1 15 +trap "rm -f $TMP $OUT $SCRIPT" 0 1 15 cat <$SCRIPT ( diff --git a/regress/new-session-no-client.sh b/regress/new-session-no-client.sh index 88c064cd..84fddaec 100644 --- a/regress/new-session-no-client.sh +++ b/regress/new-session-no-client.sh @@ -8,7 +8,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/new-session-size.sh b/regress/new-session-size.sh index 89fc580d..029bd6ee 100644 --- a/regress/new-session-size.sh +++ b/regress/new-session-size.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/new-window-command.sh b/regress/new-window-command.sh index b83376e4..18349115 100644 --- a/regress/new-window-command.sh +++ b/regress/new-window-command.sh @@ -6,7 +6,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/osc-11colours.sh b/regress/osc-11colours.sh index a049a49a..2fba76d8 100644 --- a/regress/osc-11colours.sh +++ b/regress/osc-11colours.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null $TMUX new -d diff --git a/regress/run-shell-output.sh b/regress/run-shell-output.sh index fd9634d6..3b44504c 100644 --- a/regress/run-shell-output.sh +++ b/regress/run-shell-output.sh @@ -7,7 +7,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/session-group-resize.sh b/regress/session-group-resize.sh index 84eb31a4..28e7a85e 100755 --- a/regress/session-group-resize.sh +++ b/regress/session-group-resize.sh @@ -10,7 +10,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null TMP1=$(mktemp) diff --git a/regress/style-trim.sh b/regress/style-trim.sh index c90d647d..5b03482d 100644 --- a/regress/style-trim.sh +++ b/regress/style-trim.sh @@ -17,9 +17,9 @@ if command -v bash >/dev/null 2>&1; then fi [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null -TMUX2="$TEST_TMUX -Ltest2" +TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" $TMUX2 kill-server 2>/dev/null $TMUX2 -f/dev/null new -d "$TMUX -f/dev/null new -- $shell" diff --git a/regress/tty-keys.sh b/regress/tty-keys.sh index 0a1fe6bf..849f6f33 100644 --- a/regress/tty-keys.sh +++ b/regress/tty-keys.sh @@ -4,9 +4,9 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" $TMUX kill-server 2>/dev/null -TMUX2="$TEST_TMUX -Ltest2" +TMUX2="$TEST_TMUX -Ltest2 -f/dev/null" $TMUX2 kill-server 2>/dev/null TMP=$(mktemp) diff --git a/regress/utf8-test.sh b/regress/utf8-test.sh index 3b2b22c5..b3cf41bd 100644 --- a/regress/utf8-test.sh +++ b/regress/utf8-test.sh @@ -4,7 +4,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest -f/dev/null" TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX kill-server 2>/dev/null From a134c43f4df1ec5f14382b8d46556406473b0359 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 7 Jun 2026 16:15:32 +0100 Subject: [PATCH 16/27] Update test result. --- regress/utf8-test.result | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regress/utf8-test.result b/regress/utf8-test.result index f15d049d..e700cb17 100644 --- a/regress/utf8-test.result +++ b/regress/utf8-test.result @@ -90,7 +90,7 @@ 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 = "" | +2.3.5 U-00110000 = f4 90 80 80 = "�" | | 3 Malformed sequences | | From bbea0ef439e2191d62760fb5810119c377d53e3a Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 7 Jun 2026 17:22:14 +0100 Subject: [PATCH 17/27] Update copy mode vi test, from Max Vim. --- regress/copy-mode-test-vi.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/regress/copy-mode-test-vi.sh b/regress/copy-mode-test-vi.sh index 911a1f1b..2d6bfc28 100644 --- a/regress/copy-mode-test-vi.sh +++ b/regress/copy-mode-test-vi.sh @@ -34,14 +34,17 @@ $TMUX send-keys -X previous-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "line" ] || exit 1 -# Test that `next-word-end` wraps around indented line breaks. +# Test that `next-word-end` stops at the end of the line. $TMUX send-keys -X next-word $TMUX send-keys -X next-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word-end $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection -[ "$($TMUX show-buffer)" = "$(printf "words\n\tIndented")" ] || exit 1 +[ "$($TMUX show-buffer)" = "words" ] || exit 1 + +# Move to the next word for the following tests. +$TMUX send-keys -X next-word # Test that `next-word` wraps around un-indented line breaks. $TMUX send-keys -X next-word From ae5e0e8c050cc7bf3ab9b8cd8a373f1eab5ff053 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 16:23:05 +0000 Subject: [PATCH 18/27] Add -L to show line numbers with capture-pane and -F to show line flags. --- cmd-capture-pane.c | 45 +++++++++++++++++++++++++++++++++++++++------ tmux.1 | 16 +++++++++++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index d42adf88..0f37ad5d 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_capture_pane_entry = { .name = "capture-pane", .alias = "capturep", - .args = { "ab:CeE:JMNpPqS:Tt:", 0, 0, NULL }, - .usage = "[-aCeJMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " + .args = { "ab:CeE:FJLMNpPqS:Tt:", 0, 0, NULL }, + .usage = "[-aCeFJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " "[-S start-line] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -112,9 +112,10 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, struct screen *s; struct grid_cell *gc = NULL; struct window_mode_entry *wme; - int n, join_lines, flags = 0; + int n, join_lines, number_lines, flags = 0; + int show_flags; u_int i, sx, top, bottom, tmp; - char *cause, *buf, *line; + char *cause, *buf, *line, b[64], *cp; const char *Sflag, *Eflag; size_t linelen; @@ -152,7 +153,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, if (cause != NULL) { top = gd->hsize; free(cause); - } else if (n < 0 && (u_int) -n > gd->hsize) + } else if (n < 0 && (u_int)-n > gd->hsize) top = 0; else top = gd->hsize + n; @@ -169,7 +170,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, if (cause != NULL) { bottom = gd->hsize + gd->sy - 1; free(cause); - } else if (n < 0 && (u_int) -n > gd->hsize) + } else if (n < 0 && (u_int)-n > gd->hsize) bottom = 0; else bottom = gd->hsize + n; @@ -192,12 +193,44 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, flags |= GRID_STRING_EMPTY_CELLS; if (!join_lines && !args_has(args, 'N')) flags |= GRID_STRING_TRIM_SPACES; + number_lines = args_has(args, 'L'); + show_flags = args_has(args, 'F'); buf = NULL; for (i = top; i <= bottom; i++) { line = grid_string_cells(gd, 0, i, sx, &gc, flags, s); linelen = strlen(line); + if (number_lines) { + if (i >= gd->hsize) + n = i - gd->hsize; + else + n = (int)i - (int)gd->hsize; + n = snprintf(b, sizeof b, "%d ", n); + if (n >= 0) + buf = cmd_capture_pane_append(buf, len, b, n); + } + if (show_flags) { + cp = b; + *cp = '\0'; + + gl = grid_peek_line(gd, i); + if (gl->flags & GRID_LINE_DEAD) + *cp++ = 'D'; + if (gl->flags & GRID_LINE_START_OUTPUT) + *cp++ = 'O'; + if (gl->flags & GRID_LINE_START_PROMPT) + *cp++ = 'P'; + if (gl->flags & GRID_LINE_WRAPPED) + *cp++ = 'W'; + if (gl->flags & GRID_LINE_EXTENDED) + *cp++ = 'X'; + if (b == cp) + *cp++ = '-'; + *cp++ = ' '; + *cp = '\0'; + buf = cmd_capture_pane_append(buf, len, b, strlen (b)); + } buf = cmd_capture_pane_append(buf, len, line, linelen); gl = grid_peek_line(gd, i); diff --git a/tmux.1 b/tmux.1 index 166fb680..07f379f0 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2666,7 +2666,7 @@ but a different format may be specified with .Fl F . .Tg capturep .It Xo Ic capture\-pane -.Op Fl aepPqCJMN +.Op Fl aeFLpPqCJMN .Op Fl b Ar buffer\-name .Op Fl E Ar end\-line .Op Fl S Ar start\-line @@ -2706,6 +2706,20 @@ implies .Fl P captures only any output that the pane has received that is the beginning of an as-yet incomplete escape sequence. +.Fl L +includes the line number at the start of each line and +.Fl F +includes the flags (where +.Ql - +is no flags, +.Ql D +is an unused line, +.Ql O +is a line marked as output, +.Ql P +is a line marked as a prompt and +.Ql X +is a line containing extended cells). .Pp .Fl S and From a532f4868f63b3197e4e28a4563ad022b1a8b497 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 20:03:02 +0000 Subject: [PATCH 19/27] Add -H flag to capture-pane to show hyperlinks. --- cmd-capture-pane.c | 81 ++++++++++++++++++++++++++++++++++++++++------ grid.c | 2 ++ tmux.1 | 12 +++++-- tmux.h | 1 + 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 0f37ad5d..8f537b98 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -29,18 +29,21 @@ static enum cmd_retval cmd_capture_pane_exec(struct cmd *, struct cmdq_item *); -static char *cmd_capture_pane_append(char *, size_t *, char *, size_t); +static char *cmd_capture_pane_append(char *, size_t *, const char *, + size_t); static char *cmd_capture_pane_pending(struct args *, struct window_pane *, size_t *); static char *cmd_capture_pane_history(struct args *, struct cmdq_item *, struct window_pane *, size_t *); +static char *cmd_capture_pane_hyperlinks(struct grid *, struct screen *, + u_int, u_int *, u_int *, size_t *); const struct cmd_entry cmd_capture_pane_entry = { .name = "capture-pane", .alias = "capturep", - .args = { "ab:CeE:FJLMNpPqS:Tt:", 0, 0, NULL }, - .usage = "[-aCeFJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " + .args = { "ab:CeE:FHJLMNpPqS:Tt:", 0, 0, NULL }, + .usage = "[-aCeFHJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " "[-S start-line] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -63,7 +66,8 @@ const struct cmd_entry cmd_clear_history_entry = { }; static char * -cmd_capture_pane_append(char *buf, size_t *len, char *line, size_t linelen) +cmd_capture_pane_append(char *buf, size_t *len, const char *line, + size_t linelen) { buf = xrealloc(buf, *len + linelen + 1); memcpy(buf + *len, line, linelen); @@ -103,6 +107,46 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp, return (buf); } +static char * +cmd_capture_pane_hyperlinks(struct grid *gd, struct screen *s, u_int py, + u_int *links, u_int *nlinks, size_t *len) +{ + const struct grid_line *gl = grid_peek_line(gd, py); + struct grid_cell gc; + const char *uri; + char *line = xstrdup(""); + u_int i, j; + + *len = 0; + + if (s->hyperlinks == NULL || (~gl->flags & GRID_LINE_HYPERLINK)) + return (line); + + for (i = 0; i < gl->cellused; i++) { + grid_get_cell(gd, i, py, &gc); + if (gc.link == 0) + continue; + for (j = 0; j < *nlinks; j++) { + if (links[j] == gc.link) + break; + } + if (j != *nlinks) + continue; + + if (!hyperlinks_get(s->hyperlinks, gc.link, &uri, NULL, NULL)) + continue; + + if (*nlinks == gd->sx) + break; + links[(*nlinks)++] = gc.link; + + if (*len != 0) + line = cmd_capture_pane_append(line, len, " ", 1); + line = cmd_capture_pane_append(line, len, uri, strlen(uri)); + } + return (line); +} + static char * cmd_capture_pane_history(struct args *args, struct cmdq_item *item, struct window_pane *wp, size_t *len) @@ -113,9 +157,10 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, struct grid_cell *gc = NULL; struct window_mode_entry *wme; int n, join_lines, number_lines, flags = 0; - int show_flags; + int show_flags, hyperlinks; + u_int *links = NULL, nlinks = 0; u_int i, sx, top, bottom, tmp; - char *cause, *buf, *line, b[64], *cp; + char *cause, *buf = NULL, *line, b[64], *cp; const char *Sflag, *Eflag; size_t linelen; @@ -195,11 +240,22 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, flags |= GRID_STRING_TRIM_SPACES; number_lines = args_has(args, 'L'); show_flags = args_has(args, 'F'); + hyperlinks = args_has(args, 'H'); + if (hyperlinks) + links = xreallocarray(NULL, gd->sx, sizeof *links); - buf = NULL; for (i = top; i <= bottom; i++) { - line = grid_string_cells(gd, 0, i, sx, &gc, flags, s); - linelen = strlen(line); + if (hyperlinks) { + line = cmd_capture_pane_hyperlinks(gd, s, i, links, + &nlinks, &linelen); + } else { + line = grid_string_cells(gd, 0, i, sx, &gc, flags, s); + linelen = strlen(line); + } + if (hyperlinks && linelen == 0) { + free(line); + continue; + } if (number_lines) { if (i >= gd->hsize) @@ -217,6 +273,8 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, gl = grid_peek_line(gd, i); if (gl->flags & GRID_LINE_DEAD) *cp++ = 'D'; + if (gl->flags & GRID_LINE_HYPERLINK) + *cp++ = 'H'; if (gl->flags & GRID_LINE_START_OUTPUT) *cp++ = 'O'; if (gl->flags & GRID_LINE_START_PROMPT) @@ -239,6 +297,9 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, free(line); } + free(links); + if (buf == NULL) + buf = xstrdup(""); return (buf); } @@ -261,7 +322,7 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) } len = 0; - if (args_has(args, 'P')) + if (args_has(args, 'P') && !args_has(args, 'H')) buf = cmd_capture_pane_pending(args, wp, &len); else buf = cmd_capture_pane_history(args, item, wp, &len); diff --git a/grid.c b/grid.c index 20deb936..36c99184 100644 --- a/grid.c +++ b/grid.c @@ -125,6 +125,8 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, else if (gce->offset >= gl->extdsize) fatalx("offset too big"); gl->flags |= GRID_LINE_EXTENDED; + if (gc->link != 0) + gl->flags |= GRID_LINE_HYPERLINK; if (gc->flags & GRID_FLAG_TAB) uc = gc->data.width; diff --git a/tmux.1 b/tmux.1 index 07f379f0..2b1d543f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2666,7 +2666,7 @@ but a different format may be specified with .Fl F . .Tg capturep .It Xo Ic capture\-pane -.Op Fl aeFLpPqCJMN +.Op Fl aeFHLpPqCJMN .Op Fl b Ar buffer\-name .Op Fl E Ar end\-line .Op Fl S Ar start\-line @@ -2717,9 +2717,15 @@ is an unused line, .Ql O is a line marked as output, .Ql P -is a line marked as a prompt and +is a line marked as a prompt, .Ql X -is a line containing extended cells). +is a line containing extended cells and +.Ql H +is a line with hyperlinks). +With +.Fl H , +only hyperlinks in the specified lines are captured. +Multiple hyperlinks on the same line are separated by spaces. .Pp .Fl S and diff --git a/tmux.h b/tmux.h index 7f0e438a..887d21bb 100644 --- a/tmux.h +++ b/tmux.h @@ -771,6 +771,7 @@ struct colour_palette { #define GRID_LINE_DEAD 0x4 #define GRID_LINE_START_PROMPT 0x8 #define GRID_LINE_START_OUTPUT 0x10 +#define GRID_LINE_HYPERLINK 0x20 /* Grid string flags. */ #define GRID_STRING_WITH_SEQUENCES 0x1 From 529afada822a72887a94f17146460809134697d0 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 20:05:16 +0000 Subject: [PATCH 20/27] Move checking of whether the cursor is visible inside the if so that it always hits the calculation of the oy offset when the status line is at the top. From Michael Grant. --- server-client.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server-client.c b/server-client.c index dd4b6e08..481d5a71 100644 --- a/server-client.c +++ b/server-client.c @@ -1819,12 +1819,13 @@ server_client_reset_state(struct client *c) cx = wp->xoff + (int)s->cx - (int)ox; cy = wp->yoff + (int)s->cy - (int)oy; + r = screen_redraw_get_visible_ranges(wp, cx, cy, 1, NULL); + if (!screen_redraw_is_visible(r, cx)) + cursor = 0; + if (status_at_line(c) == 0) cy += status_line_size(c); } - r = screen_redraw_get_visible_ranges(wp, cx, cy, 1, NULL); - if (!screen_redraw_is_visible(r, cx)) - cursor = 0; if (!cursor) mode &= ~MODE_CURSOR; From 85d72b9952ba49cf796d2e464c9adef0540b00b5 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Jun 2026 15:16:21 +0000 Subject: [PATCH 21/27] Return immediately if the list is empty in mode_tree_key instead of crashing, from Bryce Miller in GitHub issue 5170. --- mode-tree.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mode-tree.c b/mode-tree.c index 6479d78d..82535497 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1224,6 +1224,11 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, u_int i, x, y; int choice, preview; + if (mtd->line_size == 0) { + *key = KEYC_NONE; + return (1); + } + if (KEYC_IS_MOUSE(*key) && m != NULL) { if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) { *key = KEYC_NONE; From 34fd261a4f777250a0fe66652c2049f6a51d6988 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Jun 2026 20:38:54 +0000 Subject: [PATCH 22/27] Add a -k flag to choose commands to kill the pane when the mode is exited (useful with floating panes). --- cmd-choose-tree.c | 16 ++++++++-------- window.c | 6 ++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 186672f4..1dfb04f5 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -33,8 +33,8 @@ const struct cmd_entry cmd_choose_tree_entry = { .name = "choose-tree", .alias = NULL, - .args = { "F:f:GK:NO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse }, - .usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] " + .args = { "F:f:GK:kNO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-GkNrswZ] [-F format] [-f filter] [-K key-format] " "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -47,8 +47,8 @@ const struct cmd_entry cmd_choose_client_entry = { .name = "choose-client", .alias = NULL, - .args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, - .usage = "[-NrZ] [-F format] [-f filter] [-K key-format] " + .args = { "F:f:K:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-kNrZ] [-F format] [-f filter] [-K key-format] " "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -61,8 +61,8 @@ const struct cmd_entry cmd_choose_buffer_entry = { .name = "choose-buffer", .alias = NULL, - .args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, - .usage = "[-NrZ] [-F format] [-f filter] [-K key-format] " + .args = { "F:f:K:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-kNrZ] [-F format] [-f filter] [-K key-format] " "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -75,8 +75,8 @@ const struct cmd_entry cmd_customize_mode_entry = { .name = "customize-mode", .alias = NULL, - .args = { "F:f:Nt:yZ", 0, 0, NULL }, - .usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE, + .args = { "F:f:kNt:yZ", 0, 0, NULL }, + .usage = "[-kNZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, diff --git a/window.c b/window.c index c0d9078c..ddd1d6f0 100644 --- a/window.c +++ b/window.c @@ -1213,6 +1213,7 @@ window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, TAILQ_INSERT_HEAD(&wp->modes, wme, entry); wme->screen = wme->mode->init(wme, fs, args); } + wme->kill = args_has(args, 'k'); wp->screen = wme->screen; wp->flags |= (PANE_REDRAW|PANE_REDRAWSCROLLBAR|PANE_CHANGED); @@ -1230,11 +1231,13 @@ window_pane_reset_mode(struct window_pane *wp) { struct window_mode_entry *wme, *next; struct window *w = wp->window; + int kill; if (TAILQ_EMPTY(&wp->modes)) return; wme = TAILQ_FIRST(&wp->modes); + kill = wme->kill; TAILQ_REMOVE(&wp->modes, wme, entry); wme->mode->free(wme); free(wme); @@ -1257,6 +1260,9 @@ window_pane_reset_mode(struct window_pane *wp) server_redraw_window_borders(wp->window); server_status_window(wp->window); notify_pane("pane-mode-changed", wp); + + if (kill) + server_kill_pane(wp); } void From fe986a52d6bc9f11b90e52f677d679b839fa4643 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Jun 2026 20:41:21 +0000 Subject: [PATCH 23/27] When entering or leaving the alternate screen, discard any pending resizes. Improves flicking with scrollbars and programs that leave and enter the alternate screen on every WINCH like nano. GitHub issue 4772. Cvs: ---------------------------------------------------------------------- --- screen-write.c | 15 +++++++++++---- screen.c | 12 ++++++++---- server-client.c | 5 +---- tmux.h | 6 ++++-- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/screen-write.c b/screen-write.c index 47d6ebc9..9aa86c41 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2765,16 +2765,22 @@ void screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, int cursor) { - struct tty_ctx ttyctx; - struct window_pane *wp = ctx->wp; + struct tty_ctx ttyctx; + struct window_pane *wp = ctx->wp; + struct window_pane_resize *r, *r1; if (wp != NULL && !options_get_number(wp->options, "alternate-screen")) return; screen_write_collect_flush(ctx, 0, __func__); - screen_alternate_on(ctx->s, gc, cursor); + if (!screen_alternate_on(ctx->s, gc, cursor)) + return; if (wp != NULL) { + TAILQ_FOREACH_SAFE (r, &wp->resize_queue, entry, r1) { + TAILQ_REMOVE(&wp->resize_queue, r, entry); + free(r); + } layout_fix_panes(wp->window, NULL); server_redraw_window_borders(wp->window); } @@ -2796,7 +2802,8 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc, return; screen_write_collect_flush(ctx, 0, __func__); - screen_alternate_off(ctx->s, gc, cursor); + if (!screen_alternate_off(ctx->s, gc, cursor)) + return; if (wp != NULL) { layout_fix_panes(wp->window, NULL); diff --git a/screen.c b/screen.c index 3d9ead65..459ebcb2 100644 --- a/screen.c +++ b/screen.c @@ -655,13 +655,13 @@ screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy, int cursor) * Enter alternative screen mode. A copy of the visible screen is saved and the * history is not updated. */ -void +int screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) { u_int sx, sy; if (SCREEN_IS_ALTERNATE(s)) - return; + return 0; sx = screen_size_x(s); sy = screen_size_y(s); @@ -677,10 +677,12 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) s->saved_flags = s->grid->flags; s->grid->flags &= ~GRID_HISTORY; + + return 1; } /* Exit alternate screen mode and restore the copied grid. */ -void +int screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) { u_int sx = screen_size_x(s), sy = screen_size_y(s); @@ -709,7 +711,7 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) s->cx = screen_size_x(s) - 1; if (s->cy > screen_size_y(s) - 1) s->cy = screen_size_y(s) - 1; - return; + return 0; } /* Restore the saved grid. */ @@ -731,6 +733,8 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) s->cx = screen_size_x(s) - 1; if (s->cy > screen_size_y(s) - 1) s->cy = screen_size_y(s) - 1; + + return 1; } /* Get mode as a string. */ diff --git a/server-client.c b/server-client.c index 481d5a71..a819e6f9 100644 --- a/server-client.c +++ b/server-client.c @@ -1590,10 +1590,7 @@ server_client_resize_timer(__unused int fd, __unused short events, void *data) static void server_client_check_pane_resize(struct window_pane *wp) { - struct window_pane_resize *r; - struct window_pane_resize *r1; - struct window_pane_resize *first; - struct window_pane_resize *last; + struct window_pane_resize *r, *r1, *first, *last; struct timeval tv = { .tv_usec = 250000 }; if (TAILQ_EMPTY(&wp->resize_queue)) diff --git a/tmux.h b/tmux.h index 887d21bb..b618b3be 100644 --- a/tmux.h +++ b/tmux.h @@ -1147,6 +1147,8 @@ struct window_mode_entry { struct screen *screen; u_int prefix; + int kill; + TAILQ_ENTRY(window_mode_entry) entry; }; @@ -3335,8 +3337,8 @@ void screen_hide_selection(struct screen *); int screen_check_selection(struct screen *, u_int, u_int); int screen_select_cell(struct screen *, struct grid_cell *, const struct grid_cell *); -void screen_alternate_on(struct screen *, struct grid_cell *, int); -void screen_alternate_off(struct screen *, struct grid_cell *, int); +int screen_alternate_on(struct screen *, struct grid_cell *, int); +int screen_alternate_off(struct screen *, struct grid_cell *, int); const char *screen_mode_to_string(int); const char *screen_print(struct screen *, int); From ea51cdb3f2c946da8ef9d9fb36fe6208c29724b8 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Jun 2026 20:42:39 +0000 Subject: [PATCH 24/27] Add display-panes-format to change the top-of-pane text shown with display-panes. --- cmd-display-panes.c | 47 ++++++++++++++++++++++++++++++++++++--------- options-table.c | 8 ++++++++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/cmd-display-panes.c b/cmd-display-panes.c index efa7c7e4..5a1782ff 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -76,6 +76,40 @@ cmd_display_panes_put(struct screen_redraw_ctx *ctx, } } +static void +cmd_display_panes_draw_format(struct screen_redraw_ctx *ctx, + struct window_pane *wp, u_int xoff, u_int yoff, u_int sx, + const struct grid_cell *gc) +{ + struct client *c = ctx->c; + struct tty *tty = &c->tty; + struct session *s = c->session; + struct screen screen; + struct screen_write_ctx sctx; + struct visible_ranges *r; + struct visible_range *ri; + const char *format; + char *expanded; + u_int i, px = ctx->ox + xoff; + + format = options_get_string(s->options, "display-panes-format"); + expanded = format_single(NULL, format, c, s, s->curw, wp); + + screen_init(&screen, sx, 1, 0); + screen_write_start(&sctx, &screen); + format_draw(&sctx, gc, sx, expanded, NULL, 0); + screen_write_stop(&sctx); + free(expanded); + + r = screen_redraw_get_visible_ranges(wp, px, wp->yoff, sx, NULL); + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + tty_draw_line(tty, &screen, ri->px - px, 0, ri->nx, + ri->px - ctx->ox, yoff, gc, NULL); + } + screen_free(&screen); +} + static void cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) @@ -89,8 +123,8 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy; u_int cx, cy; int colour, active_colour; - char buf[16], lbuf[16], rbuf[16], *ptr; - size_t len, llen, rlen; + char buf[16], lbuf[16], *ptr; + size_t len, llen; if (wp->xoff + (int)wp->sx <= ctx->ox || wp->xoff >= ctx->ox + (int)ctx->sx || @@ -159,7 +193,6 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, bgc.bg = colour; } - rlen = xsnprintf(rbuf, sizeof rbuf, "%ux%u", wp->sx, wp->sy); if (pane > 9 && pane < 35) llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10)); else @@ -207,13 +240,9 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, if (sy <= 6) goto out; - tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); - if (rlen != 0 && sx >= rlen) { - cx = xoff + sx - rlen; - cy = yoff; - cmd_display_panes_put(ctx, wp, cx, cy, rbuf, rlen); - } + cmd_display_panes_draw_format(ctx, wp, xoff, yoff, sx, &fgc); if (llen != 0) { + tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); cx = xoff + sx / 2 + len * 3 - llen - 1; cy = yoff + py + 5; cmd_display_panes_put(ctx, wp, cx, cy, lbuf, llen); diff --git a/options-table.c b/options-table.c index 42e835b7..81f28be6 100644 --- a/options-table.c +++ b/options-table.c @@ -648,6 +648,14 @@ const struct options_table_entry options_table[] = { .text = "Colour of not active panes for 'display-panes'." }, + { .name = "display-panes-format", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SESSION, + .default_str = "#[align=right]#{pane_width}x#{pane_height}", + .text = "Format of text shown by 'display-panes', expanded for each " + "pane." + }, + { .name = "display-panes-time", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SESSION, From df7c2e605be8918cd663959b96a98125b120c021 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Jun 2026 20:44:57 +0000 Subject: [PATCH 25/27] Add -T to new-pane to set the title; GitHub issue 5176 from Michael Grant. --- cmd-split-window.c | 16 +++++++++++----- tmux.1 | 26 ++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index aa90256d..cc6427b5 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -39,11 +39,11 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL }, + .args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vx:X:y:Y:Z", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " - "[-R inactive-border-style] [-x width] [-y height] " + "[-R inactive-border-style] [-T title] [-x width] [-y height] " "[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " " "[shell-command [argument ...]]", @@ -57,11 +57,11 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:vZ", 0, -1, NULL }, + .args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:T:vZ", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " - "[-R inactive-border-style] " CMD_TARGET_PANE_USAGE " " + "[-R inactive-border-style] [-T title] " CMD_TARGET_PANE_USAGE " " "[shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -86,7 +86,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct cmd_find_state fs; int input, empty, is_floating, flags = 0; const char *template, *style; - char *cause = NULL, *cp; + char *cause = NULL, *cp, *title; struct args_value *av; u_int count = args_count(args); @@ -195,6 +195,12 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) "remain-on-exit-format", 0, "%s", args_get(args, 'm')); } + if (args_has(args, 'T')) { + title = format_single_from_target(item, args_get(args, 'T')); + screen_set_title(&new_wp->base, title); + notify_pane("pane-title-changed", new_wp); + free(title); + } if (input) { switch (window_pane_start_input(new_wp, item, &cause)) { diff --git a/tmux.1 b/tmux.1 index 2b1d543f..fce6e1d3 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2741,7 +2741,7 @@ the end of the visible pane. The default is to capture only the visible contents of the pane. .It Xo .Ic choose\-client -.Op Fl NryZ +.Op Fl kNryZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl K Ar key\-format @@ -2814,10 +2814,12 @@ specifies the format for each item in the list and a format for each shortcut key; both are evaluated once for each line. .Fl N starts without the preview or if given twice with the larger preview. +.Fl k +kills the pane when the mode is exited. This command works only if at least one client is attached. .It Xo .Ic choose\-tree -.Op Fl GNrswyZ +.Op Fl GkNrswyZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl K Ar key\-format @@ -2907,10 +2909,12 @@ starts without the preview or if given twice with the larger preview. .Fl G includes all sessions in any session groups in the tree rather than only the first. +.Fl k +kills the pane when the mode is exited. This command works only if at least one client is attached. .It Xo .Ic customize\-mode -.Op Fl NZ +.Op Fl kNZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl t Ar target\-pane @@ -2958,6 +2962,8 @@ If a filter would lead to an empty list, it is ignored. specifies the format for each item in the tree. .Fl N starts without the option information. +.Fl k +kills the pane when the mode is exited. This command works only if at least one client is attached. .It Xo .Tg displayp @@ -3339,6 +3345,7 @@ but a different format may be specified with .Op Fl s Ar style .Op Fl S Ar active\-border\-style .Op Fl t Ar target\-pane +.Op Fl T Ar title .Op Ar shell\-command Op Ar argument ... .Xc .D1 Pq alias: Ic newp @@ -3357,6 +3364,8 @@ sets the border style when the pane is active and .Fl R sets the border style when the pane is inactive (see .Sx STYLES ) . +.Fl T +sets the pane title. .Pp .Fl h does a horizontal split and @@ -3754,6 +3763,7 @@ the command behaves like .Op Fl s Ar style .Op Fl S Ar active\-border\-style .Op Fl t Ar target\-pane +.Op Fl T Ar title .Op Ar shell\-command Op Ar argument ... .Xc .D1 Pq alias: Ic splitw @@ -4792,6 +4802,12 @@ command to show the indicator for the active pane. Set the colour used by the .Ic display\-panes command to show the indicators for inactive panes. +.It Ic display\-panes\-format Ar format +Set the +.Ar format +of the text shown by the +.Ic display\-panes +command, expanded for each pane. .It Ic display\-panes\-time Ar time Set the time in milliseconds for which the indicators shown by the .Ic display\-panes @@ -7615,7 +7631,7 @@ The buffer commands are as follows: .Bl -tag -width Ds .It Xo .Ic choose\-buffer -.Op Fl NryZ +.Op Fl kNryZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl K Ar key\-format @@ -7686,6 +7702,8 @@ specifies the format for each item in the list and a format for each shortcut key; both are evaluated once for each line. .Fl N starts without the preview. +.Fl k +kills the pane when the mode is exited. This command works only if at least one client is attached. .Tg clearhist .It Xo Ic clear\-history From a0f4038df2f61fa0b096a5d4463ddc2e6f98874d Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Jun 2026 21:01:33 +0000 Subject: [PATCH 26/27] Add a -h flag to choose-tree and choose-client to hide the pane containing the mode, intended for use with floating panes. From Michael Grant, GitHub issue 5177. --- cmd-choose-tree.c | 8 ++++---- tmux.1 | 46 ++++++++++++++++++++++++++++++++++++---------- window-client.c | 14 ++++++++++++-- window-tree.c | 16 +++++++++++++++- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 1dfb04f5..6bb17fc2 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -33,8 +33,8 @@ const struct cmd_entry cmd_choose_tree_entry = { .name = "choose-tree", .alias = NULL, - .args = { "F:f:GK:kNO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse }, - .usage = "[-GkNrswZ] [-F format] [-f filter] [-K key-format] " + .args = { "F:f:GhK:kNO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-GhkNrswZ] [-F format] [-f filter] [-K key-format] " "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -47,8 +47,8 @@ const struct cmd_entry cmd_choose_client_entry = { .name = "choose-client", .alias = NULL, - .args = { "F:f:K:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, - .usage = "[-kNrZ] [-F format] [-f filter] [-K key-format] " + .args = { "F:f:hK:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-hkNrZ] [-F format] [-f filter] [-K key-format] " "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, diff --git a/tmux.1 b/tmux.1 index fce6e1d3..130b8cf3 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2310,7 +2310,6 @@ cursor on that line. Scroll pane in copy\-mode when bound to a mouse drag event. .Fl e causes copy mode to exit when at the bottom. -.Pp .It Xo .Ic scroll\-top .Xc @@ -2741,7 +2740,7 @@ the end of the visible pane. The default is to capture only the visible contents of the pane. .It Xo .Ic choose\-client -.Op Fl kNryZ +.Op Fl hkNryZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl K Ar key\-format @@ -2812,14 +2811,20 @@ If a filter would lead to an empty list, it is ignored. specifies the format for each item in the list and .Fl K a format for each shortcut key; both are evaluated once for each line. +.Pp .Fl N starts without the preview or if given twice with the larger preview. +.Fl h +hides the pane containing the mode. .Fl k kills the pane when the mode is exited. -This command works only if at least one client is attached. +.Pp +The +.Ic choose-client +command works only if at least one client is attached. .It Xo .Ic choose\-tree -.Op Fl GkNrswyZ +.Op Fl GhkNrswyZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl K Ar key\-format @@ -2904,14 +2909,27 @@ If a filter would lead to an empty list, it is ignored. specifies the format for each item in the tree and .Fl K a format for each shortcut key; both are evaluated once for each line. -.Fl N -starts without the preview or if given twice with the larger preview. .Fl G includes all sessions in any session groups in the tree rather than only the first. +.Pp +.Fl N +starts without the preview or if given twice with the larger preview. +.Fl h +hides the pane containing the mode. .Fl k kills the pane when the mode is exited. -This command works only if at least one client is attached. +.Fl h +and +.Fl k +are intended to ease use of the mode in a floating pane; for example: +.Bd -literal -offset indent +bind s 'new-pane -x50% -y50% -E; choose-tree -hks' +.Ed +.Pp +The +.Ic choose-tree +command works only if at least one client is attached. .It Xo .Ic customize\-mode .Op Fl kNZ @@ -2960,11 +2978,15 @@ the item in the list is not shown, otherwise it is shown. If a filter would lead to an empty list, it is ignored. .Fl F specifies the format for each item in the tree. +.Pp .Fl N starts without the option information. .Fl k kills the pane when the mode is exited. -This command works only if at least one client is attached. +.Pp +The +.Ic customize-mode +command works only if at least one client is attached. .It Xo .Tg displayp .Ic display\-panes @@ -3035,7 +3057,9 @@ The default is .Fl Z zooms the pane. .Pp -This command works only if at least one client is attached. +The +.Ic find-window +command works only if at least one client is attached. .Tg joinp .It Xo Ic join\-pane .Op Fl bdfhv @@ -7704,7 +7728,9 @@ a format for each shortcut key; both are evaluated once for each line. starts without the preview. .Fl k kills the pane when the mode is exited. -This command works only if at least one client is attached. +The +.Ic choose-buffer +command works only if at least one client is attached. .Tg clearhist .It Xo Ic clear\-history .Op Fl H diff --git a/window-client.c b/window-client.c index be1fd5fa..519222d9 100644 --- a/window-client.c +++ b/window-client.c @@ -82,6 +82,7 @@ struct window_client_modedata { char *format; char *key_format; char *command; + int hide_preview_this_pane; struct window_client_itemdata **item_list; u_int item_size; @@ -162,9 +163,10 @@ window_client_build(void *modedata, struct sort_criteria *sort_crit, } static void -window_client_draw(__unused void *modedata, void *itemdata, +window_client_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, u_int sx, u_int sy) { + struct window_client_modedata *data = modedata; struct window_client_itemdata *item = itemdata; struct client *c = item->c; struct screen *s = ctx->s; @@ -174,6 +176,12 @@ window_client_draw(__unused void *modedata, void *itemdata, if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; wp = c->session->curw->window->active; + if (data->hide_preview_this_pane && wp == data->wp) { + if (!TAILQ_EMPTY(&c->session->curw->window->last_panes)) + wp = TAILQ_FIRST(&c->session->curw->window->last_panes); + else + wp = NULL; + } lines = status_line_size(c); if (lines >= sy) @@ -184,7 +192,8 @@ window_client_draw(__unused void *modedata, void *itemdata, at = 0; screen_write_cursormove(ctx, cx, cy + at, 0); - screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines); + if (wp != NULL) + screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines); if (at != 0) screen_write_cursormove(ctx, cx, cy + 2, 0); @@ -270,6 +279,7 @@ window_client_init(struct window_mode_entry *wme, wme->data = data = xcalloc(1, sizeof *data); data->wp = wp; + data->hide_preview_this_pane = args != NULL && args_has(args, 'h'); if (args == NULL || !args_has(args, 'F')) data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT); diff --git a/window-tree.c b/window-tree.c index 2f4dccd4..7a0db7c9 100644 --- a/window-tree.c +++ b/window-tree.c @@ -113,6 +113,7 @@ struct window_tree_modedata { char *key_format; char *command; int squash_groups; + int hide_preview_this_pane; int prompt_flags; struct window_tree_itemdata **item_list; @@ -289,6 +290,8 @@ window_tree_build_window(struct session *s, struct winlink *wl, if (n == 0) goto empty; for (i = 0; i < n; i++) { + if (data->hide_preview_this_pane && l[i] == data->wp) + continue; if (window_tree_filter_pane(s, wl, l[i], filter)) window_tree_build_pane(s, wl, l[i], modedata, mti); } @@ -580,6 +583,10 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, struct options *oo; total = window_count_panes(w, 1); + if (data->hide_preview_this_pane && data->wp->window == w) + total--; + if (total == 0) + return; if (sx / total < 24) { visible = sx / 24; @@ -590,6 +597,8 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, current = 0; TAILQ_FOREACH(wp, &w->panes, entry) { + if (data->hide_preview_this_pane && wp == data->wp) + continue; if (wp == w->active) break; current++; @@ -653,6 +662,8 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, i = loop = 0; TAILQ_FOREACH(wp, &w->panes, entry) { + if (data->hide_preview_this_pane && wp == data->wp) + continue; if (loop == end) break; if (loop < start) { @@ -704,6 +715,7 @@ static void window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, u_int sx, u_int sy) { + struct window_tree_modedata *data = modedata; struct window_tree_itemdata *item = itemdata; struct session *sp; struct winlink *wl; @@ -723,7 +735,8 @@ window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, window_tree_draw_window(modedata, sp, wl, ctx, sx, sy); break; case WINDOW_TREE_PANE: - screen_write_preview(ctx, &wp->base, sx, sy); + if (!data->hide_preview_this_pane || wp != data->wp) + screen_write_preview(ctx, &wp->base, sx, sy); break; } } @@ -932,6 +945,7 @@ window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, else data->command = xstrdup(args_string(args, 0)); data->squash_groups = !args_has(args, 'G'); + data->hide_preview_this_pane = args_has(args, 'h'); if (args_has(args, 'y')) data->prompt_flags = PROMPT_ACCEPT; From bf2e078ecf88746ee4e2a739185fc20d2d932654 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Jun 2026 21:19:52 +0000 Subject: [PATCH 27/27] Add a relative time option for time formatting, GitHub issue 5009. --- format.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- tmux.1 | 10 ++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/format.c b/format.c index 7b184613..15afbe0b 100644 --- a/format.c +++ b/format.c @@ -117,6 +117,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_NOT_NOT 0x100000 #define FORMAT_REPEAT 0x200000 #define FORMAT_QUOTE_ARGUMENTS 0x400000 +#define FORMAT_RELATIVE 0x800000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -4045,6 +4046,50 @@ format_pretty_time(time_t t, int seconds) return (xstrdup(s)); } +/* Make a relative time. */ +static char * +format_relative_time(time_t t) +{ + time_t now, age; + u_int d, h, m, s; + char out[32], sign; + + time(&now); + if (t == now) + return (xstrdup("0s")); + if (t > now) { + sign = '+'; + age = t - now; + } else { + sign = '-'; + age = now - t; + } + + d = age / 86400; + h = (age % 86400) / 3600; + m = (age % 3600) / 60; + s = age % 60; + + if (d != 0) { + if (h != 0) + xsnprintf(out, sizeof out, "%c%ud%uh", sign, d, h); + else + xsnprintf(out, sizeof out, "%c%ud", sign, d); + } else if (h != 0) { + if (m != 0) + xsnprintf(out, sizeof out, "%c%uh%um", sign, h, m); + else + xsnprintf(out, sizeof out, "%c%uh", sign, h); + } else if (m != 0) { + if (s != 0) + xsnprintf(out, sizeof out, "%c%um%us", sign, m, s); + else + xsnprintf(out, sizeof out, "%c%um", sign, m); + } else + xsnprintf(out, sizeof out, "%c%us", sign, s); + return (xstrdup(out)); +} + /* Find a format entry. */ static char * format_find(struct format_tree *ft, const char *key, int modifiers, @@ -4126,7 +4171,9 @@ found: } if (t == 0) return (NULL); - if (modifiers & FORMAT_PRETTY) + if (modifiers & FORMAT_RELATIVE) + found = format_relative_time(t); + else if (modifiers & FORMAT_PRETTY) found = format_pretty_time(t, 0); else { if (time_format != NULL) { @@ -5131,6 +5178,8 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, break; if (strchr(fm->argv[0], 'p') != NULL) modifiers |= FORMAT_PRETTY; + else if (strchr(fm->argv[0], 'r') != NULL) + modifiers |= FORMAT_RELATIVE; else if (fm->argc >= 2 && strchr(fm->argv[0], 'f') != NULL) { free(time_format); diff --git a/tmux.1 b/tmux.1 index 130b8cf3..57746ac7 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6323,9 +6323,15 @@ gives gives .Ql Sun Oct 25 09:25:02 2015 . Adding -.Ql p ( -.Ql `t/p` ) +.Ql p +.Pq Ql t/p will use shorter but less accurate time format for times in the past. +.Ql r +.Pq Ql t/r +will show the time relative to the current time, for example +.Ql \-1m +or +.Ql +2m23s . A custom format may be given using an .Ql f suffix (note that