From ab1f2ef71c48bc9617db0874ca21681679f4d678 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 20 Jan 2026 16:32:05 +0000 Subject: [PATCH 1/7] 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/7] 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/7] 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/7] 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/7] 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/7] 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); } From f70150a6637b55de522d99b623dce51428fec6c8 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 23 Jan 2026 10:45:53 +0000 Subject: [PATCH 7/7] Replace overlay_ranges with visible_ranges which can hold more than three ranges (will be needed for floating panes); move the visible ranges checks outside of tty_draw_line and rewrite it to fix issues with partially-obscured wide characters. With Michael Grant. --- Makefile | 1 + grid.c | 2 +- menu.c | 10 +- popup.c | 67 ++++++---- screen-redraw.c | 23 +++- screen.c | 5 +- server-client.c | 59 ++++++--- tmux.h | 51 +++++--- tty-draw.c | 319 ++++++++++++++++++++++++++++++++++++++++++++++++ tty.c | 281 ++++++------------------------------------ 10 files changed, 502 insertions(+), 316 deletions(-) create mode 100644 tty-draw.c diff --git a/Makefile b/Makefile index 6cd1b1b5..14cf2fd2 100644 --- a/Makefile +++ b/Makefile @@ -116,6 +116,7 @@ SRCS= alerts.c \ style.c \ tmux.c \ tty-acs.c \ + tty-draw.c \ tty-features.c \ tty-keys.c \ tty-term.c \ diff --git a/grid.c b/grid.c index 92a9ce76..c783f7b7 100644 --- a/grid.c +++ b/grid.c @@ -235,7 +235,7 @@ grid_check_y(struct grid *gd, const char *from, u_int py) int grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) { - int flags1 = gc1->flags, flags2 = gc2->flags;; + int flags1 = gc1->flags, flags2 = gc2->flags; if (gc1->fg != gc2->fg || gc1->bg != gc2->bg) return (0); diff --git a/menu.c b/menu.c index 11e04279..c458e68b 100644 --- a/menu.c +++ b/menu.c @@ -38,6 +38,7 @@ struct menu_data { struct cmd_find_state fs; struct screen s; + struct visible_ranges r; u_int px; u_int py; @@ -185,15 +186,16 @@ menu_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) } /* Return parts of the input range which are not obstructed by the menu. */ -void +struct visible_ranges * menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py, - u_int nx, struct overlay_ranges *r) + u_int nx) { struct menu_data *md = data; struct menu *menu = md->menu; server_client_overlay_range(md->px, md->py, menu->width + 4, - menu->count + 2, px, py, nx, r); + menu->count + 2, px, py, nx, &md->r); + return (&md->r); } static void @@ -292,7 +294,9 @@ menu_free_cb(__unused struct client *c, void *data) if (md->cb != NULL) md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data); + free(md->r.ranges); screen_free(&md->s); + menu_free(md->menu); free(md->style); free(md->selected_style); diff --git a/popup.c b/popup.c index e8a3cbf4..70f4def4 100644 --- a/popup.c +++ b/popup.c @@ -42,6 +42,9 @@ struct popup_data { struct grid_cell defaults; struct colour_palette palette; + struct visible_ranges r; + struct visible_ranges or[2]; + struct job *job; struct input_ctx *ictx; int status; @@ -210,48 +213,57 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) } /* Return parts of the input range which are not obstructed by the popup. */ -static void -popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx, - struct overlay_ranges *r) +static struct visible_ranges * +popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) { struct popup_data *pd = data; - struct overlay_ranges or[2]; + struct visible_ranges *r = &pd->r; + struct visible_ranges *mr; u_int i, j, k = 0; if (pd->md != NULL) { - /* Check each returned range for the menu against the popup. */ - menu_check_cb(c, pd->md, px, py, nx, r); - for (i = 0; i < 2; i++) { + /* + * Work out the visible ranges for the menu (that is, the + * ranges not covered by the menu). A menu should have at most + * two ranges and we rely on this being the case. + */ + mr = menu_check_cb(c, pd->md, px, py, nx); + if (mr->used > 2) + fatalx("too many menu ranges"); + + /* + * Walk the ranges still visible under the menu and check if + * each is visible under the popup as well. At most there can be + * three total ranges if popup and menu do not intersect. + */ + for (i = 0; i < mr->used; i++) { server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, r->px[i], py, r->nx[i], &or[i]); + pd->sy, r->ranges[i].px, py, r->ranges[i].nx, + &pd->or[i]); } /* - * or has up to OVERLAY_MAX_RANGES non-overlapping ranges, - * ordered from left to right. Collect them in the output. + * We now have nonoverlapping ranges from left to right. + * Combine them together into the output. */ - for (i = 0; i < 2; i++) { - /* Each or[i] only has 2 ranges. */ - for (j = 0; j < 2; j++) { - if (or[i].nx[j] > 0) { - r->px[k] = or[i].px[j]; - r->nx[k] = or[i].nx[j]; - k++; - } + server_client_ensure_ranges(r, 3); + for (i = 0; i < mr->used; i++) { + for (j = 0; j < pd->or[i].used; j++) { + if (pd->or[i].ranges[j].nx == 0) + continue; + if (k >= 3) + fatalx("too many popup & menu ranges"); + r->ranges[k].px = pd->or[i].ranges[j].px; + r->ranges[k].nx = pd->or[i].ranges[j].nx; + k++; } } - - /* Zero remaining ranges if any. */ - for (i = k; i < OVERLAY_MAX_RANGES; i++) { - r->px[i] = 0; - r->nx[i] = 0; - } - - return; + return (r); } server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx, r); + return (r); } static void @@ -334,6 +346,9 @@ popup_free_cb(struct client *c, void *data) job_free(pd->job); input_free(pd->ictx); + free(pd->or[0].ranges); + free(pd->or[1].ranges); + free(pd->r.ranges); screen_free(&pd->s); colour_palette_free(&pd->palette); diff --git a/screen-redraw.c b/screen-redraw.c index 57ad0107..138edd52 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -817,13 +817,14 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) struct window_pane *wp, *active = server_client_get_pane(c); struct grid_cell gc; const struct grid_cell *tmp; - struct overlay_ranges r; - u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; + u_int cell_type; + u_int x = ctx->ox + i, y = ctx->oy + j; int isolates; + struct visible_ranges *r; if (c->overlay_check != NULL) { - c->overlay_check(c, c->overlay_data, x, y, 1, &r); - if (r.nx[0] + r.nx[1] == 0) + r = c->overlay_check(c, c->overlay_data, x, y, 1); + if (server_client_ranges_is_empty(r)) return; } @@ -943,7 +944,9 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct screen *s = wp->screen; struct colour_palette *palette = &wp->palette; struct grid_cell defaults; - u_int i, j, top, x, y, width; + struct visible_ranges *r; + struct visible_range *rr; + u_int i, j, k, top, x, y, width; if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); @@ -988,7 +991,15 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, x, y, width); tty_default_colours(&defaults, wp); - tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette); + + r = tty_check_overlay_range(tty, x, y, width); + for (k = 0; k < r->used; k++) { + rr = &r->ranges[k]; + if (rr->nx != 0) { + tty_draw_line(tty, s, rr->px - wp->xoff, j, + rr->nx, rr->px, y, &defaults, palette); + } + } } } diff --git a/screen.c b/screen.c index b65a9106..4f382ded 100644 --- a/screen.c +++ b/screen.c @@ -564,12 +564,12 @@ screen_check_selection(struct screen *s, u_int px, u_int py) } /* Get selected grid cell. */ -void +int screen_select_cell(struct screen *s, struct grid_cell *dst, const struct grid_cell *src) { if (s->sel == NULL || s->sel->hidden) - return; + return (0); memcpy(dst, &s->sel->cell, sizeof *dst); if (COLOUR_DEFAULT(dst->fg)) @@ -583,6 +583,7 @@ screen_select_cell(struct screen *s, struct grid_cell *dst, dst->attr |= (src->attr & GRID_ATTR_CHARSET); else dst->attr |= src->attr; + return (1); } /* Reflow wrapped lines. */ diff --git a/server-client.c b/server-client.c index 9e82179b..050ca10a 100644 --- a/server-client.c +++ b/server-client.c @@ -165,38 +165,58 @@ server_client_clear_overlay(struct client *c) server_redraw_client(c); } +/* Are these ranges empty? That is, nothing is visible. */ +int +server_client_ranges_is_empty(struct visible_ranges *r) +{ + u_int i; + + for (i = 0; i < r->used; i++) { + if (r->ranges[i].nx != 0) + return (0); + } + return (1); +} + +/* Ensure we have space for at least n ranges. */ +void +server_client_ensure_ranges(struct visible_ranges *r, u_int n) +{ + if (r->size >= n) + return; + r->ranges = xrecallocarray(r->ranges, r->size, n, sizeof *r->ranges); + r->size = n; +} + /* * Given overlay position and dimensions, return parts of the input range which * are visible. */ void server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, - u_int py, u_int nx, struct overlay_ranges *r) + u_int py, u_int nx, struct visible_ranges *r) { u_int ox, onx; - /* Return up to 2 ranges. */ - r->px[2] = 0; - r->nx[2] = 0; - /* Trivial case of no overlap in the y direction. */ if (py < y || py > y + sy - 1) { - r->px[0] = px; - r->nx[0] = nx; - r->px[1] = 0; - r->nx[1] = 0; + server_client_ensure_ranges(r, 1); + r->ranges[0].px = px; + r->ranges[0].nx = nx; + r->used = 1; return; } + server_client_ensure_ranges(r, 2); /* Visible bit to the left of the popup. */ if (px < x) { - r->px[0] = px; - r->nx[0] = x - px; - if (r->nx[0] > nx) - r->nx[0] = nx; + r->ranges[0].px = px; + r->ranges[0].nx = x - px; + if (r->ranges[0].nx > nx) + r->ranges[0].nx = nx; } else { - r->px[0] = 0; - r->nx[0] = 0; + r->ranges[0].px = 0; + r->ranges[0].nx = 0; } /* Visible bit to the right of the popup. */ @@ -205,12 +225,13 @@ server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, ox = px; onx = px + nx; if (onx > ox) { - r->px[1] = ox; - r->nx[1] = onx - ox; + r->ranges[1].px = ox; + r->ranges[1].nx = onx - ox; } else { - r->px[1] = 0; - r->nx[1] = 0; + r->ranges[1].px = 0; + r->ranges[1].nx = 0; } + r->used = 2; } /* Check if this client is inside this server. */ diff --git a/tmux.h b/tmux.h index 74447785..545b002f 100644 --- a/tmux.h +++ b/tmux.h @@ -929,7 +929,7 @@ struct screen_sel; struct screen_titles; struct screen { char *title; - char *path; + char *path; struct screen_titles *titles; struct grid *grid; /* grid data */ @@ -1500,6 +1500,19 @@ struct key_event { size_t len; }; +/* Visible range array element. */ +struct visible_range { + u_int px; /* start */ + u_int nx; /* length */ +}; + +/* Visible areas not obstructed. */ +struct visible_ranges { + struct visible_range *ranges; /* dynamically allocated array */ + u_int used; /* number of entries in ranges */ + u_int size; /* allocated capacity of ranges */ +}; + /* Terminal definition. */ struct tty_term { char *name; @@ -1564,6 +1577,7 @@ struct tty { size_t discarded; struct termios tio; + struct visible_ranges r; struct grid_cell cell; struct grid_cell last_cell; @@ -1880,22 +1894,15 @@ struct client_window { }; RB_HEAD(client_windows, client_window); -/* Visible areas not obstructed by overlays. */ -#define OVERLAY_MAX_RANGES 3 -struct overlay_ranges { - u_int px[OVERLAY_MAX_RANGES]; - u_int nx[OVERLAY_MAX_RANGES]; -}; - /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); -typedef void (*overlay_check_cb)(struct client*, void *, u_int, u_int, u_int, - struct overlay_ranges *); +typedef struct visible_ranges *(*overlay_check_cb)(struct client*, void *, + u_int, u_int, u_int); typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *, - u_int *); + u_int *); typedef void (*overlay_draw_cb)(struct client *, void *, - struct screen_redraw_ctx *); + struct screen_redraw_ctx *); typedef int (*overlay_key_cb)(struct client *, void *, struct key_event *); typedef void (*overlay_free_cb)(struct client *, void *); typedef void (*overlay_resize_cb)(struct client *, void *); @@ -2487,6 +2494,8 @@ void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); void tty_cursor(struct tty *, u_int, u_int); +int tty_fake_bce(const struct tty *, const struct grid_cell *, u_int); +void tty_repeat_space(struct tty *, u_int); void tty_clipboard_query(struct tty *); void tty_putcode(struct tty *, enum tty_code_code); void tty_putcode_i(struct tty *, enum tty_code_code, int); @@ -2511,7 +2520,15 @@ void tty_repeat_requests(struct tty *, int); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); void tty_set_path(struct tty *, const char *); +void tty_default_attributes(struct tty *, const struct grid_cell *, + struct colour_palette *, u_int, struct hyperlinks *); void tty_update_mode(struct tty *, int, struct screen *); +const struct grid_cell *tty_check_codeset(struct tty *, + const struct grid_cell *); +struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, + u_int); + +/* tty-draw.c */ void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, u_int, u_int, const struct grid_cell *, struct colour_palette *); void tty_sync_start(struct tty *); @@ -2858,8 +2875,10 @@ void server_client_set_overlay(struct client *, u_int, overlay_check_cb, overlay_mode_cb, overlay_draw_cb, overlay_key_cb, overlay_free_cb, overlay_resize_cb, void *); void server_client_clear_overlay(struct client *); +void server_client_ensure_ranges(struct visible_ranges *, u_int); +int server_client_ranges_is_empty(struct visible_ranges *); void server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, - u_int, struct overlay_ranges *); + u_int, struct visible_ranges *); void server_client_set_key_table(struct client *, const char *); const char *server_client_get_key_table(struct client *); int server_client_check_nested(struct client *); @@ -3171,7 +3190,7 @@ void screen_set_selection(struct screen *, u_int, u_int, u_int, u_int, void screen_clear_selection(struct screen *); void screen_hide_selection(struct screen *); int screen_check_selection(struct screen *, u_int, u_int); -void screen_select_cell(struct screen *, struct grid_cell *, +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); @@ -3557,8 +3576,8 @@ int menu_display(struct menu *, int, int, struct cmdq_item *, const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); -void menu_check_cb(struct client *, void *, u_int, u_int, u_int, - struct overlay_ranges *); +struct visible_ranges *menu_check_cb(struct client *, void *, u_int, u_int, + u_int); void menu_draw_cb(struct client *, void *, struct screen_redraw_ctx *); void menu_free_cb(struct client *, void *); diff --git a/tty-draw.c b/tty-draw.c new file mode 100644 index 00000000..eac92a8f --- /dev/null +++ b/tty-draw.c @@ -0,0 +1,319 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include + +#include "tmux.h" + +enum tty_draw_line_state { + TTY_DRAW_LINE_FIRST, + TTY_DRAW_LINE_FLUSH, + TTY_DRAW_LINE_NEW1, + TTY_DRAW_LINE_NEW2, + TTY_DRAW_LINE_EMPTY, + TTY_DRAW_LINE_SAME, + TTY_DRAW_LINE_PAD, + TTY_DRAW_LINE_DONE +}; +static const char* tty_draw_line_states[] = { + "FIRST", + "FLUSH", + "NEW1", + "NEW2", + "EMPTY", + "SAME", + "PAD", + "DONE" +}; + +/* Clear part of the line. */ +static void +tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx, + const struct grid_cell *defaults, u_int bg, int wrapped) +{ + /* Nothing to clear. */ + if (nx == 0) + return; + + /* If genuine BCE is available, can try escape sequences. */ + if (!wrapped && nx >= 10 && !tty_fake_bce(tty, defaults, bg)) { + /* Off the end of the line, use EL if available. */ + if (px + nx >= tty->sx && tty_term_has(tty->term, TTYC_EL)) { + tty_cursor(tty, px, py); + tty_putcode(tty, TTYC_EL); + return; + } + + /* At the start of the line. Use EL1. */ + if (px == 0 && tty_term_has(tty->term, TTYC_EL1)) { + tty_cursor(tty, px + nx - 1, py); + tty_putcode(tty, TTYC_EL1); + return; + } + + /* Section of line. Use ECH if possible. */ + if (tty_term_has(tty->term, TTYC_ECH)) { + tty_cursor(tty, px, py); + tty_putcode_i(tty, TTYC_ECH, nx); + return; + } + } + + /* Couldn't use an escape sequence, use spaces. */ + if (px != 0 || !wrapped) + tty_cursor(tty, px, py); + if (nx == 1) + tty_putc(tty, ' '); + else if (nx == 2) + tty_putn(tty, " ", 2, 2); + else + tty_repeat_space(tty, nx); +} + +/* Is this cell empty? */ +static u_int +tty_draw_line_get_empty(const struct grid_cell *gc, u_int nx) +{ + u_int empty = 0; + + if (gc->data.width != 1 && gc->data.width > nx) + empty = nx; + else if (gc->attr == 0 && gc->link == 0) { + if (gc->flags & GRID_FLAG_CLEARED) + empty = 1; + else if (gc->flags & GRID_FLAG_TAB) + empty = gc->data.width; + else if (gc->data.size == 1 && *gc->data.data == ' ') + empty = 1; + } + return (empty); +} + +/* Draw a line from screen to tty. */ +void +tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, + u_int atx, u_int aty, const struct grid_cell *defaults, + struct colour_palette *palette) +{ + struct grid *gd = s->grid; + const struct grid_cell *gcp; + struct grid_cell gc, ngc, last; + struct grid_line *gl; + u_int i, j, last_i, cx, ex, width; + u_int cellsize, bg; + int flags, empty, wrapped = 0; + char buf[1000]; + size_t len; + enum tty_draw_line_state current_state, next_state; + + /* + * py is the line in the screen to draw. px is the start x and nx is + * the width to draw. atx,aty is the line on the terminal to draw it. + */ + log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__, px, py, nx, + atx, aty); + + /* + * Clamp the width to cellsize - note this is not cellused, because + * there may be empty background cells after it (from BCE). + */ + cellsize = grid_get_line(gd, gd->hsize + py)->cellsize; + if (screen_size_x(s) > cellsize) + ex = cellsize; + else { + ex = screen_size_x(s); + if (px > ex) + return; + if (px + nx > ex) + nx = ex - px; + } + if (ex < nx) + ex = nx; + log_debug("%s: drawing %u-%u,%u (end %u) at %u,%u; defaults: fg=%d, " + "bg=%d", __func__, px, px + nx, py, ex, atx, aty, defaults->fg, + defaults->bg); + + /* + * If there is padding at the start, we must have truncated a wide + * character. Clear it. + */ + cx = 0; + for (i = px; i < px + nx; i++) { + grid_view_get_cell(gd, i, py, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + cx++; + } + if (cx != 0) { + /* Find the previous cell for the background colour. */ + for (i = px + 1; i > 0; i--) { + grid_view_get_cell(gd, i - 1, py, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + } + if (i == 0) + bg = defaults->bg; + else { + bg = gc.bg; + if (gc.flags & GRID_FLAG_SELECTED) { + memcpy(&ngc, &gc, sizeof ngc); + if (screen_select_cell(s, &ngc, &gc)) + bg = ngc.bg; + } + } + tty_attributes(tty, &last, defaults, palette, s->hyperlinks); + log_debug("%s: clearing %u padding cells", __func__, cx); + tty_draw_line_clear(tty, atx, aty, cx, defaults, bg, 0); + if (cx == ex) + return; + atx += cx; + px += cx; + nx -= cx; + ex -= cx; + } + + /* Did the previous line wrap on to this one? */ + if (py != 0 && atx == 0 && tty->cx >= tty->sx && nx == tty->sx) { + gl = grid_get_line(gd, gd->hsize + py - 1); + if (gl->flags & GRID_LINE_WRAPPED) + wrapped = 1; + } + + /* Turn off cursor while redrawing and reset region and margins. */ + flags = (tty->flags & TTY_NOCURSOR); + tty->flags |= TTY_NOCURSOR; + tty_update_mode(tty, tty->mode, s); + tty_region_off(tty); + tty_margin_off(tty); + + /* Start with the default cell as the last cell. */ + memcpy(&last, &grid_default_cell, sizeof last); + last.bg = defaults->bg; + tty_default_attributes(tty, defaults, palette, 8, s->hyperlinks); + + /* Loop over each character in the range. */ + last_i = i = 0; + len = 0; + width = 0; + current_state = TTY_DRAW_LINE_FIRST; + for (;;) { + /* Work out the next state. */ + if (i == nx) { + /* + * If this is the last cell, we are done. But we need to + * go through the loop again to flush anything in + * the buffer. + */ + empty = 0; + next_state = TTY_DRAW_LINE_DONE; + } else { + /* Get the current cell. */ + grid_view_get_cell(gd, px + i, py, &gc); + + /* Update for codeset if needed. */ + gcp = tty_check_codeset(tty, &gc); + + /* And for selection. */ + if (gcp->flags & GRID_FLAG_SELECTED) { + memcpy(&ngc, gcp, sizeof ngc); + if (screen_select_cell(s, &ngc, gcp)) + gcp = &ngc; + } + + /* Work out the the empty width. */ + if (i >= ex) + empty = 1; + else + empty = tty_draw_line_get_empty(gcp, nx - i); + + /* Work out the next state. */ + if (empty != 0) + next_state = TTY_DRAW_LINE_EMPTY; + else if (current_state == TTY_DRAW_LINE_FIRST) + next_state = TTY_DRAW_LINE_SAME; + else if (gcp->flags & GRID_FLAG_PADDING) + next_state = TTY_DRAW_LINE_PAD; + else if (grid_cells_look_equal(gcp, &last)) { + if (gcp->data.size > (sizeof buf) - len) + next_state = TTY_DRAW_LINE_FLUSH; + else + next_state = TTY_DRAW_LINE_SAME; + } else if (current_state == TTY_DRAW_LINE_NEW1) + next_state = TTY_DRAW_LINE_NEW2; + else + next_state = TTY_DRAW_LINE_NEW1; + } + log_debug("%s: cell %u empty %u, bg %u; state: current %s, " + "next %s", __func__, px + i, empty, gcp->bg, + tty_draw_line_states[current_state], + tty_draw_line_states[next_state]); + + /* If the state has changed, flush any collected data. */ + if (next_state != current_state) { + if (current_state == TTY_DRAW_LINE_EMPTY) { + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); + tty_draw_line_clear(tty, atx + last_i, aty, + i - last_i, defaults, last.bg, wrapped); + wrapped = 0; + } else if (next_state != TTY_DRAW_LINE_SAME && + len != 0) { + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); + if (atx + i - width != 0 || !wrapped) + tty_cursor(tty, atx + i - width, aty); + if (~last.attr & GRID_ATTR_CHARSET) + tty_putn(tty, buf, len, width); + else { + for (j = 0; j < len; j++) + tty_putc(tty, buf[j]); + } + len = 0; + width = 0; + wrapped = 0; + } + last_i = i; + } + + /* Append the cell if it is not empty and not padding. */ + if (next_state != TTY_DRAW_LINE_EMPTY && + next_state != TTY_DRAW_LINE_PAD) { + memcpy(buf + len, gcp->data.data, gcp->data.size); + len += gcp->data.size; + width += gcp->data.width; + } + + /* If this is the last cell, we are done. */ + if (next_state == TTY_DRAW_LINE_DONE) + break; + + /* Otherwise move to the next. */ + current_state = next_state; + memcpy(&last, gcp, sizeof last); + if (empty != 0) + i += empty; + else + i += gcp->data.width; + } + + tty->flags = (tty->flags & ~TTY_NOCURSOR)|flags; + tty_update_mode(tty, tty->mode, s); +} + diff --git a/tty.c b/tty.c index 8b670b3f..6e413572 100644 --- a/tty.c +++ b/tty.c @@ -61,18 +61,11 @@ static void tty_region(struct tty *, u_int, u_int); static void tty_margin_pane(struct tty *, const struct tty_ctx *); static void tty_margin(struct tty *, u_int, u_int); static int tty_large_region(struct tty *, const struct tty_ctx *); -static int tty_fake_bce(const struct tty *, const struct grid_cell *, - u_int); static void tty_redraw_region(struct tty *, const struct tty_ctx *); static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); -static void tty_repeat_space(struct tty *, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); -static void tty_default_attributes(struct tty *, const struct grid_cell *, - struct colour_palette *, u_int, struct hyperlinks *); static int tty_check_overlay(struct tty *, u_int, u_int); -static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, - struct overlay_ranges *); #define tty_use_margin(tty) \ (tty->term->flags & TERM_DECSLRM) @@ -523,6 +516,8 @@ void tty_free(struct tty *tty) { tty_close(tty); + + free(tty->r.ranges); } void @@ -907,7 +902,7 @@ tty_emulate_repeat(struct tty *tty, enum tty_code_code code, } } -static void +void tty_repeat_space(struct tty *tty, u_int n) { static char s[500]; @@ -1066,7 +1061,7 @@ tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx) * Return if BCE is needed but the terminal doesn't have it - it'll need to be * emulated. */ -static int +int tty_fake_bce(const struct tty *tty, const struct grid_cell *gc, u_int bg) { if (tty_term_flag(tty->term, TTYC_BCE)) @@ -1161,8 +1156,6 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, u_int px, u_int nx, u_int bg) { struct client *c = tty->client; - struct overlay_ranges r; - u_int i; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); @@ -1198,13 +1191,8 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, * Couldn't use an escape sequence, use spaces. Clear only the visible * bit if there is an overlay. */ - tty_check_overlay_range(tty, px, py, nx, &r); - for (i = 0; i < OVERLAY_MAX_RANGES; i++) { - if (r.nx[i] == 0) - continue; - tty_cursor(tty, r.px[i], py); - tty_repeat_space(tty, r.nx[i]); - } + tty_cursor(tty, px, py); + tty_repeat_space(tty, nx); } /* Clear a line, adjusting to visible part of pane. */ @@ -1385,7 +1373,7 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) } } -static const struct grid_cell * +const struct grid_cell * tty_check_codeset(struct tty *tty, const struct grid_cell *gc) { static struct grid_cell new; @@ -1418,229 +1406,34 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) return (&new); } -/* - * Check if a single character is obstructed by the overlay and return a - * boolean. - */ +/* Check if a single character is covered by the overlay. */ static int tty_check_overlay(struct tty *tty, u_int px, u_int py) { - struct overlay_ranges r; + struct visible_ranges *r; /* - * A unit width range will always return nx[2] == 0 from a check, even - * with multiple overlays, so it's sufficient to check just the first - * two entries. + * With a single character, if there is anything visible (that is, the + * range is not empty), it must be that character. */ - tty_check_overlay_range(tty, px, py, 1, &r); - if (r.nx[0] + r.nx[1] == 0) - return (0); - return (1); + r = tty_check_overlay_range(tty, px, py, 1); + return (!server_client_ranges_is_empty(r)); } /* Return parts of the input range which are visible. */ -static void -tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, - struct overlay_ranges *r) +struct visible_ranges * +tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx) { struct client *c = tty->client; if (c->overlay_check == NULL) { - r->px[0] = px; - r->nx[0] = nx; - r->px[1] = 0; - r->nx[1] = 0; - r->px[2] = 0; - r->nx[2] = 0; - return; + server_client_ensure_ranges(&tty->r, 1); + tty->r.ranges[0].px = px; + tty->r.ranges[0].nx = nx; + tty->r.used = 1; + return (&tty->r); } - - c->overlay_check(c, c->overlay_data, px, py, nx, r); -} - -void -tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, - u_int atx, u_int aty, const struct grid_cell *defaults, - struct colour_palette *palette) -{ - struct grid *gd = s->grid; - struct grid_cell gc, last; - const struct grid_cell *gcp; - struct grid_line *gl; - struct client *c = tty->client; - struct overlay_ranges r; - u_int i, j, ux, sx, width, hidden, eux, nxx; - u_int cellsize; - int flags, cleared = 0, wrapped = 0; - char buf[512]; - size_t len; - - log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__, - px, py, nx, atx, aty); - log_debug("%s: defaults: fg=%d, bg=%d", __func__, defaults->fg, - defaults->bg); - - /* - * py is the line in the screen to draw. - * px is the start x and nx is the width to draw. - * atx,aty is the line on the terminal to draw it. - */ - - flags = (tty->flags & TTY_NOCURSOR); - tty->flags |= TTY_NOCURSOR; - tty_update_mode(tty, tty->mode, s); - - tty_region_off(tty); - tty_margin_off(tty); - - /* - * Clamp the width to cellsize - note this is not cellused, because - * there may be empty background cells after it (from BCE). - */ - sx = screen_size_x(s); - if (nx > sx) - nx = sx; - cellsize = grid_get_line(gd, gd->hsize + py)->cellsize; - if (sx > cellsize) - sx = cellsize; - if (sx > tty->sx) - sx = tty->sx; - if (sx > nx) - sx = nx; - ux = 0; - - if (py == 0) - gl = NULL; - else - gl = grid_get_line(gd, gd->hsize + py - 1); - if (gl == NULL || - (~gl->flags & GRID_LINE_WRAPPED) || - atx != 0 || - tty->cx < tty->sx || - nx < tty->sx) { - if (nx < tty->sx && - atx == 0 && - px + sx != nx && - tty_term_has(tty->term, TTYC_EL1) && - !tty_fake_bce(tty, defaults, 8) && - c->overlay_check == NULL) { - tty_default_attributes(tty, defaults, palette, 8, - s->hyperlinks); - tty_cursor(tty, nx - 1, aty); - tty_putcode(tty, TTYC_EL1); - cleared = 1; - } - } else { - log_debug("%s: wrapped line %u", __func__, aty); - wrapped = 1; - } - - memcpy(&last, &grid_default_cell, sizeof last); - len = 0; - width = 0; - - for (i = 0; i < sx; i++) { - grid_view_get_cell(gd, px + i, py, &gc); - gcp = tty_check_codeset(tty, &gc); - if (len != 0 && - (!tty_check_overlay(tty, atx + ux + width, aty) || - (gcp->attr & GRID_ATTR_CHARSET) || - gcp->flags != last.flags || - gcp->attr != last.attr || - gcp->fg != last.fg || - gcp->bg != last.bg || - gcp->us != last.us || - gcp->link != last.link || - ux + width + gcp->data.width > nx || - (sizeof buf) - len < gcp->data.size)) { - tty_attributes(tty, &last, defaults, palette, - s->hyperlinks); - if (last.flags & GRID_FLAG_CLEARED) { - log_debug("%s: %zu cleared", __func__, len); - tty_clear_line(tty, defaults, aty, atx + ux, - width, last.bg); - } else { - if (!wrapped || atx != 0 || ux != 0) - tty_cursor(tty, atx + ux, aty); - tty_putn(tty, buf, len, width); - } - ux += width; - - len = 0; - width = 0; - wrapped = 0; - } - - if (gcp->flags & GRID_FLAG_SELECTED) - screen_select_cell(s, &last, gcp); - else - memcpy(&last, gcp, sizeof last); - - tty_check_overlay_range(tty, atx + ux, aty, gcp->data.width, - &r); - hidden = 0; - for (j = 0; j < OVERLAY_MAX_RANGES; j++) - hidden += r.nx[j]; - hidden = gcp->data.width - hidden; - if (hidden != 0 && hidden == gcp->data.width) { - if (~gcp->flags & GRID_FLAG_PADDING) - ux += gcp->data.width; - } else if (hidden != 0 || ux + gcp->data.width > nx) { - if (~gcp->flags & GRID_FLAG_PADDING) { - tty_attributes(tty, &last, defaults, palette, - s->hyperlinks); - for (j = 0; j < OVERLAY_MAX_RANGES; j++) { - if (r.nx[j] == 0) - continue; - /* Effective width drawn so far. */ - eux = r.px[j] - atx; - if (eux < nx) { - tty_cursor(tty, r.px[j], aty); - nxx = nx - eux; - if (r.nx[j] > nxx) - r.nx[j] = nxx; - tty_repeat_space(tty, r.nx[j]); - ux = eux + r.nx[j]; - } - } - } - } else if (gcp->attr & GRID_ATTR_CHARSET) { - tty_attributes(tty, &last, defaults, palette, - s->hyperlinks); - tty_cursor(tty, atx + ux, aty); - for (j = 0; j < gcp->data.size; j++) - tty_putc(tty, gcp->data.data[j]); - ux += gcp->data.width; - } else if (~gcp->flags & GRID_FLAG_PADDING) { - memcpy(buf + len, gcp->data.data, gcp->data.size); - len += gcp->data.size; - width += gcp->data.width; - } - } - if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) { - tty_attributes(tty, &last, defaults, palette, s->hyperlinks); - if (last.flags & GRID_FLAG_CLEARED) { - log_debug("%s: %zu cleared (end)", __func__, len); - tty_clear_line(tty, defaults, aty, atx + ux, width, - last.bg); - } else { - if (!wrapped || atx != 0 || ux != 0) - tty_cursor(tty, atx + ux, aty); - tty_putn(tty, buf, len, width); - } - ux += width; - } - - if (!cleared && ux < nx) { - log_debug("%s: %u to end of line (%zu cleared)", __func__, - nx - ux, len); - tty_default_attributes(tty, defaults, palette, 8, - s->hyperlinks); - tty_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8); - } - - tty->flags = (tty->flags & ~TTY_NOCURSOR) | flags; - tty_update_mode(tty, tty->mode, s); + return (c->overlay_check(c, c->overlay_data, px, py, nx)); } void @@ -2103,7 +1896,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; - struct overlay_ranges r; + struct visible_ranges *r; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2120,9 +1913,9 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { - tty_check_overlay_range(tty, px, py, gcp->data.width, &r); - for (i = 0; i < OVERLAY_MAX_RANGES; i++) - vis += r.nx[i]; + r = tty_check_overlay_range(tty, px, py, gcp->data.width); + for (i = 0; i < r->used; i++) + vis += r->ranges[i].nx; if (vis < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, px, py, &ctx->defaults, ctx->palette); @@ -2148,7 +1941,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { - struct overlay_ranges r; + struct visible_ranges *r; + struct visible_range *rr; u_int i, px, py, cx; char *cp = ctx->ptr; @@ -2173,20 +1967,21 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, ctx->s->hyperlinks); + tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, + ctx->s->hyperlinks); /* Get tty position from pane position for overlay check. */ px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; - tty_check_overlay_range(tty, px, py, ctx->num, &r); - for (i = 0; i < OVERLAY_MAX_RANGES; i++) { - if (r.nx[i] == 0) - continue; - /* Convert back to pane position for printing. */ - cx = r.px[i] - ctx->xoff + ctx->wox; - tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); - tty_putn(tty, cp + r.px[i] - px, r.nx[i], r.nx[i]); + r = tty_check_overlay_range(tty, px, py, ctx->num); + for (i = 0; i < r->used; i++) { + rr = &r->ranges[i]; + if (rr->nx != 0) { + cx = rr->px - ctx->xoff + ctx->wox; + tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); + tty_putn(tty, cp + rr->px - px, rr->nx, rr->nx); + } } } @@ -3081,7 +2876,7 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) } } -static void +void tty_default_attributes(struct tty *tty, const struct grid_cell *defaults, struct colour_palette *palette, u_int bg, struct hyperlinks *hl) {