mirror of
https://github.com/tmux/tmux.git
synced 2026-10-06 17:49:52 +00:00
As well as tracking modified (dirty) lines during a synchronized update,
also track scrolled lines. This means they can be scrolled as one instead of forcing each line to be redrawn - much less expensive for a very common operation. From Ben Maurer in GitHub issue 5611.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: screen-redraw.c,v 1.161 2026/09/22 16:56:07 nicm Exp $ */
|
/* $OpenBSD: screen-redraw.c,v 1.162 2026/09/24 11:19:39 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1738,14 +1738,14 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
|||||||
if (wp != NULL) {
|
if (wp != NULL) {
|
||||||
if (wp->base.mode & MODE_SYNC)
|
if (wp->base.mode & MODE_SYNC)
|
||||||
screen_write_stop_sync(wp);
|
screen_write_stop_sync(wp);
|
||||||
screen_write_clear_dirty(wp);
|
screen_write_sync_clear_dirty(wp);
|
||||||
} else {
|
} else {
|
||||||
TAILQ_FOREACH(loop, &scene->w->panes, entry) {
|
TAILQ_FOREACH(loop, &scene->w->panes, entry) {
|
||||||
if (!window_pane_is_visible(loop))
|
if (!window_pane_is_visible(loop))
|
||||||
continue;
|
continue;
|
||||||
if (loop->base.mode & MODE_SYNC)
|
if (loop->base.mode & MODE_SYNC)
|
||||||
screen_write_stop_sync(loop);
|
screen_write_stop_sync(loop);
|
||||||
screen_write_clear_dirty(loop);
|
screen_write_sync_clear_dirty(loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
183
screen-write.c
183
screen-write.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: screen-write.c,v 1.292 2026/09/21 12:14:32 nicm Exp $ */
|
/* $OpenBSD: screen-write.c,v 1.293 2026/09/24 11:19:39 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* 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);
|
struct grid_cell *, u_int);
|
||||||
static int screen_write_combine(struct screen_write_ctx *,
|
static int screen_write_combine(struct screen_write_ctx *,
|
||||||
const struct grid_cell *);
|
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 {
|
struct screen_write_citem {
|
||||||
u_int x;
|
u_int x;
|
||||||
@@ -215,6 +217,27 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx)
|
|||||||
return (0);
|
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? */
|
/* Should we draw to the TTY? */
|
||||||
static int
|
static int
|
||||||
screen_write_should_draw_lines(struct screen_write_ctx *ctx, u_int y, u_int ny)
|
screen_write_should_draw_lines(struct screen_write_ctx *ctx, u_int y, u_int ny)
|
||||||
@@ -228,21 +251,9 @@ screen_write_should_draw_lines(struct screen_write_ctx *ctx, u_int y, u_int ny)
|
|||||||
return (0);
|
return (0);
|
||||||
if (s->mode & MODE_SYNC) {
|
if (s->mode & MODE_SYNC) {
|
||||||
if (wp != NULL && y < sy && ny != 0) {
|
if (wp != NULL && y < sy && ny != 0) {
|
||||||
bs = wp->sync_dirty;
|
|
||||||
if (ny > sy - y)
|
if (ny > sy - y)
|
||||||
ny = sy - y;
|
ny = sy - y;
|
||||||
if (bs == NULL || wp->sync_dirty_size != sy) {
|
bs = screen_write_sync_allocate_dirty(wp, 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;
|
|
||||||
}
|
|
||||||
bit_nset(bs, y, y + ny - 1);
|
bit_nset(bs, y, y + ny - 1);
|
||||||
}
|
}
|
||||||
return (0);
|
return (0);
|
||||||
@@ -1032,7 +1043,7 @@ screen_write_sync_callback(__unused int fd, __unused short events, void *arg)
|
|||||||
|
|
||||||
if (wp->base.mode & MODE_SYNC) {
|
if (wp->base.mode & MODE_SYNC) {
|
||||||
wp->base.mode &= ~MODE_SYNC;
|
wp->base.mode &= ~MODE_SYNC;
|
||||||
screen_write_flush_dirty(wp);
|
screen_write_sync_flush_dirty(wp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1064,7 +1075,7 @@ screen_write_stop_sync(struct window_pane *wp)
|
|||||||
evtimer_del(&wp->sync_timer);
|
evtimer_del(&wp->sync_timer);
|
||||||
wp->base.mode &= ~MODE_SYNC;
|
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);
|
log_debug("%s: %%%u stopped sync mode", __func__, wp->id);
|
||||||
}
|
}
|
||||||
@@ -1272,7 +1283,7 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
|
|||||||
|
|
||||||
/* Redraw dirty lines. */
|
/* Redraw dirty lines. */
|
||||||
static void
|
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 screen_write_ctx ctx;
|
||||||
struct tty_ctx ttyctx;
|
struct tty_ctx ttyctx;
|
||||||
@@ -1285,29 +1296,143 @@ screen_write_flush_dirty(struct window_pane *wp)
|
|||||||
screen_write_start_pane(&ctx, wp, s);
|
screen_write_start_pane(&ctx, wp, s);
|
||||||
screen_write_initctx(&ctx, &ttyctx, 1, 1);
|
screen_write_initctx(&ctx, &ttyctx, 1, 1);
|
||||||
|
|
||||||
for (y = 0; y < sy; y++) {
|
if (wp->sync_scrolled != 0)
|
||||||
if (bit_test(wp->sync_dirty, y)) {
|
screen_write_sync_apply_scroll(&ctx, &ttyctx);
|
||||||
screen_write_redraw_line(&ctx, &ttyctx, y);
|
|
||||||
lines++;
|
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);
|
log_debug("%s: %%%u had %u dirty lines", __func__, wp->id, lines);
|
||||||
|
|
||||||
screen_write_stop(&ctx);
|
screen_write_stop(&ctx);
|
||||||
screen_write_clear_dirty(wp);
|
screen_write_sync_clear_dirty(wp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear any dirty lines. */
|
/* Clear pending synchronized output. */
|
||||||
void
|
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);
|
free(wp->sync_dirty);
|
||||||
wp->sync_dirty = NULL;
|
wp->sync_dirty = NULL;
|
||||||
wp->sync_dirty_size = 0;
|
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)
|
||||||
|
{
|
||||||
|
struct window_pane *wp = ttyctx->arg;
|
||||||
|
|
||||||
|
bit_nset(wp->sync_dirty, wp->sync_rupper, wp->sync_rlower);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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. */
|
/* Redraw all visible cells in a pane. */
|
||||||
static void
|
static void
|
||||||
screen_write_redraw_pane(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
|
screen_write_redraw_pane(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
|
||||||
@@ -2340,10 +2465,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only,
|
|||||||
if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP)))
|
if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP)))
|
||||||
goto discard;
|
goto discard;
|
||||||
if (s->mode & MODE_SYNC) {
|
if (s->mode & MODE_SYNC) {
|
||||||
if (ctx->scrolled != 0) {
|
if (ctx->scrolled != 0)
|
||||||
screen_write_should_draw_lines(ctx, s->rupper,
|
screen_write_sync_scroll_dirty(ctx);
|
||||||
s->rlower + 1 - s->rupper);
|
|
||||||
}
|
|
||||||
for (y = 0; y < screen_size_y(s); y++) {
|
for (y = 0; y < screen_size_y(s); y++) {
|
||||||
cl = &s->write_list[y];
|
cl = &s->write_list[y];
|
||||||
if (!TAILQ_EMPTY(&cl->items))
|
if (!TAILQ_EMPTY(&cl->items))
|
||||||
|
|||||||
8
tmux.h
8
tmux.h
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: tmux.h,v 1.1447 2026/09/22 14:10:26 nicm Exp $ */
|
/* $OpenBSD: tmux.h,v 1.1448 2026/09/24 11:19:39 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1313,6 +1313,10 @@ struct window_pane {
|
|||||||
|
|
||||||
bitstr_t *sync_dirty;
|
bitstr_t *sync_dirty;
|
||||||
u_int sync_dirty_size;
|
u_int sync_dirty_size;
|
||||||
|
u_int sync_scrolled;
|
||||||
|
u_int sync_rupper;
|
||||||
|
u_int sync_rlower;
|
||||||
|
u_int sync_bg;
|
||||||
|
|
||||||
u_int sb_slider_y;
|
u_int sb_slider_y;
|
||||||
u_int sb_slider_h;
|
u_int sb_slider_h;
|
||||||
@@ -3540,7 +3544,7 @@ void screen_write_mode_clear(struct screen_write_ctx *, int);
|
|||||||
void screen_write_start_sync(struct window_pane *);
|
void screen_write_start_sync(struct window_pane *);
|
||||||
void screen_write_stop_sync(struct window_pane *);
|
void screen_write_stop_sync(struct window_pane *);
|
||||||
void screen_write_end_sync(struct screen_write_ctx *);
|
void screen_write_end_sync(struct screen_write_ctx *);
|
||||||
void screen_write_clear_dirty(struct window_pane *);
|
void screen_write_sync_clear_dirty(struct window_pane *);
|
||||||
void screen_write_cursorup(struct screen_write_ctx *, u_int);
|
void screen_write_cursorup(struct screen_write_ctx *, u_int);
|
||||||
void screen_write_cursordown(struct screen_write_ctx *, u_int);
|
void screen_write_cursordown(struct screen_write_ctx *, u_int);
|
||||||
void screen_write_cursorright(struct screen_write_ctx *, u_int);
|
void screen_write_cursorright(struct screen_write_ctx *, u_int);
|
||||||
|
|||||||
4
window.c
4
window.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: window.c,v 1.377 2026/09/21 10:33:16 nicm Exp $ */
|
/* $OpenBSD: window.c,v 1.378 2026/09/24 11:19:39 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1521,7 +1521,7 @@ window_pane_destroy(struct window_pane *wp)
|
|||||||
window_pane_clear_prompt(wp);
|
window_pane_clear_prompt(wp);
|
||||||
|
|
||||||
window_pane_free_modes(wp);
|
window_pane_free_modes(wp);
|
||||||
screen_write_clear_dirty(wp);
|
screen_write_sync_clear_dirty(wp);
|
||||||
|
|
||||||
if (wp->fd != -1) {
|
if (wp->fd != -1) {
|
||||||
bufferevent_free(wp->event);
|
bufferevent_free(wp->event);
|
||||||
|
|||||||
Reference in New Issue
Block a user