From 25f72cf2401cde45e69c9d9bc876b76f540f1cac Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 20 Jan 2026 21:08:49 +0000 Subject: [PATCH 1/7] Rewrite tty_draw_line to be simpler and not to check overlay ranges. --- Makefile.am | 1 + grid.c | 2 +- tmux.h | 6 ++ tty-draw.c | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tty.c | 196 +--------------------------------- 5 files changed, 313 insertions(+), 194 deletions(-) create mode 100644 tty-draw.c diff --git a/Makefile.am b/Makefile.am index 81c9c2fc..12bfddf5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -195,6 +195,7 @@ dist_tmux_SOURCES = \ tmux.h \ tmux-protocol.h \ tty-acs.c \ + tty-draw.c \ tty-features.c \ tty-keys.c \ tty-term.c \ diff --git a/grid.c b/grid.c index 7ad6770b..edfbe494 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/tmux.h b/tmux.h index 1917ee19..33da541a 100644 --- a/tmux.h +++ b/tmux.h @@ -2522,6 +2522,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); @@ -2546,7 +2548,11 @@ 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 *); + +/* 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 *); diff --git a/tty-draw.c b/tty-draw.c new file mode 100644 index 00000000..d79325c3 --- /dev/null +++ b/tty-draw.c @@ -0,0 +1,302 @@ +/* $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(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; + struct grid_cell gc, 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 = gc.bg; + else + bg = defaults->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); + + /* Work out the the empty width. */ + if (i >= ex) + empty = 1; + else + empty = tty_draw_line_get_empty(&gc, 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 (gc.flags & GRID_FLAG_PADDING) + next_state = TTY_DRAW_LINE_PAD; + else if (grid_cells_look_equal(&gc, &last)) { + if (gc.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, gc.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, gc.data.data, gc.data.size); + len += gc.data.size; + width += gc.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, &gc, sizeof last); + if (empty != 0) + i += empty; + else + i++; + } + + tty->flags = (tty->flags & ~TTY_NOCURSOR)|flags; + tty_update_mode(tty, tty->mode, s); +} + diff --git a/tty.c b/tty.c index 72debdcc..bc1df836 100644 --- a/tty.c +++ b/tty.c @@ -61,15 +61,10 @@ 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 *); @@ -912,7 +907,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]; @@ -1071,7 +1066,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)) @@ -1463,191 +1458,6 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, 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); -} - #ifdef ENABLE_SIXEL /* Update context for client. */ static int @@ -3203,7 +3013,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) { From b108653f02060ba75edce64c768a7526545ec569 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 20 Jan 2026 21:09:30 +0000 Subject: [PATCH 2/7] Change overlay_ranges to visible_ranges. --- menu.c | 10 +++--- popup.c | 39 +++++++++------------- screen-redraw.c | 26 ++++++++++++--- screen.c | 69 +++++++++++++++++++++++++++++++++++++++ server-client.c | 47 ++++++++++++++------------ tmux.h | 23 +++++++++---- tty.c | 87 ++++++++++++++++++++++++++++++------------------- 7 files changed, 209 insertions(+), 92 deletions(-) diff --git a/menu.c b/menu.c index 7449d88d..cd361e00 100644 --- a/menu.c +++ b/menu.c @@ -181,15 +181,17 @@ 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; + struct visible_ranges *r; - server_client_overlay_range(md->px, md->py, menu->width + 4, - menu->count + 2, px, py, nx, r); + r = server_client_overlay_range(md->px, md->py, menu->width + 4, + menu->count + 2, px, py, nx); + return (r); } void diff --git a/popup.c b/popup.c index 2146693a..6d0e1ffd 100644 --- a/popup.c +++ b/popup.c @@ -164,48 +164,41 @@ 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 *or[2], *r; 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++) { - server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, r->px[i], py, r->nx[i], &or[i]); + r = menu_check_cb(c, pd->md, px, py, nx); + for (i = 0; i < r->used; i++) { + or[i] = server_client_overlay_range(pd->px, pd->py, pd->sx, + pd->sy, r->px[i], py, r->nx[i]); } /* * or has up to OVERLAY_MAX_RANGES non-overlapping ranges, * ordered from left to right. Collect them in 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]; + for (i = 0; i < r->used; i++) { + /* Each or[i] only has up to 2 ranges. */ + for (j = 0; j < or[i]->used; j++) { + if (or[i]->nx[j] > 0) { + r->px[k] = or[i]->px[j]; + r->nx[k] = or[i]->nx[j]; 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 (server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, + nx)); } static void diff --git a/screen-redraw.c b/screen-redraw.c index cce06011..da3f40b5 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -817,13 +817,13 @@ 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; + struct visible_ranges *r; u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; int isolates; 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 (r->nx[0] + r->nx[1] == 0) return; } @@ -943,7 +943,8 @@ 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 *vr; + u_int i, j, top, x, y, px, width, r; if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); @@ -987,8 +988,23 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", __func__, c->name, wp->id, i, j, x, y, width); + /* xxxx Breaking up the tty_draw_line like this isn't fully working. */ + vr = tty_check_overlay_range(tty, x, y, width); + tty_default_colours(&defaults, wp); - tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette); + + for (r=0; r < vr->used; r++) { + if (vr->nx[r] == 0) + continue; + /* Convert window coordinates to tty coordinates. */ + px = vr->px[r]; + /* i is px of cell, add px of region, sub the + * pane offset. If you don't sub offset, + * contents of pane shifted. note: i apparently unnec. + */ + tty_draw_line(tty, s, /* i + */ vr->px[r] - wp->xoff, j, + vr->nx[r], px, y, &defaults, palette); + } } #ifdef ENABLE_SIXEL diff --git a/screen.c b/screen.c index 7f4f9383..247d596d 100644 --- a/screen.c +++ b/screen.c @@ -770,3 +770,72 @@ screen_mode_to_string(int mode) tmp[strlen(tmp) - 1] = '\0'; return (tmp); } + +#ifdef DEBUG +/* Debug function. Usage in gdb: + * printf "%S",screen_print(s) + */ +__unused char * +screen_print(struct screen *s) { + static char buf[16384]; + const char *acs; + u_int x, y; + size_t last = 0, n; + struct utf8_data ud; + struct grid_line *gl; + struct grid_cell_entry *gce; + + for (y = 0; y < screen_hsize(s)+s->grid->sy; y++) { + if (last + 6 > sizeof buf) goto out; + n = snprintf(buf + last, sizeof buf - last, "%.4d ", y); + last += n; + buf[last++] = '"'; + 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) { + /* single-byte cell stored inline */ + if (last + 2 > sizeof buf) goto out; + buf[last++] = gce->data.data; + } else if (gce->flags & GRID_FLAG_TAB) { + if (last + 2 > sizeof buf) goto out; + buf[last++] = '\t'; + } else if ((gce->data.data & 0xff) && + (gce->flags & GRID_ATTR_CHARSET)) { + /* single-byte ACS inline: try to map */ + acs = tty_acs_get(NULL, gce->data.data); + if (acs != NULL) { + n = strlen(acs); + if (last + n + 1 > sizeof buf) goto out; + memcpy(buf + last, acs, n); + last += n; + continue; + } + buf[last++] = gce->data.data; + } else { + /* extended cell: convert utf8_char -> bytes */ + utf8_to_data(gl->extddata[gce->offset].data, + &ud); + if (ud.size > 0) { + if (last + ud.size + 1 > sizeof buf) + goto out; + memcpy(buf + last, ud.data, ud.size); + last += ud.size; + } + } + } + + if (last + 3 > sizeof buf) goto out; + buf[last++] = '"'; + buf[last++] = '\n'; + } + +out: buf[last] = '\0'; + return (buf); +} +#endif diff --git a/server-client.c b/server-client.c index 1ea2fe3e..a222ac6f 100644 --- a/server-client.c +++ b/server-client.c @@ -165,34 +165,37 @@ server_client_clear_overlay(struct client *c) * Given overlay position and dimensions, return parts of the input range which * are visible. */ -void +struct visible_ranges * 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) { - u_int ox, onx; + u_int ox, onx; + static struct visible_ranges r = {NULL, NULL, 0, 0}; - /* Return up to 2 ranges. */ - r->px[2] = 0; - r->nx[2] = 0; + /* For efficiency vr is static and space reused. */ + if (r.size == 0) { + r.px = xcalloc(2, sizeof(u_int)); + r.nx = xcalloc(2, sizeof(u_int)); + r.size = 2; + } /* 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; - return; + r.px[0] = px; + r.nx[0] = nx; + r.used = 1; + return (&r); } /* 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.px[0] = px; + r.nx[0] = x - px; + if (r.nx[0] > nx) + r.nx[0] = nx; } else { - r->px[0] = 0; - r->nx[0] = 0; + r.px[0] = 0; + r.nx[0] = 0; } /* Visible bit to the right of the popup. */ @@ -201,12 +204,14 @@ 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.px[1] = ox; + r.nx[1] = onx - ox; } else { - r->px[1] = 0; - r->nx[1] = 0; + r.px[1] = 0; + r.nx[1] = 0; } + r.used = 2; + return (&r); } /* Check if this client is inside this server. */ diff --git a/tmux.h b/tmux.h index 33da541a..ec1bd543 100644 --- a/tmux.h +++ b/tmux.h @@ -1922,11 +1922,19 @@ struct overlay_ranges { u_int nx[OVERLAY_MAX_RANGES]; }; +/* Visible range array element. */ +struct visible_ranges { + u_int *px; /* Start */ + u_int *nx; /* Length */ + size_t used; + size_t size; +}; + /* 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 *); typedef void (*overlay_draw_cb)(struct client *, void *, @@ -2551,6 +2559,8 @@ 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 *); +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, @@ -2567,6 +2577,7 @@ void tty_close(struct tty *); void tty_free(struct tty *); void tty_update_features(struct tty *); void tty_set_selection(struct tty *, const char *, const char *, size_t); +u_int tty_cell_width(const struct grid_cell *, u_int); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); @@ -2909,8 +2920,8 @@ 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_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, - u_int, struct overlay_ranges *); +struct visible_ranges *server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, + u_int); 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 *); @@ -3231,6 +3242,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); +__unused char * screen_print(struct screen *s); /* window.c */ extern struct windows windows; @@ -3611,8 +3623,7 @@ 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.c b/tty.c index bc1df836..5ff225d6 100644 --- a/tty.c +++ b/tty.c @@ -66,8 +66,7 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); 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 *); +struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, u_int); #ifdef ENABLE_SIXEL static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), @@ -1161,7 +1160,7 @@ 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; + struct visible_ranges *r; u_int i; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); @@ -1198,13 +1197,17 @@ 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) + tty_cursor(tty, px, py); + tty_repeat_space(tty, nx); + /* + r = tty_check_overlay_range(tty, px, py, nx); + for (i = 0; i < r->used; i++) { + if (r->nx[i] == 0) continue; - tty_cursor(tty, r.px[i], py); - tty_repeat_space(tty, r.nx[i]); + tty_cursor(tty, r->px[i], py); + tty_repeat_space(tty, r->nx[i]); } + */ } /* Clear a line, adjusting to visible part of pane. */ @@ -1418,6 +1421,20 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) return (&new); } +/* + * Compute the effective display width (in terminal columns) of a grid cell + * when it will be drawn at terminal column atcol. + */ +u_int +tty_cell_width(const struct grid_cell *gcp, u_int atcol) +{ + /* Tabs expand to the next tab stop (tab width = 8). */ + if (gcp->flags & GRID_FLAG_TAB) + return (8 - (atcol % 8)); + /* Normal characters: width stored in cell (1 or 2 usually). */ + return (gcp->data.width); +} + /* * Check if a single character is obstructed by the overlay and return a * boolean. @@ -1425,37 +1442,41 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) 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. */ - tty_check_overlay_range(tty, px, py, 1, &r); - if (r.nx[0] + r.nx[1] == 0) + r = tty_check_overlay_range(tty, px, py, 1); + if (r->nx[0] + r->nx[1] == 0) return (0); return (1); } /* 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) { + static struct visible_ranges r = {NULL, NULL, 0, 0}; 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; + /* For efficiency vr is static and space reused. */ + if (r.size == 0) { + r.px = xcalloc(1, sizeof(u_int)); + r.nx = xcalloc(1, sizeof(u_int)); + r.size = 1; } - c->overlay_check(c, c->overlay_data, px, py, nx, r); + if (c->overlay_check == NULL) { + r.px[0] = px; + r.nx[0] = nx; + r.used = 1; + return (&r); + } + + return (c->overlay_check(c, c->overlay_data, px, py, nx)); } #ifdef ENABLE_SIXEL @@ -1983,7 +2004,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; @@ -2000,9 +2021,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->nx[i]; if (vis < gcp->data.width) { tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, px, py, &ctx->defaults, ctx->palette); @@ -2028,7 +2049,7 @@ 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; u_int i, px, py, cx; char *cp = ctx->ptr; @@ -2059,14 +2080,14 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) 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) + r = tty_check_overlay_range(tty, px, py, ctx->num); + for (i = 0; i < r->used; i++) { + if (r->nx[i] == 0) continue; /* Convert back to pane position for printing. */ - cx = r.px[i] - ctx->xoff + ctx->wox; + 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]); + tty_putn(tty, cp + r->px[i] - px, r->nx[i], r->nx[i]); } } From 35485f2b5ed19b5fb5d49f5c8b1a400c94dca6eb Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 21 Jan 2026 12:23:46 +0000 Subject: [PATCH 3/7] Fix selection with tty_draw_line. --- screen.c | 5 +++-- tmux.h | 4 +++- tty-draw.c | 25 +++++++++++++++++-------- tty.c | 2 +- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/screen.c b/screen.c index 247d596d..693b50dc 100644 --- a/screen.c +++ b/screen.c @@ -581,12 +581,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)) @@ -600,6 +600,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/tmux.h b/tmux.h index ec1bd543..424b6093 100644 --- a/tmux.h +++ b/tmux.h @@ -2559,6 +2559,8 @@ 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); @@ -3237,7 +3239,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); diff --git a/tty-draw.c b/tty-draw.c index d79325c3..3d77d4a1 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -113,7 +113,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, struct colour_palette *palette) { struct grid *gd = s->grid; - struct grid_cell gc, last; + struct grid_cell gc, *gcp, ngc, last; struct grid_line *gl; u_int i, j, last_i, cx, ex, width; u_int cellsize, bg; @@ -169,6 +169,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, } if (i == 0) bg = gc.bg; + else if (screen_select_cell(s, &ngc, &gc)) + bg = ngc.bg; else bg = defaults->bg; tty_attributes(tty, &last, defaults, palette, s->hyperlinks); @@ -220,6 +222,13 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* 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 (screen_select_cell(s, &ngc, gcp)) + gcp = &ngc; + /* Work out the the empty width. */ if (i >= ex) empty = 1; @@ -231,10 +240,10 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, next_state = TTY_DRAW_LINE_EMPTY; else if (current_state == TTY_DRAW_LINE_FIRST) next_state = TTY_DRAW_LINE_SAME; - else if (gc.flags & GRID_FLAG_PADDING) + else if (gcp->flags & GRID_FLAG_PADDING) next_state = TTY_DRAW_LINE_PAD; else if (grid_cells_look_equal(&gc, &last)) { - if (gc.data.size > (sizeof buf) - len) + if (gcp->data.size > (sizeof buf) - len) next_state = TTY_DRAW_LINE_FLUSH; else next_state = TTY_DRAW_LINE_SAME; @@ -244,7 +253,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, next_state = TTY_DRAW_LINE_NEW1; } log_debug("%s: cell %u empty %u, bg %u; state: current %s, " - "next %s", __func__, px + i, empty, gc.bg, + "next %s", __func__, px + i, empty, gcp->bg, tty_draw_line_states[current_state], tty_draw_line_states[next_state]); @@ -278,9 +287,9 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* 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, gc.data.data, gc.data.size); - len += gc.data.size; - width += gc.data.width; + 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. */ @@ -289,7 +298,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* Otherwise move to the next. */ current_state = next_state; - memcpy(&last, &gc, sizeof last); + memcpy(&last, gcp, sizeof last); if (empty != 0) i += empty; else diff --git a/tty.c b/tty.c index 5ff225d6..2b5e91c5 100644 --- a/tty.c +++ b/tty.c @@ -1388,7 +1388,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; From 58e498c9d3de650d2b802712bf24f8e572ccd1c9 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 21 Jan 2026 15:35:33 +0000 Subject: [PATCH 4/7] Use right cell for empty check. --- tty-draw.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tty-draw.c b/tty-draw.c index 3d77d4a1..a3712161 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -89,7 +89,7 @@ tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx, /* Is this cell empty? */ static u_int -tty_draw_line_get_empty(struct grid_cell *gc, u_int nx) +tty_draw_line_get_empty(const struct grid_cell *gc, u_int nx) { u_int empty = 0; @@ -113,7 +113,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, struct colour_palette *palette) { struct grid *gd = s->grid; - struct grid_cell gc, *gcp, ngc, last; + struct grid_cell gc, ngc, last; + const struct grid_cell *gcp; struct grid_line *gl; u_int i, j, last_i, cx, ex, width; u_int cellsize, bg; @@ -233,7 +234,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, if (i >= ex) empty = 1; else - empty = tty_draw_line_get_empty(&gc, nx - i); + empty = tty_draw_line_get_empty(gcp, nx - i); /* Work out the next state. */ if (empty != 0) @@ -242,7 +243,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, 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(&gc, &last)) { + else if (grid_cells_look_equal(gcp, &last)) { if (gcp->data.size > (sizeof buf) - len) next_state = TTY_DRAW_LINE_FLUSH; else From 7730d38339f43d48246bcc7990a40bf9cb26b250 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 21 Jan 2026 15:58:11 +0000 Subject: [PATCH 5/7] Skip correct width when moving to next position. --- tty-draw.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tty-draw.c b/tty-draw.c index a3712161..ed831677 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -169,11 +169,15 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, break; } if (i == 0) - bg = gc.bg; - else if (screen_select_cell(s, &ngc, &gc)) - bg = ngc.bg; - else 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); @@ -227,8 +231,11 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, gcp = tty_check_codeset(tty, &gc); /* And for selection. */ - if (screen_select_cell(s, &ngc, gcp)) - gcp = &ngc; + 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) @@ -303,7 +310,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, if (empty != 0) i += empty; else - i++; + i += gcp->data.width; } tty->flags = (tty->flags & ~TTY_NOCURSOR)|flags; From a25c14d472b2b365ba9cb22c7f49594af5766096 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 21 Jan 2026 19:34:19 +0000 Subject: [PATCH 6/7] Change overlay_ranges into visible_ranges. --- menu.c | 10 +++---- popup.c | 41 ++++++++++++++++++----------- screen-redraw.c | 57 ++++++++++++++++++++-------------------- server-client.c | 46 ++++++++++++++++---------------- tmux.h | 33 +++++++++++------------ tty-draw.c | 3 ++- tty.c | 70 ++++++++++++++++++++++--------------------------- 7 files changed, 130 insertions(+), 130 deletions(-) diff --git a/menu.c b/menu.c index cd361e00..901e0197 100644 --- a/menu.c +++ b/menu.c @@ -181,17 +181,15 @@ 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. */ -struct visible_ranges * +void menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py, - u_int nx) + u_int nx, struct visible_ranges *r) { struct menu_data *md = data; struct menu *menu = md->menu; - struct visible_ranges *r; - r = server_client_overlay_range(md->px, md->py, menu->width + 4, - menu->count + 2, px, py, nx); - return (r); + server_client_overlay_range(md->px, md->py, menu->width + 4, + menu->count + 2, px, py, nx, r); } void diff --git a/popup.c b/popup.c index 6d0e1ffd..5422809c 100644 --- a/popup.c +++ b/popup.c @@ -164,19 +164,27 @@ 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 struct visible_ranges * -popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) +static void +popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx, + struct visible_ranges *r) { - struct popup_data *pd = data; - struct visible_ranges *or[2], *r; - u_int i, j, k = 0; + struct popup_data *pd = data; + static struct visible_ranges or[2] = { { NULL, 0, 0 }, { NULL, 0, 0 } }; + u_int i, j, k = 0; if (pd->md != NULL) { /* Check each returned range for the menu against the popup. */ - r = menu_check_cb(c, pd->md, px, py, nx); + menu_check_cb(c, pd->md, px, py, nx, r); for (i = 0; i < r->used; i++) { - or[i] = server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, r->px[i], py, r->nx[i]); + server_client_overlay_range(pd->px, pd->py, pd->sx, + pd->sy, r->ranges[i].px, py, r->ranges[i].nx, &or[i]); + } + + /* Caller must free when no longer used. */ + if (r->size == 0) { + r->ranges = xcalloc(2, sizeof(struct visible_range)); + r->size = 2; + r->used = 0; } /* @@ -185,20 +193,21 @@ popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) */ for (i = 0; i < r->used; i++) { /* Each or[i] only has up to 2 ranges. */ - for (j = 0; j < or[i]->used; j++) { - if (or[i]->nx[j] > 0) { - r->px[k] = or[i]->px[j]; - r->nx[k] = or[i]->nx[j]; + for (j = 0; j < or[i].used; j++) { + if (or[i].ranges[j].nx > 0) { + r->ranges[k].px = or[i].ranges[j].px; + r->ranges[k].nx = or[i].ranges[j].nx; k++; } } } - - return (r); + return; } - return (server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, - nx)); + server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, + px, py, nx, r); + + return; } static void diff --git a/screen-redraw.c b/screen-redraw.c index da3f40b5..15ca76b7 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -808,22 +808,23 @@ screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, u_int i, static void screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) { - struct client *c = ctx->c; - struct session *s = c->session; - struct window *w = s->curw->window; - struct options *oo = w->options; - struct tty *tty = &c->tty; - struct format_tree *ft; - struct window_pane *wp, *active = server_client_get_pane(c); - struct grid_cell gc; - const struct grid_cell *tmp; - struct visible_ranges *r; - u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; - int isolates; + struct client *c = ctx->c; + struct session *s = c->session; + struct window *w = s->curw->window; + struct options *oo = w->options; + struct tty *tty = &c->tty; + struct format_tree *ft; + struct window_pane *wp, *active = server_client_get_pane(c); + struct grid_cell gc; + const struct grid_cell *tmp; + static struct visible_ranges r = { NULL, 0, 0 }; + u_int cell_type; + u_int x = ctx->ox + i, y = ctx->oy + j; + int isolates; if (c->overlay_check != NULL) { - r = c->overlay_check(c, c->overlay_data, x, y, 1); - if (r->nx[0] + r->nx[1] == 0) + c->overlay_check(c, c->overlay_data, x, y, 1, &r); + if (r.ranges[0].nx + r.ranges[1].nx == 0) return; } @@ -937,14 +938,14 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) static void screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) { - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct tty *tty = &c->tty; - struct screen *s = wp->screen; - struct colour_palette *palette = &wp->palette; - struct grid_cell defaults; - struct visible_ranges *vr; - u_int i, j, top, x, y, px, width, r; + struct client *c = ctx->c; + struct window *w = c->session->curw->window; + struct tty *tty = &c->tty; + struct screen *s = wp->screen; + struct colour_palette *palette = &wp->palette; + struct grid_cell defaults; + static struct visible_ranges vr; + u_int i, j, top, x, y, px, width, r; if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); @@ -989,21 +990,21 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, x, y, width); /* xxxx Breaking up the tty_draw_line like this isn't fully working. */ - vr = tty_check_overlay_range(tty, x, y, width); + tty_check_overlay_range(tty, x, y, width, &vr); tty_default_colours(&defaults, wp); - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) + for (r=0; r < vr.used; r++) { + if (vr.ranges[r].nx == 0) continue; /* Convert window coordinates to tty coordinates. */ - px = vr->px[r]; + px = vr.ranges[r].px; /* i is px of cell, add px of region, sub the * pane offset. If you don't sub offset, * contents of pane shifted. note: i apparently unnec. */ - tty_draw_line(tty, s, /* i + */ vr->px[r] - wp->xoff, j, - vr->nx[r], px, y, &defaults, palette); + tty_draw_line(tty, s, /* i + */ vr.ranges[r].px - wp->xoff, j, + vr.ranges[r].nx, px, y, &defaults, palette); } } diff --git a/server-client.c b/server-client.c index a222ac6f..c4952a92 100644 --- a/server-client.c +++ b/server-client.c @@ -165,37 +165,36 @@ server_client_clear_overlay(struct client *c) * Given overlay position and dimensions, return parts of the input range which * are visible. */ -struct visible_ranges * +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) + u_int py, u_int nx, struct visible_ranges *r) { u_int ox, onx; - static struct visible_ranges r = {NULL, NULL, 0, 0}; - /* For efficiency vr is static and space reused. */ - if (r.size == 0) { - r.px = xcalloc(2, sizeof(u_int)); - r.nx = xcalloc(2, sizeof(u_int)); - r.size = 2; + /* Caller must free when no longer used. */ + if (r->size == 0) { + r->ranges = xcalloc(2, sizeof(struct visible_range)); + r->size = 2; + r->used = 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.used = 1; - return (&r); + r->ranges[0].px = px; + r->ranges[0].nx = nx; + r->used = 1; + return; } /* 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. */ @@ -204,14 +203,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; - return (&r); + r->used = 2; } /* Check if this client is inside this server. */ diff --git a/tmux.h b/tmux.h index 424b6093..4544411c 100644 --- a/tmux.h +++ b/tmux.h @@ -1915,26 +1915,24 @@ 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]; +/* Visible range array element. */ +struct visible_range { + u_int px; /* Start */ + u_int nx; /* Length */ }; -/* Visible range array element. */ +/* Visible areas not obstructed. */ struct visible_ranges { - u_int *px; /* Start */ - u_int *nx; /* Length */ - size_t used; - size_t size; + struct visible_range *ranges; /* dynamically allocated array */ + size_t used; /* number of entries in ranges */ + size_t size; /* allocated capacity of ranges */ }; /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); -typedef struct visible_ranges *(*overlay_check_cb)(struct client*, void *, - u_int, u_int, u_int); +typedef void (*overlay_check_cb)(struct client*, void *, u_int, u_int, u_int, + struct visible_ranges *); typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *, u_int *); typedef void (*overlay_draw_cb)(struct client *, void *, @@ -2561,8 +2559,8 @@ void tty_default_attributes(struct tty *, const struct grid_cell *, 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); +void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, + struct visible_ranges *); /* tty-draw.c */ void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, @@ -2922,8 +2920,8 @@ 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 *); -struct visible_ranges *server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, - u_int); +void server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, + 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 *); @@ -3625,7 +3623,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 *); -struct visible_ranges *menu_check_cb(struct client *, void *, u_int, u_int, u_int); +void menu_check_cb(struct client *, void *, u_int, u_int, u_int, + struct visible_ranges *); 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 index 3d77d4a1..d23bf39e 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -113,7 +113,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, struct colour_palette *palette) { struct grid *gd = s->grid; - struct grid_cell gc, *gcp, ngc, last; + 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; diff --git a/tty.c b/tty.c index 2b5e91c5..081b56de 100644 --- a/tty.c +++ b/tty.c @@ -66,7 +66,8 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static int tty_check_overlay(struct tty *, u_int, u_int); -struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int, u_int); +void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, + struct visible_ranges *); #ifdef ENABLE_SIXEL static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), @@ -1160,8 +1161,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 visible_ranges *r; - u_int i; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); @@ -1199,15 +1198,6 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, */ tty_cursor(tty, px, py); tty_repeat_space(tty, nx); - /* - r = tty_check_overlay_range(tty, px, py, nx); - for (i = 0; i < r->used; i++) { - if (r->nx[i] == 0) - continue; - tty_cursor(tty, r->px[i], py); - tty_repeat_space(tty, r->nx[i]); - } - */ } /* Clear a line, adjusting to visible part of pane. */ @@ -1442,41 +1432,45 @@ tty_cell_width(const struct grid_cell *gcp, u_int atcol) static int tty_check_overlay(struct tty *tty, u_int px, u_int py) { - struct visible_ranges *r; + static struct visible_ranges r = { NULL, 0, 0 }; /* * 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. */ - r = tty_check_overlay_range(tty, px, py, 1); - if (r->nx[0] + r->nx[1] == 0) + if (r.size == 0) { + r.ranges = xcalloc(2, sizeof(struct visible_range)); + r.size = 2; + } + + tty_check_overlay_range(tty, px, py, 1, &r); + if (r.ranges[0].nx + r.ranges[1].nx == 0) return (0); return (1); } /* Return parts of the input range which are visible. */ -struct visible_ranges * -tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx) +void +tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, + struct visible_ranges *r) { - static struct visible_ranges r = {NULL, NULL, 0, 0}; struct client *c = tty->client; - /* For efficiency vr is static and space reused. */ - if (r.size == 0) { - r.px = xcalloc(1, sizeof(u_int)); - r.nx = xcalloc(1, sizeof(u_int)); - r.size = 1; + if (r->size == 0) { + r->ranges = xcalloc(2, sizeof(struct visible_range)); + r->size = 2; } if (c->overlay_check == NULL) { - r.px[0] = px; - r.nx[0] = nx; - r.used = 1; - return (&r); + r->ranges[0].px = px; + r->ranges[0].nx = nx; + r->used = 1; + return; } - return (c->overlay_check(c, c->overlay_data, px, py, nx)); + c->overlay_check(c, c->overlay_data, px, py, nx, r); + return; } #ifdef ENABLE_SIXEL @@ -2004,7 +1998,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 visible_ranges *r; + static struct visible_ranges r = { NULL, 0, 0 }; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2021,9 +2015,9 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { - r = tty_check_overlay_range(tty, px, py, gcp->data.width); - for (i = 0; i < r->used; i++) - vis += r->nx[i]; + tty_check_overlay_range(tty, px, py, gcp->data.width, &r); + 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); @@ -2049,7 +2043,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { - struct visible_ranges *r; + struct visible_ranges r = { NULL, 0, 0 }; u_int i, px, py, cx; char *cp = ctx->ptr; @@ -2080,14 +2074,14 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; - r = tty_check_overlay_range(tty, px, py, ctx->num); - for (i = 0; i < r->used; i++) { - if (r->nx[i] == 0) + tty_check_overlay_range(tty, px, py, ctx->num, &r); + for (i = 0; i < r.used; i++) { + if (r.ranges[i].nx == 0) continue; /* Convert back to pane position for printing. */ - cx = r->px[i] - ctx->xoff + ctx->wox; + cx = r.ranges[i].px - 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]); + tty_putn(tty, cp + r.ranges[i].px - px, r.ranges[i].nx, r.ranges[i].nx); } } From d1a6ce8e7fbfee502dd6deed72fe7bdd0e4e033d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 21 Jan 2026 21:26:32 +0000 Subject: [PATCH 7/7] Associate each visible_ranges with some other object (tty, popup_data, etc) so it is easier to keep track of its lifecycle, but still avoid allocating for each use. --- menu.c | 10 ++++-- popup.c | 79 ++++++++++++++++++++++++----------------- screen-redraw.c | 68 ++++++++++++++++------------------- server-client.c | 34 +++++++++++++----- tmux.h | 48 +++++++++++++------------ tty.c | 94 +++++++++++++++++-------------------------------- 6 files changed, 167 insertions(+), 166 deletions(-) diff --git a/menu.c b/menu.c index 901e0197..7b18772a 100644 --- a/menu.c +++ b/menu.c @@ -34,6 +34,7 @@ struct menu_data { struct cmd_find_state fs; struct screen s; + struct visible_ranges r; u_int px; u_int py; @@ -181,15 +182,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 visible_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); } void @@ -232,7 +234,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); } diff --git a/popup.c b/popup.c index 5422809c..adebb912 100644 --- a/popup.c +++ b/popup.c @@ -39,6 +39,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; @@ -164,50 +167,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 visible_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; - static struct visible_ranges or[2] = { { NULL, 0, 0 }, { NULL, 0, 0 } }; - u_int i, j, k = 0; + struct popup_data *pd = data; + 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 < r->used; i++) { - server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, r->ranges[i].px, py, r->ranges[i].nx, &or[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"); - /* Caller must free when no longer used. */ - if (r->size == 0) { - r->ranges = xcalloc(2, sizeof(struct visible_range)); - r->size = 2; - r->used = 0; + /* + * 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->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 < r->used; i++) { - /* Each or[i] only has up to 2 ranges. */ - for (j = 0; j < or[i].used; j++) { - if (or[i].ranges[j].nx > 0) { - r->ranges[k].px = or[i].ranges[j].px; - r->ranges[k].nx = or[i].ranges[j].nx; - 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++; } } - return; + return (r); } - server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, - px, py, nx, r); - - return; + server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx, + r); + return (r); } static void @@ -288,6 +298,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); @@ -551,7 +564,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)) { diff --git a/screen-redraw.c b/screen-redraw.c index 15ca76b7..8449f02f 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -808,23 +808,23 @@ screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, u_int i, static void screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) { - struct client *c = ctx->c; - struct session *s = c->session; - struct window *w = s->curw->window; - struct options *oo = w->options; - struct tty *tty = &c->tty; - struct format_tree *ft; - struct window_pane *wp, *active = server_client_get_pane(c); - struct grid_cell gc; - const struct grid_cell *tmp; - static struct visible_ranges r = { NULL, 0, 0 }; - u_int cell_type; - u_int x = ctx->ox + i, y = ctx->oy + j; - int isolates; + struct client *c = ctx->c; + struct session *s = c->session; + struct window *w = s->curw->window; + struct options *oo = w->options; + struct tty *tty = &c->tty; + struct format_tree *ft; + struct window_pane *wp, *active = server_client_get_pane(c); + struct grid_cell gc; + const struct grid_cell *tmp; + 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.ranges[0].nx + r.ranges[1].nx == 0) + r = c->overlay_check(c, c->overlay_data, x, y, 1); + if (server_client_ranges_is_empty(r)) return; } @@ -938,14 +938,15 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) static void screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) { - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct tty *tty = &c->tty; - struct screen *s = wp->screen; - struct colour_palette *palette = &wp->palette; - struct grid_cell defaults; - static struct visible_ranges vr; - u_int i, j, top, x, y, px, width, r; + struct client *c = ctx->c; + struct window *w = c->session->curw->window; + struct tty *tty = &c->tty; + struct screen *s = wp->screen; + struct colour_palette *palette = &wp->palette; + struct grid_cell defaults; + 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); @@ -989,22 +990,15 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", __func__, c->name, wp->id, i, j, x, y, width); - /* xxxx Breaking up the tty_draw_line like this isn't fully working. */ - tty_check_overlay_range(tty, x, y, width, &vr); - tty_default_colours(&defaults, wp); - for (r=0; r < vr.used; r++) { - if (vr.ranges[r].nx == 0) - continue; - /* Convert window coordinates to tty coordinates. */ - px = vr.ranges[r].px; - /* i is px of cell, add px of region, sub the - * pane offset. If you don't sub offset, - * contents of pane shifted. note: i apparently unnec. - */ - tty_draw_line(tty, s, /* i + */ vr.ranges[r].px - wp->xoff, j, - vr.ranges[r].nx, px, 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/server-client.c b/server-client.c index c4952a92..f9c938cc 100644 --- a/server-client.c +++ b/server-client.c @@ -161,6 +161,29 @@ 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. @@ -169,22 +192,17 @@ 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 visible_ranges *r) { - u_int ox, onx; - - /* Caller must free when no longer used. */ - if (r->size == 0) { - r->ranges = xcalloc(2, sizeof(struct visible_range)); - r->size = 2; - r->used = 0; - } + u_int ox, onx; /* Trivial case of no overlap in the y direction. */ if (py < y || py > y + sy - 1) { + 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) { diff --git a/tmux.h b/tmux.h index 4544411c..247eced0 100644 --- a/tmux.h +++ b/tmux.h @@ -956,7 +956,7 @@ struct screen_sel; struct screen_titles; struct screen { char *title; - char *path; + char *path; struct screen_titles *titles; struct grid *grid; /* grid data */ @@ -1532,6 +1532,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; @@ -1599,6 +1612,7 @@ struct tty { size_t discarded; struct termios tio; + struct visible_ranges r; struct grid_cell cell; struct grid_cell last_cell; @@ -1915,28 +1929,15 @@ struct client_window { }; RB_HEAD(client_windows, client_window); -/* 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 */ - size_t used; /* number of entries in ranges */ - size_t size; /* allocated capacity of 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 visible_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 *); @@ -2559,8 +2560,8 @@ void tty_default_attributes(struct tty *, const struct grid_cell *, void tty_update_mode(struct tty *, int, struct screen *); const struct grid_cell *tty_check_codeset(struct tty *, const struct grid_cell *); -void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, - struct visible_ranges *); +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, @@ -2577,7 +2578,6 @@ void tty_close(struct tty *); void tty_free(struct tty *); void tty_update_features(struct tty *); void tty_set_selection(struct tty *, const char *, const char *, size_t); -u_int tty_cell_width(const struct grid_cell *, u_int); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); @@ -2920,6 +2920,8 @@ 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 visible_ranges *); void server_client_set_key_table(struct client *, const char *); @@ -3623,8 +3625,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 visible_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.c b/tty.c index 081b56de..d1c98d03 100644 --- a/tty.c +++ b/tty.c @@ -66,8 +66,6 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static int tty_check_overlay(struct tty *, u_int, u_int); -void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, - struct visible_ranges *); #ifdef ENABLE_SIXEL static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), @@ -523,6 +521,8 @@ void tty_free(struct tty *tty) { tty_close(tty); + + free(tty->r.ranges); } void @@ -1411,66 +1411,34 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) return (&new); } -/* - * Compute the effective display width (in terminal columns) of a grid cell - * when it will be drawn at terminal column atcol. - */ -u_int -tty_cell_width(const struct grid_cell *gcp, u_int atcol) -{ - /* Tabs expand to the next tab stop (tab width = 8). */ - if (gcp->flags & GRID_FLAG_TAB) - return (8 - (atcol % 8)); - /* Normal characters: width stored in cell (1 or 2 usually). */ - return (gcp->data.width); -} - -/* - * 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) { - static struct visible_ranges r = { NULL, 0, 0 }; + 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. */ - if (r.size == 0) { - r.ranges = xcalloc(2, sizeof(struct visible_range)); - r.size = 2; - } - - tty_check_overlay_range(tty, px, py, 1, &r); - if (r.ranges[0].nx + r.ranges[1].nx == 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. */ -void -tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, - struct visible_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 (r->size == 0) { - r->ranges = xcalloc(2, sizeof(struct visible_range)); - r->size = 2; - } - if (c->overlay_check == NULL) { - r->ranges[0].px = px; - r->ranges[0].nx = nx; - r->used = 1; - 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); - return; + return (c->overlay_check(c, c->overlay_data, px, py, nx)); } #ifdef ENABLE_SIXEL @@ -1998,7 +1966,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; - static struct visible_ranges r = { NULL, 0, 0 }; + struct visible_ranges *r; u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2015,9 +1983,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 < r.used; i++) - vis += r.ranges[i].nx; + 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); @@ -2043,7 +2011,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 visible_ranges r = { NULL, 0, 0 }; + struct visible_ranges *r; + struct visible_range *rr; u_int i, px, py, cx; char *cp = ctx->ptr; @@ -2068,20 +2037,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 < r.used; i++) { - if (r.ranges[i].nx == 0) - continue; - /* Convert back to pane position for printing. */ - cx = r.ranges[i].px - ctx->xoff + ctx->wox; - tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); - tty_putn(tty, cp + r.ranges[i].px - px, r.ranges[i].nx, r.ranges[i].nx); + 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); + } } }