Merge branch 'master' into command_parser

# Conflicts:
#	cmd-parse.y
#	regress/control-client-exit.sh
#	regress/if-shell-error.sh
#	tmux.h
This commit is contained in:
Nicholas Marriott
2026-10-06 13:16:00 +01:00
205 changed files with 14822 additions and 2883 deletions

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: screen-write.c,v 1.290 2026/08/24 15:05:26 nicm Exp $ */
/* $OpenBSD: screen-write.c,v 1.299 2026/10/05 08:41:23 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -38,7 +38,9 @@ static int screen_write_overwrite(struct screen_write_ctx *,
struct grid_cell *, u_int);
static int screen_write_combine(struct screen_write_ctx *,
const struct grid_cell *);
static void screen_write_flush_dirty(struct window_pane *);
static void screen_write_sync_flush_dirty(struct window_pane *);
static void screen_write_sync_apply_scroll(struct screen_write_ctx *,
struct tty_ctx *);
struct screen_write_citem {
u_int x;
@@ -120,14 +122,26 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy)
evtimer_add(&w->offset_timer, &tv);
}
/* Do a full redraw. */
/* Redraw lines. */
static void
screen_write_redraw_cb(const struct tty_ctx *ttyctx)
screen_write_redraw_cb(const struct tty_ctx *ttyctx, u_int py, u_int ny)
{
struct window_pane *wp = ttyctx->arg;
int x0, y0, x1, y1;
if (wp != NULL)
wp->flags |= PANE_REDRAW;
if (wp == NULL)
return;
x0 = wp->xoff;
y0 = wp->yoff + (int)py;
x1 = x0 + (int)wp->sx;
y1 = y0 + (int)ny;
if (x0 < 0)
x0 = 0;
if (y0 < 0)
y0 = 0;
if (x1 > x0 && y1 > y0)
redraw_damage_window(wp->window, x0, y0, x1 - x0, y1 - y0);
}
/* Update context for client. */
@@ -175,11 +189,12 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
return (1);
}
/* Return 1 if there is a floating window pane overlapping this pane. */
/* Return 1 if a menu or floating pane overlaps this pane. */
static int
screen_write_pane_is_obscured(struct screen_write_ctx *ctx)
{
struct window_pane *wp = ctx->wp;
struct menu_data *md;
if (ctx->wp == NULL)
return (0);
@@ -190,6 +205,16 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx)
}
ctx->flags |= SCREEN_WRITE_CHECKED_IF_OBSCURED;
md = wp->window->menu;
if (md != NULL &&
(int)menu_x(md) < wp->xoff + (int)wp->sx &&
(int)(menu_x(md) + menu_width(md)) > wp->xoff &&
(int)menu_y(md) < wp->yoff + (int)wp->sy &&
(int)(menu_y(md) + menu_height(md)) > wp->yoff) {
ctx->flags |= SCREEN_WRITE_OBSCURED;
return (1);
}
if (ctx->wp->xoff < 0 ||
ctx->wp->yoff < 0 ||
ctx->wp->xoff + ctx->wp->sx > ctx->wp->window->sx ||
@@ -199,15 +224,7 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx)
}
while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) {
if (window_pane_is_floating(wp) &&
((wp->yoff >= ctx->wp->yoff &&
wp->yoff <= ctx->wp->yoff + (int)ctx->wp->sy) ||
(wp->yoff + (int)wp->sy >= ctx->wp->yoff &&
wp->yoff + wp->sy <= ctx->wp->yoff + ctx->wp->sy)) &&
((wp->xoff >= ctx->wp->xoff &&
wp->xoff <= ctx->wp->xoff + (int)ctx->wp->sx) ||
(wp->xoff + (int)wp->sx >= ctx->wp->xoff &&
wp->xoff + wp->sx <= ctx->wp->xoff + ctx->wp->sx))) {
if (window_pane_floating_overlaps(wp, ctx->wp)) {
ctx->flags |= SCREEN_WRITE_OBSCURED;
return (1);
}
@@ -215,6 +232,27 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx)
return (0);
}
/* Allocate dirty lines for this screen size. */
static bitstr_t *
screen_write_sync_allocate_dirty(struct window_pane *wp, u_int sy)
{
bitstr_t *bs = wp->sync_dirty;
if (bs != NULL && wp->sync_dirty_size == sy)
return (bs);
free(bs);
bs = wp->sync_dirty = bit_alloc(sy);
if (bs == NULL)
fatal("bit_alloc failed");
if (wp->sync_dirty_size != 0) {
bit_nset(bs, 0, sy - 1);
wp->sync_scrolled = 0;
}
wp->sync_dirty_size = sy;
return (bs);
}
/* Should we draw to the TTY? */
static int
screen_write_should_draw_lines(struct screen_write_ctx *ctx, u_int y, u_int ny)
@@ -228,21 +266,9 @@ screen_write_should_draw_lines(struct screen_write_ctx *ctx, u_int y, u_int ny)
return (0);
if (s->mode & MODE_SYNC) {
if (wp != NULL && y < sy && ny != 0) {
bs = wp->sync_dirty;
if (ny > sy - y)
ny = sy - y;
if (bs == NULL || wp->sync_dirty_size != sy) {
if (bs != NULL && wp->sync_dirty_size != sy) {
y = 0;
ny = sy;
}
free(bs);
bs = wp->sync_dirty = bit_alloc(sy);
if (bs == NULL)
fatal("bit_alloc failed");
wp->sync_dirty_size = sy;
}
bs = screen_write_sync_allocate_dirty(wp, sy);
bit_nset(bs, y, y + ny - 1);
}
return (0);
@@ -305,21 +331,16 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
if (~ctx->flags & SCREEN_WRITE_SYNC) {
/*
* For the active pane showing its base screen or for an
* overlay (no pane), only use synchronized updates if
* requested (commands that move the cursor); for other panes
* or a pane in a mode, always use it, since the cursor will
* have to move.
* For the active pane showing its base screen, only use
* synchronized updates if requested (commands that move the
* cursor); for other panes or a pane in a mode, always use it,
* since the cursor will have to move.
*/
if (ctx->wp != NULL && (ctx->wp != ctx->wp->window->active ||
ctx->wp->screen != &ctx->wp->base))
ttyctx->flags |= TTY_CTX_SYNC;
else {
if (ctx->wp == NULL)
ttyctx->flags |= TTY_CTX_OVERLAY_SYNC;
if (is_sync)
ttyctx->flags |= TTY_CTX_SYNC;
}
else if (is_sync)
ttyctx->flags |= TTY_CTX_SYNC;
tty_write(tty_cmd_syncstart, ttyctx);
ctx->flags |= SCREEN_WRITE_SYNC;
}
@@ -731,7 +752,7 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src,
if (!window_position_is_visible(r, xoff + s->cx))
break;
ttyctx.cell = &gc;
ttyctx.flags &= (TTY_CTX_OVERLAY_SYNC|TTY_CTX_SYNC);
ttyctx.flags &= TTY_CTX_SYNC;
tty_write(tty_cmd_cell, &ttyctx);
ttyctx.ocx++;
@@ -855,34 +876,39 @@ screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, int choice,
struct screen *s = ctx->s;
struct grid_cell default_gc;
const struct grid_cell *gc = &default_gc;
u_int cx, cy, i, j, width = menu->width;
u_int border, cx = s->cx, cy = s->cy, i, j, width;
const char *name;
cx = s->cx;
cy = s->cy;
memcpy(&default_gc, menu_gc, sizeof default_gc);
screen_write_box(ctx, menu->width + 4, menu->count + 2, lines,
border_gc, menu->title);
if (lines == BOX_LINES_NONE) {
border = 0;
width = menu->item_width;
} else {
border = 1;
width = menu->width;
screen_write_box(ctx, width + 4, menu->count + 2, lines,
border_gc, menu->title);
}
for (i = 0; i < menu->count; i++) {
name = menu->items[i].name;
if (name == NULL) {
screen_write_cursormove(ctx, cx, cy + 1 + i, 0);
screen_write_hline(ctx, width + 4, 1, 1, lines,
border_gc);
screen_write_cursormove(ctx, cx, cy + border + i, 0);
screen_write_hline(ctx, width + 2 + (2 * border), 1, 1,
lines, border_gc);
continue;
}
if (choice >= 0 && i == (u_int)choice && *name != '-')
gc = choice_gc;
screen_write_cursormove(ctx, cx + 1, cy + 1 + i, 0);
screen_write_cursormove(ctx, cx + border, cy + border + i, 0);
for (j = 0; j < width + 2; j++)
screen_write_putc(ctx, gc, ' ');
screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0);
screen_write_cursormove(ctx, cx + border + 1, cy + border + i,
0);
if (*name == '-') {
default_gc.attr |= GRID_ATTR_DIM;
format_draw(ctx, gc, width, name + 1, NULL, 0);
@@ -1049,7 +1075,7 @@ screen_write_sync_callback(__unused int fd, __unused short events, void *arg)
if (wp->base.mode & MODE_SYNC) {
wp->base.mode &= ~MODE_SYNC;
screen_write_flush_dirty(wp);
screen_write_sync_flush_dirty(wp);
}
}
@@ -1081,7 +1107,7 @@ screen_write_stop_sync(struct window_pane *wp)
evtimer_del(&wp->sync_timer);
wp->base.mode &= ~MODE_SYNC;
screen_write_flush_dirty(wp);
screen_write_sync_flush_dirty(wp);
log_debug("%s: %%%u stopped sync mode", __func__, wp->id);
}
@@ -1289,7 +1315,7 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
/* Redraw dirty lines. */
static void
screen_write_flush_dirty(struct window_pane *wp)
screen_write_sync_flush_dirty(struct window_pane *wp)
{
struct screen_write_ctx ctx;
struct tty_ctx ttyctx;
@@ -1302,29 +1328,143 @@ screen_write_flush_dirty(struct window_pane *wp)
screen_write_start_pane(&ctx, wp, s);
screen_write_initctx(&ctx, &ttyctx, 1, 1);
for (y = 0; y < sy; y++) {
if (bit_test(wp->sync_dirty, y)) {
screen_write_redraw_line(&ctx, &ttyctx, y);
lines++;
if (wp->sync_scrolled != 0)
screen_write_sync_apply_scroll(&ctx, &ttyctx);
if (~wp->flags & PANE_REDRAW) {
for (y = 0; y < sy; y++) {
if (bit_test(wp->sync_dirty, y)) {
screen_write_redraw_line(&ctx, &ttyctx, y);
lines++;
}
}
}
log_debug("%s: %%%u had %u dirty lines", __func__, wp->id, lines);
screen_write_stop(&ctx);
screen_write_clear_dirty(wp);
screen_write_sync_clear_dirty(wp);
}
/* Clear any dirty lines. */
/* Clear pending synchronized output. */
void
screen_write_clear_dirty(struct window_pane *wp)
screen_write_sync_clear_dirty(struct window_pane *wp)
{
if (wp != NULL && wp->sync_dirty != NULL) {
if (wp != NULL) {
free(wp->sync_dirty);
wp->sync_dirty = NULL;
wp->sync_dirty_size = 0;
wp->sync_scrolled = 0;
}
}
/* Defer scrolling until the synchronized update ends. */
static void
screen_write_sync_scroll_dirty(struct screen_write_ctx *ctx)
{
struct window_pane *wp = ctx->wp;
struct screen *s = ctx->s;
u_int n = ctx->scrolled, y, sy = screen_size_y(s);
u_int ry = s->rlower + 1 - s->rupper;
bitstr_t *bs;
if (n > ry)
n = ry;
if (wp == NULL ||
n == ry ||
s->rlower >= sy ||
window_pane_scrollbar_overlay_visible(wp)) {
screen_write_should_draw_lines(ctx, s->rupper, ry);
return;
}
if (wp->flags & (PANE_REDRAW|PANE_DROP))
return;
bs = screen_write_sync_allocate_dirty(wp, sy);
if (wp->sync_scrolled != 0 &&
(wp->sync_rupper != s->rupper ||
wp->sync_rlower != s->rlower ||
wp->sync_bg != ctx->bg)) {
log_debug("%s: %%%u region changed, redrawing all", __func__,
wp->id);
bit_nset(bs, 0, sy - 1);
wp->sync_scrolled = 0;
return;
}
/* Keep dirty lines aligned with the scrolled grid. */
for (y = s->rupper; y + n <= s->rlower; y++) {
if (bit_test(bs, y + n))
bit_set(bs, y);
else
bit_clear(bs, y);
}
bit_nset(bs, s->rlower + 1 - n, s->rlower);
wp->sync_rupper = s->rupper;
wp->sync_rlower = s->rlower;
wp->sync_bg = ctx->bg;
wp->sync_scrolled += n;
if (wp->sync_scrolled >= ry) {
bit_nset(bs, s->rupper, s->rlower);
wp->sync_scrolled = 0;
}
window_pane_scrollbar_redraw(wp);
log_debug("%s: %%%u deferred scroll of %u (region %u-%u)", __func__,
wp->id, wp->sync_scrolled, s->rupper, s->rlower);
}
/* Redraw the scrolled lines for a client which cannot scroll them. */
static void
screen_write_sync_redraw_cb(const struct tty_ctx *ttyctx, u_int py, u_int ny)
{
struct window_pane *wp = ttyctx->arg;
bit_nset(wp->sync_dirty, py, py + ny - 1);
}
/* Send the deferred scroll to the client. */
static void
screen_write_sync_apply_scroll(struct screen_write_ctx *ctx,
struct tty_ctx *ttyctx)
{
struct window_pane *wp = ctx->wp;
struct screen *s = ctx->s;
u_int sy = screen_size_y(s), n = wp->sync_scrolled;
tty_ctx_redraw_cb redraw_cb;
wp->sync_scrolled = 0;
if (wp->flags & PANE_REDRAW)
return;
if (wp->sync_rlower >= sy || wp->sync_rupper > wp->sync_rlower) {
bit_nset(wp->sync_dirty, 0, sy - 1);
return;
}
if ((ttyctx->flags & TTY_CTX_PANE_OBSCURED) ||
window_pane_scrollbar_overlay_visible(wp)) {
bit_nset(wp->sync_dirty, wp->sync_rupper, wp->sync_rlower);
return;
}
log_debug("%s: %%%u scroll of %u (region %u-%u)", __func__, wp->id, n,
wp->sync_rupper, wp->sync_rlower);
ttyctx->orupper = wp->sync_rupper;
ttyctx->orlower = wp->sync_rlower;
if (wp->yoff + wp->sy > wp->window->sy)
ttyctx->orlower -= (wp->yoff + wp->sy - wp->window->sy);
ttyctx->n = n;
ttyctx->bg = wp->sync_bg;
redraw_cb = ttyctx->redraw_cb;
ttyctx->redraw_cb = screen_write_sync_redraw_cb;
tty_write(tty_cmd_scrollup, ttyctx);
ttyctx->redraw_cb = redraw_cb;
ttyctx->orupper = s->rupper;
ttyctx->orlower = s->rlower;
ttyctx->n = 0;
}
/* Redraw all visible cells in a pane. */
static void
screen_write_redraw_pane(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
@@ -2172,7 +2312,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx)
screen_write_initctx(ctx, &ttyctx, 1, 0);
if (ttyctx.redraw_cb != NULL)
ttyctx.redraw_cb(&ttyctx);
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
}
/* Trim collected items. */
@@ -2410,13 +2550,18 @@ screen_write_collect_flush_line(struct screen_write_ctx *ctx, u_int y)
ttyctx.n = w_length;
tty_write(tty_cmd_clearcharacter, &ttyctx);
} else {
screen_write_initctx(ctx, &ttyctx, 0, 0);
ttyctx.cell = &ci->gc;
if (ci->wrapped)
ttyctx.flags |= TTY_CTX_WRAPPED;
ttyctx.data.data = cl->data + w_start;
ttyctx.data.size = w_length;
tty_write(tty_cmd_cells, &ttyctx);
screen_write_initctx(ctx, &ttyctx, 0, 1);
if (ttyctx.flags & TTY_CTX_PANE_OBSCURED) {
ttyctx.n = w_length;
tty_write(tty_cmd_redrawline, &ttyctx);
} else {
ttyctx.cell = &ci->gc;
if (ci->wrapped)
ttyctx.flags |= TTY_CTX_WRAPPED;
ttyctx.data.data = cl->data + w_start;
ttyctx.data.size = w_length;
tty_write(tty_cmd_cells, &ttyctx);
}
}
items++;
written = 1;
@@ -2444,10 +2589,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only,
if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP)))
goto discard;
if (s->mode & MODE_SYNC) {
if (ctx->scrolled != 0) {
screen_write_should_draw_lines(ctx, s->rupper,
s->rlower + 1 - s->rupper);
}
if (ctx->scrolled != 0)
screen_write_sync_scroll_dirty(ctx);
for (y = 0; y < screen_size_y(s); y++) {
cl = &s->write_list[y];
if (!TAILQ_EMPTY(&cl->items))
@@ -2646,6 +2789,10 @@ screen_write_collect_add(struct screen_write_ctx *ctx,
* Don't need to check that the attributes and whatnot are still the
* same - input_parse will end the collection when anything that isn't
* a plain character is encountered.
*
* Without wrapping, collect up to but not including the last column.
* Leave that cell to screen_write_cell so repeated writes overwrite it
* and the cursor remains at the right edge.
*/
collect = 1;
@@ -2655,7 +2802,8 @@ screen_write_collect_add(struct screen_write_ctx *ctx,
collect = 0;
else if (gc->attr & GRID_ATTR_CHARSET)
collect = 0;
else if (~s->mode & MODE_WRAP)
else if ((~s->mode & MODE_WRAP) &&
s->cx + ctx->item->used >= sx - 1)
collect = 0;
else if (s->mode & MODE_INSERT)
collect = 0;
@@ -2821,6 +2969,11 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
/* Create space for character in insert mode. */
if (s->mode & MODE_INSERT) {
screen_write_collect_flush(ctx, 0, __func__);
if (wp != NULL && screen_write_pane_is_obscured(ctx)) {
if (screen_write_should_draw_line(ctx, s->cy))
screen_write_redraw_line(ctx, &ttyctx, s->cy);
return;
}
ttyctx.n = width;
if (screen_write_should_draw_line(ctx, s->cy))
tty_write(tty_cmd_insertcharacter, &ttyctx);
@@ -3170,6 +3323,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
{
struct tty_ctx ttyctx;
struct window_pane *wp = ctx->wp;
int pending;
if (wp != NULL && !options_get_number(wp->options, "alternate-screen"))
return;
@@ -3179,11 +3333,12 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
return;
if (wp != NULL) {
pending = !TAILQ_EMPTY(&wp->resize_queue);
window_pane_clear_resizes(wp, NULL);
if (event_initialized(&wp->resize_timer))
evtimer_del(&wp->resize_timer);
layout_fix_panes(wp->window, NULL);
if (!TAILQ_EMPTY(&wp->resize_queue)) {
if (pending || !TAILQ_EMPTY(&wp->resize_queue)) {
window_pane_send_resize(wp, wp->sx, wp->sy);
window_pane_clear_resizes(wp, NULL);
}
@@ -3192,7 +3347,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
screen_write_initctx(ctx, &ttyctx, 1, 0);
if (ttyctx.redraw_cb != NULL)
ttyctx.redraw_cb(&ttyctx);
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
}
/* Turn alternate screen off. */
@@ -3217,5 +3372,5 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc,
screen_write_initctx(ctx, &ttyctx, 1, 0);
if (ttyctx.redraw_cb != NULL)
ttyctx.redraw_cb(&ttyctx);
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
}