From 65b3a95be8bfaf24d755210049e3b91fe190e45e Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 9 Jan 2026 15:04:00 +0000 Subject: [PATCH 01/12] Removing padding needs to go through screen_write_collect_trim or there may end up multiple items covering the same region. --- screen-write.c | 103 +++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/screen-write.c b/screen-write.c index 47b370ca..0eee94bb 100644 --- a/screen-write.c +++ b/screen-write.c @@ -25,6 +25,8 @@ static struct screen_write_citem *screen_write_collect_trim( struct screen_write_ctx *, u_int, u_int, u_int, int *); +static void screen_write_collect_insert(struct screen_write_ctx *, + struct screen_write_citem *); static void screen_write_collect_clear(struct screen_write_ctx *, u_int, u_int); static void screen_write_collect_scroll(struct screen_write_ctx *, u_int); @@ -1295,7 +1297,7 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) struct screen *s = ctx->s; struct grid_line *gl; u_int sx = screen_size_x(s); - struct screen_write_citem *ci = ctx->item, *before; + struct screen_write_citem *ci = ctx->item; if (s->cx == 0) { screen_write_clearline(ctx, bg); @@ -1308,16 +1310,11 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg); - before = screen_write_collect_trim(ctx, s->cy, s->cx, sx - s->cx, NULL); ci->x = s->cx; ci->used = sx - s->cx; ci->type = CLEAR; ci->bg = bg; - if (before == NULL) - TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); - else - TAILQ_INSERT_BEFORE(before, ci, entry); - ctx->item = screen_write_get_citem(); + screen_write_collect_insert(ctx, ci); } /* Clear to start of line from cursor. */ @@ -1326,7 +1323,7 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) { struct screen *s = ctx->s; u_int sx = screen_size_x(s); - struct screen_write_citem *ci = ctx->item, *before; + struct screen_write_citem *ci = ctx->item; if (s->cx >= sx - 1) { screen_write_clearline(ctx, bg); @@ -1338,16 +1335,11 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) else grid_view_clear(s->grid, 0, s->cy, s->cx + 1, 1, bg); - before = screen_write_collect_trim(ctx, s->cy, 0, s->cx + 1, NULL); ci->x = 0; ci->used = s->cx + 1; ci->type = CLEAR; ci->bg = bg; - if (before == NULL) - TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); - else - TAILQ_INSERT_BEFORE(before, ci, entry); - ctx->item = screen_write_get_citem(); + screen_write_collect_insert(ctx, ci); } /* Move cursor to px,py. */ @@ -1774,6 +1766,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, cl = &ctx->s->write_list[y]; last = UINT_MAX; TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { + log_debug("collect list: x=%u (last %u), y=%u, used=%u", + ci->x, last, y, ci->used); if (last != UINT_MAX && ci->x <= last) { fatalx("collect list not in order: %u <= %u", ci->x, last); @@ -1804,29 +1798,39 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, log_debug("%s: flushed %u items (%s)", __func__, items, from); } -/* Finish and store collected cells. */ +/* Insert an item on current line. */ void -screen_write_collect_end(struct screen_write_ctx *ctx) +screen_write_collect_insert(struct screen_write_ctx *ctx, + struct screen_write_citem *ci) { struct screen *s = ctx->s; - struct screen_write_citem *ci = ctx->item, *nci, *before; struct screen_write_cline *cl = &s->write_list[s->cy]; - struct grid_cell gc; - u_int xx; - int wrapped = ci->wrapped; + struct screen_write_citem *before; - if (ci->used == 0) - return; - - before = screen_write_collect_trim(ctx, s->cy, s->cx, ci->used, - &wrapped); - ci->x = s->cx; - ci->wrapped = wrapped; + before = screen_write_collect_trim(ctx, s->cy, ci->x, ci->used, + &ci->wrapped); if (before == NULL) TAILQ_INSERT_TAIL(&cl->items, ci, entry); else TAILQ_INSERT_BEFORE(before, ci, entry); ctx->item = screen_write_get_citem(); +} + +/* Finish and store collected cells. */ +void +screen_write_collect_end(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + struct screen_write_citem *ci = ctx->item, *bci = NULL, *aci; + struct screen_write_cline *cl = &s->write_list[s->cy]; + struct grid_cell gc; + u_int xx; + + if (ci->used == 0) + return; + + ci->x = s->cx; + screen_write_collect_insert(ctx, ci); log_debug("%s: %u %.*s (at %u,%u)", __func__, ci->used, (int)ci->used, cl->data + ci->x, s->cx, s->cy); @@ -1838,32 +1842,35 @@ screen_write_collect_end(struct screen_write_ctx *ctx) break; grid_view_set_cell(s->grid, xx, s->cy, &grid_default_cell); - log_debug("%s: padding erased (before) at %u", - __func__, xx); + log_debug("%s: padding erased (before) at %u (cx %u)", + __func__, xx, s->cx); } if (xx != s->cx) { if (xx == 0) grid_view_get_cell(s->grid, 0, s->cy, &gc); - if (gc.data.width > 1) { + if (gc.data.width > 1 || + (gc.flags & GRID_FLAG_PADDING)) { grid_view_set_cell(s->grid, xx, s->cy, &grid_default_cell); - log_debug("%s: padding erased (before) at %u", - __func__, xx); + log_debug("%s: padding erased (before) at %u " + "(cx %u)", __func__, xx, s->cx); } } if (xx != s->cx) { - nci = ctx->item; - nci->type = CLEAR; - nci->x = xx; - nci->bg = 8; - nci->used = s->cx - xx; - TAILQ_INSERT_BEFORE(ci, nci, entry); - ctx->item = screen_write_get_citem(); + bci = ctx->item; + bci->type = CLEAR; + bci->x = xx; + bci->bg = 8; + bci->used = s->cx - xx; + log_debug("%s: padding erased (before): from %u, " + "size %u", __func__, bci->x, bci->used); } } grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x, ci->used); + if (bci != NULL) + screen_write_collect_insert(ctx, bci); screen_write_set_cursor(ctx, s->cx + ci->used, -1); for (xx = s->cx; xx < screen_size_x(s); xx++) { @@ -1871,16 +1878,18 @@ screen_write_collect_end(struct screen_write_ctx *ctx) if (~gc.flags & GRID_FLAG_PADDING) break; grid_view_set_cell(s->grid, xx, s->cy, &grid_default_cell); - log_debug("%s: padding erased (after) at %u", __func__, xx); + log_debug("%s: padding erased (after) at %u (cx %u)", + __func__, xx, s->cx); } if (xx != s->cx) { - nci = ctx->item; - nci->type = CLEAR; - nci->x = s->cx; - nci->bg = 8; - nci->used = xx - s->cx; - TAILQ_INSERT_AFTER(&cl->items, ci, nci, entry); - ctx->item = screen_write_get_citem(); + aci = ctx->item; + aci->type = CLEAR; + aci->x = s->cx; + aci->bg = 8; + aci->used = xx - s->cx; + log_debug("%s: padding erased (after): from %u, size %u", + __func__, aci->x, aci->used); + screen_write_collect_insert(ctx, aci); } } From 426467856de6cc599943732bd80a2816e32f2f6f Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 11 Jan 2026 11:48:46 +0000 Subject: [PATCH 02/12] Add paste to the default pane menu, GitHub issue 4763. --- key-bindings.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/key-bindings.c b/key-bindings.c index 2dd2b742..5e6449b7 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -49,6 +49,8 @@ " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}' '<' {send -X history-top}" \ " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \ " ''" \ + " '#{?#{&&:#{buffer_size},#{!:#{pane_in_mode}}},Paste #[underscore]#{=/9/...:buffer_sample},}' 'p' {paste-buffer}" \ + " ''" \ " '#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}' 'C-r' {if -F '#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}' 'copy-mode -t='; send -Xt= search-backward -- \"#{q:mouse_word}\"}" \ " '#{?mouse_word,Type #[underscore]#{=/9/...:mouse_word},}' 'C-y' {copy-mode -q; send-keys -l -- \"#{q:mouse_word}\"}" \ " '#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}' 'c' {copy-mode -q; set-buffer -- \"#{q:mouse_word}\"}" \ From a443531280c49b5e98291245f29010a2c3336231 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 12 Jan 2026 07:48:36 +0000 Subject: [PATCH 03/12] 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. GitHub issue 4795 from Shaobo Song. --- server-client.c | 61 ++++++++++++++++++++++++++++++------------------- tmux.h | 2 ++ 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/server-client.c b/server-client.c index af67a327..c443d4c4 100644 --- a/server-client.c +++ b/server-client.c @@ -315,6 +315,8 @@ server_client_create(int fd) evtimer_set(&c->repeat_timer, server_client_repeat_timer, c); evtimer_set(&c->click_timer, server_client_click_timer, c); + c->click_wp = -1; + TAILQ_INIT(&c->input_requests); TAILQ_INSERT_TAIL(&clients, c, entry); @@ -737,21 +739,17 @@ server_client_check_mouse(struct client *c, struct key_event *event) if (c->flags & CLIENT_DOUBLECLICK) { evtimer_del(&c->click_timer); c->flags &= ~CLIENT_DOUBLECLICK; - if (m->b == c->click_button) { - type = SECOND; - x = m->x, y = m->y, b = m->b; - log_debug("second-click at %u,%u", x, y); - c->flags |= CLIENT_TRIPLECLICK; - } + type = SECOND; + x = m->x, y = m->y, b = m->b; + log_debug("second-click at %u,%u", x, y); + c->flags |= CLIENT_TRIPLECLICK; } else if (c->flags & CLIENT_TRIPLECLICK) { evtimer_del(&c->click_timer); c->flags &= ~CLIENT_TRIPLECLICK; - if (m->b == c->click_button) { - type = TRIPLE; - x = m->x, y = m->y, b = m->b; - log_debug("triple-click at %u,%u", x, y); - goto have_event; - } + type = TRIPLE; + x = m->x, y = m->y, b = m->b; + log_debug("triple-click at %u,%u", x, y); + goto have_event; } /* DOWN is the only remaining event type. */ @@ -761,17 +759,6 @@ server_client_check_mouse(struct client *c, struct key_event *event) log_debug("down at %u,%u", x, y); c->flags |= CLIENT_DOUBLECLICK; } - - if (KEYC_CLICK_TIMEOUT != 0) { - memcpy(&c->click_event, m, sizeof c->click_event); - c->click_button = m->b; - - log_debug("click timer started"); - tv.tv_sec = KEYC_CLICK_TIMEOUT / 1000; - tv.tv_usec = (KEYC_CLICK_TIMEOUT % 1000) * 1000L; - evtimer_del(&c->click_timer); - evtimer_add(&c->click_timer, &tv); - } } have_event: @@ -887,6 +874,34 @@ have_event: } } + /* Reset click type or add a click timer if needed. */ + if (type == DOWN || + type == SECOND || + type == TRIPLE) { + if (type != DOWN && + (m->b != c->click_button || + where != (enum mouse_where)c->click_where || + m->wp != c->click_wp)) { + type = DOWN; + log_debug("click sequence reset at %u,%u", x, y); + c->flags &= ~CLIENT_TRIPLECLICK; + c->flags |= CLIENT_DOUBLECLICK; + } + + if (type != TRIPLE && KEYC_CLICK_TIMEOUT != 0) { + memcpy(&c->click_event, m, sizeof c->click_event); + c->click_button = m->b; + c->click_where = where; + c->click_wp = m->wp; + + log_debug("click timer started"); + tv.tv_sec = KEYC_CLICK_TIMEOUT / 1000; + tv.tv_usec = (KEYC_CLICK_TIMEOUT % 1000) * 1000L; + evtimer_del(&c->click_timer); + evtimer_add(&c->click_timer, &tv); + } + } + /* Stop dragging if needed. */ if (type != DRAG && type != WHEEL && diff --git a/tmux.h b/tmux.h index 36549125..a3604935 100644 --- a/tmux.h +++ b/tmux.h @@ -1942,6 +1942,8 @@ struct client { struct event repeat_timer; struct event click_timer; + int click_where; + int click_wp; u_int click_button; struct mouse_event click_event; From 5721767921d78461f2d1f32e58bc51c14442a754 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 12 Jan 2026 07:50:16 +0000 Subject: [PATCH 04/12] Correctly draw indicators when pane-border-indicators is set to both. Reported by Ilya Grigoriev in GitHub issue 4780. --- screen-redraw.c | 103 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 28 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 84ab17a6..57ad0107 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -165,7 +165,7 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, if (px == wp->xoff + wp->sx + sb_w - 1) return (SCREEN_REDRAW_BORDER_RIGHT); } - } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled*/ + } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled */ if (wp->xoff == 0 && px == wp->sx + sb_w) if (!hsplit || (hsplit && py <= wp->sy / 2)) return (SCREEN_REDRAW_BORDER_RIGHT); @@ -732,6 +732,78 @@ screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, return (&wp->border_gc); } +/* Draw arrow indicator if enabled. */ +static void +screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, u_int i, + u_int j, u_int cell_type, struct window_pane *wp, + struct window_pane *active, struct grid_cell *gc) +{ + struct client *c = ctx->c; + struct session *s = c->session; + struct window *w = s->curw->window; + struct options *oo = w->options; + u_int x = ctx->ox + i, y = ctx->oy + j; + int value, arrows = 0, border; + + if (wp == NULL) + return; + if (i != wp->xoff + 1 && j != wp->yoff + 1) + return; + + value = options_get_number(oo, "pane-border-indicators"); + if (value != PANE_BORDER_ARROWS && value != PANE_BORDER_BOTH) + return; + + border = screen_redraw_pane_border(ctx, active, x, y); + if (border == SCREEN_REDRAW_INSIDE) + return; + + if (i == wp->xoff + 1) { + if (border == SCREEN_REDRAW_OUTSIDE) { + if (screen_redraw_two_panes(wp->window, 1)) { + if (active == TAILQ_FIRST(&w->panes)) + border = SCREEN_REDRAW_BORDER_BOTTOM; + else + border = SCREEN_REDRAW_BORDER_TOP; + arrows = 1; + } + } else { + if (cell_type == CELL_LEFTRIGHT) + arrows = 1; + else if (cell_type == CELL_TOPJOIN && + border == SCREEN_REDRAW_BORDER_BOTTOM) + arrows = 1; + else if (cell_type == CELL_BOTTOMJOIN && + border == SCREEN_REDRAW_BORDER_TOP) + arrows = 1; + } + } + if (j == wp->yoff + 1) { + if (border == SCREEN_REDRAW_OUTSIDE) { + if (screen_redraw_two_panes(wp->window, 0)) { + if (active == TAILQ_FIRST(&w->panes)) + border = SCREEN_REDRAW_BORDER_RIGHT; + else + border = SCREEN_REDRAW_BORDER_LEFT; + arrows = 1; + } + } else { + if (cell_type == CELL_TOPBOTTOM) + arrows = 1; + else if (cell_type == CELL_LEFTJOIN && + border == SCREEN_REDRAW_BORDER_RIGHT) + arrows = 1; + else if (cell_type == CELL_RIGHTJOIN && + border == SCREEN_REDRAW_BORDER_LEFT) + arrows = 1; + } + } + if (arrows) { + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, BORDER_MARKERS[border]); + } +} + /* Draw a border cell. */ static void screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) @@ -747,7 +819,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) const struct grid_cell *tmp; struct overlay_ranges r; u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; - int arrows = 0, border, isolates; + int isolates; if (c->overlay_check != NULL) { c->overlay_check(c, c->overlay_data, x, y, 1, &r); @@ -795,32 +867,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) if (isolates) tty_puts(tty, END_ISOLATE); - switch (options_get_number(oo, "pane-border-indicators")) { - case PANE_BORDER_ARROWS: - case PANE_BORDER_BOTH: - arrows = 1; - break; - } - - if (wp != NULL && arrows) { - border = screen_redraw_pane_border(ctx, active, x, y); - if (((i == wp->xoff + 1 && - (cell_type == CELL_LEFTRIGHT || - (cell_type == CELL_TOPJOIN && - border == SCREEN_REDRAW_BORDER_BOTTOM) || - (cell_type == CELL_BOTTOMJOIN && - border == SCREEN_REDRAW_BORDER_TOP))) || - (j == wp->yoff + 1 && - (cell_type == CELL_TOPBOTTOM || - (cell_type == CELL_LEFTJOIN && - border == SCREEN_REDRAW_BORDER_RIGHT) || - (cell_type == CELL_RIGHTJOIN && - border == SCREEN_REDRAW_BORDER_LEFT)))) && - screen_redraw_check_is(ctx, x, y, active)) { - gc.attr |= GRID_ATTR_CHARSET; - utf8_set(&gc.data, BORDER_MARKERS[border]); - } - } + screen_redraw_draw_border_arrows(ctx, i, j, cell_type, wp, active, &gc); tty_cell(tty, &gc, &grid_default_cell, NULL, NULL); if (isolates) From b89d46bb002d48905a952e57cbf6eb2f1c03daa8 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 13 Jan 2026 20:29:21 +0000 Subject: [PATCH 05/12] Add a regression test from Ilya Grigoriev. --- regress/border-arrows.sh | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 regress/border-arrows.sh diff --git a/regress/border-arrows.sh b/regress/border-arrows.sh new file mode 100644 index 00000000..7e7bffd0 --- /dev/null +++ b/regress/border-arrows.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +# Test for GitHub issue #4780 - pane-border-indicators both arrows missing +# on second pane in a two-pane horizontal split. +# +# When pane-border-indicators is set to "both", arrow indicators should +# appear when EITHER pane is selected. Before the fix, arrows only appeared +# when the LEFT pane was selected. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +$TMUX kill-server 2>/dev/null +TMUX_OUTER="$TEST_TMUX -Ltest2" +$TMUX_OUTER kill-server 2>/dev/null + +trap "$TMUX kill-server 2>/dev/null; $TMUX_OUTER kill-server 2>/dev/null" 0 1 15 + +# Start outer tmux that will capture the inner tmux's rendering +$TMUX_OUTER -f/dev/null new -d -x80 -y24 "$TMUX -f/dev/null new -x78 -y22" || exit 1 +sleep 1 + +# Set pane-border-indicators to "both" in inner tmux +$TMUX set -g pane-border-indicators both || exit 1 + +# Create horizontal split (two panes side by side) +$TMUX splitw -h || exit 1 +sleep 1 + +# Helper to check for arrow characters in captured output +has_arrow() { + echo "$1" | grep -qE '(←|→|↑|↓)' +} + +# Test 1: Select left pane (pane 0) and check for arrows +$TMUX selectp -t 0 +sleep 1 +left_output=$($TMUX_OUTER capturep -Cep 2>/dev/null) +has_arrow "$left_output" || exit 1 + +# Test 2: Select right pane (pane 1) and check for arrows +# This is the case that failed before the fix +$TMUX selectp -t 1 +sleep 1 +right_output=$($TMUX_OUTER capturep -Cep 2>/dev/null) +has_arrow "$right_output" || exit 1 + +$TMUX kill-server 2>/dev/null +$TMUX_OUTER kill-server 2>/dev/null +exit 0 From 1e5f93b7b633b47518e57cfb8b060069cf6cc9f3 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 14 Jan 2026 19:43:43 +0000 Subject: [PATCH 06/12] Add -e flag to command-prompt to close if empty, from Dane Jensen in GitHub issue 4812. --- cmd-command-prompt.c | 10 ++++++---- status.c | 5 +++++ tmux.1 | 6 +++++- tmux.h | 1 + 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 85a1bf8a..e5c2b12c 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -42,8 +42,8 @@ const struct cmd_entry cmd_command_prompt_entry = { .name = "command-prompt", .alias = NULL, - .args = { "1bFkliI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse }, - .usage = "[-1bFkliN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE + .args = { "1beFiklI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse }, + .usage = "[-1beFiklN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " [-T prompt-type] [template]", .flags = CMD_CLIENT_TFLAG, @@ -84,7 +84,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) struct client *tc = cmdq_get_target_client(item); struct cmd_find_state *target = cmdq_get_target(item); const char *type, *s, *input; - struct cmd_command_prompt_cdata *cdata; + struct cmd_command_prompt_cdata *cdata; char *tmp, *prompts, *prompt, *next_prompt; char *inputs = NULL, *next_input; u_int count = args_count(args); @@ -163,6 +163,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->flags |= PROMPT_INCREMENTAL; else if (args_has(args, 'k')) cdata->flags |= PROMPT_KEY; + else if (args_has(args, 'e')) + cdata->flags |= PROMPT_BSPACE_EXIT; status_prompt_set(tc, target, cdata->prompts[0].prompt, cdata->prompts[0].input, cmd_command_prompt_callback, cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type); @@ -234,7 +236,7 @@ out: static void cmd_command_prompt_free(void *data) { - struct cmd_command_prompt_cdata *cdata = data; + struct cmd_command_prompt_cdata *cdata = data; u_int i; for (i = 0; i < cdata->count; i++) { diff --git a/status.c b/status.c index 79ce9cad..edbb04ad 100644 --- a/status.c +++ b/status.c @@ -1383,6 +1383,11 @@ process_key: break; case KEYC_BSPACE: case 'h'|KEYC_CTRL: + if (c->prompt_flags & PROMPT_BSPACE_EXIT && size == 0) { + if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) + status_prompt_clear(c); + break; + } if (c->prompt_index != 0) { if (c->prompt_index == size) c->prompt_buffer[--c->prompt_index].size = 0; diff --git a/tmux.1 b/tmux.1 index 4b88e203..bf051794 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6716,7 +6716,7 @@ See for possible values for .Ar prompt-type . .It Xo Ic command-prompt -.Op Fl 1bFiklN +.Op Fl 1beFiklN .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target-client @@ -6789,6 +6789,10 @@ makes the prompt only accept numeric key presses. .Fl i executes the command every time the prompt input changes instead of when the user exits the command prompt. +.Fl e +makes +.Em BSpace +cancel an empty prompt. .Pp .Fl T tells diff --git a/tmux.h b/tmux.h index a3604935..e14113ff 100644 --- a/tmux.h +++ b/tmux.h @@ -2054,6 +2054,7 @@ struct client { #define PROMPT_KEY 0x10 #define PROMPT_ACCEPT 0x20 #define PROMPT_QUOTENEXT 0x40 +#define PROMPT_BSPACE_EXIT 0x80 int prompt_flags; enum prompt_type prompt_type; int prompt_cursor; From 9c0aeaff40c430ab20c434414ecb5c60fb9a584e Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 19 Jan 2026 08:20:51 +0000 Subject: [PATCH 07/12] Now the copy mode indicator can be changed, we need to redraw it when the cursor is moved. GitHub issue 4774. --- window-copy.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/window-copy.c b/window-copy.c index 87d0dea8..82c0f47f 100644 --- a/window-copy.c +++ b/window-copy.c @@ -3237,6 +3237,15 @@ window_copy_command(struct window_mode_entry *wme, struct client *c, window_pane_reset_mode(wp); else if (action == WINDOW_COPY_CMD_REDRAW) window_copy_redraw_screen(wme); + else if (action == WINDOW_COPY_CMD_NOTHING) { + /* + * Nothing is not actually nothing - most commands at least + * move the cursor (what would be the point of a command that + * literally does nothing?) and in that case we need to redraw + * the first line to update the indicator. + */ + window_copy_redraw_lines(wme, 0, 1); + } } static void From d2e09cb2597bf585ea45c0e584326a73e222a438 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 20 Jan 2026 15:42:22 +0000 Subject: [PATCH 08/12] Add a --enable-optimizations configure flag. --- Makefile.am | 7 ++++++- configure.ac | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index ff0de9f8..81c9c2fc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,7 +20,12 @@ LDADD = $(LIBOBJS) # Set flags for gcc. if IS_GCC -AM_CFLAGS += -std=gnu99 -O2 +AM_CFLAGS += -std=gnu99 +if IS_OPTIMIZED +AM_CFLAGS += -O2 +else +AM_CFLAGS += -O0 +endif if IS_DEBUG AM_CFLAGS += -g AM_CFLAGS += -Wno-long-long -Wall -W -Wformat=2 diff --git a/configure.ac b/configure.ac index e6d4b548..fa474825 100644 --- a/configure.ac +++ b/configure.ac @@ -64,6 +64,15 @@ AC_ARG_ENABLE( ) AM_CONDITIONAL(IS_DEBUG, test "x$enable_debug" = xyes) +# Is this --enable-optimizations? +AC_ARG_ENABLE( + optimizations, + AS_HELP_STRING(--enable-optimizations, enable optimization build flags), + , + enable_optimizations=yes +) +AM_CONDITIONAL(IS_OPTIMIZED, test "x$enable_optimizations" = xyes) + # Is this a static build? AC_ARG_ENABLE( static, From 0790e74f849e7774649776347ca2b4109290ab6d Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 22 Jan 2026 14:11:00 +0000 Subject: [PATCH 09/12] Fix strftime warning in gcc. --- format.c | 15 +++++++++++++-- window-clock.c | 11 ++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/format.c b/format.c index 31fba2a9..332b21c2 100644 --- a/format.c +++ b/format.c @@ -454,6 +454,17 @@ format_job_tidy(struct format_job_tree *jobs, int force) } } +/* Workaround this needless gcc warning: + * warning: format not a string literal, format string not checked [-Wformat-nonliteral] + */ +static size_t +format_strftime(char *s, size_t max, const char *time_format, const struct tm *tm) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + return strftime(s, max, time_format, tm); +#pragma GCC diagnostic pop +} + /* Tidy old jobs for all clients. */ void format_tidy_jobs(void) @@ -3963,7 +3974,7 @@ found: else { if (time_format != NULL) { localtime_r(&t, &tm); - strftime(s, sizeof s, time_format, &tm); + format_strftime(s, sizeof s, time_format, &tm); } else { ctime_r(&t, s); s[strcspn(s, "\n")] = '\0'; @@ -5540,7 +5551,7 @@ format_expand1(struct format_expand_state *es, const char *fmt) es->time = time(NULL); localtime_r(&es->time, &es->tm); } - if (strftime(expanded, sizeof expanded, fmt, &es->tm) == 0) { + if (format_strftime(expanded, sizeof expanded, fmt, &es->tm) == 0) { format_log(es, "format is too long"); return (xstrdup("")); } diff --git a/window-clock.c b/window-clock.c index 81d33858..ecac43ec 100644 --- a/window-clock.c +++ b/window-clock.c @@ -228,7 +228,6 @@ window_clock_draw_screen(struct window_mode_entry *wme) struct screen *s = &data->screen; struct grid_cell gc; char tim[64], *ptr; - const char *timeformat; time_t t; struct tm *tm; u_int i, j, x, y, idx; @@ -242,20 +241,18 @@ window_clock_draw_screen(struct window_mode_entry *wme) tm = localtime(&t); if (style == 0 || style == 2) { if (style == 2) - timeformat = "%l:%M:%S "; + strftime(tim, sizeof tim, "%l:%M:%S ", localtime(&t)); else - timeformat = "%l:%M "; - strftime(tim, sizeof tim, timeformat, localtime(&t)); + strftime(tim, sizeof tim, "%l:%M ", localtime(&t)); if (tm->tm_hour >= 12) strlcat(tim, "PM", sizeof tim); else strlcat(tim, "AM", sizeof tim); } else { if (style == 3) - timeformat = "%H:%M:%S"; + strftime(tim, sizeof tim, "%H:%M:%S", tm); else - timeformat = "%H:%M"; - strftime(tim, sizeof tim, timeformat, tm); + strftime(tim, sizeof tim, "%H:%M", tm); } screen_write_clearscreen(&ctx, 8); From 6525bb7cef57642aefcd42e58c10e22731d0f73a Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 22 Jan 2026 14:16:39 +0000 Subject: [PATCH 10/12] Line length/style nits. --- format.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/format.c b/format.c index 332b21c2..d4af471d 100644 --- a/format.c +++ b/format.c @@ -454,14 +454,13 @@ format_job_tidy(struct format_job_tree *jobs, int force) } } -/* Workaround this needless gcc warning: - * warning: format not a string literal, format string not checked [-Wformat-nonliteral] - */ +/* Work around needless -Wformat-nonliteral gcc warning. */ static size_t -format_strftime(char *s, size_t max, const char *time_format, const struct tm *tm) { +format_strftime(char *s, size_t max, const char *fmt, const struct tm *tm) +{ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" - return strftime(s, max, time_format, tm); + return (strftime(s, max, fmt, tm)); #pragma GCC diagnostic pop } @@ -5551,7 +5550,8 @@ format_expand1(struct format_expand_state *es, const char *fmt) es->time = time(NULL); localtime_r(&es->time, &es->tm); } - if (format_strftime(expanded, sizeof expanded, fmt, &es->tm) == 0) { + if (format_strftime(expanded, sizeof expanded, fmt, + &es->tm) == 0) { format_log(es, "format is too long"); return (xstrdup("")); } From 0d9c3c895cd2f38cfd1687ca6dc4bd7d31fa9415 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 22 Jan 2026 14:29:29 +0000 Subject: [PATCH 11/12] Stick the pragmas outside the function (make GCC 4 happier) and under __GNUC__. --- format.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/format.c b/format.c index d4af471d..d0090bc8 100644 --- a/format.c +++ b/format.c @@ -455,14 +455,18 @@ format_job_tidy(struct format_job_tree *jobs, int force) } /* Work around needless -Wformat-nonliteral gcc warning. */ +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif static size_t format_strftime(char *s, size_t max, const char *fmt, const struct tm *tm) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" return (strftime(s, max, fmt, tm)); -#pragma GCC diagnostic pop } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif /* Tidy old jobs for all clients. */ void From fe52f76913ae8db2b6607ab800979ec72284bbc7 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 22 Jan 2026 15:02:42 +0000 Subject: [PATCH 12/12] Fiddle with some more warnings. --- Makefile.am | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index 81c9c2fc..267f4406 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,14 +28,15 @@ AM_CFLAGS += -O0 endif if IS_DEBUG AM_CFLAGS += -g -AM_CFLAGS += -Wno-long-long -Wall -W -Wformat=2 +AM_CFLAGS += -Wno-long-long -Wall -W -Wformat=2 -Wno-use-after-free AM_CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations AM_CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare -AM_CFLAGS += -Wundef -Wbad-function-cast -Winline -Wcast-align +AM_CFLAGS += -Wundef -Wbad-function-cast -Winline -Wno-cast-align AM_CFLAGS += -Wdeclaration-after-statement -Wno-pointer-sign -Wno-attributes -AM_CFLAGS += -Wno-unused-result -Wno-format-y2k +AM_CFLAGS += -Wno-unused-result -Wno-format-y2k -Wno-unknown-warning-option +AM_CFLAGS += -Wno-maybe-uninitialized if IS_DARWIN -AM_CFLAGS += -Wno-deprecated-declarations -Wno-cast-align -Wno-macro-redefined +AM_CFLAGS += -Wno-deprecated-declarations -Wno-macro-redefined endif AM_CPPFLAGS += -DDEBUG endif