Files
tmux/screen-redraw.c
2026-06-16 23:04:57 +01:00

1575 lines
38 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* 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 <sys/types.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
/* Type of span in the scene. */
enum redraw_span_type {
REDRAW_SPAN_PANE, /* inside a pane */
REDRAW_SPAN_OUTSIDE, /* outside the window */
REDRAW_SPAN_EMPTY, /* inside the window but nothing visible */
REDRAW_SPAN_STATUS, /* pane status line */
REDRAW_SPAN_BORDER, /* pane border */
REDRAW_SPAN_SCROLLBAR, /* pane scrollbar */
};
#define REDRAW_SPAN_TYPES 6
/* Border connections to adjacent cells. */
#define REDRAW_BORDER_L 0x1
#define REDRAW_BORDER_R 0x2
#define REDRAW_BORDER_U 0x4
#define REDRAW_BORDER_D 0x8
/* Span flags. */
#define REDRAW_BORDER_IS_ARROW 0x1
#define REDRAW_SCROLLBAR_LEFT 0x2
#define REDRAW_SCROLLBAR_RIGHT 0x4
/* Draw operations. */
#define REDRAW_PANE 0x1
#define REDRAW_OUTSIDE 0x2
#define REDRAW_EMPTY 0x4
#define REDRAW_PANE_BORDER 0x8
#define REDRAW_PANE_STATUS 0x10
#define REDRAW_PANE_SCROLLBAR 0x20
#define REDRAW_STATUS 0x40
#define REDRAW_OVERLAY 0x80
/* Draw everything. */
#define REDRAW_ALL 0x7fffffff
#define REDRAW_IS_ALL(flags) ((flags) == REDRAW_ALL)
/* UTF-8 isolate characters. */
#define REDRAW_START_ISOLATE "\342\201\246"
#define REDRAW_END_ISOLATE "\342\201\251"
/* Data for a span. */
struct redraw_span_data {
enum redraw_span_type type;
union {
struct {
/* Pane this span belongs to. */
struct window_pane *wp;
/* Position of span inside the pane. */
u_int px;
u_int py;
} p;
struct {
/* Adjacent panes on each side. */
struct window_pane *top_wp;
struct window_pane *bottom_wp;
struct window_pane *left_wp;
struct window_pane *right_wp;
/*
* The pane this span belongs if that is known when the
* scene is built. This is used for the half coloured
* active pane indicator.
*/
struct window_pane *style_wp;
/* Cell type for this span. */
int cell_type;
int cell_mask;
/* Line styles for this span. */
enum pane_lines top_lines;
enum pane_lines bottom_lines;
enum pane_lines left_lines;
enum pane_lines right_lines;
/* Flags for this span. */
int flags;
} b;
struct {
/* The pane and the offset into the status line. */
struct window_pane *wp;
u_int offset;
int cell_type;
} st;
struct {
/* Pane this span belongs to. */
struct window_pane *wp;
/* Line within the scrollbar. */
u_int y;
/* Full height of scrollbar. */
u_int height;
/* Flags for this span. */
int flags;
} sb;
};
};
/* A span of cells of the same type inside a line. */
struct redraw_span {
u_int x;
u_int width;
struct redraw_span_data data;
TAILQ_ENTRY(redraw_span) entry;
};
TAILQ_HEAD(redraw_spans, redraw_span);
/* A visible line on the client. */
struct redraw_line {
struct redraw_spans spans[REDRAW_SPAN_TYPES];
};
/* A scene representing all the spans on the client. */
struct redraw_scene {
struct client *c;
struct window *w;
uint64_t generation;
u_int sx;
u_int sy;
u_int ox;
u_int oy;
struct redraw_line *lines;
};
/* Cell for building the scene. */
struct redraw_build_cell {
struct redraw_span_data data;
};
/* Context for building the scene. */
struct redraw_build_ctx {
struct client *c;
struct window *w;
u_int ox;
u_int oy;
u_int sx;
u_int sy;
int sb;
int sbp;
int ind;
struct redraw_build_cell *cells;
};
/* Context for redrawing. */
struct redraw_draw_ctx {
struct redraw_scene *scene;
struct window_pane *active;
struct window_pane *marked;
u_int status_lines;
struct grid_cell default_gc;
int flags;
#define REDRAW_ISOLATES 0x1
#define REDRAW_DEFAULT_SET 0x2
#define REDRAW_STATUS_TOP 0x4
};
/* Initialize the context for building scene. */
static void
screen_redraw_set_context(struct client *c, struct redraw_build_ctx *bctx)
{
struct session *s = c->session;
struct window *w = s->curw->window;
struct options *oo = w->options;
memset(bctx, 0, sizeof *bctx);
bctx->c = c;
bctx->w = w;
tty_window_offset(&c->tty, &bctx->ox, &bctx->oy, &bctx->sx, &bctx->sy);
bctx->sb = options_get_number(oo, "pane-scrollbars");
bctx->sbp = options_get_number(oo, "pane-scrollbars-position");
bctx->ind = options_get_number(oo, "pane-border-indicators");
}
/* Return a cell. */
static struct redraw_build_cell *
screen_redraw_cell(struct redraw_build_ctx *bctx, u_int x, u_int y)
{
return (&bctx->cells[(y * bctx->sx) + x]);
}
/* Reset cell to either empty or outside the window. */
static void
screen_redraw_reset_cell(struct redraw_build_ctx *bctx, u_int x, u_int y)
{
struct redraw_build_cell *bc = screen_redraw_cell(bctx, x, y);
struct window *w = bctx->w;
memset(bc, 0, sizeof *bc);
if (bctx->ox + x <= w->sx && bctx->oy + y <= w->sy)
bc->data.type = REDRAW_SPAN_EMPTY;
else
bc->data.type = REDRAW_SPAN_OUTSIDE;
}
/* Convert window position to scene position. */
static int
screen_redraw_window_scene(struct redraw_build_ctx *bctx, int wx, int wy,
u_int *x, u_int *y)
{
if (wx < 0 || wy < 0)
return (0);
if ((u_int)wx > bctx->w->sx || (u_int)wy > bctx->w->sy)
return (0);
if (wx < (int)bctx->ox || wy < (int)bctx->oy)
return (0);
wx -= bctx->ox;
wy -= bctx->oy;
if (wx < 0 || wy < 0)
return (0);
if ((u_int)wx >= bctx->sx || (u_int)wy >= bctx->sy)
return (0);
*x = wx;
*y = wy;
return (1);
}
/* Convert pane position to scene. */
static int
screen_redraw_pane_scene(struct redraw_build_ctx *bctx,
struct window_pane *wp, int px, int py, u_int *x, u_int *y)
{
int wx = wp->xoff + px, wy = wp->yoff + py;
return (screen_redraw_window_scene(bctx, wx, wy, x, y));
}
/* Convert redraw border mask to a cell type. */
static int
screen_redraw_get_cell_type(int mask)
{
switch (mask) {
case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_U|REDRAW_BORDER_D:
return (CELL_JOIN);
case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_U:
return (CELL_BOTTOMJOIN);
case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_D:
return (CELL_TOPJOIN);
case REDRAW_BORDER_L|REDRAW_BORDER_R:
case REDRAW_BORDER_L:
case REDRAW_BORDER_R:
return (CELL_LEFTRIGHT);
case REDRAW_BORDER_L|REDRAW_BORDER_U|REDRAW_BORDER_D:
return (CELL_RIGHTJOIN);
case REDRAW_BORDER_L|REDRAW_BORDER_U:
return (CELL_BOTTOMRIGHT);
case REDRAW_BORDER_L|REDRAW_BORDER_D:
return (CELL_TOPRIGHT);
case REDRAW_BORDER_R|REDRAW_BORDER_U|REDRAW_BORDER_D:
return (CELL_LEFTJOIN);
case REDRAW_BORDER_R|REDRAW_BORDER_U:
return (CELL_BOTTOMLEFT);
case REDRAW_BORDER_R|REDRAW_BORDER_D:
return (CELL_TOPLEFT);
case REDRAW_BORDER_U|REDRAW_BORDER_D:
case REDRAW_BORDER_U:
case REDRAW_BORDER_D:
return (CELL_TOPBOTTOM);
}
return (CELL_OUTSIDE);
}
/* Return if this cell has exactly two panes with a shared border. */
static int
screen_redraw_two_panes(struct window *w, enum layout_type *type)
{
struct window_pane *wp;
u_int count = 0;
TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_is_floating(wp) || wp->layout_cell == NULL)
continue;
count++;
if (count > 2 || wp->layout_cell->parent == NULL)
return (0);
*type = wp->layout_cell->parent->type;
}
return (count == 2);
}
/* Clear the cells covered by a floating pane. */
static void
screen_redraw_clear_floating_pane(struct redraw_build_ctx *bctx,
struct window_pane *wp, int sb_w, int sb_left)
{
enum pane_lines pane_lines;
u_int x, y;
int left, right, top, bottom, wx, wy;
if (!window_pane_is_floating(wp))
return;
pane_lines = window_pane_get_pane_lines(wp);
if (pane_lines == PANE_LINES_NONE) {
left = wp->xoff;
right = wp->xoff + (int)wp->sx - 1;
top = wp->yoff;
bottom = wp->yoff + (int)wp->sy - 1;
} else {
left = wp->xoff - 1;
right = wp->xoff + (int)wp->sx;
top = wp->yoff - 1;
bottom = wp->yoff + (int)wp->sy;
}
if (sb_left)
left -= sb_w;
else
right += sb_w;
for (wy = top; wy <= bottom; wy++) {
for (wx = left; wx <= right; wx++) {
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y))
screen_redraw_reset_cell(bctx, x, y);
}
}
}
/* Mark pane inside data. */
static void
screen_redraw_mark_pane_inside(struct redraw_build_ctx *bctx,
struct window_pane *wp)
{
struct redraw_build_cell *bc;
u_int px, py, x, y;
for (py = 0; py < wp->sy; py++) {
for (px = 0; px < wp->sx; px++) {
if (!screen_redraw_pane_scene(bctx, wp, px, py, &x, &y))
continue;
bc = screen_redraw_cell(bctx, x, y);
memset(bc, 0, sizeof *bc);
bc->data.type = REDRAW_SPAN_PANE;
bc->data.p.wp = wp;
bc->data.p.px = px;
bc->data.p.py = py;
}
}
}
/* Mark scrollbar data. */
static void
screen_redraw_mark_pane_scrollbar(struct redraw_build_ctx *bctx,
struct window_pane *wp, int sb_w, int sb_left)
{
struct redraw_build_cell *bc;
u_int x, y;
int wx, wy, sx, ex;
u_int sy;
if (sb_w == 0)
return;
if (sb_left) {
sx = wp->xoff - sb_w;
ex = wp->xoff - 1;
} else {
sx = wp->xoff + (int)wp->sx;
ex = sx + sb_w - 1;
}
for (sy = 0; sy < wp->sy; sy++) {
wy = wp->yoff + (int)sy;
for (wx = sx; wx <= ex; wx++) {
if (!screen_redraw_window_scene(bctx, wx, wy, &x, &y))
continue;
bc = screen_redraw_cell(bctx, x, y);
memset(bc, 0, sizeof *bc);
bc->data.type = REDRAW_SPAN_SCROLLBAR;
bc->data.sb.wp = wp;
bc->data.sb.y = sy;
bc->data.sb.height = wp->sy;
if (sb_left)
bc->data.sb.flags |= REDRAW_SCROLLBAR_LEFT;
else
bc->data.sb.flags |= REDRAW_SCROLLBAR_RIGHT;
}
}
}
/*
* Mark one border cell. If a non-border cell is being marked as a border,
* replace it. If it is already a border, merge it.
*/
static void
screen_redraw_mark_border_cell(struct redraw_build_ctx *bctx, int wx, int wy,
struct window_pane *wp, int top_owner, int bottom_owner, int mask)
{
struct redraw_build_cell *bc;
enum pane_lines pane_lines;
u_int x, y;
if (!screen_redraw_window_scene(bctx, wx, wy, &x, &y))
return;
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type != REDRAW_SPAN_BORDER) {
if (bc->data.type != REDRAW_SPAN_EMPTY)
return;
memset(bc, 0, sizeof *bc);
bc->data.type = REDRAW_SPAN_BORDER;
}
pane_lines = window_pane_get_pane_lines(wp);
if (top_owner) {
bc->data.b.top_wp = wp;
bc->data.b.top_lines = pane_lines;
}
if (bottom_owner) {
bc->data.b.bottom_wp = wp;
bc->data.b.bottom_lines = pane_lines;
}
if (mask & (REDRAW_BORDER_U|REDRAW_BORDER_D)) {
if (wx < wp->xoff) {
bc->data.b.right_wp = wp;
bc->data.b.right_lines = pane_lines;
} else if (wx >= wp->xoff + (int)wp->sx) {
bc->data.b.left_wp = wp;
bc->data.b.left_lines = pane_lines;
}
}
mask |= bc->data.b.cell_mask;
bc->data.b.cell_mask = mask;
bc->data.b.cell_type = screen_redraw_get_cell_type(mask);
}
/* Mark area available for pane status line. */
static void
screen_redraw_mark_border_status(struct redraw_build_ctx *bctx,
struct window_pane *wp, int left, int right, int top, int bottom)
{
struct redraw_build_cell *bc;
u_int x, y, off = 0;
int pane_status, wy, sx, ex, wx, cell_type;
pane_status = window_pane_get_pane_status(wp);
if (pane_status == PANE_STATUS_OFF)
return;
if (pane_status == PANE_STATUS_TOP)
wy = top;
else
wy = bottom;
sx = left + 1;
ex = right - 1;
if (bctx->ind == PANE_BORDER_ARROWS || bctx->ind == PANE_BORDER_BOTH) {
wx = wp->xoff + 1;
if (wx >= sx && wx <= ex)
sx = wx + 1;
}
if (sx > ex)
return;
for (wx = sx; wx <= ex; wx++, off++) {
if (!screen_redraw_window_scene(bctx, wx, wy, &x, &y))
continue;
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type != REDRAW_SPAN_BORDER)
continue;
cell_type = bc->data.b.cell_type;
bc->data.type = REDRAW_SPAN_STATUS;
bc->data.st.wp = wp;
bc->data.st.offset = off;
bc->data.st.cell_type = cell_type;
}
}
/* Mark where indicator arrows will go, if enabled. */
static void
screen_redraw_mark_border_arrows(struct redraw_build_ctx *bctx,
struct window_pane *wp, int left, int right, int top, int bottom)
{
struct redraw_build_cell *bc;
u_int x, y;
int wx, wy;
if (bctx->ind != PANE_BORDER_ARROWS && bctx->ind != PANE_BORDER_BOTH)
return;
wx = wp->xoff + 1;
if (wx >= left && wx <= right) {
wy = top;
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y)) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type == REDRAW_SPAN_BORDER)
bc->data.b.flags |= REDRAW_BORDER_IS_ARROW;
}
wy = bottom;
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y)) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type == REDRAW_SPAN_BORDER)
bc->data.b.flags |= REDRAW_BORDER_IS_ARROW;
}
}
wy = wp->yoff + 1;
if (wy >= top && wy <= bottom) {
wx = left;
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y)) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type == REDRAW_SPAN_BORDER)
bc->data.b.flags |= REDRAW_BORDER_IS_ARROW;
}
wx = right;
if (screen_redraw_window_scene(bctx, wx, wy, &x, &y)) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type == REDRAW_SPAN_BORDER)
bc->data.b.flags |= REDRAW_BORDER_IS_ARROW;
}
}
}
/* Mark pane borders. */
static void
screen_redraw_mark_pane_borders(struct redraw_build_ctx *bctx,
struct window_pane *wp, int sb_w, int sb_left)
{
enum pane_lines pane_lines = window_pane_get_pane_lines(wp);
int pane_status, left, right, top, bottom, wx, wy;
int draw_top, draw_bottom, draw_left, draw_right, mask = 0;
if (window_pane_is_floating(wp) && pane_lines == PANE_LINES_NONE)
return;
pane_status = window_pane_get_pane_status(wp);
left = wp->xoff - 1;
right = wp->xoff + (int)wp->sx;
if (sb_w != 0) {
if (sb_left)
left -= sb_w;
else
right += sb_w;
}
top = wp->yoff - 1;
bottom = wp->yoff + (int)wp->sy;
draw_left = (left >= 0);
draw_right = (right <= (int)bctx->w->sx);
draw_top = (top >= 0);
draw_bottom = (bottom <= (int)bctx->w->sy);
if (!window_pane_is_floating(wp)) {
if (pane_status == PANE_STATUS_TOP)
draw_bottom = 0;
else if (pane_status == PANE_STATUS_BOTTOM)
draw_top = 0;
}
if (draw_top) {
for (wx = left; wx <= right; wx++) {
mask = 0;
if (wx > left)
mask |= REDRAW_BORDER_L;
if (wx < right)
mask |= REDRAW_BORDER_R;
screen_redraw_mark_border_cell(bctx, wx, top, wp,
0, 1, mask);
}
}
if (draw_bottom) {
for (wx = left; wx <= right; wx++) {
mask = 0;
if (wx > left)
mask |= REDRAW_BORDER_L;
if (wx < right)
mask |= REDRAW_BORDER_R;
screen_redraw_mark_border_cell(bctx, wx, bottom, wp,
1, 0, mask);
}
}
if (draw_left) {
for (wy = top; wy <= bottom; wy++) {
mask = 0;
if (wy > top)
mask |= REDRAW_BORDER_U;
if (wy < bottom)
mask |= REDRAW_BORDER_D;
screen_redraw_mark_border_cell(bctx, left, wy, wp,
0, 0, mask);
}
}
if (draw_right) {
for (wy = top; wy <= bottom; wy++) {
mask = 0;
if (wy > top)
mask |= REDRAW_BORDER_U;
if (wy < bottom)
mask |= REDRAW_BORDER_D;
screen_redraw_mark_border_cell(bctx, right, wy, wp,
0, 0, mask);
}
}
screen_redraw_mark_border_status(bctx, wp, left, right, top, bottom);
screen_redraw_mark_border_arrows(bctx, wp, left, right, top, bottom);
}
/*
* Mark an entire pane in the build grid. Floating panes overwrite anything
* already below them.
*/
static void
screen_redraw_mark_pane(struct redraw_build_ctx *bctx, struct window_pane *wp)
{
int sb_w = 0, sb_left = 0;
if (!window_pane_is_visible(wp))
return;
if (window_pane_show_scrollbar(wp, bctx->sb))
sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
if (sb_w != 0 && bctx->sbp == PANE_SCROLLBARS_LEFT)
sb_left = 1;
screen_redraw_clear_floating_pane(bctx, wp, sb_w, sb_left);
screen_redraw_mark_pane_inside(bctx, wp);
screen_redraw_mark_pane_borders(bctx, wp, sb_w, sb_left);
screen_redraw_mark_pane_scrollbar(bctx, wp, sb_w, sb_left);
}
/* Mark the indicator. */
static void
screen_redraw_mark_two_pane_colours(struct redraw_build_ctx *bctx)
{
struct redraw_build_cell *bc;
struct redraw_span_data *sd;
enum layout_type type;
u_int x, y, wx, wy;
if (bctx->ind != PANE_BORDER_COLOUR && bctx->ind != PANE_BORDER_BOTH)
return;
if (!screen_redraw_two_panes(bctx->w, &type))
return;
for (y = 0; y < bctx->sy; y++) {
for (x = 0; x < bctx->sx; x++) {
bc = screen_redraw_cell(bctx, x, y);
if (bc->data.type != REDRAW_SPAN_BORDER)
continue;
sd = &bc->data;
wx = bctx->ox + x;
wy = bctx->oy + y;
if (type == LAYOUT_LEFTRIGHT &&
sd->b.left_wp != NULL &&
sd->b.right_wp != NULL) {
if (wy <= bctx->w->sy / 2)
sd->b.style_wp = sd->b.left_wp;
else
sd->b.style_wp = sd->b.right_wp;
} else if (type == LAYOUT_TOPBOTTOM &&
sd->b.top_wp != NULL &&
sd->b.bottom_wp != NULL) {
if (wx <= bctx->w->sx / 2)
sd->b.style_wp = sd->b.top_wp;
else
sd->b.style_wp = sd->b.bottom_wp;
}
}
}
}
/* Return true if two adjacent build data can be coalesced into one span. */
static int
screen_redraw_data_cmp(struct redraw_build_cell *a, struct redraw_build_cell *b)
{
struct redraw_span_data *ad = &a->data, *bd = &b->data;
if (ad->type != bd->type)
return (0);
switch (ad->type) {
case REDRAW_SPAN_PANE:
if (ad->p.wp != bd->p.wp ||
ad->p.py != bd->p.py ||
ad->p.px + 1 != bd->p.px)
return (0);
return (1);
case REDRAW_SPAN_BORDER:
if (ad->b.top_wp != bd->b.top_wp ||
ad->b.bottom_wp != bd->b.bottom_wp ||
ad->b.left_wp != bd->b.left_wp ||
ad->b.right_wp != bd->b.right_wp ||
ad->b.style_wp != bd->b.style_wp ||
ad->b.top_lines != bd->b.top_lines ||
ad->b.bottom_lines != bd->b.bottom_lines ||
ad->b.left_lines != bd->b.left_lines ||
ad->b.right_lines != bd->b.right_lines ||
ad->b.cell_type != bd->b.cell_type ||
ad->b.cell_mask != bd->b.cell_mask ||
ad->b.flags != bd->b.flags)
return (0);
if (ad->b.flags & REDRAW_BORDER_IS_ARROW)
return (0);
return (1);
case REDRAW_SPAN_STATUS:
if (ad->st.wp != bd->st.wp ||
ad->st.offset + 1 != bd->st.offset ||
ad->st.cell_type != bd->st.cell_type)
return (0);
return (1);
case REDRAW_SPAN_SCROLLBAR:
if (ad->sb.wp != bd->sb.wp ||
ad->sb.y != bd->sb.y ||
ad->sb.height != bd->sb.height ||
ad->sb.flags != bd->sb.flags)
return (0);
return (1);
case REDRAW_SPAN_OUTSIDE:
case REDRAW_SPAN_EMPTY:
return (1);
}
return (0);
}
/* Convert the cells into spans. */
static void
screen_redraw_finish_scene(struct redraw_build_ctx *bctx,
struct redraw_scene *scene)
{
struct redraw_build_cell *bc, *last;
struct redraw_line *line;
struct redraw_span *span;
enum redraw_span_type type;
u_int x, y, x0;
for (y = 0; y < bctx->sy; y++) {
line = &scene->lines[y];
x = 0;
while (x < bctx->sx) {
x0 = x;
last = screen_redraw_cell(bctx, x, y);
x++;
while (x < bctx->sx) {
bc = screen_redraw_cell(bctx, x, y);
if (!screen_redraw_data_cmp(last, bc))
break;
last = bc;
x++;
}
bc = screen_redraw_cell(bctx, x0, y);
type = bc->data.type;
span = xcalloc(1, sizeof *span);
span->x = x0;
span->width = x - x0;
span->data = bc->data;
TAILQ_INSERT_TAIL(&line->spans[type], span, entry);
}
}
}
/*
* Build and return a redraw scene for c. The caller owns the scene and must
* free it with screen_redraw_free_scene().
*/
static struct redraw_scene *
screen_redraw_make_scene(struct client *c)
{
struct redraw_build_ctx bctx;
struct redraw_scene *scene;
struct window_pane *wp;
u_int x, y, type;
size_t ncells;
if (c->flags & CLIENT_SUSPENDED)
return (NULL);
screen_redraw_set_context(c, &bctx);
scene = xcalloc(1, sizeof *scene);
scene->c = c;
scene->w = bctx.w;
scene->generation = bctx.w->redraw_scene_generation;
scene->sx = bctx.sx;
scene->sy = bctx.sy;
scene->ox = bctx.ox;
scene->oy = bctx.oy;
scene->lines = xcalloc(scene->sy, sizeof *scene->lines);
for (y = 0; y < scene->sy; y++) {
for (type = 0; type < REDRAW_SPAN_TYPES; type++)
TAILQ_INIT(&scene->lines[y].spans[type]);
}
if (bctx.sx != 0 && bctx.sy > SIZE_MAX / bctx.sx)
fatalx("%s: too many cells", __func__);
ncells = (size_t)bctx.sx * bctx.sy;
if (ncells > SIZE_MAX / sizeof *bctx.cells)
fatalx("%s: too many cells", __func__);
bctx.cells = xmalloc(ncells * sizeof *bctx.cells);
for (y = 0; y < bctx.sy; y++) {
for (x = 0; x < bctx.sx; x++)
screen_redraw_reset_cell(&bctx, x, y);
}
TAILQ_FOREACH_REVERSE(wp, &bctx.w->z_index, window_panes_zindex, zentry)
screen_redraw_mark_pane(&bctx, wp);
screen_redraw_mark_two_pane_colours(&bctx);
screen_redraw_finish_scene(&bctx, scene);
free(bctx.cells);
return (scene);
}
/* Free a scene. */
void
screen_redraw_free_scene(struct redraw_scene *scene)
{
struct redraw_spans *spans;
struct redraw_span *span, *span1;
u_int y, type;
if (scene == NULL)
return;
for (y = 0; y < scene->sy; y++) {
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
spans = &scene->lines[y].spans[type];
TAILQ_FOREACH_SAFE(span, spans, entry, span1) {
TAILQ_REMOVE(spans, span, entry);
free(span);
}
}
}
free(scene->lines);
free(scene);
}
/* Mark a window's cached redraw scenes as out of date. */
void
screen_redraw_invalidate_scene(struct window *w)
{
w->redraw_scene_generation++;
}
/* Mark all cached redraw scenes as out of date. */
void
screen_redraw_invalidate_all_scenes(void)
{
struct window *w;
RB_FOREACH(w, windows, &windows)
screen_redraw_invalidate_scene(w);
}
/* Get the cached redraw scene, rebuilding it if needed. */
static struct redraw_scene *
screen_redraw_get_scene(struct client *c)
{
struct redraw_scene *scene = c->redraw_scene;
struct window *w = c->session->curw->window;
u_int ox, oy, sx, sy;
tty_window_offset(&c->tty, &ox, &oy, &sx, &sy);
if (scene == NULL ||
scene->w != w ||
scene->generation != w->redraw_scene_generation ||
scene->ox != ox ||
scene->oy != oy ||
scene->sx != sx ||
scene->sy != sy) {
screen_redraw_free_scene(scene);
scene = screen_redraw_make_scene(c);
c->redraw_scene = scene;
}
return (scene);
}
/* Is this span adjacent to this pane? */
static int
screen_redraw_span_has_pane(struct redraw_span *span, struct window_pane *wp)
{
if (span->data.b.top_wp == wp)
return (1);
if (span->data.b.bottom_wp == wp)
return (1);
if (span->data.b.left_wp == wp)
return (1);
if (span->data.b.right_wp == wp)
return (1);
return (0);
}
/* Draw a pane span. */
static void
screen_redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct redraw_scene *scene = dctx->scene;
struct client *c = scene->c;
struct tty *tty = &c->tty;
struct window_pane *wp = span->data.p.wp;
struct screen *s = wp->screen;
struct grid_cell defaults;
struct tty_style_ctx style_ctx;
u_int px, py;
tty_default_colours(&defaults, wp);
style_ctx.defaults = &defaults;
style_ctx.palette = &wp->palette;
style_ctx.hyperlinks = s->hyperlinks;
px = span->data.p.px + (x - span->x);
py = span->data.p.py;
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
}
/* Get style for cells without a pane. */
static void
screen_redraw_draw_get_default_style(struct redraw_draw_ctx *dctx,
struct grid_cell *gc)
{
struct redraw_scene *scene = dctx->scene;
struct client *c = scene->c;
struct session *s = c->session;
struct format_tree *ft;
struct grid_cell *dgc = &dctx->default_gc;
if (~dctx->flags & REDRAW_DEFAULT_SET) {
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
memcpy(dgc, &grid_default_cell, sizeof *dgc);
style_add(dgc, scene->w->options, "pane-border-style", ft);
format_free(ft);
dctx->flags |= REDRAW_DEFAULT_SET;
}
memcpy(gc, dgc, sizeof *gc);
}
/* Find pane to use for this span. */
static struct window_pane *
screen_redraw_draw_get_style_pane(struct redraw_draw_ctx *dctx,
struct redraw_span *span)
{
struct window_pane *active = dctx->active;
if (span->data.type != REDRAW_SPAN_BORDER)
return (NULL);
if (span->data.b.style_wp != NULL)
return (span->data.b.style_wp);
if (active != NULL && screen_redraw_span_has_pane(span, active))
return (active);
if (span->data.b.top_wp != NULL)
return (span->data.b.top_wp);
if (span->data.b.bottom_wp != NULL)
return (span->data.b.bottom_wp);
if (span->data.b.left_wp != NULL)
return (span->data.b.left_wp);
if (span->data.b.right_wp != NULL)
return (span->data.b.right_wp);
return (NULL);
}
/* Draw arrow indicator if this border span is an arrow cell. */
static void
screen_redraw_draw_border_arrow(struct redraw_draw_ctx *dctx,
struct redraw_span *span, struct grid_cell *gc)
{
struct window_pane *active = dctx->active;
char ch;
if (span->data.type != REDRAW_SPAN_BORDER || active == NULL)
return;
if (~span->data.b.flags & REDRAW_BORDER_IS_ARROW)
return;
if (span->data.b.left_wp == active)
ch = ',';
else if (span->data.b.right_wp == active)
ch = '+';
else if (span->data.b.top_wp == active)
ch = '-';
else if (span->data.b.bottom_wp == active)
ch = '.';
else
return;
utf8_set(&gc->data, ch);
gc->attr |= GRID_ATTR_CHARSET;
}
/* Draw a border span. */
static void
screen_redraw_draw_border_span(struct redraw_draw_ctx *dctx,
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct redraw_scene *scene = dctx->scene;
struct client *c = scene->c;
struct tty *tty = &c->tty;
struct window *w = scene->w;
struct options *oo = w->options;
struct window_pane *wp = NULL;
struct grid_cell gc;
enum pane_lines pane_lines;
u_int i, cell_type;
int isolates = 0;
if (span->data.type != REDRAW_SPAN_BORDER)
cell_type = CELL_OUTSIDE;
else {
wp = screen_redraw_draw_get_style_pane(dctx, span);
cell_type = span->data.b.cell_type;
}
if (wp == NULL) {
pane_lines = options_get_number(oo, "pane-border-lines");
screen_redraw_draw_get_default_style(dctx, &gc);
window_get_border_cell(w, NULL, pane_lines, cell_type, &gc);
} else {
window_pane_get_border_style(wp, c, &gc);
window_pane_get_border_cell(wp, cell_type, &gc);
}
if (span->data.type == REDRAW_SPAN_BORDER &&
dctx->marked != NULL &&
screen_redraw_span_has_pane(span, dctx->marked))
gc.attr ^= GRID_ATTR_REVERSE;
screen_redraw_draw_border_arrow(dctx, span, &gc);
if (cell_type == CELL_TOPBOTTOM && (dctx->flags & REDRAW_ISOLATES))
isolates = 1;
tty_cursor(tty, x, y);
if (isolates)
tty_puts(tty, REDRAW_END_ISOLATE);
for (i = 0; i < n; i++)
tty_cell(tty, &gc, NULL);
if (isolates)
tty_puts(tty, REDRAW_START_ISOLATE);
}
/* Draw a pane status span. */
static void
screen_redraw_draw_status_span(struct redraw_draw_ctx *dctx,
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct redraw_scene *scene = dctx->scene;
struct client *c = scene->c;
struct tty *tty = &c->tty;
struct window_pane *wp = span->data.st.wp;
struct screen *s = &wp->status_screen;
u_int px, sx = screen_size_x(s);
px = span->data.st.offset + (x - span->x);
if (px < sx) {
if (n > sx - px)
n = sx - px;
tty_draw_line(tty, s, px, 0, n, x, y, NULL);
}
}
/* Draw a scrollbar span. */
static void
screen_redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx,
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct redraw_scene *scene = dctx->scene;
struct window_pane *wp = span->data.sb.wp;
struct screen *s = wp->screen;
struct tty *tty = &scene->c->tty;
struct style *sb_style = &wp->scrollbar_style;
struct grid_cell gc, slgc, *gcp;
double pct_view;
u_int total_height, slider_h, slider_y;
u_int sb_h = span->data.sb.height;
u_int sb_y = span->data.sb.y;
u_int i, off, sb_w, sb_pad;
int cm_y, cm_size;
if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) {
total_height = screen_size_y(s) + screen_hsize(s);
if (total_height == 0)
return;
pct_view = (double)sb_h / total_height;
slider_h = (double)sb_h * pct_view;
slider_y = sb_h - slider_h;
} else {
if (TAILQ_FIRST(&wp->modes) == NULL)
return;
if (window_copy_get_current_offset(wp, &cm_y, &cm_size) == 0)
return;
total_height = cm_size + sb_h;
if (total_height == 0)
return;
pct_view = (double)sb_h / total_height;
slider_h = (double)sb_h * pct_view;
slider_y = (sb_h + 1) * ((double)cm_y / total_height);
}
if (slider_h < 1)
slider_h = 1;
if (slider_y >= sb_h)
slider_y = sb_h - 1;
wp->sb_slider_y = slider_y;
wp->sb_slider_h = slider_h;
gc = sb_style->gc;
memcpy(&slgc, &gc, sizeof slgc);
slgc.fg = gc.bg;
slgc.bg = gc.fg;
sb_w = sb_style->width;
sb_pad = sb_style->pad;
off = x - span->x;
tty_cursor(tty, x, y);
for (i = 0; i < n; i++) {
if (span->data.sb.flags & REDRAW_SCROLLBAR_LEFT) {
if (off + i >= sb_w && off + i < sb_w + sb_pad) {
tty_cell(tty, &grid_default_cell, NULL);
continue;
}
} else {
if (off + i < sb_pad) {
tty_cell(tty, &grid_default_cell, NULL);
continue;
}
}
if (sb_y >= slider_y && sb_y < slider_y + slider_h)
gcp = &slgc;
else
gcp = &gc;
tty_cell(tty, gcp, NULL);
}
}
/* Draw a span. */
static void
screen_redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
u_int y)
{
struct redraw_scene *scene = dctx->scene;
struct redraw_span_data *data = &span->data;
enum redraw_span_type type = data->type;
struct client *c = scene->c;
struct tty *tty = &c->tty;
struct visible_ranges *r;
struct visible_range *rr;
u_int i, x, n;
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
return;
r = tty_check_overlay_range(tty, span->x, y, span->width);
for (i = 0; i < r->used; i++) {
rr = &r->ranges[i];
if (rr->nx == 0)
continue;
x = rr->px;
n = rr->nx;
switch (span->data.type) {
case REDRAW_SPAN_PANE:
screen_redraw_draw_pane_span(dctx, span, x, y, n);
break;
case REDRAW_SPAN_BORDER:
case REDRAW_SPAN_EMPTY:
case REDRAW_SPAN_OUTSIDE:
screen_redraw_draw_border_span(dctx, span, x, y, n);
break;
case REDRAW_SPAN_STATUS:
screen_redraw_draw_status_span(dctx, span, x, y, n);
break;
case REDRAW_SPAN_SCROLLBAR:
screen_redraw_draw_scrollbar_span(dctx, span, x, y, n);
break;
}
}
}
/* Draw pane lines. */
static void
screen_redraw_draw_pane_lines(struct redraw_draw_ctx *dctx,
struct window_pane *wp, int flags)
{
struct redraw_scene *scene = dctx->scene;
struct redraw_line *line;
struct redraw_spans *spans;
struct redraw_span *span;
u_int cy;
int y, top, bottom;
top = wp->yoff - (int)scene->oy;
if (top < 0)
top = 0;
bottom = wp->yoff + (int)wp->sy - (int)scene->oy;
if (bottom < 0)
bottom = 0;
if (bottom > (int)scene->sy)
bottom = scene->sy;
for (y = top; y < bottom; y++) {
line = &scene->lines[y];
if (dctx->flags & REDRAW_STATUS_TOP)
cy = dctx->status_lines + y;
else
cy = y;
if (flags & REDRAW_PANE) {
spans = &line->spans[REDRAW_SPAN_PANE];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.p.wp == wp)
screen_redraw_draw_span(dctx, span, cy);
}
}
if (flags & REDRAW_PANE_SCROLLBAR) {
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.sb.wp == wp)
screen_redraw_draw_span(dctx, span, cy);
}
}
}
}
/* Draw lines. */
static void
screen_redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
{
struct redraw_scene *scene = dctx->scene;
struct redraw_line *line;
struct redraw_spans *spans;
struct redraw_span *span;
u_int y, cy, type;
for (y = 0; y < scene->sy; y++) {
line = &scene->lines[y];
if (dctx->flags & REDRAW_STATUS_TOP)
cy = dctx->status_lines + y;
else
cy = y;
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
if (!REDRAW_IS_ALL(flags)) {
switch (type) {
case REDRAW_SPAN_PANE:
if (~flags & REDRAW_PANE)
continue;
break;
case REDRAW_SPAN_OUTSIDE:
if (~flags & REDRAW_OUTSIDE)
continue;
break;
case REDRAW_SPAN_EMPTY:
if (~flags & REDRAW_EMPTY)
continue;
break;
case REDRAW_SPAN_BORDER:
if (~flags & REDRAW_PANE_BORDER)
continue;
break;
case REDRAW_SPAN_STATUS:
if (~flags & REDRAW_PANE_STATUS)
continue;
break;
case REDRAW_SPAN_SCROLLBAR:
if (~flags & REDRAW_PANE_SCROLLBAR)
continue;
break;
default:
continue;
}
}
spans = &line->spans[type];
TAILQ_FOREACH(span, spans, entry)
screen_redraw_draw_span(dctx, span, cy);
}
}
}
/* Get line for pane status line. */
static int
screen_redraw_pane_status_line(struct redraw_draw_ctx *dctx,
struct window_pane *wp, u_int *line)
{
struct redraw_scene *scene = dctx->scene;
int pane_status, wy;
pane_status = window_pane_get_pane_status(wp);
if (pane_status == PANE_STATUS_OFF)
return (0);
if (pane_status == PANE_STATUS_TOP)
wy = (int)wp->yoff - 1;
else
wy = (int)wp->yoff + wp->sy;
if (wy < 0 || wy < (int)scene->oy)
return (0);
if ((u_int)wy >= scene->oy + scene->sy)
return (0);
*line = wy - scene->oy;
return (1);
}
/* Get available width for pane status line. */
static u_int
screen_redraw_pane_status_width(struct redraw_draw_ctx *dctx,
struct window_pane *wp, struct redraw_span **first)
{
struct redraw_scene *scene = dctx->scene;
struct redraw_span *span;
u_int y, width = 0, end;
if (!screen_redraw_pane_status_line(dctx, wp, &y))
return (0);
*first = NULL;
TAILQ_FOREACH(span, &scene->lines[y].spans[REDRAW_SPAN_STATUS], entry) {
if (span->data.st.wp == wp) {
if (*first == NULL)
*first = span;
end = span->data.st.offset + span->width;
if (end > width)
width = end;
}
}
return (width);
}
/* Set up draw context. */
static void
screen_redraw_set_draw_context(struct redraw_draw_ctx *dctx,
struct redraw_scene *scene)
{
struct client *c = scene->c;
struct session *s = c->session;
struct options *oo = s->options;
struct tty *tty = &c->tty;
u_int lines;
memset(dctx, 0, sizeof *dctx);
dctx->scene = scene;
if (server_is_marked(s, s->curw, marked_pane.wp))
dctx->marked = marked_pane.wp;
dctx->active = server_client_get_pane(c);
lines = status_line_size(c);
if (options_get_number(oo, "status-position") == 0)
dctx->flags |= REDRAW_STATUS_TOP;
dctx->status_lines = lines;
if ((c->flags & CLIENT_UTF8) && tty_term_has(tty->term, TTYC_BIDI))
dctx->flags |= REDRAW_ISOLATES;
}
/* Draw scene to client. */
static void
screen_redraw_draw(struct client *c, struct window_pane *wp, int flags)
{
struct redraw_draw_ctx dctx;
struct tty *tty = &c->tty;
struct screen *sl;
struct redraw_scene *scene;
struct window_pane *loop;
u_int width, i, y, lines;
struct redraw_span *first;
int redraw;
if (c->flags & CLIENT_SUSPENDED)
return;
if (flags & REDRAW_STATUS) {
if (c->message_string != NULL)
redraw = status_message_redraw(c);
else if (c->prompt_string != NULL)
redraw = status_prompt_redraw(c);
else
redraw = status_redraw(c);
if (!redraw && !REDRAW_IS_ALL(flags)) {
flags &= ~REDRAW_STATUS;
if (flags == 0)
return;
}
}
scene = screen_redraw_get_scene(c);
if (scene == NULL)
return;
if (scene == NULL)
return;
screen_redraw_set_draw_context(&dctx, scene);
if (flags & (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS)) {
TAILQ_FOREACH(loop, &scene->w->panes, entry) {
loop->border_gc_set = 0;
loop->active_border_gc_set = 0;
}
}
if (flags & REDRAW_PANE_STATUS) {
redraw = 0;
TAILQ_FOREACH(loop, &scene->w->panes, entry) {
if (REDRAW_IS_ALL(flags))
loop->flags |= PANE_NEWSTATUS;
else
loop->flags &= ~PANE_NEWSTATUS;
width = screen_redraw_pane_status_width(&dctx, loop,
&first);
if (width == 0)
continue;
if (window_make_pane_status(loop, c, width, first)) {
loop->flags |= PANE_NEWSTATUS;
redraw = 1;
}
}
if (!redraw && !REDRAW_IS_ALL(flags)) {
flags &= ~REDRAW_PANE_STATUS;
if (flags == 0)
return;
}
}
if (flags & REDRAW_PANE) {
if (wp != NULL) {
if (wp->base.mode & MODE_SYNC)
screen_write_stop_sync(wp);
} else {
TAILQ_FOREACH(loop, &scene->w->panes, entry) {
if (!window_pane_is_visible(loop))
continue;
if (loop->base.mode & MODE_SYNC)
screen_write_stop_sync(loop);
}
}
}
tty_sync_start(tty);
tty_update_mode(tty, 0, NULL);
if (wp != NULL)
screen_redraw_draw_pane_lines(&dctx, wp, flags);
else
screen_redraw_draw_lines(&dctx, flags);
if (flags & REDRAW_STATUS) {
lines = dctx.status_lines;
if (c->message_string != NULL || c->prompt_string != NULL)
lines = (lines == 0 ? 1 : lines);
if (dctx.flags & REDRAW_STATUS_TOP)
y = 0;
else
y = c->tty.sy - lines;
sl = c->status.active;
for (i = 0; i < lines; i++)
tty_draw_line(tty, sl, 0, i, UINT_MAX, 0, y + i, NULL);
}
if (c->overlay_draw != NULL && (flags & REDRAW_OVERLAY))
c->overlay_draw(c, c->overlay_data);
tty_reset(tty);
tty_sync_end(tty);
}
/* Get cell type for offset from span. */
int
screen_redraw_get_span_cell_type(struct redraw_span **spanp, u_int x)
{
struct redraw_span *span = *spanp;
struct window_pane *wp;
u_int start, end;
if (span == NULL || span->data.type != REDRAW_SPAN_STATUS)
return (CELL_LEFTRIGHT);
wp = span->data.st.wp;
for (; span != NULL; span = TAILQ_NEXT(span, entry)) {
if (span->data.type != REDRAW_SPAN_STATUS)
continue;
if (span->data.st.wp != wp)
continue;
start = span->data.st.offset;
end = start + span->width;
if (x >= start && x < end) {
*spanp = span;
return (span->data.st.cell_type);
}
if (start > x) {
*spanp = span;
break;
}
}
if (span == NULL)
*spanp = NULL;
return (CELL_LEFTRIGHT);
}
/* Draw screen. */
void
screen_redraw_screen(struct client *c)
{
int flags = 0;
if (c->flags & CLIENT_REDRAWWINDOW)
screen_redraw_draw(c, NULL, REDRAW_ALL);
else {
if (c->flags & CLIENT_REDRAWBORDERS)
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
if (c->flags & CLIENT_REDRAWSTATUS)
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
if (c->flags & CLIENT_REDRAWOVERLAY)
flags |= REDRAW_OVERLAY;
if (flags != 0)
screen_redraw_draw(c, NULL, flags);
}
}
/* Draw a single pane. */
void
screen_redraw_pane(struct client *c, struct window_pane *wp)
{
screen_redraw_draw(c, wp, REDRAW_PANE|REDRAW_PANE_SCROLLBAR);
}
/* Draw a pane's scrollbar. */
void
screen_redraw_pane_scrollbar(struct client *c, struct window_pane *wp)
{
screen_redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR);
}