From f70150a6637b55de522d99b623dce51428fec6c8 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 23 Jan 2026 10:45:53 +0000 Subject: [PATCH] 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) {