From f92cb43ce4dc0127c139c01937a8a623b7c22950 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 8 Jul 2026 12:36:19 +0100 Subject: [PATCH 1/8] t/d test. --- regress/format-modifiers.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/regress/format-modifiers.sh b/regress/format-modifiers.sh index 631f1e10e..58c02af7c 100644 --- a/regress/format-modifiers.sh +++ b/regress/format-modifiers.sh @@ -291,6 +291,15 @@ if [ -z "$($TMUX display-message -p '#{t/r:@ts}')" ]; then echo "Format test failed for '#{t/r:@ts}': empty result" exit 1 fi +# t/d: difference from the current time in seconds. +diff=$($TMUX display-message -p '#{t/d:@ts}') +case "$diff" in +[0-9]*) ;; +*) + echo "Format test failed for '#{t/d:@ts}': expected positive value, got '$diff'" + exit 1 + ;; +esac # t/f: custom strftime format applied to the variable's time. Tested in a # format_expand context (list-windows -F), where a single strftime specifier is @@ -326,6 +335,14 @@ done # A time in the future has no relative form. $TMUX set -g @future "$((now + 100000))" test_format "#{t/r:@future}" "" +diff=$($TMUX display-message -p '#{t/d:@future}') +case "$diff" in +-[0-9]*) ;; +*) + echo "Format test failed for '#{t/d:@future}': expected negative value, got '$diff'" + exit 1 + ;; +esac # --- Content search (C) -------------------------------------------------- From 1fefb8044545115cf745f56b90d996a043836a30 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 9 Jul 2026 06:33:19 +0000 Subject: [PATCH 2/8] Add t/d for time difference in seconds. --- format.c | 16 ++++++++++++++++ tmux.1 | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/format.c b/format.c index f2bc22a7a..7ec4f06b9 100644 --- a/format.c +++ b/format.c @@ -127,6 +127,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_QUOTE_SHELL_SQ 0x20000000 #define FORMAT_OPTIONS 0x40000000 #define FORMAT_ENVIRON 0x80000000ULL +#define FORMAT_DIFFERENCE 0x100000000ULL /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -4203,6 +4204,17 @@ format_relative_time(time_t t) return (xstrdup(out)); } +/* Make a time difference in seconds. */ +static char * +format_time_difference(time_t t) +{ + time_t now = time(NULL); + char *out; + + xasprintf(&out, "%ld", (long)now - (long)t); + return (out); +} + /* Find a format entry. */ static char * format_find(struct format_tree *ft, const char *key, uint64_t modifiers, @@ -4287,6 +4299,8 @@ found: return (NULL); if (modifiers & FORMAT_RELATIVE) found = format_relative_time(t); + else if (modifiers & FORMAT_DIFFERENCE) + found = format_time_difference(t); else if (modifiers & FORMAT_PRETTY) found = format_pretty_time(t, 0); else { @@ -5666,6 +5680,8 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, modifiers |= FORMAT_PRETTY; else if (strchr(fm->argv[0], 'r') != NULL) modifiers |= FORMAT_RELATIVE; + else if (strchr(fm->argv[0], 'd') != NULL) + modifiers |= FORMAT_DIFFERENCE; else if (fm->argc >= 2 && strchr(fm->argv[0], 'f') != NULL) { free(time_format); diff --git a/tmux.1 b/tmux.1 index 593d4de06..dbfb7a30a 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6813,10 +6813,14 @@ Adding 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 +will give the time relative to the current time, for example .Ql \&1m or .Ql 2m23s . +.Ql d +.Pq Ql t/d +will give the difference from the time to current time in seconds (future +times are negative). A custom format may be given using an .Ql f suffix (note that From dea309cf8d818665043aa9df0b3f1adb63ab0010 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 9 Jul 2026 07:19:24 +0000 Subject: [PATCH 3/8] Allow empty name with new-window again, from Jacob Koziej. --- spawn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spawn.c b/spawn.c index 3a211e3c5..09eec948d 100644 --- a/spawn.c +++ b/spawn.c @@ -179,7 +179,7 @@ spawn_window(struct spawn_context *sc, char **cause) /* Set the name of the new window. */ if (~sc->flags & SPAWN_RESPAWN) { free(w->name); - if (sc->name == NULL || *sc->name == '\0') + if (sc->name == NULL) w->name = default_window_name(w); else { w->name = xstrdup(sc->name); From e242da168bb6d50c687ccd1897b82c1d282a651a Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 9 Jul 2026 07:32:58 +0000 Subject: [PATCH 4/8] Clip the status line and menu against an open popup overlay, GitHub issue 5350 from Noam Stolero. --- popup.c | 2 +- screen-redraw.c | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/popup.c b/popup.c index 72a8259cb..913c6c527 100644 --- a/popup.c +++ b/popup.c @@ -245,7 +245,7 @@ popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) */ for (i = 0; i < mr->used; i++) { server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, r->ranges[i].px, py, r->ranges[i].nx, + pd->sy, mr->ranges[i].px, py, mr->ranges[i].nx, &pd->or[i]); } diff --git a/screen-redraw.c b/screen-redraw.c index 6e8b65b76..70a830e6a 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1588,8 +1588,10 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) struct screen *sl; struct redraw_scene *scene; struct window_pane *loop; - u_int width, i, y, lines; + u_int width, i, y, lines, j; struct redraw_span *first; + struct visible_ranges *r; + struct visible_range *rr; int redraw; if (c->flags & CLIENT_SUSPENDED) @@ -1694,8 +1696,16 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) else y = c->tty.sy - lines; sl = c->status.active; - for (i = 0; i < lines; i++) - tty_draw_line(tty, sl, 0, i, UINT_MAX, 0, y + i, NULL); + for (i = 0; i < lines; i++) { + r = tty_check_overlay_range(tty, 0, y + i, tty->sx); + for (j = 0; j < r->used; j++) { + rr = &r->ranges[j]; + if (rr->nx == 0) + continue; + tty_draw_line(tty, sl, rr->px, i, rr->nx, + rr->px, y + i, NULL); + } + } } if (c->overlay_draw != NULL && (flags & REDRAW_OVERLAY)) c->overlay_draw(c, c->overlay_data); From 161dbaf1ced65dc0d89df5fb3b99fb091cf34b7a Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 9 Jul 2026 07:35:05 +0000 Subject: [PATCH 5/8] Cancel pending resizes and resize immediately when entering alternate screen, fixes flickering with scrollbars. GitHub issue 5351 from Michael Grant. --- screen-write.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/screen-write.c b/screen-write.c index ebc40b484..4d4f90900 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2962,7 +2962,13 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, if (wp != NULL) { window_pane_clear_resizes(wp, NULL); + if (event_initialized(&wp->resize_timer)) + evtimer_del(&wp->resize_timer); layout_fix_panes(wp->window, NULL); + if (!TAILQ_EMPTY(&wp->resize_queue)) { + window_pane_send_resize(wp, wp->sx, wp->sy); + window_pane_clear_resizes(wp, NULL); + } server_redraw_window_borders(wp->window); } From 3a6c717bb81ee5b219d56e2250ad563ce1523025 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 9 Jul 2026 07:54:07 +0000 Subject: [PATCH 6/8] Do not exit at bottom if a selection is in progress, GitHub issue 5349 from xiaomublue at gmail dot com. --- tmux.1 | 4 +++- window-copy.c | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tmux.1 b/tmux.1 index dbfb7a30a..50d398b7c 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2340,7 +2340,8 @@ but also exit copy mode if the cursor reaches the bottom. .It Xo .Ic scroll\-exit\-on .Xc -Turn on exiting copy mode when scrolling to the end of the buffer. +Turn on exiting copy mode when scrolling to the end of the buffer, unless a +selection is present. .It Xo .Ic scroll\-exit\-off .Xc @@ -2617,6 +2618,7 @@ instead of .Fl e specifies that scrolling to the bottom of the history (to the visible screen) should exit copy mode. +This will not happen if a selection is present. While in copy mode, pressing a key other than those used for scrolling will disable this behaviour. This is intended to allow fast scrolling through a pane's history, for diff --git a/window-copy.c b/window-copy.c index aedbba99b..33743cea3 100644 --- a/window-copy.c +++ b/window-copy.c @@ -854,7 +854,7 @@ window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, window_copy_cursor_end_of_line(wme); } - if (scroll_exit && data->oy == 0) { + if (scroll_exit && data->oy == 0 && data->screen.sel == NULL) { window_pane_reset_mode(wp); return; } @@ -972,7 +972,7 @@ window_copy_pagedown1(struct window_mode_entry *wme, int half_page, window_copy_cursor_end_of_line(wme); } - if (scroll_exit && data->oy == 0) + if (scroll_exit && data->oy == 0 && data->screen.sel == NULL) return (1); if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); @@ -2352,7 +2352,7 @@ window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs) for (; np != 0; np--) window_copy_cursor_down(wme, 1); - if (data->scroll_exit && data->oy == 0) + if (data->scroll_exit && data->oy == 0 && data->screen.sel == NULL) return (WINDOW_COPY_CMD_CANCEL); return (WINDOW_COPY_CMD_NOTHING); } From 041b6a6bc75b1735f91243527bad7cf394d58fba Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 9 Jul 2026 08:56:06 +0100 Subject: [PATCH 7/8] Add test from xiaomublue at gmail dot com. --- regress/copy-mode-scroll-exit.sh | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 regress/copy-mode-scroll-exit.sh diff --git a/regress/copy-mode-scroll-exit.sh b/regress/copy-mode-scroll-exit.sh new file mode 100644 index 000000000..0f9e2d335 --- /dev/null +++ b/regress/copy-mode-scroll-exit.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +$TMUX kill-server 2>/dev/null + +cleanup() +{ + $TMUX kill-server 2>/dev/null +} +trap cleanup 0 +trap 'exit 1' 1 2 3 15 + +$TMUX new -d -x40 -y10 \ + 'i=0; while [ $i -lt 80 ]; do echo "line $i"; i=$((i + 1)); done; cat' || + exit 1 +$TMUX set -g window-size manual || exit 1 + +$TMUX copy-mode -e || exit 1 +$TMUX send-keys -X history-top || exit 1 +$TMUX send-keys -X start-of-line || exit 1 +$TMUX send-keys -X begin-selection || exit 1 +$TMUX send-keys -X cursor-down || exit 1 + +[ "$($TMUX display-message -p '#{selection_present}')" = "1" ] || exit 1 +$TMUX send-keys -N200 -X scroll-down || exit 1 +[ "$($TMUX display-message -p '#{pane_in_mode} #{scroll_position}')" = "1 0" ] || + exit 1 + +$TMUX send-keys -X clear-selection || exit 1 +$TMUX send-keys -X scroll-down || exit 1 +[ "$($TMUX display-message -p '#{pane_in_mode}')" = "0" ] || exit 1 + +exit 0 From ec31f4566dca2315dee04c9c23834c0020a27b86 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 9 Jul 2026 09:39:44 +0100 Subject: [PATCH 8/8] Tweak test. --- regress/input-reflow-stress.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regress/input-reflow-stress.sh b/regress/input-reflow-stress.sh index 926e8cdd0..767f08bec 100644 --- a/regress/input-reflow-stress.sh +++ b/regress/input-reflow-stress.sh @@ -448,7 +448,7 @@ sleep 0.1 $TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1 $TMUX set-option -g history-limit "$HISTORY_LIMIT" || exit 1 -$TMUX new-session -d -x 80 -y "$HEIGHT" -s stress 'cat' || exit 1 +$TMUX new-session -d -x 80 -y "$HEIGHT" -s stress 'stty -echo; exec cat' || exit 1 $TMUX kill-session -t test-setup || exit 1 sleep 0.3