From ab1f2ef71c48bc9617db0874ca21681679f4d678 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 20 Jan 2026 16:32:05 +0000 Subject: [PATCH 1/6] Add a function to convert a screen to a string, from Michael Grant. --- screen.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tmux.h | 1 + 2 files changed, 72 insertions(+) diff --git a/screen.c b/screen.c index 12a55097..b65a9106 100644 --- a/screen.c +++ b/screen.c @@ -744,3 +744,74 @@ screen_mode_to_string(int mode) tmp[strlen(tmp) - 1] = '\0'; return (tmp); } + +const char * +screen_print(struct screen *s) +{ + static char *buf; + static size_t len = 16384; + const char *acs; + u_int x, y; + int n; + size_t last = 0; + struct utf8_data ud; + struct grid_line *gl; + struct grid_cell_entry *gce; + + if (buf == NULL) + buf = xmalloc(len); + + for (y = 0; y < screen_hsize(s) + s->grid->sy; y++) { + n = snprintf(buf + last, len - last, "%.4d \"", y); + if (n <= 0 || (u_int)n >= len - last) + goto out; + last += n; + + gl = &s->grid->linedata[y]; + for (x = 0; x < gl->cellused; x++) { + gce = &gl->celldata[x]; + if (gce->flags & GRID_FLAG_PADDING) + continue; + + if (~gce->flags & GRID_FLAG_EXTENDED) { + if (last + 2 >= len) + goto out; + buf[last++] = gce->data.data; + } else if (gce->flags & GRID_FLAG_TAB) { + if (last + 2 >= len) + goto out; + buf[last++] = '\t'; + } else if (gce->flags & GRID_ATTR_CHARSET) { + acs = tty_acs_get(NULL, gce->data.data); + if (acs != NULL) + n = strlen(acs); + else { + acs = &gce->data.data; + n = 1; + } + if (last + n + 1 >= len) + goto out; + memcpy(buf + last, acs, n); + last += n; + } else { + utf8_to_data(gl->extddata[gce->offset].data, + &ud); + if (ud.size > 0) { + if (last + ud.size + 1 >= len) + goto out; + memcpy(buf + last, ud.data, ud.size); + last += ud.size; + } + } + } + + if (last + 3 >= len) + goto out; + buf[last++] = '"'; + buf[last++] = '\n'; + } + +out: + buf[last] = '\0'; + return (buf); +} diff --git a/tmux.h b/tmux.h index e14113ff..e5145b10 100644 --- a/tmux.h +++ b/tmux.h @@ -3176,6 +3176,7 @@ void screen_select_cell(struct screen *, struct grid_cell *, void screen_alternate_on(struct screen *, struct grid_cell *, int); void screen_alternate_off(struct screen *, struct grid_cell *, int); const char *screen_mode_to_string(int); +const char *screen_print(struct screen *); /* window.c */ extern struct windows windows; From 8e06739e266db5d076c2598778b665fdac10bf7c Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 20 Jan 2026 22:50:08 +0000 Subject: [PATCH 2/6] Fix window-size=latest not resizing on switch-client in session groups. From Ilya Grigoriev in GitHub issue 4818. --- server-client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-client.c b/server-client.c index c443d4c4..d60a53c2 100644 --- a/server-client.c +++ b/server-client.c @@ -407,13 +407,13 @@ server_client_set_session(struct client *c, struct session *s) if (old != NULL && old->curw != NULL) window_update_focus(old->curw->window); if (s != NULL) { + s->curw->window->latest = c; recalculate_sizes(); window_update_focus(s->curw->window); session_update_activity(s, NULL); session_theme_changed(s); gettimeofday(&s->last_attached_time, NULL); s->curw->flags &= ~WINLINK_ALERTFLAGS; - s->curw->window->latest = c; alerts_check_session(s); tty_update_client_offset(c); status_timer_start(c); From 26aacd0e3215b4cc63682180c714b775620735c1 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 22 Jan 2026 07:39:35 +0000 Subject: [PATCH 3/6] Handle theme keys earlier so they are processed even if a popup is open. From Josh Cooper in GitHub issue 4827. --- server-client.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/server-client.c b/server-client.c index d60a53c2..9e82179b 100644 --- a/server-client.c +++ b/server-client.c @@ -2417,16 +2417,6 @@ server_client_key_callback(struct cmdq_item *item, void *data) event->key = key; } - /* Handle theme reporting keys. */ - if (key == KEYC_REPORT_LIGHT_THEME) { - server_client_report_theme(c, THEME_LIGHT); - goto out; - } - if (key == KEYC_REPORT_DARK_THEME) { - server_client_report_theme(c, THEME_DARK); - goto out; - } - /* Find affected pane. */ if (!KEYC_IS_MOUSE(key) || cmd_find_from_mouse(&fs, m, 0) != 0) cmd_find_from_client(&fs, c, 0); @@ -2645,6 +2635,19 @@ server_client_handle_key(struct client *c, struct key_event *event) if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return (0); + /* + * Handle theme reporting keys before overlays so they work even when a + * popup is open. + */ + if (event->key == KEYC_REPORT_LIGHT_THEME) { + server_client_report_theme(c, THEME_LIGHT); + return (0); + } + if (event->key == KEYC_REPORT_DARK_THEME) { + server_client_report_theme(c, THEME_DARK); + return (0); + } + /* * Key presses in overlay mode and the command prompt are a special * case. The queue might be blocked so they need to be processed From 818745a6056fdeb1e761e6c27fb91ea286b2ccbf Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 22 Jan 2026 07:42:30 +0000 Subject: [PATCH 4/6] Set PANE_STYLECHANGED when user options change, from Josh Cooper in GitHub issue 4825. --- options.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/options.c b/options.c index bfbec5c7..37160b7a 100644 --- a/options.c +++ b/options.c @@ -1226,6 +1226,10 @@ options_push_changes(const char *name) RB_FOREACH(wp, window_pane_tree, &all_window_panes) wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); } + if (*name == '@') { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) + wp->flags |= PANE_STYLECHANGED; + } if (strcmp(name, "pane-colours") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) colour_palette_from_option(&wp->palette, wp->options); From ecbf8d76d0df0dcc3c05ea59e280de1b15b149c3 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 22 Jan 2026 07:52:24 +0000 Subject: [PATCH 5/6] Reevaluate menu and popup styles on each draw to allow them to change when options change, from Josh Cooper in GitHub issues 4828 and 4829. --- menu.c | 101 +++++++++++++++++++++++++++++++++++++++++--------------- popup.c | 63 +++++++++++++++++++++++++++++++++-- 2 files changed, 135 insertions(+), 29 deletions(-) diff --git a/menu.c b/menu.c index 7449d88d..11e04279 100644 --- a/menu.c +++ b/menu.c @@ -27,9 +27,13 @@ struct menu_data { struct cmdq_item *item; int flags; - struct grid_cell style; - struct grid_cell border_style; - struct grid_cell selected_style; + char *style; + char *border_style; + char *selected_style; + + struct grid_cell style_gc; + struct grid_cell border_style_gc; + struct grid_cell selected_style_gc; enum box_lines border_lines; struct cmd_find_state fs; @@ -192,6 +196,60 @@ menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py, menu->count + 2, px, py, nx, r); } +static void +menu_reapply_styles(struct menu_data *md, struct client *c) +{ + struct session *s = c->session; + struct options *o; + struct format_tree *ft; + struct style sytmp; + + if (s == NULL) + return; + o = s->curw->window->options; + + ft = format_create_defaults(NULL, c, s, s->curw, NULL); + + /* Reapply menu style from options. */ + memcpy(&md->style_gc, &grid_default_cell, sizeof md->style_gc); + style_apply(&md->style_gc, o, "menu-style", ft); + if (md->style != NULL) { + style_set(&sytmp, &grid_default_cell); + if (style_parse(&sytmp, &md->style_gc, md->style) == 0) { + md->style_gc.fg = sytmp.gc.fg; + md->style_gc.bg = sytmp.gc.bg; + } + } + + /* Reapply selected style from options. */ + memcpy(&md->selected_style_gc, &grid_default_cell, + sizeof md->selected_style_gc); + style_apply(&md->selected_style_gc, o, "menu-selected-style", ft); + if (md->selected_style != NULL) { + style_set(&sytmp, &grid_default_cell); + if (style_parse(&sytmp, &md->selected_style_gc, + md->selected_style) == 0) { + md->selected_style_gc.fg = sytmp.gc.fg; + md->selected_style_gc.bg = sytmp.gc.bg; + } + } + + /* Reapply border style from options. */ + memcpy(&md->border_style_gc, &grid_default_cell, + sizeof md->border_style_gc); + style_apply(&md->border_style_gc, o, "menu-border-style", ft); + if (md->border_style != NULL) { + style_set(&sytmp, &grid_default_cell); + if (style_parse(&sytmp, &md->border_style_gc, + md->border_style) == 0) { + md->border_style_gc.fg = sytmp.gc.fg; + md->border_style_gc.bg = sytmp.gc.bg; + } + } + + format_free(ft); +} + void menu_draw_cb(struct client *c, void *data, __unused struct screen_redraw_ctx *rctx) @@ -203,16 +261,18 @@ menu_draw_cb(struct client *c, void *data, struct screen_write_ctx ctx; u_int i, px = md->px, py = md->py; + menu_reapply_styles(md, c); + screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); if (md->border_lines != BOX_LINES_NONE) { screen_write_box(&ctx, menu->width + 4, menu->count + 2, - md->border_lines, &md->border_style, menu->title); + md->border_lines, &md->border_style_gc, menu->title); } screen_write_menu(&ctx, menu, md->choice, md->border_lines, - &md->style, &md->border_style, &md->selected_style); + &md->style_gc, &md->border_style_gc, &md->selected_style_gc); screen_write_stop(&ctx); for (i = 0; i < screen_size_y(&md->s); i++) { @@ -234,6 +294,9 @@ menu_free_cb(__unused struct client *c, void *data) screen_free(&md->s); menu_free(md->menu); + free(md->style); + free(md->selected_style); + free(md->border_style); free(md); } @@ -470,24 +533,6 @@ menu_resize_cb(struct client *c, void *data) md->py = ny; } -static void -menu_set_style(struct client *c, struct grid_cell *gc, const char *style, - const char *option) -{ - struct style sytmp; - struct options *o = c->session->curw->window->options; - - memcpy(gc, &grid_default_cell, sizeof *gc); - style_apply(gc, o, option, NULL); - if (style != NULL) { - style_set(&sytmp, &grid_default_cell); - if (style_parse(&sytmp, gc, style) == 0) { - gc->fg = sytmp.gc.fg; - gc->bg = sytmp.gc.bg; - } - } -} - struct menu_data * menu_prepare(struct menu *menu, int flags, int starting_choice, struct cmdq_item *item, u_int px, u_int py, struct client *c, @@ -515,10 +560,12 @@ menu_prepare(struct menu *menu, int flags, int starting_choice, md->flags = flags; md->border_lines = lines; - menu_set_style(c, &md->style, style, "menu-style"); - menu_set_style(c, &md->selected_style, selected_style, - "menu-selected-style"); - menu_set_style(c, &md->border_style, border_style, "menu-border-style"); + if (style != NULL) + md->style = xstrdup(style); + if (selected_style != NULL) + md->selected_style = xstrdup(selected_style); + if (border_style != NULL) + md->border_style = xstrdup(border_style); if (fs != NULL) cmd_find_copy_state(&md->fs, fs); diff --git a/popup.c b/popup.c index 12cd7362..e8a3cbf4 100644 --- a/popup.c +++ b/popup.c @@ -33,6 +33,8 @@ struct popup_data { int flags; char *title; + char *style; + char *border_style; struct grid_cell border_cell; enum box_lines border_lines; @@ -99,6 +101,49 @@ static const struct menu_item popup_internal_menu_items[] = { { NULL, KEYC_NONE, NULL } }; +static void +popup_reapply_styles(struct popup_data *pd) +{ + struct client *c = pd->c; + struct session *s = c->session; + struct options *o; + struct format_tree *ft; + struct style sytmp; + + if (s == NULL) + return; + o = s->curw->window->options; + + ft = format_create_defaults(NULL, c, s, s->curw, NULL); + + /* Reapply popup style from options. */ + memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults); + style_apply(&pd->defaults, o, "popup-style", ft); + if (pd->style != NULL) { + style_set(&sytmp, &grid_default_cell); + if (style_parse(&sytmp, &pd->defaults, pd->style) == 0) { + pd->defaults.fg = sytmp.gc.fg; + pd->defaults.bg = sytmp.gc.bg; + } + } + pd->defaults.attr = 0; + + /* Reapply border style from options. */ + memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell); + style_apply(&pd->border_cell, o, "popup-border-style", ft); + if (pd->border_style != NULL) { + style_set(&sytmp, &grid_default_cell); + if (style_parse(&sytmp, &pd->border_cell, + pd->border_style) == 0) { + pd->border_cell.fg = sytmp.gc.fg; + pd->border_cell.bg = sytmp.gc.bg; + } + } + pd->border_cell.attr = 0; + + format_free(ft); +} + static void popup_redraw_cb(const struct tty_ctx *ttyctx) { @@ -220,6 +265,8 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) struct colour_palette *palette = &pd->palette; struct grid_cell defaults; + popup_reapply_styles(pd); + screen_init(&s, pd->sx, pd->sy, 0); screen_write_start(&ctx, &s); screen_write_clearscreen(&ctx, 8); @@ -291,6 +338,8 @@ popup_free_cb(struct client *c, void *data) colour_palette_free(&pd->palette); free(pd->title); + free(pd->style); + free(pd->border_style); free(pd); } @@ -550,7 +599,7 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) (event->key == '\033' || event->key == ('c'|KEYC_CTRL))) return (1); if (pd->job == NULL && (pd->flags & POPUP_CLOSEANYKEY) && - !KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key)) + !KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key)) return (1); if (pd->job != NULL) { if (KEYC_IS_MOUSE(event->key)) { @@ -659,6 +708,8 @@ popup_modify(struct client *c, const char *title, const char *style, pd->title = xstrdup(title); } if (border_style != NULL) { + free(pd->border_style); + pd->border_style = xstrdup(border_style); style_set(&sytmp, &pd->border_cell); if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) { pd->border_cell.fg = sytmp.gc.fg; @@ -666,6 +717,8 @@ popup_modify(struct client *c, const char *title, const char *style, } } if (style != NULL) { + free(pd->style); + pd->style = xstrdup(style); style_set(&sytmp, &pd->defaults); if (style_parse(&sytmp, &pd->defaults, style) == 0) { pd->defaults.fg = sytmp.gc.fg; @@ -676,7 +729,8 @@ popup_modify(struct client *c, const char *title, const char *style, if (lines == BOX_LINES_NONE && pd->border_lines != lines) { screen_resize(&pd->s, pd->sx, pd->sy, 1); job_resize(pd->job, pd->sx, pd->sy); - } else if (pd->border_lines == BOX_LINES_NONE && pd->border_lines != lines) { + } else if (pd->border_lines == BOX_LINES_NONE && + pd->border_lines != lines) { screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 1); job_resize(pd->job, pd->sx - 2, pd->sy - 2); } @@ -726,8 +780,13 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px, pd = xcalloc(1, sizeof *pd); pd->item = item; pd->flags = flags; + if (title != NULL) pd->title = xstrdup(title); + if (style != NULL) + pd->style = xstrdup(style); + if (border_style != NULL) + pd->border_style = xstrdup(border_style); pd->c = c; pd->c->references++; From 195a9cfd8885d631633b414495a1f2540faa2601 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 22 Jan 2026 08:55:01 +0000 Subject: [PATCH 6/6] When history-limit is changed, apply to existing panes, not just new ones. GitHub issue 4705. --- grid-view.c | 4 ++-- grid.c | 7 +++++-- options.c | 4 ++++ session.c | 26 ++++++++++++++++++++++++++ tmux.1 | 4 +--- tmux.h | 3 ++- window-copy.c | 5 +++++ 7 files changed, 45 insertions(+), 8 deletions(-) diff --git a/grid-view.c b/grid-view.c index 7590ea42..655ec9e4 100644 --- a/grid-view.c +++ b/grid-view.c @@ -82,7 +82,7 @@ grid_view_clear_history(struct grid *gd, u_int bg) /* Scroll the lines into the history. */ for (yy = 0; yy < last; yy++) { - grid_collect_history(gd); + grid_collect_history(gd, 0); grid_scroll_history(gd, bg); } if (last < gd->sy) @@ -107,7 +107,7 @@ grid_view_scroll_region_up(struct grid *gd, u_int rupper, u_int rlower, u_int bg) { if (gd->flags & GRID_HISTORY) { - grid_collect_history(gd); + grid_collect_history(gd, 0); if (rupper == 0 && rlower == gd->sy - 1) grid_scroll_history(gd, bg); else { diff --git a/grid.c b/grid.c index 7ad6770b..92a9ce76 100644 --- a/grid.c +++ b/grid.c @@ -375,14 +375,17 @@ grid_trim_history(struct grid *gd, u_int ny) * and shift up. */ void -grid_collect_history(struct grid *gd) +grid_collect_history(struct grid *gd, int all) { u_int ny; if (gd->hsize == 0 || gd->hsize < gd->hlimit) return; - ny = gd->hlimit / 10; + if (all) + ny = gd->hsize - gd->hlimit; + else + ny = gd->hlimit / 10; if (ny < 1) ny = 1; if (ny > gd->hsize) diff --git a/options.c b/options.c index 37160b7a..a847ad9c 100644 --- a/options.c +++ b/options.c @@ -1252,6 +1252,10 @@ options_push_changes(const char *name) utf8_update_width_cache(); if (strcmp(name, "input-buffer-size") == 0) input_set_buffer_size(options_get_number(global_options, name)); + if (strcmp(name, "history-limit") == 0) { + RB_FOREACH(s, sessions, &sessions) + session_update_history(s); + } RB_FOREACH(s, sessions, &sessions) status_update_cache(s); diff --git a/session.c b/session.c index 51430678..afa48637 100644 --- a/session.c +++ b/session.c @@ -768,3 +768,29 @@ session_theme_changed(struct session *s) } } } + +/* Update history for all panes. */ +void +session_update_history(struct session *s) +{ + struct winlink *wl; + struct window_pane *wp; + struct grid *gd; + u_int limit, osize; + + limit = options_get_number(s->options, "history-limit"); + RB_FOREACH(wl, winlinks, &s->windows) { + TAILQ_FOREACH(wp, &wl->window->panes, entry) { + gd = wp->base.grid; + + osize = gd->hsize; + gd->hlimit = limit; + grid_collect_history(gd, 1); + + if (gd->hsize != osize) { + log_debug("%s: %%%u %u -> %u", __func__, wp->id, + osize, gd->hsize); + } + } + } +} diff --git a/tmux.1 b/tmux.1 index bf051794..f58511f2 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4583,9 +4583,7 @@ If set to 0, messages and indicators are displayed until a key is pressed. .Ar time is in milliseconds. .It Ic history-limit Ar lines -Set the maximum number of lines held in window history. -This setting applies only to new windows - existing window histories are not -resized and retain the limit at the point they were created. +Set the maximum number of lines held in pane history. .It Ic initial-repeat-time Ar time Set the time in milliseconds for the initial repeat when a key is bound with the .Fl r diff --git a/tmux.h b/tmux.h index e5145b10..74447785 100644 --- a/tmux.h +++ b/tmux.h @@ -3002,7 +3002,7 @@ int grid_cells_look_equal(const struct grid_cell *, struct grid *grid_create(u_int, u_int, u_int); void grid_destroy(struct grid *); int grid_compare(struct grid *, struct grid *); -void grid_collect_history(struct grid *); +void grid_collect_history(struct grid *, int); void grid_remove_history(struct grid *, u_int ); void grid_scroll_history(struct grid *, u_int); void grid_scroll_history_region(struct grid *, u_int, u_int, u_int); @@ -3485,6 +3485,7 @@ u_int session_group_count(struct session_group *); u_int session_group_attached_count(struct session_group *); void session_renumber_windows(struct session *); void session_theme_changed(struct session *); +void session_update_history(struct session *); /* utf8.c */ enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *); diff --git a/window-copy.c b/window-copy.c index 82c0f47f..04cfe85d 100644 --- a/window-copy.c +++ b/window-copy.c @@ -2708,6 +2708,11 @@ window_copy_cmd_refresh_from_pane(struct window_copy_cmd_state *cs) data->backing = window_copy_clone_screen(&wp->base, &data->screen, NULL, NULL, wme->swp != wme->wp); + if (data->oy > screen_hsize(data->backing)) { + data->cy = 0; + data->oy = screen_hsize(data->backing); + } + window_copy_size_changed(wme); return (WINDOW_COPY_CMD_REDRAW); }