diff --git a/Makefile.am b/Makefile.am index 279fe144..1df49f6b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -198,6 +198,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/menu.c b/menu.c index 7449d88d..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 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); } 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 2146693a..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,48 +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 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 @@ -286,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); @@ -549,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 ea232830..decdb272 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -674,8 +674,9 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) struct tty *tty = &c->tty; struct window_pane *wp; struct screen *s; - struct visible_ranges *vr; - u_int i, x, width, size, r; + struct visible_ranges *r; + struct visible_range *ri; + u_int i, l, x, width, size; int xoff, yoff; log_debug("%s: %s @%u", __func__, c->name, w->id); @@ -700,36 +701,38 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) { /* All visible. */ - i = 0; + l = 0; x = xoff - ctx->ox; width = size; } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) { /* Both left and right not visible. */ - i = ctx->ox; + l = ctx->ox; x = 0; width = ctx->sx; } else if (xoff < ctx->ox) { /* Left not visible. */ - i = ctx->ox - xoff; + l = ctx->ox - xoff; x = 0; width = size - i; } else { /* Right not visible. */ - i = 0; + l = 0; x = xoff - ctx->ox; width = size - x; } - vr = screen_redraw_get_visible_ranges(wp, x, yoff, width); + r = tty_check_overlay_range(&c->tty, x, yoff, width); + r = screen_redraw_get_visible_ranges(wp, x, yoff, width, r); if (ctx->statustop) yoff += ctx->statuslines; - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; - tty_draw_line(tty, s, i + (vr->px[r] - x), 0, vr->nx[r], - vr->px[r], yoff - ctx->oy, + tty_draw_line(tty, s, l + (ri->px - x), 0, ri->nx, + ri->px, yoff - ctx->oy, &grid_default_cell, NULL); } } @@ -986,13 +989,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; } @@ -1106,18 +1110,20 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) * floating window pane). Returns a boolean. */ int -screen_redraw_is_visible(struct visible_ranges *vr, u_int px) +screen_redraw_is_visible(struct visible_ranges *r, u_int px) { - u_int r; + u_int i; + struct visible_range *ri; /* No visible_ranges if called from a popup or menu. Always visible. */ - if (vr == NULL) + if (r == NULL) return (1); - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; - if ((px >= vr->px[r]) && (px <= vr->px[r] + vr->nx[r])) + if ((px >= ri->px) && (px <= ri->px + ri->nx)) return (1); } return (0); @@ -1127,33 +1133,26 @@ screen_redraw_is_visible(struct visible_ranges *vr, u_int px) cells of base_wp that are unobsructed. */ struct visible_ranges * screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, - u_int py, u_int width) { + u_int py, u_int width, struct visible_ranges *r) { struct window_pane *wp; struct window *w; - static struct visible_ranges vr = {NULL, NULL, 0, 0}; + struct visible_range *ri; int found_self, sb_w, sb_pos; u_int lb, rb, tb, bb; - u_int r, s; + u_int i, s; - /* For efficiency vr is static and space reused. */ - if (vr.size == 0) { - vr.px = xcalloc(1, sizeof(u_int)); - vr.nx = xcalloc(1, sizeof(u_int)); - vr.size = 1; + if (r == NULL) { + server_client_ensure_ranges(&base_wp->r, 1); + r = &base_wp->r; + + /* Start with the entire width of the range. */ + r->ranges[0].px = px; + r->ranges[0].nx = width; + r->used = 1; } - /* debugging: - * display *vr.px@vr.used - * display *vr.nx@vr.used - */ - - /* Start with the entire width of the range. */ - vr.px[0] = px; - vr.nx[0] = width; - vr.used = 1; - if (base_wp == NULL) - return (&vr); + return (r); w = base_wp->window; sb_pos = options_get_number(w->options, "pane-scrollbars-position"); @@ -1184,7 +1183,8 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, sb_pos = 0; } - for (r=0; r < vr.used; r++) { + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; if (sb_pos == PANE_SCROLLBARS_LEFT) { if (wp->xoff > sb_w) lb = wp->xoff - 1 - sb_w; @@ -1204,60 +1204,54 @@ screen_redraw_get_visible_ranges(struct window_pane *base_wp, u_int px, rb = w->sx - 1; /* If the left edge of floating wp falls inside this range and right - edge covers up to right of range, + edge covers up to right of range, then shrink left edge of range. */ - if (lb > vr.px[r] && - lb < vr.px[r] + vr.nx[r] && - rb >= vr.px[r] + vr.nx[r]) { - vr.nx[r] = lb - vr.px[r]; + if (lb > ri->px && + lb < ri->px + ri->nx && + rb >= ri->px + ri->nx) { + ri->nx = lb - ri->px; } /* Else if the right edge of floating wp falls inside of this range and left edge covers the left of range, then move px forward to right edge of wp. */ - else if (rb >= vr.px[r] && - rb < vr.px[r] + vr.nx[r] && - lb <= vr.px[r]) { - vr.nx[r] = vr.nx[r] - (rb + 1 - vr.px[r]); - vr.px[r] = vr.px[r] + (rb + 1 - vr.px[r]); + else if (rb >= ri->px && + rb < ri->px + ri->nx && + lb <= ri->px) { + ri->nx = ri->nx - (rb + 1 - ri->px); + ri->px = ri->px + (rb + 1 - ri->px); } /* Else if wp fully inside range then split range into 2 ranges. */ - else if (lb > vr.px[r] && - rb < vr.px[r] + vr.nx[r]) { - if (vr.size == vr.used) { - vr.size++; - vr.px = xreallocarray(vr.px, - vr.size, sizeof (u_int)); - vr.nx = xreallocarray(vr.nx, - vr.size, sizeof (u_int)); - } - for (s=vr.used; s>r; s--) { - vr.px[s] = vr.px[s-1]; - vr.nx[s] = vr.nx[s-1]; - } - vr.px[r+1] = rb + 1; - vr.nx[r+1] = (vr.px[r] + vr.nx[r]) - (rb + 1); - /* vr.px[r] was copied, unchanged. */ - vr.nx[r] = lb - vr.px[r]; - vr.used++; + else if (lb > ri->px && + rb < ri->px + ri->nx) { + server_client_ensure_ranges(r, r->size + 1); + for (s=r->used; s>i; s--) + memcpy(&r->ranges[s-1], &r->ranges[s], + sizeof (struct visible_range)); + r->ranges[i+1].px = rb + 1; + r->ranges[i+1].nx = ri->px + ri->nx - (rb + 1); + /* ri->px was copied, unchanged. */ + ri->nx = lb - ri->px; + r->used++; } /* If floating wp completely covers this range * then delete it (make it 0 length). */ - else if (lb <= vr.px[r] && - rb >= vr.px[r] + vr.nx[r]) { - vr.nx[r] = 0; + else if (lb <= ri->px && + rb >= ri->px + ri->nx) { + ri->nx = 0; } /* Else the range is already obscured, do nothing. */ } } - for (r=0; rid, py, r, vr.px[r], vr.nx[r]); + for (i=0; iused; i++) { + ri = &r->ranges[i]; + log_debug("%s: %%%u visible_range py=%u r[%u]: [px=%u nx=%u]", + __func__, base_wp->id, py, i, ri->px, ri->nx); } - return (&vr); + return (r); } @@ -1271,8 +1265,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; - struct visible_ranges *vr; - u_int i, j, woy, wx, wy, px, py, width, r; + u_int i, j, woy, wx, wy, px, py, width; + struct visible_ranges *r; + struct visible_range *ri; if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); @@ -1329,21 +1324,18 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) __func__, c->name, wp->id, i, j, wx, wy, width); /* Get visible ranges of line before we draw it. */ - vr = screen_redraw_get_visible_ranges(wp, wx, wy, width); + r = tty_check_overlay_range(tty, px, py, width); + r = screen_redraw_get_visible_ranges(wp, wx, wy, width, r); tty_default_colours(&defaults, wp); - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 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, py, &defaults, palette); + px = ri->px; + tty_draw_line(tty, s, ri->px - wp->xoff, j, + ri->nx, px, py, &defaults, palette); } } @@ -1435,7 +1427,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, int px, py, wx, wy, ox, oy, sx, sy; int xoff = wp->xoff; int yoff = wp->yoff; - struct visible_ranges *vr; + struct visible_ranges *r; /* * Size and offset of window relative to tty. @@ -1487,7 +1479,8 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, for (j = jmin; j < jmax; j++) { py = sb_y + j; /* tty y coordinate. */ wy = sb_y + j + oy; /* window y coordinate. */ - vr = screen_redraw_get_visible_ranges(wp, sb_x, wy, imax); + r = tty_check_overlay_range(tty, sb_x, wy, imax); + r = screen_redraw_get_visible_ranges(wp, sb_x, wy, imax, r); for (i = imin; i < imax; i++) { px = sb_x + ox + i; /* tty x coordinate. */ wx = sb_x + i; /* window x coordinate. */ @@ -1495,7 +1488,7 @@ screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, px >= sx || px < 0 || wy < yoff - 1 || py >= sy || py < 0 || - ! screen_redraw_is_visible(vr, wx)) + ! screen_redraw_is_visible(r, wx)) continue; tty_cursor(tty, px, py); if ((sb_pos == PANE_SCROLLBARS_LEFT && diff --git a/screen-write.c b/screen-write.c index 22d1874a..d7362da0 100644 --- a/screen-write.c +++ b/screen-write.c @@ -574,6 +574,7 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, struct grid *gd = src->grid; struct grid_cell gc; u_int xx, yy, cx = s->cx, cy = s->cy; + struct visible_ranges *r; if (nx == 0 || ny == 0) return; @@ -584,15 +585,19 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, s->cx = cx; if (wp != NULL) screen_write_initctx(ctx, &ttyctx, 0); + r = screen_redraw_get_visible_ranges(wp, px, py, nx, NULL); for (xx = px; xx < px + nx; xx++) { if (xx >= grid_get_line(gd, yy)->cellsize && - s->cx >= grid_get_line(ctx->s->grid, s->cy)->cellsize) + s->cx >= grid_get_line(ctx->s->grid, + s->cy)->cellsize) break; grid_get_cell(gd, xx, yy, &gc); if (xx + gc.data.width > px + nx) break; grid_view_set_cell(ctx->s->grid, s->cx, s->cy, &gc); if (wp != NULL) { + if (! screen_redraw_is_visible(r, px)) + break; ttyctx.cell = &gc; tty_write(tty_cmd_cell, &ttyctx); ttyctx.ocx++; @@ -1817,12 +1822,13 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen *s = ctx->s; struct screen_write_citem *ci, *tmp; struct screen_write_cline *cl; - u_int y, cx, cy, last, items = 0, r; + u_int y, cx, cy, last, items = 0, i; u_int wr_start, wr_end, wr_length, wsx, wsy; int r_start, r_end, ci_start, ci_end; int xoff, yoff; struct tty_ctx ttyctx; - struct visible_ranges *vr; + struct visible_ranges *r; + struct visible_range *ri; struct window_pane *wp = ctx->wp; if (s->mode & MODE_SYNC) { @@ -1879,8 +1885,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, continue; cl = &ctx->s->write_list[y]; - vr = screen_redraw_get_visible_ranges(wp, 0, y + yoff, - wsx); + r = screen_redraw_get_visible_ranges(wp, 0, y + yoff, wsx, + NULL); last = UINT_MAX; TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { @@ -1891,22 +1897,26 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ci->x, last); } wr_length = 0; - for (r = 0; r < vr->used; r++) { - if (vr->nx[r] == 0) continue; - r_start = vr->px[r]; - r_end = vr->px[r] + vr->nx[r]; + for (i = 0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; + r_start = ri->px; + r_end = ri->px + ri->nx; ci_start = ci->x; ci_end = ci->x + ci->used; - if (ci_start + xoff > r_end || ci_end + xoff < r_start) + if (ci_start + xoff > r_end || + ci_end + xoff < r_start) continue; if (r_start > ci_start + xoff) - wr_start = ci_start + (r_start - (ci_start + xoff)); + wr_start = ci_start + + (r_start - (ci_start + xoff)); else wr_start = ci_start; if (ci_end + xoff > r_end) - wr_end = ci_end - ((ci_end + xoff) - r_end); + wr_end = ci_end - + ((ci_end + xoff) - r_end); else wr_end = ci_end; wr_length = wr_end - wr_start; @@ -2101,6 +2111,7 @@ void screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) { struct screen *s = ctx->s; + struct window_pane *wp = ctx->wp; struct grid *gd = s->grid; const struct utf8_data *ud = &gc->data; struct grid_line *gl; @@ -2108,8 +2119,10 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) struct grid_cell tmp_gc, now_gc; struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); - u_int width = ud->width, xx, not_wrap; + u_int width = ud->width, xx, not_wrap, i, n, vis; int selected, skip = 1, redraw = 0; + struct visible_ranges *r; + struct visible_range *ri; /* Ignore padding cells. */ if (gc->flags & GRID_FLAG_PADDING) @@ -2228,11 +2241,33 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (!skip && !(s->mode & MODE_SYNC)) { if (selected) { screen_select_cell(s, &tmp_gc, gc); - ttyctx.cell = &tmp_gc; } else - ttyctx.cell = gc; + memcpy(&tmp_gc, gc, sizeof tmp_gc); + ttyctx.cell = &tmp_gc; ttyctx.num = redraw ? 2 : 0; - tty_write(tty_cmd_cell, &ttyctx); + /* xxx to be cached in wp */ + r = screen_redraw_get_visible_ranges(wp, s->cx, s->cy, width, + NULL); + for (i=0; i < r->used; i++) vis += r->ranges[i].nx; + if (vis < width) { + /* Wide character or tab partly obscured. Write + * spaces one by one in unobscured region(s). + */ + *tmp_gc.data.data = ' '; + tmp_gc.data.width = tmp_gc.data.size = + tmp_gc.data.have = 1; + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; + for (n = 0; n < ri->nx; n++) { + screen_write_set_cursor(ctx, ri->px + n, + -1); + tty_write(tty_cmd_cell, &ttyctx); + } + } + } else { + tty_write(tty_cmd_cell, &ttyctx); + } } } @@ -2241,12 +2276,14 @@ static int screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) { struct screen *s = ctx->s; + struct window_pane *wp = ctx->wp; struct grid *gd = s->grid; const struct utf8_data *ud = &gc->data; - u_int n, cx = s->cx, cy = s->cy; + u_int i, n, cx = s->cx, cy = s->cy, vis; struct grid_cell last; struct tty_ctx ttyctx; int force_wide = 0, zero_width = 0; + struct visible_ranges *r; /* Ignore U+3164 HANGUL_FILLER entirely. */ if (utf8_is_hangul_filler(ud)) @@ -2334,6 +2371,21 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) if (force_wide) grid_view_set_padding(gd, cx - 1, cy); + /* + * Check if all of this character is visible. No character will + * be obscured in the middle, only on left or right, but there + * could be an empty range in the visible ranges so we add them all up. + */ + r = screen_redraw_get_visible_ranges(wp, cx - n, cy, n, NULL); + for (i=0; i < r->used; i++) vis += r->ranges[i].nx; + if (vis < n) { + /* + * Part of this character is obscured. Return 1 + * and let screen_write_cell write a space. + */ + return (1); + } + /* * Redraw the combined cell. If forcing the cell to width 2, reset the * cached cursor position in the tty, since we don't really know diff --git a/screen.c b/screen.c index 7f4f9383..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. */ @@ -770,3 +771,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 6c1a3273..9e06a859 100644 --- a/server-client.c +++ b/server-client.c @@ -161,38 +161,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. */ @@ -201,12 +221,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. */ @@ -3023,7 +3044,7 @@ server_client_reset_state(struct client *c) cy += status_line_size(c); } if (!screen_redraw_is_visible( - screen_redraw_get_visible_ranges(wp, cx, cy, 1), cx)) + screen_redraw_get_visible_ranges(wp, cx, cy, 1, NULL), cx)) cursor = 0; if (!cursor) diff --git a/tmux.h b/tmux.h index 5b75043b..a29b8280 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 */ @@ -1177,6 +1177,19 @@ enum client_theme { THEME_DARK }; +/* 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 */ +}; + /* Child window structure. */ struct window_pane { u_int id; @@ -1268,6 +1281,8 @@ struct window_pane { struct style scrollbar_style; + struct visible_ranges r; + TAILQ_ENTRY(window_pane) entry; /* link in list of all panes */ TAILQ_ENTRY(window_pane) sentry; /* link in list of last visited */ TAILQ_ENTRY(window_pane) zentry; /* z-index link in list of all panes */ @@ -1606,6 +1621,7 @@ struct tty { size_t discarded; struct termios tio; + struct visible_ranges r; struct grid_cell cell; struct grid_cell last_cell; @@ -1922,22 +1938,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 *); @@ -2269,14 +2278,6 @@ struct mode_tree_sort_criteria { int reversed; }; -/* Visible range array element. nx==-1 is end of array mark. */ -struct visible_ranges { - u_int *px; /* Start */ - u_int *nx; /* Length */ - size_t used; - size_t size; -}; - /* tmux.c */ extern struct options *global_options; extern struct options *global_s_options; @@ -2538,6 +2539,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); @@ -2562,7 +2565,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 *); @@ -2919,8 +2930,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 *); @@ -3217,7 +3230,7 @@ void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *, int); int screen_redraw_is_visible(struct visible_ranges *, u_int px); struct visible_ranges *screen_redraw_get_visible_ranges(struct window_pane *, - u_int, u_int, u_int); + u_int, u_int, u_int, struct visible_ranges *); /* screen.c */ @@ -3240,11 +3253,12 @@ 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); const char *screen_mode_to_string(int); +__unused char * screen_print(struct screen *s); /* window.c */ extern struct windows windows; @@ -3631,8 +3645,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 89f0f7fb..a8044a56 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 *); #ifdef ENABLE_SIXEL static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), @@ -528,6 +521,8 @@ void tty_free(struct tty *tty) { tty_close(tty); + + free(tty->r.ranges); } void @@ -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]; @@ -1074,7 +1069,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)) @@ -1169,8 +1164,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); @@ -1206,13 +1199,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. */ @@ -1222,17 +1210,21 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, { struct client *c = tty->client; struct window_pane *wp = ctx->arg; - struct visible_ranges *vr = NULL; - u_int r, i, x, rx, ry; + struct visible_ranges *r; + struct visible_range *ri; + u_int i, l, x, rx, ry; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); - if (tty_clamp_line(tty, ctx, px, py, nx, &i, &x, &rx, &ry)) { - vr = screen_redraw_get_visible_ranges(wp, x, ry, rx); - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) + if (tty_clamp_line(tty, ctx, px, py, nx, &l, &x, &rx, &ry)) { + r = tty_check_overlay_range(tty, x, ry, rx); + r = screen_redraw_get_visible_ranges(wp, x, ry, rx, r); + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; - tty_clear_line(tty, &ctx->defaults, ry, vr->px[r], vr->nx[r], bg); + tty_clear_line(tty, &ctx->defaults, ry, ri->px, ri->nx, + bg); } } } @@ -1308,8 +1300,9 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, const struct grid_cell *defaults = &ctx->defaults; struct window *w = c->session->curw->window; struct window_pane *wpl, *wp = ctx->arg; - struct visible_ranges *vr = NULL; - u_int r, yy, overlap = 0, oy = 0; + struct visible_ranges *r; + struct visible_range *ri; + u_int i, yy, overlap = 0, oy = 0; char tmp[64]; log_debug("%s: %s, %u,%u at %u,%u", __func__, c->name, nx, ny, px, py); @@ -1388,11 +1381,12 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, /* Couldn't use an escape sequence, loop over the lines. */ for (yy = py; yy < py + ny; yy++) { - vr = screen_redraw_get_visible_ranges(wp, px, yy - oy, nx); - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) - continue; - tty_clear_line(tty, defaults, yy, vr->px[r], vr->nx[r], bg); + r = tty_check_overlay_range(tty, px, yy - oy, nx); + r = screen_redraw_get_visible_ranges(wp, px, yy - oy, nx, r); + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; + tty_clear_line(tty, defaults, yy, ri->px, ri->nx, bg); } } } @@ -1416,19 +1410,22 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) { struct screen *s = ctx->s; struct window_pane *wp = ctx->arg; - struct visible_ranges *vr = NULL; - u_int nx = ctx->sx, i, x, rx, ry, r; + struct visible_ranges *r = NULL; + struct visible_range *ri; + u_int nx = ctx->sx, i, px, x, rx, ry; log_debug("%s: %s %u %d", __func__, tty->client->name, py, ctx->bigger); if (!ctx->bigger) { if (wp) { - vr = screen_redraw_get_visible_ranges(wp, 0, ctx->yoff + py, nx); - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) - continue; - tty_draw_line(tty, s, vr->px[r], py, vr->nx[r], - ctx->xoff + vr->px[r], ctx->yoff + py, + r = tty_check_overlay_range(tty, 0, ctx->yoff + py, nx); + r = screen_redraw_get_visible_ranges(wp, 0, ctx->yoff + + py, nx, r); + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; + tty_draw_line(tty, s, ri->px, py, ri->nx, + ctx->xoff + ri->px, ctx->yoff + py, &ctx->defaults, ctx->palette); } } else { @@ -1437,24 +1434,26 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) } return; } - if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) { + if (tty_clamp_line(tty, ctx, 0, py, nx, &px, &x, &rx, &ry)) { if (wp) { - vr = screen_redraw_get_visible_ranges(wp, i, py, rx); - for (r=0; r < vr->used; r++) { - if (vr->nx[r] == 0) + r = tty_check_overlay_range(tty, i, py, rx); + r = screen_redraw_get_visible_ranges(wp, i, py, rx, r); + for (i=0; i < r->used; i++) { + ri = &r->ranges[i]; + if (ri->nx == 0) continue; - tty_draw_line(tty, s, i, py, vr->nx[r], - x + vr->px[r], ry, &ctx->defaults, + tty_draw_line(tty, s, i, py, ri->nx, + x + ri->px, ry, &ctx->defaults, ctx->palette); } } else { - tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults, + tty_draw_line(tty, s, px, py, rx, x, ry, &ctx->defaults, ctx->palette); } } } -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; @@ -1487,233 +1486,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) { - /* Do I need to check - !tty_is_obstructed(c->session->curw->window->active - here too? It's not certain that the active pane is - the one being drawn in. (delete this comment) */ - 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; /* need to check visible_ranges too? xxxx */ - 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)); } #ifdef ENABLE_SIXEL @@ -2275,10 +2075,8 @@ 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 window_pane *wp = ctx->arg; - struct visible_ranges *vr; - u_int px, py, i, vis = 0, vis2 = 0; + struct visible_ranges *r; + u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; @@ -2286,11 +2084,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) (gcp->data.width == 1 && !tty_check_overlay(tty, px, py))) return; - vr = screen_redraw_get_visible_ranges(wp, px, py, - gcp->data.width); - if (ctx->num == 2) { - /* xxxx need to check visible range */ + /* xxxx need to check visible range */ tty_draw_line(tty, s, 0, s->cy, screen_size_x(s), ctx->xoff - ctx->wox, py, &ctx->defaults, ctx->palette); return; @@ -2298,13 +2093,10 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { - for (i = 0; i < vr->used; i++) - vis2 += vr->nx[i]; - tty_check_overlay_range(tty, px, py, gcp->data.width, &r); - for (i = 0; i < OVERLAY_MAX_RANGES; i++) - vis += r.nx[i]; - if (vis < gcp->data.width || - vis2 < gcp->data.width) { /* xxxx need to check visible range */ + 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); return; @@ -2319,7 +2111,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - if (screen_redraw_is_visible(vr, px)) + if (screen_redraw_is_visible(r, px)) tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, ctx->s->hyperlinks); @@ -2330,7 +2122,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 *ri; u_int i, px, py, cx; char *cp = ctx->ptr; @@ -2355,20 +2148,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++) { + ri = &r->ranges[i]; + if (ri->nx != 0) { + cx = ri->px - ctx->xoff + ctx->wox; + tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); + tty_putn(tty, cp + ri->px - px, ri->nx, ri->nx); + } } } @@ -3324,7 +3118,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) {