mirror of
https://github.com/tmux/tmux.git
synced 2026-09-18 19:14:47 +00:00
Compare commits
15 Commits
master
...
redraw-dam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07d40d5fa9 | ||
|
|
80e87aaf16 | ||
|
|
f41b983e04 | ||
|
|
48e33179d4 | ||
|
|
aed1209c02 | ||
|
|
6739bd03f5 | ||
|
|
f66eeef8a5 | ||
|
|
2d220d9a7f | ||
|
|
da4895d559 | ||
|
|
b254f557e4 | ||
|
|
b26eeb5229 | ||
|
|
48cdd85886 | ||
|
|
f255f089fc | ||
|
|
d6074895bb | ||
|
|
faba411289 |
2
CHANGES
2
CHANGES
@@ -130,8 +130,6 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
* Add capture-pane -I to include the time each line entered history.
|
* Add capture-pane -I to include the time each line entered history.
|
||||||
|
|
||||||
* Various memory leak fixes from Heon Jeong.
|
|
||||||
|
|
||||||
* Add a default C-b T binding to change the current pane title.
|
* Add a default C-b T binding to change the current pane title.
|
||||||
|
|
||||||
* Menus now belong to the window, so appear on all clients.
|
* Menus now belong to the window, so appear on all clients.
|
||||||
|
|||||||
@@ -91,8 +91,10 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
|
|||||||
wp = target.wp;
|
wp = target.wp;
|
||||||
|
|
||||||
if (wl != NULL) {
|
if (wl != NULL) {
|
||||||
if (wp != NULL)
|
if (wp != NULL) {
|
||||||
|
window_redraw_active_switch(wp->window, wp);
|
||||||
window_set_active_pane(wp->window, wp, 1);
|
window_set_active_pane(wp->window, wp, 1);
|
||||||
|
}
|
||||||
session_set_current(s, wl);
|
session_set_current(s, wl);
|
||||||
if (wp != NULL)
|
if (wp != NULL)
|
||||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||||
|
|||||||
@@ -300,6 +300,7 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
|||||||
struct window_pane *wp;
|
struct window_pane *wp;
|
||||||
struct layout_cell *lc;
|
struct layout_cell *lc;
|
||||||
int y, ly, x, lx;
|
int y, ly, x, lx;
|
||||||
|
int old_xoff, old_yoff, old_sx, old_sy;
|
||||||
|
|
||||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
@@ -321,10 +322,17 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
|||||||
ly = m->statusat - 1;
|
ly = m->statusat - 1;
|
||||||
|
|
||||||
if (x != lx || y != ly) {
|
if (x != lx || y != ly) {
|
||||||
|
old_xoff = wp->xoff;
|
||||||
|
old_yoff = wp->yoff;
|
||||||
|
old_sx = wp->sx;
|
||||||
|
old_sy = wp->sy;
|
||||||
|
|
||||||
lc->g.xoff += x - lx;
|
lc->g.xoff += x - lx;
|
||||||
lc->g.yoff += y - ly;
|
lc->g.yoff += y - ly;
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
server_redraw_window(w);
|
|
||||||
|
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||||
|
old_sy);
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
||||||
int left, right;
|
int left, right;
|
||||||
int new_xoff, new_yoff, resizes = 0;
|
int new_xoff, new_yoff, resizes = 0;
|
||||||
|
int old_xoff, old_yoff, old_sx, old_sy;
|
||||||
|
|
||||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
@@ -248,6 +249,10 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
lc = wp->layout_cell;
|
lc = wp->layout_cell;
|
||||||
sx = wp->sx;
|
sx = wp->sx;
|
||||||
sy = wp->sy;
|
sy = wp->sy;
|
||||||
|
old_xoff = wp->xoff;
|
||||||
|
old_yoff = wp->yoff;
|
||||||
|
old_sx = (int)wp->sx;
|
||||||
|
old_sy = (int)wp->sy;
|
||||||
left = wp->xoff - 1;
|
left = wp->xoff - 1;
|
||||||
right = wp->xoff + sx;
|
right = wp->xoff + sx;
|
||||||
if (window_pane_scrollbar_reserve(wp) &&
|
if (window_pane_scrollbar_reserve(wp) &&
|
||||||
@@ -347,7 +352,8 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
}
|
}
|
||||||
if (resizes != 0) {
|
if (resizes != 0) {
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
server_redraw_window(w);
|
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||||
|
old_sy);
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
|
/* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
|||||||
.name = "new-pane",
|
.name = "new-pane",
|
||||||
.alias = "newp",
|
.alias = "newp",
|
||||||
|
|
||||||
.args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
.args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||||
.usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
|
.usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] "
|
||||||
"[-c start-directory] [-e environment] "
|
"[-c start-directory] [-e environment] "
|
||||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||||
"[-s style] [-S active-border-style] "
|
"[-s style] [-S active-border-style] "
|
||||||
@@ -219,8 +219,6 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||||
if (args_has(args, 'D') && args_has(args, 'O'))
|
|
||||||
new_wp->flags |= PANE_CLOSEONCANCEL;
|
|
||||||
|
|
||||||
style = args_get(args, 's');
|
style = args_get(args, 's');
|
||||||
if (style != NULL) {
|
if (style != NULL) {
|
||||||
@@ -362,6 +360,7 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
|||||||
enum pane_lines lines;
|
enum pane_lines lines;
|
||||||
u_int sx, sy;
|
u_int sx, sy;
|
||||||
int x, y, xoff, yoff, border;
|
int x, y, xoff, yoff, border;
|
||||||
|
int old_xoff, old_yoff, old_sx, old_sy;
|
||||||
|
|
||||||
if (c->tty.mouse_last_pane == -1)
|
if (c->tty.mouse_last_pane == -1)
|
||||||
return;
|
return;
|
||||||
@@ -417,8 +416,15 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
|||||||
if (sy < PANE_MINIMUM)
|
if (sy < PANE_MINIMUM)
|
||||||
sy = PANE_MINIMUM;
|
sy = PANE_MINIMUM;
|
||||||
|
|
||||||
|
old_xoff = wp->xoff;
|
||||||
|
old_yoff = wp->yoff;
|
||||||
|
old_sx = wp->sx;
|
||||||
|
old_sy = wp->sy;
|
||||||
|
|
||||||
layout_set_size(lc, sx, sy, xoff, yoff);
|
layout_set_size(lc, sx, sy, xoff, yoff);
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
server_redraw_window(w);
|
|
||||||
|
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||||
|
old_sy);
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: layout-custom.c,v 1.41 2026/09/09 09:01:19 nicm Exp $ */
|
/* $OpenBSD: layout-custom.c,v 1.40 2026/09/09 07:03:39 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
#include "tmux.h"
|
#include "tmux.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Layouts can be represented as strings in a JSON format (v2). The version 1
|
* Layouts can be represented as strings in a JSON format (v2). The legacy
|
||||||
* format will be removed in the future and should no longer be used.
|
* format (v1) will be removed in the future and should no longer be used.
|
||||||
*
|
*
|
||||||
* The current (v2) format is JSON. The top level has two keys:
|
* The current (v2) format is JSON. The top level has two keys:
|
||||||
* "V": version number, currently 2
|
* "V": version number, currently 2
|
||||||
@@ -57,9 +57,6 @@
|
|||||||
* "z": z-index, if a floating pane
|
* "z": z-index, if a floating pane
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Maximum nesting depth for version 1 layouts. */
|
|
||||||
#define LAYOUT_V1_MAX_DEPTH 1000
|
|
||||||
|
|
||||||
/* Layout string. */
|
/* Layout string. */
|
||||||
struct layout_string {
|
struct layout_string {
|
||||||
char *dat;
|
char *dat;
|
||||||
@@ -365,7 +362,7 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
|
|||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Append information for a single cell in the version 1 format. */
|
/* Append information for a single cell in the legacy (v1) format. */
|
||||||
static int
|
static int
|
||||||
layout_append_v1(struct layout_cell *lc, struct layout_string *ls)
|
layout_append_v1(struct layout_cell *lc, struct layout_string *ls)
|
||||||
{
|
{
|
||||||
@@ -781,7 +778,7 @@ layout_assign(struct window *w, struct layout_parse_ctx *pctx)
|
|||||||
layout_assign_fallback(w, w->layout_root);
|
layout_assign_fallback(w, w->layout_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Construct a cell from the version 1 format. */
|
/* Construct a cell from the legacy (v1) format. */
|
||||||
static struct layout_cell *
|
static struct layout_cell *
|
||||||
layout_construct_cell(struct layout_cell *lcparent, const char **layout)
|
layout_construct_cell(struct layout_cell *lcparent, const char **layout)
|
||||||
{
|
{
|
||||||
@@ -830,15 +827,12 @@ layout_construct_cell(struct layout_cell *lcparent, const char **layout)
|
|||||||
return (lc);
|
return (lc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Construct a layout from the version 1 format. */
|
/* Construct a layout from the legacy (v1) format. */
|
||||||
static struct layout_cell *
|
static struct layout_cell *
|
||||||
layout_construct_v1(struct layout_cell *lcparent, const char **layout, u_int depth)
|
layout_construct_v1(struct layout_cell *lcparent, const char **layout)
|
||||||
{
|
{
|
||||||
struct layout_cell *lc, *lcchild;
|
struct layout_cell *lc, *lcchild;
|
||||||
|
|
||||||
if (depth > LAYOUT_V1_MAX_DEPTH)
|
|
||||||
return (NULL);
|
|
||||||
|
|
||||||
lc = layout_construct_cell(lcparent, layout);
|
lc = layout_construct_cell(lcparent, layout);
|
||||||
if (lc == NULL)
|
if (lc == NULL)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
@@ -861,7 +855,7 @@ layout_construct_v1(struct layout_cell *lcparent, const char **layout, u_int dep
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
(*layout)++;
|
(*layout)++;
|
||||||
lcchild = layout_construct_v1(lc, layout, depth + 1);
|
lcchild = layout_construct_v1(lc, layout);
|
||||||
if (lcchild == NULL)
|
if (lcchild == NULL)
|
||||||
goto fail;
|
goto fail;
|
||||||
TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
|
TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
|
||||||
@@ -1074,8 +1068,7 @@ layout_construct(const char *input, struct layout_parse_ctx *pctx)
|
|||||||
*pctx->cause = xstrdup("invalid layout checksum");
|
*pctx->cause = xstrdup("invalid layout checksum");
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
pctx->root = layout_construct_v1(NULL, &input, 0);
|
if ((pctx->root = layout_construct_v1(NULL, &input)) == NULL) {
|
||||||
if (pctx->root == NULL) {
|
|
||||||
*pctx->cause = xstrdup("invalid layout");
|
*pctx->cause = xstrdup("invalid layout");
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
|||||||
57
layout.c
57
layout.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: layout.c,v 1.100 2026/09/11 08:16:14 nicm Exp $ */
|
/* $OpenBSD: layout.c,v 1.99 2026/09/09 07:03:39 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -469,7 +469,6 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
sx = PANE_MINIMUM;
|
sx = PANE_MINIMUM;
|
||||||
else
|
else
|
||||||
sx = sx - sb_w - sb_pad;
|
sx = sx - sb_w - sb_pad;
|
||||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window_pane_resize(wp, sx, sy);
|
window_pane_resize(wp, sx, sy);
|
||||||
@@ -477,8 +476,11 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
if (wp->xoff != old_xoff ||
|
if (wp->xoff != old_xoff ||
|
||||||
wp->yoff != old_yoff ||
|
wp->yoff != old_yoff ||
|
||||||
wp->sx != old_sx ||
|
wp->sx != old_sx ||
|
||||||
wp->sy != old_sy)
|
wp->sy != old_sy) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
|
if (window_pane_scrollbar_reserve(wp))
|
||||||
|
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (changed)
|
if (changed)
|
||||||
redraw_invalidate_scene(w);
|
redraw_invalidate_scene(w);
|
||||||
@@ -768,49 +770,6 @@ layout_free(struct window *w, int only_nodes)
|
|||||||
layout_free_cell(w->layout_root, only_nodes);
|
layout_free_cell(w->layout_root, only_nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Move and resize floating panes so they stay inside the window. */
|
|
||||||
static void
|
|
||||||
layout_clamp_floating_panes(struct window *w, u_int sx, u_int sy)
|
|
||||||
{
|
|
||||||
struct window_pane *wp;
|
|
||||||
struct layout_cell *lc;
|
|
||||||
u_int pad, avail, csx, csy;
|
|
||||||
|
|
||||||
TAILQ_FOREACH(wp, &w->z_index, zentry) {
|
|
||||||
lc = wp->layout_cell;
|
|
||||||
if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
|
|
||||||
continue;
|
|
||||||
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
|
|
||||||
pad = 0;
|
|
||||||
else
|
|
||||||
pad = 1;
|
|
||||||
|
|
||||||
csx = lc->g.sx;
|
|
||||||
avail = (sx > 2 * pad) ? sx - 2 * pad : 0;
|
|
||||||
if (csx > avail)
|
|
||||||
csx = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
|
||||||
csy = lc->g.sy;
|
|
||||||
avail = (sy > 2 * pad) ? sy - 2 * pad : 0;
|
|
||||||
if (csy > avail)
|
|
||||||
csy = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
|
||||||
if (csx != lc->g.sx || csy != lc->g.sy)
|
|
||||||
layout_set_size(lc, csx, csy, lc->g.xoff, lc->g.yoff);
|
|
||||||
|
|
||||||
if (lc->g.xoff + lc->g.sx + pad > sx) {
|
|
||||||
if (lc->g.sx + 2 * pad >= sx)
|
|
||||||
lc->g.xoff = pad;
|
|
||||||
else
|
|
||||||
lc->g.xoff = sx - lc->g.sx - pad;
|
|
||||||
}
|
|
||||||
if (lc->g.yoff + lc->g.sy + pad > sy) {
|
|
||||||
if (lc->g.sy + 2 * pad >= sy)
|
|
||||||
lc->g.yoff = pad;
|
|
||||||
else
|
|
||||||
lc->g.yoff = sy - lc->g.sy - pad;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Resize the entire layout after window resize. */
|
/* Resize the entire layout after window resize. */
|
||||||
void
|
void
|
||||||
layout_resize(struct window *w, u_int sx, u_int sy)
|
layout_resize(struct window *w, u_int sx, u_int sy)
|
||||||
@@ -831,11 +790,8 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
|||||||
* out proportionately - this should leave the layout fitting the new
|
* out proportionately - this should leave the layout fitting the new
|
||||||
* window size.
|
* window size.
|
||||||
*/
|
*/
|
||||||
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) {
|
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING))
|
||||||
layout_clamp_floating_panes(w, sx, sy);
|
|
||||||
layout_fix_panes(w, NULL);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
xchange = sx - lc->g.sx;
|
xchange = sx - lc->g.sx;
|
||||||
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
||||||
if (xchange < 0 && xchange < -xlimit)
|
if (xchange < 0 && xchange < -xlimit)
|
||||||
@@ -865,7 +821,6 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
|||||||
|
|
||||||
/* Fix cell offsets. */
|
/* Fix cell offsets. */
|
||||||
layout_fix_offsets(w);
|
layout_fix_offsets(w);
|
||||||
layout_clamp_floating_panes(w, sx, sy);
|
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: options-table.c,v 1.246 2026/09/11 10:17:16 nicm Exp $ */
|
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -76,8 +76,7 @@ static const char *options_table_pane_border_indicators_list[] = {
|
|||||||
"off", "colour", "arrows", "both", NULL
|
"off", "colour", "arrows", "both", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_pane_border_lines_list[] = {
|
static const char *options_table_pane_border_lines_list[] = {
|
||||||
"single", "double", "heavy", "simple", "number", "spaces", "none",
|
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
|
||||||
"rounded", NULL
|
|
||||||
};
|
};
|
||||||
static const char *options_table_popup_border_lines_list[] = {
|
static const char *options_table_popup_border_lines_list[] = {
|
||||||
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
|
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
|
||||||
@@ -92,7 +91,7 @@ static const char *options_table_window_size_list[] = {
|
|||||||
"largest", "smallest", "manual", "latest", NULL
|
"largest", "smallest", "manual", "latest", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_remain_on_exit_list[] = {
|
static const char *options_table_remain_on_exit_list[] = {
|
||||||
"off", "on", "failed", "key", "failed-key", NULL
|
"off", "on", "failed", "key", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_destroy_unattached_list[] = {
|
static const char *options_table_destroy_unattached_list[] = {
|
||||||
"off", "on", "keep-last", "keep-group", NULL
|
"off", "on", "keep-last", "keep-group", NULL
|
||||||
@@ -1699,8 +1698,7 @@ const struct options_table_entry options_table[] = {
|
|||||||
.choices = options_table_remain_on_exit_list,
|
.choices = options_table_remain_on_exit_list,
|
||||||
.default_num = 0,
|
.default_num = 0,
|
||||||
.text = "Whether panes should remain ('on'), remain until a key is "
|
.text = "Whether panes should remain ('on'), remain until a key is "
|
||||||
"pressed after any exit ('key') or after a failure "
|
"pressed ('key') or be automatically killed ('off' or "
|
||||||
"('failed-key'), or be automatically killed ('off' or "
|
|
||||||
"'failed') when the program inside exits."
|
"'failed') when the program inside exits."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
60
popup.c
60
popup.c
@@ -136,7 +136,8 @@ popup_reapply_styles(struct popup_data *pd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
popup_redraw_cb(const struct tty_ctx *ttyctx)
|
popup_redraw_cb(const struct tty_ctx *ttyctx, __unused u_int py,
|
||||||
|
__unused u_int ny)
|
||||||
{
|
{
|
||||||
struct popup_data *pd = ttyctx->arg;
|
struct popup_data *pd = ttyctx->arg;
|
||||||
|
|
||||||
@@ -321,11 +322,60 @@ popup_resize_cb(__unused struct client *c, void *data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Report damage for a popup's rectangle, given in raw client/tty
|
||||||
|
* coordinates. Status-line cells are outside the window scene - they have no
|
||||||
|
* corresponding window content and redraw_damage_window() can't reach them -
|
||||||
|
* so if the popup's rectangle overlaps the status line, force it to redraw
|
||||||
|
* separately. The rest of the rectangle is clipped to the actual pane area
|
||||||
|
* (above or below the status line, whichever side it's on) before being
|
||||||
|
* translated into window coordinates and reported the normal way, the same
|
||||||
|
* way mouse coordinates are translated elsewhere (e.g. cmd-join-pane.c,
|
||||||
|
* cmd-split-window.c).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
popup_damage(struct client *c, u_int px, u_int py, u_int sx, u_int sy)
|
||||||
|
{
|
||||||
|
struct window *w;
|
||||||
|
int statusat;
|
||||||
|
u_int ox, oy, osx, osy, lines, top, bottom, y0, y1;
|
||||||
|
|
||||||
|
if (c->session == NULL)
|
||||||
|
return;
|
||||||
|
w = c->session->curw->window;
|
||||||
|
|
||||||
|
lines = status_line_size(c);
|
||||||
|
statusat = status_at_line(c);
|
||||||
|
if (statusat >= 0 && py < (u_int)statusat + lines &&
|
||||||
|
py + sy > (u_int)statusat)
|
||||||
|
c->flags |= (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS);
|
||||||
|
|
||||||
|
if (statusat == 0) {
|
||||||
|
top = lines;
|
||||||
|
bottom = c->tty.sy;
|
||||||
|
} else if (statusat > 0) {
|
||||||
|
top = 0;
|
||||||
|
bottom = statusat;
|
||||||
|
} else {
|
||||||
|
top = 0;
|
||||||
|
bottom = c->tty.sy;
|
||||||
|
}
|
||||||
|
y0 = (py > top) ? py : top;
|
||||||
|
y1 = (py + sy < bottom) ? py + sy : bottom;
|
||||||
|
if (y0 >= y1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tty_window_offset(&c->tty, &ox, &oy, &osx, &osy);
|
||||||
|
redraw_damage_window(w, px + ox, y0 - top + oy, sx, y1 - y0);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
popup_handle_drag(struct client *c, struct popup_data *pd,
|
popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||||
struct mouse_event *m)
|
struct mouse_event *m)
|
||||||
{
|
{
|
||||||
u_int px, py;
|
u_int px, py;
|
||||||
|
u_int old_px = pd->px, old_py = pd->py;
|
||||||
|
u_int old_sx = pd->sx, old_sy = pd->sy;
|
||||||
|
|
||||||
if (!MOUSE_DRAG(m->b))
|
if (!MOUSE_DRAG(m->b))
|
||||||
pd->dragging = OFF;
|
pd->dragging = OFF;
|
||||||
@@ -348,7 +398,9 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
|||||||
pd->dy = m->y - pd->py;
|
pd->dy = m->y - pd->py;
|
||||||
pd->ppx = px;
|
pd->ppx = px;
|
||||||
pd->ppy = py;
|
pd->ppy = py;
|
||||||
server_redraw_client(c);
|
|
||||||
|
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
||||||
|
c->flags |= CLIENT_REDRAWOVERLAY;
|
||||||
} else if (pd->dragging == SIZE) {
|
} else if (pd->dragging == SIZE) {
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
if (m->x < pd->px + 1)
|
if (m->x < pd->px + 1)
|
||||||
@@ -375,7 +427,9 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
|||||||
if (pd->job != NULL)
|
if (pd->job != NULL)
|
||||||
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
||||||
}
|
}
|
||||||
server_redraw_client(c);
|
|
||||||
|
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
||||||
|
c->flags |= CLIENT_REDRAWOVERLAY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
79
regress/attach-session-redraw.sh
Executable file
79
regress/attach-session-redraw.sh
Executable file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Selecting a pane with attach-session from an already attached client must
|
||||||
|
# redraw pane contents when window-style and window-active-style differ.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lattach-redraw-outer-$$ -f/dev/null"
|
||||||
|
BEFORE=$DIR/before
|
||||||
|
AFTER=$DIR/after
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
LEFT=$($INNER new-session -dPF '#{pane_id}' -s inner -x 40 -y 8 \
|
||||||
|
"printf 'LEFT'; exec sleep 100") || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g default-terminal screen || exit 1
|
||||||
|
$INNER split-window -h -t "$LEFT" "printf 'RIGHT'; exec sleep 100" || exit 1
|
||||||
|
$INNER set-option -w -t "$LEFT" window-style bg=red || exit 1
|
||||||
|
$INNER set-option -w -t "$LEFT" window-active-style bg=blue || exit 1
|
||||||
|
$INNER bind-key -n x attach-session -t "$LEFT" || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
sleep 1
|
||||||
|
$OUTER send-keys -t outer:0.0 x || exit 1
|
||||||
|
sleep 1
|
||||||
|
[ "$($INNER display-message -p -t inner '#{pane_id}')" = "$LEFT" ] ||
|
||||||
|
fail "attach-session did not select the target pane"
|
||||||
|
$OUTER capture-pane -pe -t outer:0.0 >"$BEFORE" || exit 1
|
||||||
|
|
||||||
|
# A forced redraw produces the correct scene. It must be identical to the
|
||||||
|
# scene drawn immediately by attach-session.
|
||||||
|
$INNER refresh-client -t "$CLIENT" || exit 1
|
||||||
|
sleep 1
|
||||||
|
$OUTER capture-pane -pe -t outer:0.0 >"$AFTER" || exit 1
|
||||||
|
cmp -s "$BEFORE" "$AFTER" ||
|
||||||
|
fail "attach-session left stale active/inactive pane styles"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -30,11 +30,8 @@ killw
|
|||||||
EOF
|
EOF
|
||||||
sleep 1
|
sleep 1
|
||||||
$TMUX has || exit 1
|
$TMUX has || exit 1
|
||||||
# Use a control client to request legacy layouts, keeping only pane lines
|
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
|
||||||
# from the control protocol output.
|
cat <<EOF|cmp -s $TMP - || exit 1
|
||||||
$TMUX -C lsp -aF '#{pane_id} #{window_layout}' |
|
|
||||||
grep '^%[0-9]' >$TMP || exit 1
|
|
||||||
cat <<EOF|cmp $TMP - || exit 1
|
|
||||||
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||||
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||||
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
||||||
|
|||||||
108
regress/floating-pane-drag-ghost.sh
Executable file
108
regress/floating-pane-drag-ghost.sh
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Regression test for a floating-pane drag bug: cmd_resize_pane_redraw_floating()
|
||||||
|
# (cmd-resize-pane.c) reported damage for just a dragged floating pane's
|
||||||
|
# content rectangle, not the one-cell border frame drawn around it (see the
|
||||||
|
# "floating" case in screen-redraw.c, which draws that frame at
|
||||||
|
# xoff-1/yoff-1 through xoff+sx/yoff+sy - one cell outside the pane's own
|
||||||
|
# content area). Damage scoped to only the content area left the frame's
|
||||||
|
# previous position undrawn as the pane moved, so dragging it left a trail
|
||||||
|
# of un-erased border frames behind - visible as several "corners" stacked
|
||||||
|
# up rather than just the pane's current one.
|
||||||
|
#
|
||||||
|
# This bug has nothing to do with images - it reproduces with a plain
|
||||||
|
# floating pane and no image support required.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$TMUX kill-server >/dev/null 2>&1
|
||||||
|
$TMUX2 kill-server >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
cleanup
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||||
|
#
|
||||||
|
# Write a plain (unmodified) SGR button-1 press, drag update and release at
|
||||||
|
# 1-based positions to the outer pane holding the inner client - this
|
||||||
|
# matches the default MouseDown1Border/MouseDrag1Border bindings used to
|
||||||
|
# move or resize a floating pane by its border.
|
||||||
|
drag()
|
||||||
|
{
|
||||||
|
scol="$1"
|
||||||
|
srow="$2"
|
||||||
|
ecol="$3"
|
||||||
|
erow="$4"
|
||||||
|
|
||||||
|
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
TMP=$(mktemp)
|
||||||
|
trap "cleanup; rm -f $TMP" 0 1 15
|
||||||
|
|
||||||
|
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||||
|
$TMUX set -g mouse on
|
||||||
|
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||||
|
|
||||||
|
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||||
|
fail "new-pane -X -Y failed"
|
||||||
|
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
|
||||||
|
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||||
|
sleep 1
|
||||||
|
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||||
|
[ -n "$OUTER" ] || fail "No outer pane."
|
||||||
|
|
||||||
|
# Sanity check: exactly one floating pane, so exactly one top-left corner,
|
||||||
|
# before dragging anything.
|
||||||
|
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||||
|
n=$(grep -o '┌' $TMP | wc -l)
|
||||||
|
[ "$n" -eq 1 ] || fail "sanity: expected 1 corner before drag, found $n"
|
||||||
|
|
||||||
|
# Drag the floating pane by its top border (row FTOP-1, some column within
|
||||||
|
# its width) down several rows in a few separate steps, then release. A
|
||||||
|
# single drag() call already does press/motion/release, so call it several
|
||||||
|
# times in a row to simulate a multi-step real drag.
|
||||||
|
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||||
|
STARTROW=$FTOP
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 6 ]; do
|
||||||
|
newrow=$((STARTROW + i + 1))
|
||||||
|
drag $((GRABCOL + 1)) $((STARTROW + i)) $((GRABCOL + 1)) $newrow
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||||
|
|
||||||
|
# Exactly one top-left corner should remain - the pane's current position.
|
||||||
|
# This is expected to fail before the fix: multiple corners (a trail of
|
||||||
|
# un-erased frames) would remain from the intermediate drag positions.
|
||||||
|
n=$(grep -o '┌' $TMP | wc -l)
|
||||||
|
[ "$n" -eq 1 ] || fail "expected exactly 1 corner after drag, found $n (ghost frames left behind)"
|
||||||
|
|
||||||
|
exit 0
|
||||||
140
regress/floating-pane-drag-scrollbar-strip.sh
Executable file
140
regress/floating-pane-drag-scrollbar-strip.sh
Executable file
@@ -0,0 +1,140 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Regression test: dragging a floating pane across another pane's ordinary
|
||||||
|
# content must not redraw that other pane's scrollbar, unless the drag
|
||||||
|
# actually crosses the scrollbar's own strip.
|
||||||
|
#
|
||||||
|
# cmd_resize_pane_redraw_floating() (cmd-resize-pane.c) used to flag
|
||||||
|
# PANE_REDRAWSCROLLBAR on any pane whose whole *body* intersected the
|
||||||
|
# floating pane's old or new rectangle, rather than just its narrow
|
||||||
|
# scrollbar strip - so dragging a floating pane back and forth over an
|
||||||
|
# ordinary tiled pane's content (never touching its scrollbar) still
|
||||||
|
# needlessly redrew that pane's scrollbar on every motion step. See
|
||||||
|
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||||
|
#
|
||||||
|
# This is checked by giving the non-dragged pane a distinctive scrollbar
|
||||||
|
# colour and counting how many times its SGR code appears in the client's
|
||||||
|
# raw output while the floating pane is dragged vertically over that pane's
|
||||||
|
# body, well clear of its scrollbar column: with the fix, it should never
|
||||||
|
# reappear after the initial draw.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$TMUX kill-server >/dev/null 2>&1
|
||||||
|
$TMUX2 kill-server >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
cleanup
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||||
|
drag()
|
||||||
|
{
|
||||||
|
scol="$1"
|
||||||
|
srow="$2"
|
||||||
|
ecol="$3"
|
||||||
|
erow="$4"
|
||||||
|
|
||||||
|
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
TMP=$(mktemp)
|
||||||
|
trap "cleanup; rm -f $TMP" 0 1 15
|
||||||
|
|
||||||
|
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||||
|
$TMUX set -g mouse on || fail "set mouse failed"
|
||||||
|
$TMUX set -g default-command 'sh -c "sleep 100"' || fail "set default-command failed"
|
||||||
|
$TMUX set -g pane-scrollbars on || fail "set pane-scrollbars failed"
|
||||||
|
|
||||||
|
$TMUX split-window -h -t inner 'sh -c "sleep 100"' || fail "split-window failed"
|
||||||
|
|
||||||
|
PANES=$($TMUX list-panes -t inner -F '#{pane_id} #{pane_left}')
|
||||||
|
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||||
|
[ -n "$LEFT" ] || fail "could not identify left pane"
|
||||||
|
|
||||||
|
# A distinctive scrollbar colour for the non-dragged (left) pane only.
|
||||||
|
$TMUX set -p -t "$LEFT" pane-scrollbars-style 'fg=colour201,bg=colour17' ||
|
||||||
|
fail "set pane-scrollbars-style failed"
|
||||||
|
|
||||||
|
ALEFT=$($TMUX display-message -p -t "$LEFT" '#{pane_left}')
|
||||||
|
ATOP=$($TMUX display-message -p -t "$LEFT" '#{pane_top}')
|
||||||
|
AWIDTH=$($TMUX display-message -p -t "$LEFT" '#{pane_width}')
|
||||||
|
AHEIGHT=$($TMUX display-message -p -t "$LEFT" '#{pane_height}')
|
||||||
|
[ "$AWIDTH" -gt 15 ] || fail "left pane too narrow for this test ($AWIDTH)"
|
||||||
|
|
||||||
|
# A small floating pane placed well inside the left pane's content area,
|
||||||
|
# clear of its (right-hand) scrollbar column by several columns.
|
||||||
|
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 8 -y 5 \
|
||||||
|
-X $((ALEFT + 2)) -Y $((ATOP + 2))) || fail "new-pane -X -Y failed"
|
||||||
|
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
[ $((FLEFT + FWIDTH + 3)) -lt $((ALEFT + AWIDTH)) ] ||
|
||||||
|
fail "sanity: floating pane too close to the scrollbar column"
|
||||||
|
|
||||||
|
# Start the outer session with a plain shell, then start capturing before
|
||||||
|
# triggering the attach - starting the attach as the outer pane's initial
|
||||||
|
# command would mean pipe-pane only starts after the attach-driven initial
|
||||||
|
# redraw (which draws the scrollbars) has already happened, missing it.
|
||||||
|
$TMUX2 new-session -d -x 60 -y 20 || exit 1
|
||||||
|
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||||
|
[ -n "$OUTER" ] || fail "No outer pane."
|
||||||
|
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||||
|
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Sanity check: the distinctive scrollbar colour reaches the client at all.
|
||||||
|
grep -qa '48;5;201' $TMP || fail "sanity: scrollbar colour never reached the client"
|
||||||
|
: >$TMP
|
||||||
|
|
||||||
|
# Drag the floating pane straight up and down by its top border, staying at
|
||||||
|
# a fixed column the whole time - this never crosses the left pane's
|
||||||
|
# scrollbar strip, only its ordinary content.
|
||||||
|
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||||
|
row=$FTOP
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 6 ]; do
|
||||||
|
newrow=$((row + 1))
|
||||||
|
drag $GRABCOL $row $GRABCOL $newrow
|
||||||
|
row=$newrow
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 6 ]; do
|
||||||
|
newrow=$((row - 1))
|
||||||
|
drag $GRABCOL $row $GRABCOL $newrow
|
||||||
|
row=$newrow
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# The scrollbar colour should never reappear - its geometry never changed,
|
||||||
|
# and the drag never crossed its column. This is expected to fail before
|
||||||
|
# the fix - see the header comment.
|
||||||
|
n=$(grep -ac '48;5;201' $TMP)
|
||||||
|
[ "$n" -eq 0 ] ||
|
||||||
|
fail "left pane's scrollbar was redrawn $n times while dragging over its body only"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -268,149 +268,5 @@ $TMUX kill-pane -t "$floating" || exit 1
|
|||||||
$TMUX kill-pane -t "$right" || exit 1
|
$TMUX kill-pane -t "$right" || exit 1
|
||||||
$TMUX kill-pane -t "$lower" || exit 1
|
$TMUX kill-pane -t "$lower" || exit 1
|
||||||
|
|
||||||
# --- Floating panes clamped when the window shrinks (issue #5581, PR #5582) ---
|
|
||||||
#
|
|
||||||
# layout_resize clamps floating panes back inside the window when it shrinks:
|
|
||||||
# move them and, only if they cannot fit, shrink them (never below
|
|
||||||
# PANE_MINIMUM). A floating cell is the pane's content, so with the default
|
|
||||||
# single-line border the clamp counts 1 cell of border per side, exactly as
|
|
||||||
# layout_floating_args_parse does on creation; with no border it counts 0.
|
|
||||||
# Each case below gets its own window, since resize-window fixes a window at
|
|
||||||
# a manual size for the rest of its life.
|
|
||||||
|
|
||||||
# Case 1: a lone floating pane -- break-pane -W on a window's only pane --
|
|
||||||
# takes the early-return path in layout_resize, added during PR #5582's
|
|
||||||
# review round, since there is no tiled tree to walk.
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for lone float failed"
|
|
||||||
|
|
||||||
# -x 20 -y 6 -> pane 18x4; -X 60 -Y 18 -> pane_left 61, pane_top 19: with the
|
|
||||||
# border, the footprint is columns 60-79, rows 18-23, flush with the 80x24
|
|
||||||
# window's right and bottom edges, so the float fits before it is shrunk.
|
|
||||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
|
||||||
fail "break-pane -W for lone float failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
|
||||||
|
|
||||||
# Shrink to 40x16. The float still fits at its own size (18 <= 40-2, 4 <=
|
|
||||||
# 16-2) so only its position moves, flush to the new right/bottom edges:
|
|
||||||
# xoff = 40 - 18 - 1 = 21; yoff = 16 - 4 - 1 = 11.
|
|
||||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
|
||||||
fail "resize-window (lone float) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
|
||||||
|
|
||||||
# Case 5: grow the window back. The clamp must not chase the window back
|
|
||||||
# outward -- it only ever pulls a float in, never restores where it was.
|
|
||||||
$TMUX resize-window -t "$win" -x 80 -y 24 ||
|
|
||||||
fail "resize-window grow (lone float) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
# Case 2: the same float, but with a tiled sibling surviving alongside it, so
|
|
||||||
# the window's root cell stays tiled and layout_resize takes the normal path
|
|
||||||
# (the clamp call after layout_fix_offsets) instead of the early return
|
|
||||||
# above. Same geometry as case 1, so the same numbers should come out.
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for tiled sibling failed"
|
|
||||||
$TMUX split-window -t "$win" 'sleep 100' ||
|
|
||||||
fail "split-window for tiled sibling failed"
|
|
||||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
|
||||||
fail "break-pane -W for tiled sibling failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
|
||||||
|
|
||||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
|
||||||
fail "resize-window (tiled sibling) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
# Case 3: new-pane float, the original repro from issue #5581 and the PR
|
|
||||||
# body -- also the normal path, via the tiled base pane new-window creates.
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for new-pane repro failed"
|
|
||||||
$TMUX resize-window -t "$win" -x 120 -y 40 ||
|
|
||||||
fail "resize-window to 120x40 failed"
|
|
||||||
|
|
||||||
# -x 30 -y 10 -> pane 28x8; -X 85 -Y 25 -> pane_left 86, pane_top 26.
|
|
||||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
|
||||||
-x 30 -y 10 -X 85 -Y 25 'sleep 100') ||
|
|
||||||
fail "new-pane for new-pane repro failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 86
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 26
|
|
||||||
|
|
||||||
# Shrink to 60x20: xoff = 60 - 28 - 1 = 31; yoff = 20 - 8 - 1 = 11.
|
|
||||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
|
||||||
fail "resize-window (new-pane repro) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 31
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 11
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
# Case 4: a float that already fits inside the shrunk window is left alone --
|
|
||||||
# assert position and size are both unchanged, not just one of them.
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for untouched float failed"
|
|
||||||
|
|
||||||
# -x 20 -y 6 -> pane 18x4; -X 8 -Y 3 -> pane_left 9, pane_top 4 (as at the top
|
|
||||||
# of this file). Footprint columns 8-27, rows 3-8: well inside 60x20 too.
|
|
||||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
|
||||||
-x 20 -y 6 -X 8 -Y 3 'sleep 100') ||
|
|
||||||
fail "new-pane for untouched float failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
|
||||||
|
|
||||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
|
||||||
fail "resize-window (untouched float) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
# Case 6: pad = 0, via the pane-border-lines window option set to none. The
|
|
||||||
# border arithmetic differs here (no -2/+1 adjustment either on creation or
|
|
||||||
# in the clamp).
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for pad=0 float failed"
|
|
||||||
$TMUX set-option -w -t "$win" pane-border-lines none ||
|
|
||||||
fail "set pane-border-lines none failed"
|
|
||||||
|
|
||||||
# No border: -x 20 -y 6 -> pane 20x6 directly; -X 60 -Y 18 -> pane_left 60,
|
|
||||||
# pane_top 18 directly. Footprint (== content, no border) is columns 60-79,
|
|
||||||
# rows 18-23: flush right/bottom of the 80x24 window, same as case 1.
|
|
||||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
|
||||||
fail "break-pane -W for pad=0 float failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 60
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 18
|
|
||||||
|
|
||||||
# Shrink to 40x16 with pad=0: xoff = 40 - 20 - 0 = 20; yoff = 16 - 6 - 0 = 10.
|
|
||||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
|
||||||
fail "resize-window (pad=0 float) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 20
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 10
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
102
regress/floating-pane-status-format.sh
Executable file
102
regress/floating-pane-status-format.sh
Executable file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Resizing a floating pane must refresh session status formats which depend on
|
||||||
|
# its geometry, not only the pane scene and borders.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lfloating-status-outer-$$ -f/dev/null"
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has_status()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
tail -1 "$CAPTURE" | grep -q "$marker" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client status did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 50 -y 12 'sleep 100' || exit 1
|
||||||
|
FLOAT=$($INNER new-pane -PF '#{pane_id}' -x 10 -y 5 -X 5 -Y 3 \
|
||||||
|
'sleep 100') || fail "could not create floating pane"
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
$INNER set-option -g status on || exit 1
|
||||||
|
$INNER set-option -g status-position bottom || exit 1
|
||||||
|
$INNER set-option -g status-left 'WIDTH=#{pane_width}' || exit 1
|
||||||
|
$INNER set-option -g status-right '' || exit 1
|
||||||
|
$INNER set-option -g status-interval 0 || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 50 -y 12 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
OLD_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
wait_outer_has_status "WIDTH=$OLD_WIDTH"
|
||||||
|
|
||||||
|
RIGHT=$($INNER display-message -p -t "$FLOAT" '#{pane_right}')
|
||||||
|
TOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
X=$((RIGHT + 2))
|
||||||
|
Y=$((TOP + 2))
|
||||||
|
|
||||||
|
# Grab the right frame and enlarge the floating pane.
|
||||||
|
mouse 0 "$X" "$Y" M
|
||||||
|
mouse 32 "$((X + 1))" "$Y" M
|
||||||
|
mouse 32 "$((X + 8))" "$Y" M
|
||||||
|
mouse 0 "$((X + 8))" "$Y" m
|
||||||
|
|
||||||
|
NEW_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
[ "$NEW_WIDTH" -ne "$OLD_WIDTH" ] || fail "floating pane was not resized"
|
||||||
|
wait_outer_has_status "WIDTH=$NEW_WIDTH"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no
|
|||||||
$TMUX set -g @modal-root no
|
$TMUX set -g @modal-root no
|
||||||
$TMUX bind -n z set -g @modal-root yes
|
$TMUX bind -n z set -g @modal-root yes
|
||||||
|
|
||||||
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
|
||||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||||
fail "new-pane -ODK failed"
|
fail "new-pane -OK failed"
|
||||||
sleep 1
|
sleep 1
|
||||||
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||||
sleep 1
|
sleep 1
|
||||||
@@ -356,50 +356,9 @@ new_left=$(fmt "$modal" '#{pane_left}')
|
|||||||
new_top=$(fmt "$modal" '#{pane_top}')
|
new_top=$(fmt "$modal" '#{pane_top}')
|
||||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||||
fail "key-capturing modal pane did not move"
|
fail "key-capturing modal pane did not move"
|
||||||
$TMUX2 send-keys -t "$OUTER" Escape
|
|
||||||
sleep 1
|
|
||||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
|
||||||
|
|
||||||
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
|
||||||
-x 20 -y 5 -X 20 -Y 10 'trap "" INT; exec cat') ||
|
|
||||||
fail "new-pane -ODK failed"
|
|
||||||
sleep 1
|
|
||||||
$TMUX2 send-keys -t "$OUTER" C-c
|
|
||||||
sleep 1
|
|
||||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
|
||||||
|
|
||||||
# A dead modal does not close on Escape or C-c without -D.
|
|
||||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
|
||||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
|
|
||||||
fail "new-pane -O failed"
|
|
||||||
check_ok set-option -p -t "$modal" remain-on-exit on
|
|
||||||
sleep 2
|
|
||||||
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
|
|
||||||
$TMUX2 send-keys -t "$OUTER" Escape
|
|
||||||
sleep 1
|
|
||||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
|
||||||
check_ok kill-pane -t "$modal"
|
check_ok kill-pane -t "$modal"
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
# failed-key closes successful panes and retains failed panes until a key.
|
|
||||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
|
||||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
|
|
||||||
fail "new-pane -O failed"
|
|
||||||
check_ok set-option -p -t "$modal" remain-on-exit failed-key
|
|
||||||
sleep 2
|
|
||||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
|
||||||
|
|
||||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
|
||||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1; exit 1') ||
|
|
||||||
fail "new-pane -O failed"
|
|
||||||
check_ok set-option -p -t "$modal" remain-on-exit failed-key
|
|
||||||
sleep 2
|
|
||||||
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
|
|
||||||
must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key
|
|
||||||
$TMUX2 send-keys -t "$OUTER" a
|
|
||||||
sleep 1
|
|
||||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
|
||||||
|
|
||||||
# A nonmodal floating pane may remain above zoom, and switching between it and
|
# A nonmodal floating pane may remain above zoom, and switching between it and
|
||||||
# the zoomed tiled pane must not unzoom the window.
|
# the zoomed tiled pane must not unzoom the window.
|
||||||
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
||||||
|
|||||||
@@ -86,11 +86,6 @@ check_value "-gv status-keys" "vi"
|
|||||||
check_fail "unknown value: bogus" set -g status-keys bogus
|
check_fail "unknown value: bogus" set -g status-keys bogus
|
||||||
check_value "-gv status-keys" "vi"
|
check_value "-gv status-keys" "vi"
|
||||||
|
|
||||||
# pane-border-lines accepts rounded as a pane border style.
|
|
||||||
check_ok set -gw pane-border-lines rounded
|
|
||||||
check_value "-gwv pane-border-lines" "rounded"
|
|
||||||
check_ok set -gw pane-border-lines single
|
|
||||||
|
|
||||||
# --- flag options ---------------------------------------------------------
|
# --- flag options ---------------------------------------------------------
|
||||||
#
|
#
|
||||||
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
||||||
|
|||||||
@@ -104,25 +104,6 @@ assert_alive()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Compare layout geometry and pane ordering, ignoring the active pane and
|
|
||||||
# last-pane history in the JSON layout. Selection is checked separately.
|
|
||||||
pane_layout()
|
|
||||||
{
|
|
||||||
$TMUX display-message -p -t "$1" '#{window_layout}' |
|
|
||||||
sed 's/,"a":true//g; s/,"l":[0-9][0-9]*//g'
|
|
||||||
}
|
|
||||||
|
|
||||||
check_layout()
|
|
||||||
{
|
|
||||||
out=$(pane_layout "$1")
|
|
||||||
if [ "$out" != "$2" ]; then
|
|
||||||
echo "Layout for '$1' wrong."
|
|
||||||
echo "Expected: '$2'"
|
|
||||||
echo "But got: '$out'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# split-window geometry.
|
# split-window geometry.
|
||||||
|
|
||||||
@@ -328,12 +309,12 @@ check_ok kill-pane -t "$p6"
|
|||||||
|
|
||||||
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
||||||
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
||||||
layout=$(pane_layout P:0)
|
layout=$($TMUX display-message -p -t P:0 '#{window_layout}')
|
||||||
check_ok select-pane -t "$p0"
|
check_ok select-pane -t "$p0"
|
||||||
check_ok resize-pane -Z -t "$p0"
|
check_ok resize-pane -Z -t "$p0"
|
||||||
check_ok select-pane -t "$p2"
|
check_ok select-pane -t "$p2"
|
||||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||||
check_layout P:0 "$layout"
|
check_fmt P:0 '#{window_layout}' "$layout"
|
||||||
|
|
||||||
check_ok select-pane -t "$p0"
|
check_ok select-pane -t "$p0"
|
||||||
check_ok resize-pane -Z -t "$p0"
|
check_ok resize-pane -Z -t "$p0"
|
||||||
@@ -341,7 +322,7 @@ check_ok select-pane -Z -t "$p2"
|
|||||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||||
'1:1:1'
|
'1:1:1'
|
||||||
check_ok resize-pane -Z -t "$p2"
|
check_ok resize-pane -Z -t "$p2"
|
||||||
check_layout P:0 "$layout"
|
check_fmt P:0 '#{window_layout}' "$layout"
|
||||||
|
|
||||||
# Directional selection temporarily restores the full layout to find its
|
# Directional selection temporarily restores the full layout to find its
|
||||||
# neighbour, then follows the same unzoom or -Z transfer rules.
|
# neighbour, then follows the same unzoom or -Z transfer rules.
|
||||||
@@ -359,7 +340,7 @@ check_ok select-pane -D -Z -t "$p0"
|
|||||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||||
'1:1:1'
|
'1:1:1'
|
||||||
check_ok resize-pane -Z -t "$p2"
|
check_ok resize-pane -Z -t "$p2"
|
||||||
check_layout P:0 "$layout"
|
check_fmt P:0 '#{window_layout}' "$layout"
|
||||||
|
|
||||||
# The last-pane path has separate zoom handling, both with and without -Z.
|
# The last-pane path has separate zoom handling, both with and without -Z.
|
||||||
check_ok select-pane -t "$p0"
|
check_ok select-pane -t "$p0"
|
||||||
@@ -376,7 +357,7 @@ check_ok select-pane -l -Z -t P:0
|
|||||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||||
'1:1:1'
|
'1:1:1'
|
||||||
check_ok resize-pane -Z -t "$p0"
|
check_ok resize-pane -Z -t "$p0"
|
||||||
check_layout P:0 "$layout"
|
check_fmt P:0 '#{window_layout}' "$layout"
|
||||||
|
|
||||||
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
||||||
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
||||||
|
|||||||
97
regress/popup-drag-pane-prompt.sh
Executable file
97
regress/popup-drag-pane-prompt.sh
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Moving a popup away from a pane prompt must recompose the prompt over the
|
||||||
|
# pane contents restored by damage redraw.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lpopup-prompt-outer-$$ -f/dev/null"
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
POPUP_PID=
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
$INNER command-prompt -b -P -t "$CLIENT" -p 'PROMPT-MARK>' \
|
||||||
|
'display-message -- %1' || exit 1
|
||||||
|
wait_outer_has PROMPT-MARK
|
||||||
|
|
||||||
|
$INNER display-popup -t "$CLIENT" -x 0 -y 10 -w 16 -h 3 -E \
|
||||||
|
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||||
|
POPUP_PID=$!
|
||||||
|
wait_outer_has POPUP-MARKER
|
||||||
|
|
||||||
|
mouse 0 10 8 M
|
||||||
|
mouse 32 11 8 M
|
||||||
|
mouse 32 25 4 M
|
||||||
|
mouse 0 25 4 m
|
||||||
|
wait_outer_has PROMPT-MARK
|
||||||
|
|
||||||
|
exit 0
|
||||||
155
regress/popup-drag-redraw.sh
Executable file
155
regress/popup-drag-redraw.sh
Executable file
@@ -0,0 +1,155 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Moving and resizing a popup must restore its old rectangle, including pane
|
||||||
|
# border status, on the attached client's terminal.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lpopup-drag-outer-$$ -f/dev/null"
|
||||||
|
BASE=$DIR/base
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
RAW=$DIR/raw
|
||||||
|
POPUP_PID=
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_rows_match()
|
||||||
|
{
|
||||||
|
first=$1
|
||||||
|
last=$2
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
||||||
|
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
||||||
|
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer rows $first-$last were not restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
# A left-button border drag moves the popup. The first motion starts the drag;
|
||||||
|
# the second changes its position.
|
||||||
|
move_popup()
|
||||||
|
{
|
||||||
|
mouse 0 10 1 M
|
||||||
|
mouse 32 11 1 M
|
||||||
|
mouse 32 26 8 M
|
||||||
|
mouse 0 26 8 m
|
||||||
|
}
|
||||||
|
|
||||||
|
# A right-button bottom-right-border drag resizes the popup.
|
||||||
|
resize_popup()
|
||||||
|
{
|
||||||
|
mouse 2 "$1" "$2" M
|
||||||
|
mouse 34 "$3" "$4" M
|
||||||
|
mouse 34 "$5" "$6" M
|
||||||
|
mouse 2 "$5" "$6" m
|
||||||
|
}
|
||||||
|
|
||||||
|
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; printf \"\\033[19;45H\\033[38;5;201mSCOPE\\033[0m\"; exec sleep 100'"
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
$INNER set-option -w pane-border-status top || exit 1
|
||||||
|
$INNER set-option -w pane-border-format 'DAMAGE-STATUS-RESTORED' || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has SCOPE
|
||||||
|
grep -qa '38;5;201' "$RAW" || fail "scope colour was not drawn initially"
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||||
|
grep -q DAMAGE-STATUS-RESTORED "$BASE" ||
|
||||||
|
fail "pane border status was not visible before popup"
|
||||||
|
|
||||||
|
$INNER display-popup -t "$CLIENT" -x 0 -y 0 -w 28 -h 6 -E \
|
||||||
|
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||||
|
POPUP_PID=$!
|
||||||
|
wait_outer_has POPUP-MARKER
|
||||||
|
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||||
|
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
||||||
|
|
||||||
|
move_popup
|
||||||
|
wait_rows_match 1 6
|
||||||
|
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||||
|
grep -qa '38;5;201' "$RAW" &&
|
||||||
|
fail "popup move redrew content outside its old and new rectangles"
|
||||||
|
grep -q DAMAGE-STATUS-RESTORED "$CAPTURE" ||
|
||||||
|
fail "pane border status was not restored after popup move"
|
||||||
|
|
||||||
|
# The moved popup is 28x6 at zero-based 16,7. Shrink it to 21x3, then require
|
||||||
|
# the three rows vacated at the bottom to match the original scene.
|
||||||
|
resize_popup 44 13 43 13 38 11
|
||||||
|
wait_rows_match 11 13
|
||||||
|
|
||||||
|
# Grow it back to 28x6 and require a single, intact popup frame.
|
||||||
|
resize_popup 37 10 36 10 45 14
|
||||||
|
wait_outer_has POPUP-MARKER
|
||||||
|
corners=$(grep -o '┌' "$CAPTURE" | wc -l)
|
||||||
|
[ "$corners" -eq 1 ] || fail "expected one popup frame, found $corners"
|
||||||
|
|
||||||
|
exit 0
|
||||||
165
regress/popup-drag-status-line.sh
Executable file
165
regress/popup-drag-status-line.sh
Executable file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Moving a popup away from the tmux status area must restore status and window
|
||||||
|
# cells. Window damage alone cannot describe cells outside the window scene,
|
||||||
|
# and top status lines must be removed before translating to window rows.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
BASE=$DIR/base
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
INNER=
|
||||||
|
OUTER=
|
||||||
|
POPUP_PID=
|
||||||
|
N=0
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_scene()
|
||||||
|
{
|
||||||
|
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||||
|
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||||
|
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||||
|
POPUP_PID=
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
cleanup_scene
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_rows_restored()
|
||||||
|
{
|
||||||
|
first=$1
|
||||||
|
last=$2
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
||||||
|
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
||||||
|
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "rows $first-$last under the old popup were not restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
setup()
|
||||||
|
{
|
||||||
|
position=$1
|
||||||
|
lines=$2
|
||||||
|
|
||||||
|
cleanup_scene
|
||||||
|
N=$((N + 1))
|
||||||
|
INNER="$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lpopup-status-outer-$$-$N -f/dev/null"
|
||||||
|
|
||||||
|
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
if [ "$lines" -eq 1 ]; then
|
||||||
|
$INNER set-option -g status on || exit 1
|
||||||
|
else
|
||||||
|
$INNER set-option -g status "$lines" || exit 1
|
||||||
|
fi
|
||||||
|
$INNER set-option -g status-position "$position" || exit 1
|
||||||
|
$INNER set-option -g status-format[0] \
|
||||||
|
'STATUS-LINE-MARK-01234567890123456789012' || exit 1
|
||||||
|
if [ "$lines" -gt 1 ]; then
|
||||||
|
$INNER set-option -g status-format[1] \
|
||||||
|
'SECOND-STATUS-MARK-012345678901234567890' || exit 1
|
||||||
|
fi
|
||||||
|
$INNER set-option -g status-interval 0 || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has STATUS-LINE-MARK
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
open_popup()
|
||||||
|
{
|
||||||
|
y=$1
|
||||||
|
height=$2
|
||||||
|
$INNER display-popup -t "$CLIENT" -x 0 -y "$y" -w 16 -h "$height" -E \
|
||||||
|
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||||
|
POPUP_PID=$!
|
||||||
|
wait_outer_has POPUP-MARKER
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${STATUS_CASE:-all}" != top ]; then
|
||||||
|
# A popup clipped against a one-line bottom status must restore that line.
|
||||||
|
setup bottom 1
|
||||||
|
open_popup 10 3
|
||||||
|
mouse 0 10 8 M
|
||||||
|
mouse 32 11 8 M
|
||||||
|
mouse 32 25 4 M
|
||||||
|
mouse 0 25 4 m
|
||||||
|
wait_rows_restored 10 10
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${STATUS_CASE:-all}" != bottom ]; then
|
||||||
|
# A popup beginning in a two-line top status also covers the first window
|
||||||
|
# rows. Restore both coordinate spaces after it moves.
|
||||||
|
setup top 2
|
||||||
|
open_popup 1 4
|
||||||
|
mouse 0 10 1 M
|
||||||
|
mouse 32 11 1 M
|
||||||
|
mouse 32 25 7 M
|
||||||
|
mouse 0 25 7 m
|
||||||
|
wait_rows_restored 1 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
126
regress/popup-drag-wide-character.sh
Executable file
126
regress/popup-drag-wide-character.sh
Executable file
@@ -0,0 +1,126 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Damage at a popup edge must redraw complete grid characters. Drawing only a
|
||||||
|
# wide character's base or padding cell leaves a two-cell hole behind.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lpopup-wide-outer-$$ -f/dev/null"
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
BASE=$DIR/base
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
POPUP_PID=
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_old_rows_restored()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
sed -n '3,5p' "$BASE" >"$DIR/want"
|
||||||
|
sed -n '3,5p' "$CAPTURE" >"$DIR/got"
|
||||||
|
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "wide characters under the old popup edge were not restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
binmode STDOUT, ':encoding(UTF-8)';
|
||||||
|
$| = 1;
|
||||||
|
for my $row (1 .. 10) {
|
||||||
|
print "\e[$row;1H", chr(0x754c) x 20;
|
||||||
|
}
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has '界界界'
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||||
|
|
||||||
|
$INNER display-popup -t "$CLIENT" -x 5 -y 5 -w 10 -h 3 -E \
|
||||||
|
"sh -c 'printf POPUP; exec sleep 100'" &
|
||||||
|
POPUP_PID=$!
|
||||||
|
wait_outer_has POPUP
|
||||||
|
|
||||||
|
# The first motion starts the drag; the second moves the popup away from its
|
||||||
|
# old rectangle. Its odd x coordinate bisects the underlying double-width
|
||||||
|
# cells at both edges.
|
||||||
|
mouse 0 10 3 M
|
||||||
|
mouse 32 11 3 M
|
||||||
|
mouse 32 28 7 M
|
||||||
|
mouse 0 28 7 m
|
||||||
|
wait_old_rows_restored
|
||||||
|
|
||||||
|
exit 0
|
||||||
112
regress/redraw-damage-only.sh
Executable file
112
regress/redraw-damage-only.sh
Executable file
@@ -0,0 +1,112 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Check a redraw callback which has no accompanying client redraw flags. A
|
||||||
|
# wrapped row crossing a panned viewport cannot use the direct tty path.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Ldamage-only-outer-$$ -f/dev/null"
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
TRIGGER=$DIR/trigger
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_inner_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||||
|
grep -q "$marker" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner pane did not contain $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
for my $row (1 .. 12) {
|
||||||
|
print "\e[$row;1H", 'o' x 79;
|
||||||
|
}
|
||||||
|
print "\e[1;1H";
|
||||||
|
while (!-e $ENV{TRIGGER}) {
|
||||||
|
select undef, undef, undef, 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $second = ('B' x 24) . 'DAMAGE-ONLY' . ('B' x 45);
|
||||||
|
print "\e[5;1H", ('A' x 80), $second;
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 80 -y 12 \
|
||||||
|
"TRIGGER='$TRIGGER' perl '$EMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
$INNER refresh-client -t "$CLIENT" -R 20 || exit 1
|
||||||
|
wait_outer_has oooooooooo
|
||||||
|
|
||||||
|
: >"$TRIGGER"
|
||||||
|
wait_inner_has DAMAGE-ONLY
|
||||||
|
wait_outer_has DAMAGE-ONLY
|
||||||
|
|
||||||
|
exit 0
|
||||||
186
regress/redraw-multiclient.sh
Executable file
186
regress/redraw-multiclient.sh
Executable file
@@ -0,0 +1,186 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Redraw a moved floating pane on both attached clients viewing the same
|
||||||
|
# window. Window redraw work must not be consumed by only one client.
|
||||||
|
#
|
||||||
|
# Uses ASCII pane borders (rather than the default UTF-8 box-drawing) because
|
||||||
|
# this test nests a real tmux client inside another tmux's pane to get a
|
||||||
|
# genuine terminal to capture from; that nested-tmux relay has been observed
|
||||||
|
# to mis-render a cell that previously held a multi-byte UTF-8 border
|
||||||
|
# character being overwritten later by plain content, on the outer instance's
|
||||||
|
# own interpretation, independent of anything the inner tmux sends. That is a
|
||||||
|
# nested-test-harness artifact, not a real tmux bug - confirmed by replaying
|
||||||
|
# the exact same drag sequence against a real terminal (xterm), where it
|
||||||
|
# never reproduces. ASCII borders avoid the artifact entirely.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lredraw-multi-outer-$$ -f/dev/null"
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_clients()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
count=$($INNER list-clients 2>/dev/null | wc -l)
|
||||||
|
[ "$count" -eq 2 ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "two inner clients did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
target=$1
|
||||||
|
marker=$2
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t "$target" >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer pane $target did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_float_left()
|
||||||
|
{
|
||||||
|
comparison=$1
|
||||||
|
limit=$2
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
left=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
if [ "$comparison" = gt ] && [ "$left" -gt "$limit" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$comparison" = lt ] && [ "$left" -lt "$limit" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "floating pane did not move"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$2" "$3" "$4" "$5")
|
||||||
|
$OUTER send-keys -t "$1" -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
drag_float()
|
||||||
|
{
|
||||||
|
target=$1
|
||||||
|
startcol=$2
|
||||||
|
startrow=$3
|
||||||
|
endcol=$4
|
||||||
|
mouse "$target" 0 "$startcol" "$startrow" M
|
||||||
|
mouse "$target" 32 "$endcol" "$startrow" M
|
||||||
|
mouse "$target" 0 "$endcol" "$startrow" m
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_scene()
|
||||||
|
{
|
||||||
|
target=$1
|
||||||
|
base=$2
|
||||||
|
firstcol=$3
|
||||||
|
lastcol=$4
|
||||||
|
|
||||||
|
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
||||||
|
|
||||||
|
# With ASCII (simple) borders every corner and junction is the same
|
||||||
|
# '+', so one rectangular floating pane always draws exactly 4 of
|
||||||
|
# them; more means a stale frame was left behind somewhere.
|
||||||
|
corners=$(grep -o '+' "$CAPTURE" | wc -l)
|
||||||
|
[ "$corners" -eq 4 ] ||
|
||||||
|
fail "outer pane $target had $corners floating frames"
|
||||||
|
|
||||||
|
sed -n '6,11p' "$base" | cut -c"$firstcol-$lastcol" >"$DIR/want"
|
||||||
|
sed -n '6,11p' "$CAPTURE" | cut -c"$firstcol-$lastcol" >"$DIR/got"
|
||||||
|
cmp -s "$DIR/want" "$DIR/got" ||
|
||||||
|
fail "outer pane $target did not restore the old floating area"
|
||||||
|
}
|
||||||
|
|
||||||
|
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
$INNER set-option -g default-command 'sleep 100' || exit 1
|
||||||
|
$INNER set-option -g pane-border-lines simple || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen || exit 1
|
||||||
|
$OUTER split-window -h -t outer:0.0 'sleep 100' || exit 1
|
||||||
|
|
||||||
|
PANES=$($OUTER list-panes -t outer:0 -F '#{pane_id} #{pane_left}')
|
||||||
|
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||||
|
RIGHT=$(echo "$PANES" | sort -k2 -n | tail -1 | cut -d' ' -f1)
|
||||||
|
[ -n "$LEFT" ] && [ -n "$RIGHT" ] || fail "could not find outer panes"
|
||||||
|
|
||||||
|
$OUTER respawn-pane -k -t "$LEFT" \
|
||||||
|
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
$OUTER respawn-pane -k -t "$RIGHT" \
|
||||||
|
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_clients
|
||||||
|
wait_outer_has "$LEFT" BG-ROW-19
|
||||||
|
wait_outer_has "$RIGHT" BG-ROW-19
|
||||||
|
$OUTER capture-pane -p -t "$LEFT" >"$DIR/base-left" || exit 1
|
||||||
|
$OUTER capture-pane -p -t "$RIGHT" >"$DIR/base-right" || exit 1
|
||||||
|
|
||||||
|
FLOAT=$($INNER new-pane -dPF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||||
|
fail "could not create floating pane"
|
||||||
|
wait_outer_has "$LEFT" '+'
|
||||||
|
wait_outer_has "$RIGHT" '+'
|
||||||
|
|
||||||
|
FTOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
FWIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||||
|
|
||||||
|
# Move right through one client and require both clients to restore the old
|
||||||
|
# left-hand footprint.
|
||||||
|
drag_float "$LEFT" "$GRABCOL" "$FTOP" $((GRABCOL + 30))
|
||||||
|
wait_float_left gt 30
|
||||||
|
assert_scene "$LEFT" "$DIR/base-left" 1 20
|
||||||
|
assert_scene "$RIGHT" "$DIR/base-right" 1 20
|
||||||
|
|
||||||
|
# Move back through the other client and check the old right-hand footprint.
|
||||||
|
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||||
|
drag_float "$RIGHT" "$GRABCOL" "$FTOP" $((GRABCOL - 30))
|
||||||
|
wait_float_left lt 10
|
||||||
|
assert_scene "$LEFT" "$DIR/base-left" 35 60
|
||||||
|
assert_scene "$RIGHT" "$DIR/base-right" 35 60
|
||||||
|
|
||||||
|
exit 0
|
||||||
208
regress/redraw-screen-write.sh
Executable file
208
regress/redraw-screen-write.sh
Executable file
@@ -0,0 +1,208 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Check that full and region screen-write fallbacks update an attached client,
|
||||||
|
# not only tmux's internal pane grid.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
INNER=
|
||||||
|
OUTER=
|
||||||
|
N=0
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||||
|
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_lacks()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" || return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client still showed $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_inner_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||||
|
grep -q "$marker" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner pane did not contain $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_inner_lacks()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$INNER capture-pane -p -t inner:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" || return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner pane still contained $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup()
|
||||||
|
{
|
||||||
|
mode=$1
|
||||||
|
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||||
|
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||||
|
N=$((N + 1))
|
||||||
|
INNER="$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lredraw-write-outer-$$-$N -f/dev/null"
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 12 \
|
||||||
|
"MODE=$mode READY='$DIR/ready-$N' TRIGGER='$DIR/trigger-$N' perl '$EMITTER'" ||
|
||||||
|
exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger()
|
||||||
|
{
|
||||||
|
: >"$DIR/trigger-$N-${1:-1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
my $mode = $ENV{MODE};
|
||||||
|
my $ready = $ENV{READY};
|
||||||
|
my $trigger = $ENV{TRIGGER};
|
||||||
|
|
||||||
|
sub fill_screen {
|
||||||
|
my ($prefix) = @_;
|
||||||
|
print "\e[2J\e[H";
|
||||||
|
for my $row (0 .. 11) {
|
||||||
|
printf "\e[%d;1H%s-ROW-%02d", $row + 1, $prefix, $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mode eq 'ris') {
|
||||||
|
fill_screen('RIS');
|
||||||
|
} elsif ($mode eq 'alternate') {
|
||||||
|
fill_screen('BASE');
|
||||||
|
} elsif ($mode eq 'scroll') {
|
||||||
|
fill_screen('SCROLL');
|
||||||
|
} else {
|
||||||
|
die "unknown mode $mode\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
open my $fh, '>', $ready or die "$ready: $!\n";
|
||||||
|
close $fh;
|
||||||
|
while (!-e "$trigger-1") {
|
||||||
|
select undef, undef, undef, 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mode eq 'ris') {
|
||||||
|
print "\ec";
|
||||||
|
} elsif ($mode eq 'alternate') {
|
||||||
|
print "\e[?1049h";
|
||||||
|
fill_screen('ALT');
|
||||||
|
while (!-e "$trigger-2") {
|
||||||
|
select undef, undef, undef, 0.01;
|
||||||
|
}
|
||||||
|
print "\e[?1049l";
|
||||||
|
} else {
|
||||||
|
print "\e[12;1H\r\nSCROLL-NEW";
|
||||||
|
}
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
# RIS clears the complete screen. The source pane and attached client must both
|
||||||
|
# lose every old row.
|
||||||
|
setup ris
|
||||||
|
wait_outer_has RIS-ROW-11
|
||||||
|
trigger
|
||||||
|
wait_inner_lacks RIS-ROW
|
||||||
|
wait_outer_lacks RIS-ROW
|
||||||
|
|
||||||
|
# Leaving the alternate screen restores every row of the base screen.
|
||||||
|
setup alternate
|
||||||
|
wait_outer_has BASE-ROW-11
|
||||||
|
trigger
|
||||||
|
wait_inner_has ALT-ROW-11
|
||||||
|
wait_outer_has ALT-ROW-11
|
||||||
|
wait_outer_lacks BASE-ROW
|
||||||
|
trigger 2
|
||||||
|
wait_inner_has BASE-ROW-11
|
||||||
|
wait_outer_has BASE-ROW-11
|
||||||
|
wait_outer_lacks ALT-ROW
|
||||||
|
|
||||||
|
# Scrolling a pane which is narrower than the terminal redraws its complete
|
||||||
|
# region. Check the physical client row by row after the source grid shifts.
|
||||||
|
setup scroll
|
||||||
|
$INNER split-window -h -t inner:0 'sleep 100' || exit 1
|
||||||
|
wait_outer_has SCROLL-ROW-11
|
||||||
|
trigger
|
||||||
|
wait_inner_has SCROLL-NEW
|
||||||
|
wait_outer_has SCROLL-NEW
|
||||||
|
row=1
|
||||||
|
while [ "$row" -le 11 ]; do
|
||||||
|
expected=$(printf 'SCROLL-ROW-%02d' "$row")
|
||||||
|
actual=$(sed -n "${row}p" "$CAPTURE")
|
||||||
|
case "$actual" in
|
||||||
|
"$expected"*) ;;
|
||||||
|
*) fail "outer row $row was not redrawn as $expected" ;;
|
||||||
|
esac
|
||||||
|
row=$((row + 1))
|
||||||
|
done
|
||||||
|
actual=$(sed -n '12p' "$CAPTURE")
|
||||||
|
case "$actual" in
|
||||||
|
SCROLL-NEW*) ;;
|
||||||
|
*) fail "outer bottom row was not redrawn as SCROLL-NEW" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -107,12 +107,6 @@ $TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
|
|||||||
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||||
compare floating-border-double
|
compare floating-border-double
|
||||||
|
|
||||||
# Larger floating pane with rounded border lines.
|
|
||||||
new_scene 40 12
|
|
||||||
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B rounded \
|
|
||||||
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
|
||||||
compare floating-border-rounded
|
|
||||||
|
|
||||||
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
|
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
|
||||||
# the float has no border at all, only its (clipped) content over the base pane.
|
# the float has no border at all, only its (clipped) content over the base pane.
|
||||||
new_scene 40 12
|
new_scene 40 12
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
base
|
|
||||||
╭──────────────────────────╮
|
|
||||||
│FLOAT │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
╰──────────────────────────╯
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
81
regress/switch-client-redraw.sh
Executable file
81
regress/switch-client-redraw.sh
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Switching windows in the same session must redraw the attached client. The
|
||||||
|
# session object is shared, so its current winlink cannot be compared after it
|
||||||
|
# has already been changed.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lswitch-redraw-outer-$$ -f/dev/null"
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 8 \
|
||||||
|
"printf '\033[2J\033[HA-WINDOW'; exec sleep 100" || exit 1
|
||||||
|
$INNER new-window -d -t inner:1 \
|
||||||
|
"printf '\033[2J\033[HB-WINDOW'; exec sleep 100" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null attach-session -t inner:0" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has A-WINDOW
|
||||||
|
|
||||||
|
$INNER switch-client -c "$CLIENT" -t inner:1.0 || exit 1
|
||||||
|
[ "$($INNER display-message -p -t inner '#{window_index}')" -eq 1 ] ||
|
||||||
|
fail "server did not select window 1"
|
||||||
|
wait_outer_has B-WINDOW
|
||||||
|
|
||||||
|
exit 0
|
||||||
389
screen-redraw.c
389
screen-redraw.c
@@ -205,6 +205,22 @@ struct redraw_scene {
|
|||||||
u_int oy;
|
u_int oy;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* A single damaged window-coordinate rectangle. */
|
||||||
|
struct redraw_damage {
|
||||||
|
u_int x;
|
||||||
|
u_int y;
|
||||||
|
u_int sx;
|
||||||
|
u_int sy;
|
||||||
|
|
||||||
|
TAILQ_ENTRY(redraw_damage) entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cap on the number of pending damage rectangles per window before they are
|
||||||
|
* collapsed into a single rectangle covering their union.
|
||||||
|
*/
|
||||||
|
#define REDRAW_DAMAGE_MAX 16
|
||||||
|
|
||||||
/* Cell for building the scene. */
|
/* Cell for building the scene. */
|
||||||
struct redraw_build_cell {
|
struct redraw_build_cell {
|
||||||
struct redraw_span_data data;
|
struct redraw_span_data data;
|
||||||
@@ -1057,6 +1073,13 @@ redraw_free_scene(struct redraw_scene *scene)
|
|||||||
free(scene);
|
free(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Does a client's cached scene show this window? */
|
||||||
|
int
|
||||||
|
redraw_client_has_window(struct client *c, struct window *w)
|
||||||
|
{
|
||||||
|
return (c->redraw_scene != NULL && c->redraw_scene->w == w);
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark a window's cached redraw scenes as out of date. */
|
/* Mark a window's cached redraw scenes as out of date. */
|
||||||
void
|
void
|
||||||
redraw_invalidate_scene(struct window *w)
|
redraw_invalidate_scene(struct window *w)
|
||||||
@@ -1064,6 +1087,114 @@ redraw_invalidate_scene(struct window *w)
|
|||||||
w->redraw_scene_generation++;
|
w->redraw_scene_generation++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Free all pending damage for a window. */
|
||||||
|
void
|
||||||
|
redraw_free_damage(struct window *w)
|
||||||
|
{
|
||||||
|
struct redraw_damage *rd, *rd1;
|
||||||
|
|
||||||
|
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||||
|
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||||
|
free(rd);
|
||||||
|
}
|
||||||
|
w->damage_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collapse all pending damage for a window into one rectangle - its union. */
|
||||||
|
static void
|
||||||
|
redraw_collapse_damage(struct window *w)
|
||||||
|
{
|
||||||
|
struct redraw_damage *rd, *rd1, *first;
|
||||||
|
u_int x0, y0, x1, y1;
|
||||||
|
|
||||||
|
first = TAILQ_FIRST(&w->damage);
|
||||||
|
if (first == NULL)
|
||||||
|
return;
|
||||||
|
x0 = first->x;
|
||||||
|
y0 = first->y;
|
||||||
|
x1 = first->x + first->sx;
|
||||||
|
y1 = first->y + first->sy;
|
||||||
|
|
||||||
|
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||||
|
if (rd->x < x0)
|
||||||
|
x0 = rd->x;
|
||||||
|
if (rd->y < y0)
|
||||||
|
y0 = rd->y;
|
||||||
|
if (rd->x + rd->sx > x1)
|
||||||
|
x1 = rd->x + rd->sx;
|
||||||
|
if (rd->y + rd->sy > y1)
|
||||||
|
y1 = rd->y + rd->sy;
|
||||||
|
if (rd != first) {
|
||||||
|
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||||
|
free(rd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
first->x = x0;
|
||||||
|
first->y = y0;
|
||||||
|
first->sx = x1 - x0;
|
||||||
|
first->sy = y1 - y0;
|
||||||
|
w->damage_count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Record a damaged window-coordinate rectangle. Clips it to the window,
|
||||||
|
* merges it with an existing rectangle where doing so does not make the
|
||||||
|
* result substantially larger than the two combined, and collapses the
|
||||||
|
* whole list to its union once it grows past a modest cap.
|
||||||
|
*
|
||||||
|
* This only records damage - nothing consumes it yet.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
redraw_damage_window(struct window *w, u_int x, u_int y, u_int sx, u_int sy)
|
||||||
|
{
|
||||||
|
struct redraw_damage *rd;
|
||||||
|
u_int x0, y0, x1, y1, area, union_area;
|
||||||
|
|
||||||
|
if (x >= w->sx || y >= w->sy)
|
||||||
|
return;
|
||||||
|
if (x + sx > w->sx)
|
||||||
|
sx = w->sx - x;
|
||||||
|
if (y + sy > w->sy)
|
||||||
|
sy = w->sy - y;
|
||||||
|
if (sx == 0 || sy == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||||
|
/* Skip unless overlapping or directly adjacent. */
|
||||||
|
if (x > rd->x + rd->sx || rd->x > x + sx ||
|
||||||
|
y > rd->y + rd->sy || rd->y > y + sy)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
x0 = (x < rd->x) ? x : rd->x;
|
||||||
|
y0 = (y < rd->y) ? y : rd->y;
|
||||||
|
x1 = (x + sx > rd->x + rd->sx) ? x + sx : rd->x + rd->sx;
|
||||||
|
y1 = (y + sy > rd->y + rd->sy) ? y + sy : rd->y + rd->sy;
|
||||||
|
|
||||||
|
area = sx * sy + rd->sx * rd->sy;
|
||||||
|
union_area = (x1 - x0) * (y1 - y0);
|
||||||
|
if (union_area > 2 * area)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rd->x = x0;
|
||||||
|
rd->y = y0;
|
||||||
|
rd->sx = x1 - x0;
|
||||||
|
rd->sy = y1 - y0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rd = xcalloc(1, sizeof *rd);
|
||||||
|
rd->x = x;
|
||||||
|
rd->y = y;
|
||||||
|
rd->sx = sx;
|
||||||
|
rd->sy = sy;
|
||||||
|
TAILQ_INSERT_TAIL(&w->damage, rd, entry);
|
||||||
|
w->damage_count++;
|
||||||
|
|
||||||
|
if (w->damage_count > REDRAW_DAMAGE_MAX)
|
||||||
|
redraw_collapse_damage(w);
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark all cached redraw scenes as out of date. */
|
/* Mark all cached redraw scenes as out of date. */
|
||||||
void
|
void
|
||||||
redraw_invalidate_all_scenes(void)
|
redraw_invalidate_all_scenes(void)
|
||||||
@@ -1376,10 +1507,15 @@ redraw_draw_menu_span(struct redraw_draw_ctx *dctx,
|
|||||||
tty_draw_line(tty, s, px, span->data.m.py, n, x, y, NULL);
|
tty_draw_line(tty, s, px, span->data.m.py, n, x, y, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draw a span. */
|
/*
|
||||||
|
* Draw a span, restricted to [clip_x, clip_x + clip_n) - a caller drawing
|
||||||
|
* the whole span passes the span's own x/width here; a caller drawing only
|
||||||
|
* a damaged sub-range passes that range instead. Overlay clipping (menus,
|
||||||
|
* popups) is then applied on top of this, exactly as before.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||||
u_int y)
|
u_int y, u_int clip_x, u_int clip_n)
|
||||||
{
|
{
|
||||||
struct redraw_scene *scene = dctx->scene;
|
struct redraw_scene *scene = dctx->scene;
|
||||||
struct redraw_span_data *data = &span->data;
|
struct redraw_span_data *data = &span->data;
|
||||||
@@ -1393,7 +1529,7 @@ redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
|||||||
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
|
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
r = tty_check_overlay_range(tty, span->x, y, span->width);
|
r = tty_check_overlay_range(tty, clip_x, y, clip_n);
|
||||||
for (i = 0; i < r->used; i++) {
|
for (i = 0; i < r->used; i++) {
|
||||||
rr = &r->ranges[i];
|
rr = &r->ranges[i];
|
||||||
if (rr->nx == 0)
|
if (rr->nx == 0)
|
||||||
@@ -1453,15 +1589,19 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
|||||||
if (flags & REDRAW_PANE) {
|
if (flags & REDRAW_PANE) {
|
||||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||||
TAILQ_FOREACH(span, spans, entry) {
|
TAILQ_FOREACH(span, spans, entry) {
|
||||||
if (span->data.p.wp == wp)
|
if (span->data.p.wp == wp) {
|
||||||
redraw_draw_span(dctx, span, cy);
|
redraw_draw_span(dctx, span, cy,
|
||||||
|
span->x, span->width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (flags & REDRAW_PANE_SCROLLBAR) {
|
if (flags & REDRAW_PANE_SCROLLBAR) {
|
||||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||||
TAILQ_FOREACH(span, spans, entry) {
|
TAILQ_FOREACH(span, spans, entry) {
|
||||||
if (span->data.sb.wp == wp)
|
if (span->data.sb.wp == wp) {
|
||||||
redraw_draw_span(dctx, span, cy);
|
redraw_draw_span(dctx, span, cy,
|
||||||
|
span->x, span->width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1519,8 +1659,10 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
spans = &line->spans[type];
|
spans = &line->spans[type];
|
||||||
TAILQ_FOREACH(span, spans, entry)
|
TAILQ_FOREACH(span, spans, entry) {
|
||||||
redraw_draw_span(dctx, span, cy);
|
redraw_draw_span(dctx, span, cy, span->x,
|
||||||
|
span->width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1540,8 +1682,10 @@ redraw_draw_menu_lines(struct redraw_draw_ctx *dctx)
|
|||||||
cy = dctx->status_lines + y;
|
cy = dctx->status_lines + y;
|
||||||
else
|
else
|
||||||
cy = y;
|
cy = y;
|
||||||
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry)
|
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry) {
|
||||||
redraw_draw_span(dctx, span, cy);
|
redraw_draw_span(dctx, span, cy, span->x,
|
||||||
|
span->width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1621,6 +1765,24 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
|
|||||||
dctx->flags |= REDRAW_ISOLATES;
|
dctx->flags |= REDRAW_ISOLATES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Build a pane prompt into a one-line screen. */
|
||||||
|
static void
|
||||||
|
redraw_make_pane_prompt(struct window_pane *wp, struct screen *screen)
|
||||||
|
{
|
||||||
|
struct screen_write_ctx ctx;
|
||||||
|
struct prompt_draw_data pdd;
|
||||||
|
|
||||||
|
screen_init(screen, wp->sx, 1, 0);
|
||||||
|
screen_write_start(&ctx, screen);
|
||||||
|
pdd.ctx = &ctx;
|
||||||
|
pdd.cursor_x = &wp->prompt_cx;
|
||||||
|
pdd.area_x = 0;
|
||||||
|
pdd.area_width = wp->sx;
|
||||||
|
pdd.prompt_line = 0;
|
||||||
|
prompt_draw(wp->prompt, &pdd);
|
||||||
|
screen_write_stop(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
/* Draw a pane's prompt over its content. */
|
/* Draw a pane's prompt over its content. */
|
||||||
static void
|
static void
|
||||||
redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||||
@@ -1629,8 +1791,6 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
|||||||
struct client *c = scene->c;
|
struct client *c = scene->c;
|
||||||
struct tty *tty = &c->tty;
|
struct tty *tty = &c->tty;
|
||||||
struct screen screen;
|
struct screen screen;
|
||||||
struct screen_write_ctx ctx;
|
|
||||||
struct prompt_draw_data pdd;
|
|
||||||
int ox = scene->ox, oy = scene->oy;
|
int ox = scene->ox, oy = scene->oy;
|
||||||
int sx = scene->sx, sy = scene->sy;
|
int sx = scene->sx, sy = scene->sy;
|
||||||
int line, cy, px, offset, width, wy;
|
int line, cy, px, offset, width, wy;
|
||||||
@@ -1663,16 +1823,7 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
|||||||
if (px + width > sx)
|
if (px + width > sx)
|
||||||
width = sx - px;
|
width = sx - px;
|
||||||
|
|
||||||
screen_init(&screen, wp->sx, 1, 0);
|
redraw_make_pane_prompt(wp, &screen);
|
||||||
screen_write_start(&ctx, &screen);
|
|
||||||
pdd.ctx = &ctx;
|
|
||||||
pdd.cursor_x = &wp->prompt_cx;
|
|
||||||
pdd.area_x = 0;
|
|
||||||
pdd.area_width = wp->sx;
|
|
||||||
pdd.prompt_line = 0;
|
|
||||||
prompt_draw(wp->prompt, &pdd);
|
|
||||||
screen_write_stop(&ctx);
|
|
||||||
|
|
||||||
tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL);
|
tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL);
|
||||||
screen_free(&screen);
|
screen_free(&screen);
|
||||||
}
|
}
|
||||||
@@ -1704,7 +1855,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
|||||||
redraw = status_prompt_redraw(c);
|
redraw = status_prompt_redraw(c);
|
||||||
else
|
else
|
||||||
redraw = status_redraw(c);
|
redraw = status_redraw(c);
|
||||||
if (!redraw && !REDRAW_IS_ALL(flags)) {
|
if (!redraw && (~c->flags & CLIENT_REDRAWSTATUSALWAYS) &&
|
||||||
|
!REDRAW_IS_ALL(flags)) {
|
||||||
flags &= ~REDRAW_STATUS;
|
flags &= ~REDRAW_STATUS;
|
||||||
if (flags == 0)
|
if (flags == 0)
|
||||||
return;
|
return;
|
||||||
@@ -1875,7 +2027,7 @@ redraw_screen(struct client *c)
|
|||||||
} else {
|
} else {
|
||||||
if (c->flags & CLIENT_REDRAWBORDERS)
|
if (c->flags & CLIENT_REDRAWBORDERS)
|
||||||
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
|
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
|
||||||
if (c->flags & CLIENT_REDRAWSTATUS)
|
if (c->flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))
|
||||||
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
|
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
|
||||||
if (c->flags & CLIENT_REDRAWOVERLAY)
|
if (c->flags & CLIENT_REDRAWOVERLAY)
|
||||||
flags |= REDRAW_OVERLAY;
|
flags |= REDRAW_OVERLAY;
|
||||||
@@ -1903,3 +2055,190 @@ redraw_pane_scrollbar(struct client *c, struct window_pane *wp)
|
|||||||
{
|
{
|
||||||
redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR);
|
redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A REDRAW_SPAN_STATUS span within a damaged rectangle needs its content
|
||||||
|
* rebuilt and force-drawn regardless of whether that content has logically
|
||||||
|
* changed. window_make_pane_status()'s grid_compare() only tells us
|
||||||
|
* whether the *content* changed, not whether the physical cells were
|
||||||
|
* disturbed by something else (e.g. a floating pane sliding across this
|
||||||
|
* row) - and being inside a damage rectangle already proves that happened.
|
||||||
|
* Without this, redraw_draw_span() silently skips REDRAW_SPAN_STATUS spans
|
||||||
|
* whenever PANE_NEWSTATUS is not set, leaving a pane's border-status title
|
||||||
|
* blank until some unrelated redraw happens to touch it (e.g. a focus
|
||||||
|
* change or window resize).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
redraw_damage_refresh_status(struct redraw_draw_ctx *dctx,
|
||||||
|
struct window_pane *wp)
|
||||||
|
{
|
||||||
|
struct redraw_span *first;
|
||||||
|
u_int width;
|
||||||
|
|
||||||
|
if (wp->flags & PANE_NEWSTATUS)
|
||||||
|
return;
|
||||||
|
width = redraw_pane_status_width(dctx, wp, &first);
|
||||||
|
if (width == 0)
|
||||||
|
return;
|
||||||
|
window_make_pane_status(wp, dctx->scene->c, width, first);
|
||||||
|
wp->flags |= PANE_NEWSTATUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grow a clipped span range by one cell on either edge that isn't already at
|
||||||
|
* the span's own boundary. A clip edge that lands mid-character (this is a
|
||||||
|
* damage rectangle, so its edges are geometric and have no idea what's in
|
||||||
|
* the grid) may be sitting on the second, padding half of a wide character
|
||||||
|
* whose first half falls just outside the requested range - growing by one
|
||||||
|
* cell is always enough to pull the whole character back in, since no grid
|
||||||
|
* cell is ever wider than two columns, and clamping to the span's own x and
|
||||||
|
* width keeps this from bleeding into a neighbouring span.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
redraw_damage_grow_span_clip(struct redraw_span *span, u_int *xp, u_int *endp)
|
||||||
|
{
|
||||||
|
if (*xp > span->x)
|
||||||
|
(*xp)--;
|
||||||
|
if (*endp < span->x + span->width)
|
||||||
|
(*endp)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recompose a pane's prompt over a damaged section of its display row. */
|
||||||
|
static void
|
||||||
|
redraw_damage_draw_pane_prompt(struct redraw_draw_ctx *dctx,
|
||||||
|
struct redraw_span *span, u_int y, u_int x, u_int n)
|
||||||
|
{
|
||||||
|
struct redraw_scene *scene = dctx->scene;
|
||||||
|
struct window_pane *wp = span->data.p.wp;
|
||||||
|
struct tty *tty = &scene->c->tty;
|
||||||
|
struct visible_ranges *r;
|
||||||
|
struct visible_range *rr;
|
||||||
|
struct screen screen;
|
||||||
|
u_int i, px, width, prompt_y;
|
||||||
|
|
||||||
|
if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0)
|
||||||
|
return;
|
||||||
|
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||||
|
prompt_y = 0;
|
||||||
|
else
|
||||||
|
prompt_y = wp->sy - 1;
|
||||||
|
if (span->data.p.py != prompt_y)
|
||||||
|
return;
|
||||||
|
|
||||||
|
redraw_make_pane_prompt(wp, &screen);
|
||||||
|
r = tty_check_overlay_range(tty, x, y, n);
|
||||||
|
for (i = 0; i < r->used; i++) {
|
||||||
|
rr = &r->ranges[i];
|
||||||
|
if (rr->nx == 0)
|
||||||
|
continue;
|
||||||
|
px = span->data.p.px + (rr->px - span->x);
|
||||||
|
if (px >= screen_size_x(&screen))
|
||||||
|
continue;
|
||||||
|
width = rr->nx;
|
||||||
|
if (width > screen_size_x(&screen) - px)
|
||||||
|
width = screen_size_x(&screen) - px;
|
||||||
|
tty_draw_line(tty, &screen, px, 0, width, rr->px, y, NULL);
|
||||||
|
}
|
||||||
|
screen_free(&screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compose exactly the cells within a damaged rectangle (already in this
|
||||||
|
* client's own scene coordinates), rather than a whole pane. For each row
|
||||||
|
* in range, every span of every type whose x-range intersects the
|
||||||
|
* rectangle is drawn restricted to just the intersected sub-range.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||||
|
u_int sx, u_int sy)
|
||||||
|
{
|
||||||
|
struct redraw_scene *scene = dctx->scene;
|
||||||
|
struct redraw_line *line;
|
||||||
|
struct redraw_spans *spans;
|
||||||
|
struct redraw_span *span;
|
||||||
|
u_int cy, yy, clip_x, clip_end, type;
|
||||||
|
|
||||||
|
if (x >= scene->sx || y >= scene->sy)
|
||||||
|
return;
|
||||||
|
if (x + sx > scene->sx)
|
||||||
|
sx = scene->sx - x;
|
||||||
|
if (y + sy > scene->sy)
|
||||||
|
sy = scene->sy - y;
|
||||||
|
if (sx == 0 || sy == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (yy = y; yy < y + sy; yy++) {
|
||||||
|
line = &scene->lines[yy];
|
||||||
|
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||||
|
cy = dctx->status_lines + yy;
|
||||||
|
else
|
||||||
|
cy = yy;
|
||||||
|
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||||
|
spans = &line->spans[type];
|
||||||
|
TAILQ_FOREACH(span, spans, entry) {
|
||||||
|
clip_x = (span->x > x) ? span->x : x;
|
||||||
|
clip_end = (span->x + span->width < x + sx) ?
|
||||||
|
span->x + span->width : x + sx;
|
||||||
|
if (clip_end <= clip_x)
|
||||||
|
continue;
|
||||||
|
if (type == REDRAW_SPAN_STATUS) {
|
||||||
|
redraw_damage_refresh_status(dctx,
|
||||||
|
span->data.st.wp);
|
||||||
|
}
|
||||||
|
redraw_damage_grow_span_clip(span, &clip_x,
|
||||||
|
&clip_end);
|
||||||
|
redraw_draw_span(dctx, span, cy, clip_x,
|
||||||
|
clip_end - clip_x);
|
||||||
|
if (type == REDRAW_SPAN_PANE) {
|
||||||
|
redraw_damage_draw_pane_prompt(dctx,
|
||||||
|
span, cy, clip_x,
|
||||||
|
clip_end - clip_x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Consume a client's window's pending damage by composing exactly the
|
||||||
|
* damaged cells, after clipping each rectangle to what this client can see
|
||||||
|
* and translating it into this client's own scene coordinates.
|
||||||
|
*
|
||||||
|
* Unlike redraw_pane(), this does not redraw a whole pane's worth of cells
|
||||||
|
* for a small disturbance - only the cells within the (clipped) rectangle
|
||||||
|
* are touched, via redraw_draw_damage_rect().
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
redraw_client_damage(struct client *c)
|
||||||
|
{
|
||||||
|
struct window *w = c->session->curw->window;
|
||||||
|
struct redraw_scene *scene;
|
||||||
|
struct redraw_draw_ctx dctx;
|
||||||
|
struct redraw_damage *rd;
|
||||||
|
u_int ox, oy, sx, sy, x0, y0, x1, y1;
|
||||||
|
|
||||||
|
if (TAILQ_EMPTY(&w->damage))
|
||||||
|
return;
|
||||||
|
|
||||||
|
scene = redraw_get_scene(c);
|
||||||
|
if (scene == NULL)
|
||||||
|
return;
|
||||||
|
redraw_set_draw_context(&dctx, scene);
|
||||||
|
redraw_get_window_offset(c, &ox, &oy, &sx, &sy);
|
||||||
|
|
||||||
|
tty_sync_start(&c->tty);
|
||||||
|
tty_update_mode(&c->tty, c->tty.mode & ~CURSOR_MODES, NULL);
|
||||||
|
|
||||||
|
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||||
|
x0 = (rd->x > ox) ? rd->x : ox;
|
||||||
|
y0 = (rd->y > oy) ? rd->y : oy;
|
||||||
|
x1 = (rd->x + rd->sx < ox + sx) ? rd->x + rd->sx : ox + sx;
|
||||||
|
y1 = (rd->y + rd->sy < oy + sy) ? rd->y + rd->sy : oy + sy;
|
||||||
|
if (x0 >= x1 || y0 >= y1)
|
||||||
|
continue;
|
||||||
|
log_debug("%s: %s composing damage %u,%u %ux%u", __func__,
|
||||||
|
c->name, x0 - ox, y0 - oy, x1 - x0, y1 - y0);
|
||||||
|
redraw_draw_damage_rect(&dctx, x0 - ox, y0 - oy, x1 - x0,
|
||||||
|
y1 - y0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -120,14 +120,20 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy)
|
|||||||
evtimer_add(&w->offset_timer, &tv);
|
evtimer_add(&w->offset_timer, &tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do a full redraw. */
|
/*
|
||||||
|
* Called when a write could not be applied directly to the terminal and
|
||||||
|
* needs a redraw instead. Report damage for the requested rows. wp->yoff is
|
||||||
|
* already adjusted past any top pane-border-status row, so wp->yoff + py is
|
||||||
|
* the correct window-coordinate row.
|
||||||
|
*/
|
||||||
static void
|
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;
|
struct window_pane *wp = ttyctx->arg;
|
||||||
|
|
||||||
if (wp != NULL)
|
if (wp == NULL)
|
||||||
wp->flags |= PANE_REDRAW;
|
return;
|
||||||
|
redraw_damage_window(wp->window, wp->xoff, wp->yoff + py, wp->sx, ny);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update context for client. */
|
/* Update context for client. */
|
||||||
@@ -2155,7 +2161,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx)
|
|||||||
|
|
||||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||||
if (ttyctx.redraw_cb != NULL)
|
if (ttyctx.redraw_cb != NULL)
|
||||||
ttyctx.redraw_cb(&ttyctx);
|
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trim collected items. */
|
/* Trim collected items. */
|
||||||
@@ -3175,7 +3181,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
|||||||
|
|
||||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||||
if (ttyctx.redraw_cb != NULL)
|
if (ttyctx.redraw_cb != NULL)
|
||||||
ttyctx.redraw_cb(&ttyctx);
|
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Turn alternate screen off. */
|
/* Turn alternate screen off. */
|
||||||
@@ -3200,5 +3206,5 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
|||||||
|
|
||||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||||
if (ttyctx.redraw_cb != NULL)
|
if (ttyctx.redraw_cb != NULL)
|
||||||
ttyctx.redraw_cb(&ttyctx);
|
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||||
}
|
}
|
||||||
|
|||||||
102
server-client.c
102
server-client.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: server-client.c,v 1.512 2026/09/10 11:02:18 nicm Exp $ */
|
/* $OpenBSD: server-client.c,v 1.511 2026/09/08 10:20:08 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -473,7 +473,33 @@ server_client_set_session(struct client *c, struct session *s)
|
|||||||
tty_update_client_offset(c);
|
tty_update_client_offset(c);
|
||||||
status_timer_start(c);
|
status_timer_start(c);
|
||||||
server_client_fire_session_changed(c, old);
|
server_client_fire_session_changed(c, old);
|
||||||
server_redraw_client(c);
|
|
||||||
|
/*
|
||||||
|
* A full redraw is only needed if the client's session or
|
||||||
|
* current window actually changed - not if this merely
|
||||||
|
* confirmed the client is still looking at the same window
|
||||||
|
* (as happens when switch-client -t targets a pane in the
|
||||||
|
* already-current window, e.g. clicking a pane name in a
|
||||||
|
* second #{P:} status line: the default MouseDown1Status
|
||||||
|
* binding resolves that click to switch-client -t=, which
|
||||||
|
* reaches here regardless of whether anything besides the
|
||||||
|
* active pane changed). Redrawing unconditionally here
|
||||||
|
* forced a full window redraw for what should have been
|
||||||
|
* just an active-pane change, already handled narrowly by
|
||||||
|
* window_set_active_pane() and window_redraw_active_switch()
|
||||||
|
* before this is reached.
|
||||||
|
*
|
||||||
|
* old and s may be the same session object, whose curw was
|
||||||
|
* already updated to the new window before this function was
|
||||||
|
* called - old->curw and s->curw would then read the same,
|
||||||
|
* already-current value, so comparing them can never detect
|
||||||
|
* a same-session window change. Compare against the client's
|
||||||
|
* own cached scene instead, which only reflects what it has
|
||||||
|
* actually drawn.
|
||||||
|
*/
|
||||||
|
if (old == NULL || old != s ||
|
||||||
|
!redraw_client_has_window(c, s->curw->window))
|
||||||
|
server_redraw_client(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
server_check_unattached();
|
server_check_unattached();
|
||||||
@@ -1391,19 +1417,15 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
|
|||||||
return (repeat);
|
return (repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle a key press which closes a dead pane. */
|
/* Handle a key press on a dead pane waiting for a key. */
|
||||||
static int
|
static int
|
||||||
server_client_handle_dead_key(struct window_pane *wp, key_code key)
|
server_client_handle_dead_key(struct window_pane *wp, key_code key)
|
||||||
{
|
{
|
||||||
int remain_on_exit;
|
|
||||||
|
|
||||||
if (wp == NULL ||
|
if (wp == NULL ||
|
||||||
(~wp->flags & PANE_EXITED) ||
|
(~wp->flags & PANE_EXITED) ||
|
||||||
KEYC_IS_MOUSE(key) ||
|
KEYC_IS_MOUSE(key) ||
|
||||||
KEYC_IS_PASTE(key))
|
KEYC_IS_PASTE(key) ||
|
||||||
return (0);
|
options_get_number(wp->options, "remain-on-exit") != 3)
|
||||||
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
|
|
||||||
if (remain_on_exit != 3 && remain_on_exit != 4)
|
|
||||||
return (0);
|
return (0);
|
||||||
options_set_number(wp->options, "remain-on-exit", 0);
|
options_set_number(wp->options, "remain-on-exit", 0);
|
||||||
server_destroy_pane(wp, 0);
|
server_destroy_pane(wp, 0);
|
||||||
@@ -1466,8 +1488,19 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
|||||||
/*
|
/*
|
||||||
* Mouse drag is in progress, so fire the callback (now that
|
* Mouse drag is in progress, so fire the callback (now that
|
||||||
* the mouse event is valid).
|
* the mouse event is valid).
|
||||||
|
*
|
||||||
|
* Start a synchronized-output region here rather than
|
||||||
|
* leaving it to whatever redraw eventually follows: a drag
|
||||||
|
* callback may write directly via the pane's fast path
|
||||||
|
* immediately, with any correction only arriving later via
|
||||||
|
* redraw_client_damage(), which opens its own sync region.
|
||||||
|
* Since tty_sync_end() is only called once, at the very end
|
||||||
|
* of this client's pass in server_client_reset_state(),
|
||||||
|
* starting it here merges both into one atomic terminal
|
||||||
|
* update instead of two visible frames.
|
||||||
*/
|
*/
|
||||||
if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
|
if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
|
||||||
|
tty_sync_start(&c->tty);
|
||||||
c->tty.mouse_drag_update(c, m);
|
c->tty.mouse_drag_update(c, m);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@@ -1760,10 +1793,9 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Key presses in overlay mode, dead panes waiting for a key, modal cancel
|
* Key presses in overlay mode, for panes capturing all keys and in the
|
||||||
* keys, panes capturing all keys and the command prompt are special cases.
|
* command prompt are a special case. The queue might be blocked so they
|
||||||
* The queue might be blocked so they need to be processed immediately
|
* need to be processed immediately rather than queued.
|
||||||
* rather than queued.
|
|
||||||
*/
|
*/
|
||||||
if (~c->flags & CLIENT_READONLY) {
|
if (~c->flags & CLIENT_READONLY) {
|
||||||
if (c->message_string != NULL) {
|
if (c->message_string != NULL) {
|
||||||
@@ -1785,18 +1817,12 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
|||||||
server_client_clear_overlay(c);
|
server_client_clear_overlay(c);
|
||||||
|
|
||||||
wp = s->curw->window->active;
|
wp = s->curw->window->active;
|
||||||
if (server_client_handle_dead_key(wp, event->key))
|
|
||||||
return (0);
|
|
||||||
if (wp != NULL && wp == wp->window->modal &&
|
|
||||||
(wp->flags & PANE_CLOSEONCANCEL) &&
|
|
||||||
(event->key == '\033' || event->key == ('c'|KEYC_CTRL))) {
|
|
||||||
server_kill_pane(wp);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
if (wp != NULL &&
|
if (wp != NULL &&
|
||||||
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
||||||
TAILQ_EMPTY(&wp->modes) &&
|
TAILQ_EMPTY(&wp->modes) &&
|
||||||
!KEYC_IS_MOUSE(event->key)) {
|
!KEYC_IS_MOUSE(event->key)) {
|
||||||
|
if (server_client_handle_dead_key(wp, event->key))
|
||||||
|
return (0);
|
||||||
if (~wp->flags & PANE_EXITED) {
|
if (~wp->flags & PANE_EXITED) {
|
||||||
window_pane_key(wp, c, s, s->curw, event->key,
|
window_pane_key(wp, c, s, s->curw, event->key,
|
||||||
&event->m);
|
&event->m);
|
||||||
@@ -1911,7 +1937,12 @@ server_client_loop(void)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Any windows will have been redrawn as part of clients, so clear
|
* Any windows will have been redrawn as part of clients, so clear
|
||||||
* their flags now.
|
* their flags now. A client whose redraw was deferred this pass
|
||||||
|
* (waiting for outstanding tty output to drain) has already
|
||||||
|
* escalated to CLIENT_REDRAWWINDOW or CLIENT_REDRAWSCROLLBARS in
|
||||||
|
* server_client_check_redraw() to cover whatever it is about to
|
||||||
|
* lose here, so PANE_REDRAW/PANE_REDRAWSCROLLBAR and window damage
|
||||||
|
* can simply be cleared unconditionally.
|
||||||
*/
|
*/
|
||||||
RB_FOREACH(w, windows, &windows) {
|
RB_FOREACH(w, windows, &windows) {
|
||||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||||
@@ -1922,6 +1953,8 @@ server_client_loop(void)
|
|||||||
wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR|
|
wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR|
|
||||||
PANE_ACTIVITY);
|
PANE_ACTIVITY);
|
||||||
}
|
}
|
||||||
|
redraw_free_damage(w);
|
||||||
|
|
||||||
check_window_name(w);
|
check_window_name(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2470,6 +2503,8 @@ server_client_any_pane_redraw(struct client *c)
|
|||||||
|
|
||||||
if (c->flags & CLIENT_REDRAWWINDOW)
|
if (c->flags & CLIENT_REDRAWWINDOW)
|
||||||
return (1);
|
return (1);
|
||||||
|
if (!TAILQ_EMPTY(&w->damage))
|
||||||
|
return (1);
|
||||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||||
if (wp->flags & (PANE_REDRAW|PANE_REDRAWSCROLLBAR))
|
if (wp->flags & (PANE_REDRAW|PANE_REDRAWSCROLLBAR))
|
||||||
return (1);
|
return (1);
|
||||||
@@ -2515,7 +2550,12 @@ server_client_check_redraw(struct client *c)
|
|||||||
/*
|
/*
|
||||||
* If there is outstanding data, defer the redraw until it has been
|
* If there is outstanding data, defer the redraw until it has been
|
||||||
* consumed. We can just add a timer to get out of the event loop and
|
* consumed. We can just add a timer to get out of the event loop and
|
||||||
* end up back here.
|
* end up back here. server_client_loop() clears PANE_REDRAW,
|
||||||
|
* PANE_REDRAWSCROLLBAR and window damage unconditionally every pass,
|
||||||
|
* so escalate to a coarser, persistent client flag that survives
|
||||||
|
* that clear and forces a full catch-up redraw once this client is
|
||||||
|
* unblocked, rather than trying to keep the fine-grained state
|
||||||
|
* around for a retry.
|
||||||
*/
|
*/
|
||||||
n = EVBUFFER_LENGTH(tty->out);
|
n = EVBUFFER_LENGTH(tty->out);
|
||||||
if (n != 0 || (tty->flags & TTY_BLOCK)) {
|
if (n != 0 || (tty->flags & TTY_BLOCK)) {
|
||||||
@@ -2529,6 +2569,8 @@ server_client_check_redraw(struct client *c)
|
|||||||
log_debug("redraw timer started");
|
log_debug("redraw timer started");
|
||||||
evtimer_add(&ev, &tv);
|
evtimer_add(&ev, &tv);
|
||||||
}
|
}
|
||||||
|
if (!TAILQ_EMPTY(&w->damage))
|
||||||
|
c->flags |= CLIENT_REDRAWWINDOW;
|
||||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||||
if (wp->flags & PANE_REDRAW) {
|
if (wp->flags & PANE_REDRAW) {
|
||||||
c->flags |= CLIENT_REDRAWWINDOW;
|
c->flags |= CLIENT_REDRAWWINDOW;
|
||||||
@@ -2562,6 +2604,19 @@ server_client_check_redraw(struct client *c)
|
|||||||
redraw_pane_scrollbar(c, wp);
|
redraw_pane_scrollbar(c, wp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Window damage is also what makes server_client_any_pane_
|
||||||
|
* redraw() decide a redraw is needed at all, independently of
|
||||||
|
* any CLIENT_ALLREDRAWFLAGS bit. Every current damage source
|
||||||
|
* happens to set one of those flags too, so the block below
|
||||||
|
* always consumes it - but consume it here too in case that
|
||||||
|
* ever stops holding, since server_client_loop() clears
|
||||||
|
* window damage unconditionally every pass regardless of
|
||||||
|
* whether it was actually drawn.
|
||||||
|
*/
|
||||||
|
if (!TAILQ_EMPTY(&w->damage) && (~c->flags & CLIENT_ALLREDRAWFLAGS))
|
||||||
|
redraw_client_damage(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -2575,6 +2630,7 @@ server_client_check_redraw(struct client *c)
|
|||||||
}
|
}
|
||||||
server_client_set_progress_bar(c);
|
server_client_set_progress_bar(c);
|
||||||
redraw_screen(c);
|
redraw_screen(c);
|
||||||
|
redraw_client_damage(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Put the tty back how it was. */
|
/* Put the tty back how it was. */
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: server-fn.c,v 1.153 2026/09/10 11:02:18 nicm Exp $ */
|
/* $OpenBSD: server-fn.c,v 1.152 2026/09/03 19:12:36 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -386,7 +386,6 @@ server_destroy_pane(struct window_pane *wp, int notify)
|
|||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
case 4:
|
|
||||||
if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
|
if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
|
||||||
break;
|
break;
|
||||||
/* FALLTHROUGH */
|
/* FALLTHROUGH */
|
||||||
|
|||||||
21
tmux.1
21
tmux.1
@@ -1,4 +1,4 @@
|
|||||||
.\" $OpenBSD: tmux.1,v 1.1170 2026/09/11 10:17:16 nicm Exp $
|
.\" $OpenBSD: tmux.1,v 1.1168 2026/09/09 07:03:39 nicm Exp $
|
||||||
.\"
|
.\"
|
||||||
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
.\"
|
.\"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||||
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
.\"
|
.\"
|
||||||
.Dd $Mdocdate: September 11 2026 $
|
.Dd $Mdocdate: September 9 2026 $
|
||||||
.Dt TMUX 1
|
.Dt TMUX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -3655,7 +3655,7 @@ but a different format may be specified with
|
|||||||
.Fl F .
|
.Fl F .
|
||||||
.Tg newp
|
.Tg newp
|
||||||
.It Xo Ic new\-pane
|
.It Xo Ic new\-pane
|
||||||
.Op Fl AbCDefhIkKLMOPvWZ
|
.Op Fl AbCdefhIkKLMOPvWZ
|
||||||
.Op Fl B Ar border\-lines
|
.Op Fl B Ar border\-lines
|
||||||
.Op Fl c Ar start\-directory
|
.Op Fl c Ar start\-directory
|
||||||
.Op Fl e Ar environment
|
.Op Fl e Ar environment
|
||||||
@@ -3717,13 +3717,6 @@ all keys, including the prefix key, are passed directly to the modal pane.
|
|||||||
With
|
With
|
||||||
.Fl C ,
|
.Fl C ,
|
||||||
the modal pane is closed when the mouse is clicked outside it.
|
the modal pane is closed when the mouse is clicked outside it.
|
||||||
With
|
|
||||||
.Fl D ,
|
|
||||||
the modal pane is closed when
|
|
||||||
.Ql Escape
|
|
||||||
or
|
|
||||||
.Ql C-c
|
|
||||||
is pressed.
|
|
||||||
.Pp
|
.Pp
|
||||||
The
|
The
|
||||||
.Fl L
|
.Fl L
|
||||||
@@ -5980,8 +5973,6 @@ single lines using ACS or UTF\-8 characters
|
|||||||
double lines using UTF\-8 characters
|
double lines using UTF\-8 characters
|
||||||
.It heavy
|
.It heavy
|
||||||
heavy lines using UTF\-8 characters
|
heavy lines using UTF\-8 characters
|
||||||
.It rounded
|
|
||||||
single lines with rounded corners using UTF\-8 characters
|
|
||||||
.It simple
|
.It simple
|
||||||
simple ASCII characters
|
simple ASCII characters
|
||||||
.It number
|
.It number
|
||||||
@@ -6337,7 +6328,7 @@ uses when the colour with that index is requested.
|
|||||||
The index may be from zero to 255.
|
The index may be from zero to 255.
|
||||||
.Pp
|
.Pp
|
||||||
.It Xo Ic remain\-on\-exit
|
.It Xo Ic remain\-on\-exit
|
||||||
.Op Ic on | off | failed | key | failed\-key
|
.Op Ic on | off | failed | key
|
||||||
.Xc
|
.Xc
|
||||||
A pane with this flag set is not destroyed when the program running in it
|
A pane with this flag set is not destroyed when the program running in it
|
||||||
exits.
|
exits.
|
||||||
@@ -6347,10 +6338,6 @@ then only when the program exit status is not zero.
|
|||||||
If set to
|
If set to
|
||||||
.Ic key ,
|
.Ic key ,
|
||||||
the pane stays open and closes when a key is pressed.
|
the pane stays open and closes when a key is pressed.
|
||||||
If set to
|
|
||||||
.Ic failed\-key ,
|
|
||||||
the pane stays open and closes when a key is pressed only if the program exit
|
|
||||||
status is not zero.
|
|
||||||
The pane may be reactivated with the
|
The pane may be reactivated with the
|
||||||
.Ic respawn\-pane
|
.Ic respawn\-pane
|
||||||
command.
|
command.
|
||||||
|
|||||||
19
tmux.h
19
tmux.h
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: tmux.h,v 1.1440 2026/09/11 10:17:16 nicm Exp $ */
|
/* $OpenBSD: tmux.h,v 1.1438 2026/09/09 08:30:05 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -69,6 +69,7 @@ struct options_array_item;
|
|||||||
struct options_entry;
|
struct options_entry;
|
||||||
struct prompt;
|
struct prompt;
|
||||||
struct window_pane_prompt;
|
struct window_pane_prompt;
|
||||||
|
struct redraw_damage;
|
||||||
struct redraw_scene;
|
struct redraw_scene;
|
||||||
struct redraw_span;
|
struct redraw_span;
|
||||||
struct screen_write_citem;
|
struct screen_write_citem;
|
||||||
@@ -1150,8 +1151,7 @@ enum pane_lines {
|
|||||||
PANE_LINES_SIMPLE,
|
PANE_LINES_SIMPLE,
|
||||||
PANE_LINES_NUMBER,
|
PANE_LINES_NUMBER,
|
||||||
PANE_LINES_SPACES,
|
PANE_LINES_SPACES,
|
||||||
PANE_LINES_NONE,
|
PANE_LINES_NONE
|
||||||
PANE_LINES_ROUNDED
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Pane border indicator option. */
|
/* Pane border indicator option. */
|
||||||
@@ -1340,7 +1340,6 @@ struct window_pane {
|
|||||||
#define PANE_CLOSEONCLICK 0x80000
|
#define PANE_CLOSEONCLICK 0x80000
|
||||||
#define PANE_CAPTUREALLKEYS 0x100000
|
#define PANE_CAPTUREALLKEYS 0x100000
|
||||||
#define PANE_FLOATOVERZOOM 0x200000
|
#define PANE_FLOATOVERZOOM 0x200000
|
||||||
#define PANE_CLOSEONCANCEL 0x400000
|
|
||||||
|
|
||||||
bitstr_t *sync_dirty;
|
bitstr_t *sync_dirty;
|
||||||
u_int sync_dirty_size;
|
u_int sync_dirty_size;
|
||||||
@@ -1429,6 +1428,7 @@ struct window_pane {
|
|||||||
TAILQ_HEAD(window_panes, window_pane);
|
TAILQ_HEAD(window_panes, window_pane);
|
||||||
TAILQ_HEAD(window_panes_zindex, window_pane);
|
TAILQ_HEAD(window_panes_zindex, window_pane);
|
||||||
RB_HEAD(window_pane_tree, window_pane);
|
RB_HEAD(window_pane_tree, window_pane);
|
||||||
|
TAILQ_HEAD(redraw_damages, redraw_damage);
|
||||||
|
|
||||||
/* Window structure. */
|
/* Window structure. */
|
||||||
struct window {
|
struct window {
|
||||||
@@ -1472,6 +1472,9 @@ struct window {
|
|||||||
|
|
||||||
uint64_t redraw_scene_generation;
|
uint64_t redraw_scene_generation;
|
||||||
|
|
||||||
|
struct redraw_damages damage;
|
||||||
|
u_int damage_count;
|
||||||
|
|
||||||
struct menu_data *menu;
|
struct menu_data *menu;
|
||||||
u_int menu_last_px;
|
u_int menu_last_px;
|
||||||
u_int menu_last_py;
|
u_int menu_last_py;
|
||||||
@@ -1844,7 +1847,7 @@ struct tty {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Terminal command context. */
|
/* Terminal command context. */
|
||||||
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *);
|
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *, u_int, u_int);
|
||||||
typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *);
|
typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *);
|
||||||
struct tty_ctx {
|
struct tty_ctx {
|
||||||
struct screen *s;
|
struct screen *s;
|
||||||
@@ -3659,8 +3662,12 @@ void redraw_screen(struct client *);
|
|||||||
void redraw_pane(struct client *, struct window_pane *);
|
void redraw_pane(struct client *, struct window_pane *);
|
||||||
void redraw_pane_scrollbar(struct client *, struct window_pane *);
|
void redraw_pane_scrollbar(struct client *, struct window_pane *);
|
||||||
void redraw_free_scene(struct redraw_scene *);
|
void redraw_free_scene(struct redraw_scene *);
|
||||||
|
int redraw_client_has_window(struct client *, struct window *);
|
||||||
void redraw_invalidate_scene(struct window *);
|
void redraw_invalidate_scene(struct window *);
|
||||||
void redraw_invalidate_all_scenes(void);
|
void redraw_invalidate_all_scenes(void);
|
||||||
|
void redraw_damage_window(struct window *, u_int, u_int, u_int, u_int);
|
||||||
|
void redraw_free_damage(struct window *);
|
||||||
|
void redraw_client_damage(struct client *);
|
||||||
int redraw_get_status_border_cell_type(struct redraw_span **, u_int);
|
int redraw_get_status_border_cell_type(struct redraw_span **, u_int);
|
||||||
|
|
||||||
/* screen.c */
|
/* screen.c */
|
||||||
@@ -3834,6 +3841,8 @@ int window_pane_get_pane_status(struct window_pane *);
|
|||||||
struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
|
struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
|
||||||
u_int);
|
u_int);
|
||||||
int window_pane_is_floating(struct window_pane *);
|
int window_pane_is_floating(struct window_pane *);
|
||||||
|
void window_pane_redraw_floating(struct window *,
|
||||||
|
struct window_pane *, int, int, int, int);
|
||||||
|
|
||||||
/* window-border.c */
|
/* window-border.c */
|
||||||
void window_set_fill_cells(struct window *);
|
void window_set_fill_cells(struct window *);
|
||||||
|
|||||||
6
tty.c
6
tty.c
@@ -1120,7 +1120,7 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
*/
|
*/
|
||||||
if (tty_large_region(tty, ctx) || ctx->flags & TTY_CTX_PANE_OBSCURED) {
|
if (tty_large_region(tty, ctx) || ctx->flags & TTY_CTX_PANE_OBSCURED) {
|
||||||
log_debug("%s: %s large region redraw", __func__, c->name);
|
log_debug("%s: %s large region redraw", __func__, c->name);
|
||||||
ctx->redraw_cb(ctx);
|
ctx->redraw_cb(ctx, ctx->orupper, ctx->orlower - ctx->orupper + 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2032,7 +2032,7 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||||
c->overlay_check != NULL) {
|
c->overlay_check != NULL) {
|
||||||
ctx->redraw_cb(ctx);
|
ctx->redraw_cb(ctx, 0, ctx->sy);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2114,7 +2114,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
tty->cy == tty->rlower)
|
tty->cy == tty->rlower)
|
||||||
tty_draw_pane(tty, ctx, ctx->ocy);
|
tty_draw_pane(tty, ctx, ctx->ocy);
|
||||||
else
|
else
|
||||||
ctx->redraw_cb(ctx);
|
ctx->redraw_cb(ctx, ctx->ocy, 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: window-border.c,v 1.4 2026/09/11 10:17:16 nicm Exp $ */
|
/* $OpenBSD: window-border.c,v 1.3 2026/07/23 09:38:27 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -120,10 +120,6 @@ window_get_border_cell(struct window_pane *wp, enum pane_lines pane_lines,
|
|||||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||||
utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
|
utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
|
||||||
break;
|
break;
|
||||||
case PANE_LINES_ROUNDED:
|
|
||||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
|
||||||
utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type));
|
|
||||||
break;
|
|
||||||
case PANE_LINES_SIMPLE:
|
case PANE_LINES_SIMPLE:
|
||||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||||
utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
|
utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
|
||||||
|
|||||||
126
window.c
126
window.c
@@ -415,6 +415,7 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel)
|
|||||||
TAILQ_INIT(&w->panes);
|
TAILQ_INIT(&w->panes);
|
||||||
TAILQ_INIT(&w->z_index);
|
TAILQ_INIT(&w->z_index);
|
||||||
TAILQ_INIT(&w->last_panes);
|
TAILQ_INIT(&w->last_panes);
|
||||||
|
TAILQ_INIT(&w->damage);
|
||||||
w->active = NULL;
|
w->active = NULL;
|
||||||
|
|
||||||
w->lastlayout = -1;
|
w->lastlayout = -1;
|
||||||
@@ -460,6 +461,7 @@ window_destroy(struct window *w)
|
|||||||
|
|
||||||
menu_destroy(w);
|
menu_destroy(w);
|
||||||
window_destroy_panes(w);
|
window_destroy_panes(w);
|
||||||
|
redraw_free_damage(w);
|
||||||
|
|
||||||
if (event_initialized(&w->name_event))
|
if (event_initialized(&w->name_event))
|
||||||
evtimer_del(&w->name_event);
|
evtimer_del(&w->name_event);
|
||||||
@@ -711,6 +713,7 @@ int
|
|||||||
window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
||||||
{
|
{
|
||||||
struct window_pane *lastwp;
|
struct window_pane *lastwp;
|
||||||
|
int unzoomed;
|
||||||
|
|
||||||
log_debug("%s: pane %%%u", __func__, wp->id);
|
log_debug("%s: pane %%%u", __func__, wp->id);
|
||||||
|
|
||||||
@@ -718,7 +721,8 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
|||||||
return (0);
|
return (0);
|
||||||
if (w->modal != NULL && wp != w->modal)
|
if (w->modal != NULL && wp != w->modal)
|
||||||
return (0);
|
return (0);
|
||||||
if ((w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp))
|
unzoomed = (w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp);
|
||||||
|
if (unzoomed)
|
||||||
window_unzoom(w, 1);
|
window_unzoom(w, 1);
|
||||||
lastwp = w->active;
|
lastwp = w->active;
|
||||||
|
|
||||||
@@ -735,7 +739,19 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
|||||||
}
|
}
|
||||||
|
|
||||||
tty_update_window_offset(w);
|
tty_update_window_offset(w);
|
||||||
server_redraw_window(w);
|
|
||||||
|
/*
|
||||||
|
* Unzooming changes every pane's geometry and needs a full window
|
||||||
|
* redraw. Otherwise, only the previous and new active pane's border
|
||||||
|
* and status appearance changed, so avoid redrawing unaffected pane
|
||||||
|
* content.
|
||||||
|
*/
|
||||||
|
if (unzoomed)
|
||||||
|
server_redraw_window(w);
|
||||||
|
else {
|
||||||
|
server_redraw_window_borders(w);
|
||||||
|
server_status_window(w);
|
||||||
|
}
|
||||||
|
|
||||||
if (notify)
|
if (notify)
|
||||||
window_fire_pane_changed(w, w->active, lastwp);
|
window_fire_pane_changed(w, w->active, lastwp);
|
||||||
@@ -2906,3 +2922,109 @@ window_pane_is_floating(struct window_pane *wp)
|
|||||||
return (0);
|
return (0);
|
||||||
return (1);
|
return (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Report damage for a floating pane's rectangle, grown by one cell on every
|
||||||
|
* side - a floating pane draws its border frame at xoff-1/yoff-1 through
|
||||||
|
* xoff+sx/yoff+sy (see the "floating" case in screen-redraw.c), one cell
|
||||||
|
* outside its own content area, so damage for just the content area leaves
|
||||||
|
* the frame's previous position undrawn as the pane moves. If a scrollbar
|
||||||
|
* is reserved, its side of the frame is pushed out further still by its
|
||||||
|
* width and padding (also matched in screen-redraw.c), so grow that side
|
||||||
|
* to match.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
window_pane_damage_floating(struct window *w, struct window_pane *wp,
|
||||||
|
int xoff, int yoff, int sx, int sy)
|
||||||
|
{
|
||||||
|
int x0, x1, y0, y1, sb_left = 0, sb_right = 0;
|
||||||
|
|
||||||
|
if (window_pane_scrollbar_reserve(wp)) {
|
||||||
|
if (w->sb_pos == PANE_SCROLLBARS_LEFT)
|
||||||
|
sb_left = wp->scrollbar_style.width +
|
||||||
|
wp->scrollbar_style.pad;
|
||||||
|
else
|
||||||
|
sb_right = wp->scrollbar_style.width +
|
||||||
|
wp->scrollbar_style.pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
x0 = xoff - 1 - sb_left;
|
||||||
|
x1 = xoff + sx + sb_right;
|
||||||
|
y0 = yoff - 1;
|
||||||
|
y1 = yoff + sy;
|
||||||
|
if (x0 < 0)
|
||||||
|
x0 = 0;
|
||||||
|
if (y0 < 0)
|
||||||
|
y0 = 0;
|
||||||
|
if (x1 < x0 || y1 < y0)
|
||||||
|
return;
|
||||||
|
redraw_damage_window(w, (u_int)x0, (u_int)y0, (u_int)(x1 - x0) + 1,
|
||||||
|
(u_int)(y1 - y0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether a pane's scrollbar strip - not its whole body - intersects a
|
||||||
|
* window-coordinate rectangle. A reserved scrollbar occupies a strip of
|
||||||
|
* scrollbar_style.width+pad columns just outside the pane's own content
|
||||||
|
* area (see the scrollbar-reserve case in layout_fix_panes(), layout.c),
|
||||||
|
* on whichever side w->sb_pos points to.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
window_pane_scrollbar_intersects(struct window *w, struct window_pane *wp,
|
||||||
|
u_int x, u_int y, u_int sx, u_int sy)
|
||||||
|
{
|
||||||
|
int sb_x, sb_w, ix = (int)x, iy = (int)y, isx = (int)sx;
|
||||||
|
int isy = (int)sy;
|
||||||
|
|
||||||
|
if (!window_pane_scrollbar_reserve(wp))
|
||||||
|
return (0);
|
||||||
|
sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
|
||||||
|
if (w->sb_pos == PANE_SCROLLBARS_LEFT)
|
||||||
|
sb_x = (int)wp->xoff - sb_w;
|
||||||
|
else
|
||||||
|
sb_x = (int)wp->xoff + (int)wp->sx;
|
||||||
|
|
||||||
|
return (sb_x < ix + isx && sb_x + sb_w > ix &&
|
||||||
|
(int)wp->yoff < iy + isy && (int)wp->yoff + (int)wp->sy > iy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Report damage for only a floating pane's old and new area, rather than
|
||||||
|
* the whole window - a floating pane move or resize only disturbs what it
|
||||||
|
* was covering and what it now covers. Scrollbars aren't covered by the
|
||||||
|
* damage system, so a pane whose *scrollbar strip* (not its whole body)
|
||||||
|
* intersects either area is still flagged directly for a scrollbar redraw.
|
||||||
|
* Checking the whole pane body here, rather than just its narrow scrollbar
|
||||||
|
* strip, meant merely dragging over a pane's ordinary content set
|
||||||
|
* PANE_REDRAWSCROLLBAR on every such pane on every motion event, triggering
|
||||||
|
* a needless scrollbar redraw (and the redraw pass it forces) each time
|
||||||
|
* even though the scrollbar itself never moved.
|
||||||
|
*
|
||||||
|
* Shared by every command that drags a floating pane around by the mouse:
|
||||||
|
* resize-pane's own border drag (cmd-resize-pane.c), move-pane -M's
|
||||||
|
* alternate Alt-drag (cmd-join-pane.c), and split-window/new-pane's
|
||||||
|
* interactive resize of a newly-created floating pane (cmd-split-window.c).
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
window_pane_redraw_floating(struct window *w, struct window_pane *wp,
|
||||||
|
int old_xoff, int old_yoff, int old_sx, int old_sy)
|
||||||
|
{
|
||||||
|
struct window_pane *loop;
|
||||||
|
|
||||||
|
window_pane_damage_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||||
|
old_sy);
|
||||||
|
window_pane_damage_floating(w, wp, wp->xoff, wp->yoff, wp->sx,
|
||||||
|
wp->sy);
|
||||||
|
|
||||||
|
TAILQ_FOREACH(loop, &w->panes, entry) {
|
||||||
|
if (window_pane_scrollbar_intersects(w, loop,
|
||||||
|
(u_int)old_xoff, (u_int)old_yoff, (u_int)old_sx,
|
||||||
|
(u_int)old_sy) ||
|
||||||
|
window_pane_scrollbar_intersects(w, loop, wp->xoff,
|
||||||
|
wp->yoff, wp->sx, wp->sy))
|
||||||
|
loop->flags |= PANE_REDRAWSCROLLBAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Session status formats may depend on the pane's new geometry. */
|
||||||
|
server_status_window(w);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user