mirror of
https://github.com/tmux/tmux.git
synced 2026-09-18 11:04:46 +00:00
Compare commits
46 Commits
osc133_awa
...
osc133_lin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df134f38bd | ||
|
|
a8b05dd7d7 | ||
|
|
676ad44c96 | ||
|
|
71865a58d1 | ||
|
|
9837766857 | ||
|
|
e880cf63e0 | ||
|
|
325b40e8c4 | ||
|
|
f95363aa83 | ||
|
|
f8fc53b6f8 | ||
|
|
d215280b00 | ||
|
|
d859a0f410 | ||
|
|
10d00cc7cb | ||
|
|
14a01dd337 | ||
|
|
a958883625 | ||
|
|
d202aa7ac1 | ||
|
|
d9a5e82678 | ||
|
|
13c10f672c | ||
|
|
b7c5030004 | ||
|
|
4839123510 | ||
|
|
9b3268a2a0 | ||
|
|
5aeacf1cae | ||
|
|
7774731b95 | ||
|
|
0544f0b210 | ||
|
|
a3129249d1 | ||
|
|
930c81b819 | ||
|
|
fc695b4e86 | ||
|
|
36fb7ab084 | ||
|
|
90a7dd4654 | ||
|
|
bf43fdc0c7 | ||
|
|
077d6e53cd | ||
|
|
4e19f7a8dc | ||
|
|
7941f7b863 | ||
|
|
c7102ebbe5 | ||
|
|
1b87cb752e | ||
|
|
d9692f7ecf | ||
|
|
98637576bd | ||
|
|
d8bdb23108 | ||
|
|
2191c9b172 | ||
|
|
01d1d6580a | ||
|
|
96f4c7b9fa | ||
|
|
6a24d5437b | ||
|
|
8396a63128 | ||
|
|
d44bfda26d | ||
|
|
00f3899aea | ||
|
|
557967c36c | ||
|
|
73db0a54e5 |
15
CHANGES
15
CHANGES
@@ -33,9 +33,11 @@ CHANGES FROM 3.7c TO 3.8
|
||||
line;
|
||||
|
||||
- pane-border-status has top-floating and bottom-floating, and there are new
|
||||
default bindings under C-b g for common move and resize operations;
|
||||
default bindings under C-b g for common move and resize operations.
|
||||
|
||||
- floating panes are unzoomed before creation to avoid a crash.
|
||||
* Layout strings now use a JSON subset format which includes floating panes.
|
||||
The old format is still accepted; control mode clients receive old layouts
|
||||
unless they set the new-layouts flag.
|
||||
|
||||
* Add support for themes and improve default colours:
|
||||
|
||||
@@ -126,6 +128,10 @@ CHANGES FROM 3.7c TO 3.8
|
||||
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient
|
||||
methods to create an empty pane (rather than using '' for the command).
|
||||
|
||||
* 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.
|
||||
|
||||
* Menus now belong to the window, so appear on all clients.
|
||||
@@ -172,6 +178,8 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* The mouse option now defaults to on.
|
||||
|
||||
* The active-pane client flag has been removed.
|
||||
|
||||
* Fix send-keys -K so keys are inserted in the correct place in the input
|
||||
queue, like keys from key bindings (issue 3476).
|
||||
|
||||
@@ -180,7 +188,8 @@ CHANGES FROM 3.7c TO 3.8
|
||||
already exiting (Ben Maurer, issue 5357). Queue notifications so they are not
|
||||
sent inside %begin/%end (issue 5458), do not let a stuck client prevent the
|
||||
server from exiting (issue 5444), and reset control mode offsets when a pane
|
||||
is respawned (issue 5498).
|
||||
is respawned (issue 5498). Bound buffered command replies so a control mode
|
||||
client that stops reading cannot consume unlimited memory (issue 5565).
|
||||
|
||||
* Fix grouped sessions sometimes being left as unusable command targets while
|
||||
they are being killed (Bryce Miller, issue 5180).
|
||||
|
||||
@@ -174,6 +174,7 @@ dist_tmux_SOURCES = \
|
||||
input-keys.c \
|
||||
input.c \
|
||||
job.c \
|
||||
json.c \
|
||||
key-bindings.c \
|
||||
key-string.c \
|
||||
layout-custom.c \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-capture-pane.c,v 1.68 2026/07/20 11:16:33 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-capture-pane.c,v 1.69 2026/09/08 07:31:59 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
|
||||
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
|
||||
.name = "capture-pane",
|
||||
.alias = "capturep",
|
||||
|
||||
.args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
|
||||
.usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||
.args = { "ab:CeE:FHIJLMNpPqRS:Tt:", 0, 0, NULL },
|
||||
.usage = "[-aCeFHIJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||
"[-S start-line] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -200,7 +200,7 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
|
||||
tmp[0] = line[i];
|
||||
tmp[1] = '\0';
|
||||
} else
|
||||
xsnprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
|
||||
snprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
|
||||
buf = cmd_capture_pane_append(buf, len, tmp,
|
||||
strlen(tmp));
|
||||
}
|
||||
@@ -259,7 +259,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
struct grid_cell *gc = NULL;
|
||||
struct window_mode_entry *wme;
|
||||
int n, join_lines, number_lines, flags = 0;
|
||||
int show_flags, hyperlinks;
|
||||
int show_flags, show_time, hyperlinks;
|
||||
u_int *links = NULL, nlinks = 0;
|
||||
u_int i, sx, top, bottom, tmp;
|
||||
char *cause, *buf = NULL, *line, b[64], *cp;
|
||||
@@ -342,6 +342,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
flags |= GRID_STRING_TRIM_SPACES;
|
||||
number_lines = args_has(args, 'L');
|
||||
show_flags = args_has(args, 'F');
|
||||
show_time = args_has(args, 'I');
|
||||
hyperlinks = args_has(args, 'H');
|
||||
if (hyperlinks)
|
||||
links = xreallocarray(NULL, gd->sx, sizeof *links);
|
||||
@@ -358,6 +359,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
free(line);
|
||||
continue;
|
||||
}
|
||||
gl = grid_peek_line(gd, i);
|
||||
|
||||
if (number_lines) {
|
||||
if (i >= gd->hsize)
|
||||
@@ -368,11 +370,16 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
if (n >= 0)
|
||||
buf = cmd_capture_pane_append(buf, len, b, n);
|
||||
}
|
||||
if (show_time) {
|
||||
n = snprintf(b, sizeof b, "%llu ",
|
||||
(unsigned long long)grid_line_time(gl));
|
||||
if (n >= 0)
|
||||
buf = cmd_capture_pane_append(buf, len, b, n);
|
||||
}
|
||||
if (show_flags) {
|
||||
cp = b;
|
||||
*cp = '\0';
|
||||
|
||||
gl = grid_peek_line(gd, i);
|
||||
if (gl->flags & GRID_LINE_DEAD)
|
||||
*cp++ = 'D';
|
||||
if (gl->flags & GRID_LINE_HYPERLINK)
|
||||
@@ -393,7 +400,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
}
|
||||
buf = cmd_capture_pane_append(buf, len, line, linelen);
|
||||
|
||||
gl = grid_peek_line(gd, i);
|
||||
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
|
||||
buf[(*len)++] = '\n';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-display-message.c,v 1.65 2026/02/23 08:46:57 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-display-message.c,v 1.66 2026/09/08 08:33:10 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
|
||||
.name = "display-message",
|
||||
.alias = "display",
|
||||
|
||||
.args = { "aCc:d:lINpt:F:v", 0, 1, NULL },
|
||||
.usage = "[-aCIlNpv] [-c target-client] [-d delay] [-F format] "
|
||||
.args = { "aCc:d:jlINpt:F:v", 0, 1, NULL },
|
||||
.usage = "[-aCIjlNpv] [-c target-client] [-d delay] [-F format] "
|
||||
CMD_TARGET_PANE_USAGE " [message]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
||||
@@ -67,14 +67,15 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct winlink *wl = target->wl;
|
||||
struct window_pane *wp = target->wp;
|
||||
const char *template;
|
||||
char *msg, *cause;
|
||||
char *msg, *cause = NULL;
|
||||
int delay = -1, flags, Nflag = args_has(args, 'N');
|
||||
int Cflag = args_has(args, 'C');
|
||||
struct format_tree *ft;
|
||||
u_int count = args_count(args);
|
||||
struct evbuffer *evb;
|
||||
struct json_node *jn;
|
||||
|
||||
if (args_has(args, 'I')) {
|
||||
if (args_has(args, 'I') && !args_has(args, 'j')) {
|
||||
if (wp == NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
switch (window_pane_start_input(wp, item, &cause)) {
|
||||
@@ -107,7 +108,9 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
||||
template = args_string(args, 0);
|
||||
else
|
||||
template = args_get(args, 'F');
|
||||
if (template == NULL)
|
||||
if (args_has(args, 'j') && template == NULL)
|
||||
template = "";
|
||||
else if (template == NULL)
|
||||
template = DISPLAY_MESSAGE_TEMPLATE;
|
||||
|
||||
/*
|
||||
@@ -129,7 +132,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
|
||||
format_defaults(ft, c, s, wl, wp);
|
||||
|
||||
if (args_has(args, 'a')) {
|
||||
if (args_has(args, 'a') && !args_has(args, 'j')) {
|
||||
format_each(ft, cmd_display_message_each, item);
|
||||
format_free(ft);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
@@ -139,6 +142,19 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
||||
msg = xstrdup(template);
|
||||
else
|
||||
msg = format_expand_time(ft, template);
|
||||
if (args_has(args, 'j')) {
|
||||
jn = json_parse(msg, &cause);
|
||||
if (jn == NULL) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
free(msg);
|
||||
format_free(ft);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
free(msg);
|
||||
msg = json_to_string(jn);
|
||||
json_destroy_node(jn);
|
||||
}
|
||||
|
||||
if (cmdq_get_client(item) == NULL)
|
||||
cmdq_error(item, "%s", msg);
|
||||
|
||||
18
cmd-find.c
18
cmd-find.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-find.c,v 1.87 2026/07/17 12:42:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-find.c,v 1.88 2026/09/07 12:05:12 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -388,9 +388,11 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
|
||||
|
||||
/* Try as an offset. */
|
||||
if (!exact && (window[0] == '+' || window[0] == '-')) {
|
||||
if (window[1] != '\0')
|
||||
n = strtonum(window + 1, 1, INT_MAX, NULL);
|
||||
else
|
||||
if (window[1] != '\0') {
|
||||
n = strtonum(window + 1, 1, INT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
} else
|
||||
n = 1;
|
||||
s = fs->s;
|
||||
if (fs->flags & CMD_FIND_WINDOW_INDEX) {
|
||||
@@ -626,9 +628,11 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
|
||||
|
||||
/* Try as an offset. */
|
||||
if (pane[0] == '+' || pane[0] == '-') {
|
||||
if (pane[1] != '\0')
|
||||
n = strtonum(pane + 1, 1, INT_MAX, NULL);
|
||||
else
|
||||
if (pane[1] != '\0') {
|
||||
n = strtonum(pane + 1, 1, INT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
} else
|
||||
n = 1;
|
||||
wp = fs->w->active;
|
||||
if (pane[0] == '+')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-list-panes.c,v 1.40 2026/06/01 14:01:09 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-list-panes.c,v 1.41 2026/09/08 10:20:08 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -97,6 +97,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
|
||||
struct cmdq_item *item, int type)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct window_pane *wp, **l;
|
||||
u_int i, n;
|
||||
struct format_tree *ft;
|
||||
@@ -147,7 +148,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
|
||||
wp = l[i];
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_add(ft, "line", "%u", n);
|
||||
format_defaults(ft, NULL, s, wl, wp);
|
||||
format_defaults(ft, c, s, wl, wp);
|
||||
|
||||
if (filter != NULL) {
|
||||
expanded = format_expand(ft, filter);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-list-sessions.c,v 1.36 2026/02/27 08:25:12 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-list-sessions.c,v 1.37 2026/09/08 10:20:08 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -53,6 +53,7 @@ static enum cmd_retval
|
||||
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct session **l;
|
||||
u_int n, i;
|
||||
struct format_tree *ft;
|
||||
@@ -76,7 +77,7 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
||||
for (i = 0; i < n; i++) {
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_add(ft, "line", "%u", i);
|
||||
format_defaults(ft, NULL, l[i], NULL, NULL);
|
||||
format_defaults(ft, c, l[i], NULL, NULL);
|
||||
|
||||
if (filter != NULL) {
|
||||
expanded = format_expand(ft, filter);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-list-windows.c,v 1.50 2026/02/27 08:25:12 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-list-windows.c,v 1.51 2026/09/08 10:20:08 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -60,6 +60,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct winlink *wl, **l;
|
||||
struct session *s;
|
||||
u_int i, n;
|
||||
@@ -94,7 +95,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
|
||||
s = wl->session;
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_add(ft, "line", "%u", n);
|
||||
format_defaults(ft, NULL, s, wl, NULL);
|
||||
format_defaults(ft, c, s, wl, NULL);
|
||||
|
||||
if (filter != NULL) {
|
||||
expanded = format_expand(ft, filter);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-parse.y,v 1.59 2026/08/31 07:51:56 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-parse.y,v 1.60 2026/09/08 10:20:08 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -210,7 +210,7 @@ expanded : format
|
||||
cmd_find_from_client(&fs, c, 0);
|
||||
fsp = &fs;
|
||||
}
|
||||
ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
|
||||
ft = format_create(c, pi->item, FORMAT_NONE, flags);
|
||||
format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
|
||||
|
||||
$$ = format_expand(ft, $1);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-select-layout.c,v 1.43 2026/07/10 13:38:45 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-select-layout.c,v 1.45 2026/09/09 07:03:39 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -73,12 +73,13 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct client *c = cmdq_get_target_client(item);
|
||||
struct winlink *wl = target->wl;
|
||||
struct window *w = wl->window;
|
||||
struct window_pane *wp = target->wp;
|
||||
const char *layoutname;
|
||||
char *oldlayout, *cause;
|
||||
int next, previous, layout;
|
||||
char *oldlayout, *cause = NULL;
|
||||
int next, previous, layout, flags = 0;
|
||||
|
||||
server_unzoom_window(w);
|
||||
|
||||
@@ -89,8 +90,12 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'p'))
|
||||
previous = 1;
|
||||
|
||||
if (c != NULL &&
|
||||
(c->flags & CLIENT_CONTROL) &&
|
||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
||||
oldlayout = w->old_layout;
|
||||
w->old_layout = layout_dump(w, w->layout_root);
|
||||
w->old_layout = layout_dump(w, w->layout_root, flags);
|
||||
|
||||
if (next || previous) {
|
||||
if (next)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
||||
.name = "new-pane",
|
||||
.alias = "newp",
|
||||
|
||||
.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] "
|
||||
.args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
|
||||
"[-c start-directory] [-e environment] "
|
||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||
"[-s style] [-S active-border-style] "
|
||||
@@ -219,6 +219,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||
if (args_has(args, 'D') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CLOSEONCANCEL;
|
||||
|
||||
style = args_get(args, 's');
|
||||
if (style != NULL) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# configure.ac
|
||||
|
||||
AC_INIT([tmux], next-3.8)
|
||||
AC_INIT([tmux], next-3.9)
|
||||
AC_PREREQ([2.60])
|
||||
|
||||
AC_CONFIG_AUX_DIR(etc)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: control-notify.c,v 1.38 2026/08/03 13:38:42 nicm Exp $ */
|
||||
/* $OpenBSD: control-notify.c,v 1.39 2026/09/08 10:20:08 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -70,6 +70,7 @@ control_window_layout_changed_cb(__unused const char *name,
|
||||
struct session *s;
|
||||
struct winlink *wl;
|
||||
struct window *w = event_payload_get_window(ep, "window");
|
||||
struct format_tree *ft;
|
||||
const char *template;
|
||||
char *cp;
|
||||
|
||||
@@ -84,19 +85,25 @@ control_window_layout_changed_cb(__unused const char *name,
|
||||
* and we don't need to inform the client about the layout change
|
||||
* because the whole window will go away soon.
|
||||
*/
|
||||
wl = TAILQ_FIRST(&w->winlinks);
|
||||
if (wl == NULL || w->layout_root == NULL)
|
||||
if (TAILQ_FIRST(&w->winlinks) == NULL || w->layout_root == NULL)
|
||||
return;
|
||||
cp = format_single(NULL, template, NULL, NULL, wl, NULL);
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
|
||||
continue;
|
||||
s = c->session;
|
||||
if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
|
||||
control_notify_write(c, "%s", cp);
|
||||
wl = winlink_find_by_window_id(&s->windows, w->id);
|
||||
if (wl == NULL)
|
||||
continue;
|
||||
|
||||
ft = format_create(c, NULL, FORMAT_NONE, 0);
|
||||
format_defaults(ft, c, s, wl, NULL);
|
||||
cp = format_expand(ft, template);
|
||||
format_free(ft);
|
||||
|
||||
control_notify_write(c, "%s", cp);
|
||||
free(cp);
|
||||
}
|
||||
free(cp);
|
||||
}
|
||||
|
||||
/* Notify control clients that window pane changed. */
|
||||
|
||||
55
control.c
55
control.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: control.c,v 1.67 2026/09/03 21:35:38 nicm Exp $ */
|
||||
/* $OpenBSD: control.c,v 1.68 2026/09/09 08:30:05 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -108,6 +108,7 @@ struct control_state {
|
||||
u_int pending_count;
|
||||
|
||||
TAILQ_HEAD(, control_block) all_blocks;
|
||||
size_t queued_reply_bytes;
|
||||
|
||||
struct bufferevent *read_event;
|
||||
struct bufferevent *write_event;
|
||||
@@ -132,6 +133,9 @@ struct control_state {
|
||||
/* Maximum age for clients that are not using pause mode. */
|
||||
#define CONTROL_MAXIMUM_AGE 300000
|
||||
|
||||
/* Maximum buffered command replies for a client that is not reading. */
|
||||
#define CONTROL_MAXIMUM_REPLY_BUFFER (64 * 1024 * 1024)
|
||||
|
||||
/* Flags to ignore client. */
|
||||
#define CONTROL_IGNORE_FLAGS \
|
||||
(CLIENT_CONTROL_NOOUTPUT| \
|
||||
@@ -165,6 +169,15 @@ RB_GENERATE_STATIC(control_windows, control_window, entry, control_window_cmp);
|
||||
static void
|
||||
control_free_block(struct control_state *cs, struct control_block *cb)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
if (cb->size == 0 && cb->line != NULL) {
|
||||
size = strlen(cb->line) + 1;
|
||||
if (cs->queued_reply_bytes > size)
|
||||
cs->queued_reply_bytes -= size;
|
||||
else
|
||||
cs->queued_reply_bytes = 0;
|
||||
}
|
||||
free(cb->line);
|
||||
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
|
||||
free(cb);
|
||||
@@ -407,16 +420,52 @@ control_reset_pane(struct client *c, struct window_pane *wp)
|
||||
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the replies buffered for a client, including one about to be
|
||||
* added, have grown too large and kill it if so. Returns 1 if further output
|
||||
* for the client should be dropped.
|
||||
*/
|
||||
static int
|
||||
control_check_reply_buffer(struct client *c, size_t added)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
size_t size;
|
||||
|
||||
if (c->flags & CLIENT_CONTROL_DISCARD)
|
||||
return (1);
|
||||
size = EVBUFFER_LENGTH(cs->write_event->output);
|
||||
size += cs->queued_reply_bytes;
|
||||
size += added;
|
||||
if (size < CONTROL_MAXIMUM_REPLY_BUFFER)
|
||||
return (0);
|
||||
|
||||
log_debug("%s: %s: %zu bytes of replies buffered", __func__, c->name,
|
||||
size);
|
||||
if (~c->flags & CLIENT_EXIT) {
|
||||
c->exit_message = xstrdup("too far behind");
|
||||
c->flags |= CLIENT_EXIT;
|
||||
control_discard(c);
|
||||
}
|
||||
c->flags |= CLIENT_CONTROL_DISCARD;
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Write an already-formatted line, queueing it behind %output if needed. */
|
||||
static void
|
||||
control_write_line(struct client *c, char *line)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_block *cb;
|
||||
size_t size = strlen(line) + 1;
|
||||
|
||||
if (control_check_reply_buffer(c, size)) {
|
||||
free(line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TAILQ_EMPTY(&cs->all_blocks)) {
|
||||
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
|
||||
bufferevent_write(cs->write_event, line, strlen(line));
|
||||
bufferevent_write(cs->write_event, line, size - 1);
|
||||
bufferevent_write(cs->write_event, "\n", 1);
|
||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||
free(line);
|
||||
@@ -426,6 +475,7 @@ control_write_line(struct client *c, char *line)
|
||||
cb = xcalloc(1, sizeof *cb);
|
||||
cb->line = line;
|
||||
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
||||
cs->queued_reply_bytes += size;
|
||||
cb->t = get_timer();
|
||||
|
||||
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
||||
@@ -1007,6 +1057,7 @@ control_discard_all(struct client *c)
|
||||
control_discard(c);
|
||||
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
||||
control_free_block(cs, cb);
|
||||
cs->queued_reply_bytes = 0;
|
||||
bufferevent_disable(cs->write_event, EV_WRITE);
|
||||
}
|
||||
|
||||
|
||||
27
format.c
27
format.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: format.c,v 1.415 2026/08/31 19:34:09 nicm Exp $ */
|
||||
/* $OpenBSD: format.c,v 1.417 2026/09/08 15:42:26 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -855,26 +855,42 @@ format_cb_window_active_clients_list(struct format_tree *ft)
|
||||
static void *
|
||||
format_cb_window_layout(struct format_tree *ft)
|
||||
{
|
||||
struct window *w = ft->w;
|
||||
struct client *c = ft->client;
|
||||
struct window *w = ft->w;
|
||||
struct layout_cell *lcroot;
|
||||
int flags = 0;
|
||||
|
||||
if (w == NULL)
|
||||
return (NULL);
|
||||
|
||||
if (w->saved_layout_root != NULL)
|
||||
return (layout_dump(w, w->saved_layout_root));
|
||||
return (layout_dump(w, w->layout_root));
|
||||
lcroot = w->saved_layout_root;
|
||||
else
|
||||
lcroot = w->layout_root;
|
||||
|
||||
if (c != NULL &&
|
||||
(c->flags & CLIENT_CONTROL) &&
|
||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
||||
return (layout_dump(w, lcroot, flags));
|
||||
}
|
||||
|
||||
/* Callback for window_visible_layout. */
|
||||
static void *
|
||||
format_cb_window_visible_layout(struct format_tree *ft)
|
||||
{
|
||||
struct client *c = ft->client;
|
||||
struct window *w = ft->w;
|
||||
int flags = 0;
|
||||
|
||||
if (w == NULL)
|
||||
return (NULL);
|
||||
|
||||
return (layout_dump(w, w->layout_root));
|
||||
if (c != NULL &&
|
||||
(c->flags & CLIENT_CONTROL) &&
|
||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
||||
return (layout_dump(w, w->layout_root, flags));
|
||||
}
|
||||
|
||||
/* Callback for pane_start_command. */
|
||||
@@ -5234,6 +5250,7 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
|
||||
format_log(es, "session loop: $%u", s->id);
|
||||
if (active != NULL &&
|
||||
ft->c != NULL &&
|
||||
ft->c->session != NULL &&
|
||||
s->id == ft->c->session->id)
|
||||
use = active;
|
||||
else
|
||||
|
||||
11
input.c
11
input.c
@@ -3305,9 +3305,6 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
gl->flags |= GRID_LINE_SECOND_PROMPT;
|
||||
else
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
log_debug("%s: osc133 %s at %u,%u", __func__,
|
||||
input_osc_133_secondary_prompt(p) ? "secondary prompt" :
|
||||
"prompt", s->cx, line);
|
||||
}
|
||||
}
|
||||
if (wp != NULL) {
|
||||
@@ -3324,8 +3321,6 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
gl->flags |= GRID_LINE_SECOND_PROMPT;
|
||||
else
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
log_debug("%s: osc133 prompt at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -3334,16 +3329,12 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
if (gl != NULL && !(gl->flags & GRID_LINE_START_COMMAND)) {
|
||||
gl->flags |= GRID_LINE_START_COMMAND;
|
||||
gl->osc133_data.cmd_col = s->cx;
|
||||
log_debug("%s: osc133 command at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
if (gl != NULL && !(gl->flags & GRID_LINE_START_OUTPUT)) {
|
||||
gl->flags |= GRID_LINE_START_OUTPUT;
|
||||
gl->osc133_data.out_start_col = s->cx;
|
||||
log_debug("%s: osc133 output start at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
if (wp != NULL) {
|
||||
wp->cmd_start_time = time(NULL);
|
||||
@@ -3367,8 +3358,6 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
gl->flags |= GRID_LINE_END_OUTPUT_STATUS;
|
||||
gl->osc133_data.out_end_col = s->cx;
|
||||
gl->osc133_data.exit_status = status;
|
||||
log_debug("%s: osc133 output end at %u,%u (status %d)",
|
||||
__func__, s->cx, line, status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
1074
layout-custom.c
1074
layout-custom.c
File diff suppressed because it is too large
Load Diff
86
layout.c
86
layout.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: layout.c,v 1.98 2026/08/25 18:38:05 nicm Exp $ */
|
||||
/* $OpenBSD: layout.c,v 1.100 2026/09/11 08:16:14 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -106,7 +106,7 @@ layout_free_cell(struct layout_cell *lc, int only_nodes)
|
||||
}
|
||||
break;
|
||||
case LAYOUT_WINDOWPANE:
|
||||
if (lc->wp != NULL) {
|
||||
if (lc->wp != NULL && lc->wp->layout_cell != NULL) {
|
||||
lc->wp->layout_cell->parent = NULL;
|
||||
lc->wp->layout_cell = NULL;
|
||||
}
|
||||
@@ -233,29 +233,6 @@ layout_make_node(struct layout_cell *lc, enum layout_type type)
|
||||
lc->wp = NULL;
|
||||
}
|
||||
|
||||
/* Fix z-indexes. */
|
||||
void
|
||||
layout_fix_zindexes(struct window *w, struct layout_cell *lc)
|
||||
{
|
||||
struct layout_cell *lcchild;
|
||||
|
||||
if (lc == NULL)
|
||||
return;
|
||||
|
||||
switch (lc->type) {
|
||||
case LAYOUT_WINDOWPANE:
|
||||
TAILQ_INSERT_TAIL(&w->z_index, lc->wp, zentry);
|
||||
break;
|
||||
case LAYOUT_LEFTRIGHT:
|
||||
case LAYOUT_TOPBOTTOM:
|
||||
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
||||
layout_fix_zindexes(w, lcchild);
|
||||
return;
|
||||
default:
|
||||
fatalx("bad layout type");
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
layout_cell_is_tiled(struct layout_cell *lc)
|
||||
{
|
||||
@@ -265,7 +242,7 @@ layout_cell_is_tiled(struct layout_cell *lc)
|
||||
return is_leaf && !is_floating;
|
||||
}
|
||||
|
||||
static int
|
||||
int
|
||||
layout_cell_has_tiled_child(struct layout_cell *lc)
|
||||
{
|
||||
struct layout_cell *lcchild;
|
||||
@@ -509,18 +486,20 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
||||
|
||||
/* Count the number of available cells in a layout. */
|
||||
u_int
|
||||
layout_count_cells(struct layout_cell *lc)
|
||||
layout_count_cells(struct layout_cell *lc, int with_floating)
|
||||
{
|
||||
struct layout_cell *lcchild;
|
||||
u_int count = 0;
|
||||
|
||||
switch (lc->type) {
|
||||
case LAYOUT_WINDOWPANE:
|
||||
if (lc->flags & LAYOUT_CELL_FLOATING && !with_floating)
|
||||
return 0;
|
||||
return (1);
|
||||
case LAYOUT_LEFTRIGHT:
|
||||
case LAYOUT_TOPBOTTOM:
|
||||
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
||||
count += layout_count_cells(lcchild);
|
||||
count += layout_count_cells(lcchild, with_floating);
|
||||
return (count);
|
||||
default:
|
||||
fatalx("bad layout type");
|
||||
@@ -721,7 +700,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
|
||||
/* If no parent, this is the last pane in a window. */
|
||||
lcparent = lc->parent;
|
||||
if (lcparent == NULL) {
|
||||
if (lc->wp != NULL)
|
||||
if (*lcroot == lc)
|
||||
*lcroot = NULL;
|
||||
layout_free_cell(lc, 0);
|
||||
return;
|
||||
@@ -789,6 +768,49 @@ layout_free(struct window *w, int 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. */
|
||||
void
|
||||
layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
@@ -809,8 +831,11 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
* out proportionately - this should leave the layout fitting the new
|
||||
* 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;
|
||||
}
|
||||
xchange = sx - lc->g.sx;
|
||||
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
||||
if (xchange < 0 && xchange < -xlimit)
|
||||
@@ -840,6 +865,7 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_clamp_floating_panes(w, sx, sy);
|
||||
layout_fix_panes(w, NULL);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: monitor.c,v 1.7 2026/07/27 19:15:58 nicm Exp $ */
|
||||
/* $OpenBSD: monitor.c,v 1.8 2026/09/07 10:15:25 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -614,8 +614,10 @@ monitor_parse(const char *value, char **name, enum monitor_type *type, int *id,
|
||||
*type = MONITOR_ALL_WINDOWS;
|
||||
else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
|
||||
*type = MONITOR_WINDOW;
|
||||
else
|
||||
else if (*what == '\0')
|
||||
*type = MONITOR_SESSION;
|
||||
else
|
||||
goto fail;
|
||||
*name = xstrdup(copy);
|
||||
*format = xstrdup(split);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
||||
/* $OpenBSD: options-table.c,v 1.246 2026/09/11 10:17:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -76,7 +76,8 @@ static const char *options_table_pane_border_indicators_list[] = {
|
||||
"off", "colour", "arrows", "both", NULL
|
||||
};
|
||||
static const char *options_table_pane_border_lines_list[] = {
|
||||
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
|
||||
"single", "double", "heavy", "simple", "number", "spaces", "none",
|
||||
"rounded", NULL
|
||||
};
|
||||
static const char *options_table_popup_border_lines_list[] = {
|
||||
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
|
||||
@@ -91,7 +92,7 @@ static const char *options_table_window_size_list[] = {
|
||||
"largest", "smallest", "manual", "latest", NULL
|
||||
};
|
||||
static const char *options_table_remain_on_exit_list[] = {
|
||||
"off", "on", "failed", "key", NULL
|
||||
"off", "on", "failed", "key", "failed-key", NULL
|
||||
};
|
||||
static const char *options_table_destroy_unattached_list[] = {
|
||||
"off", "on", "keep-last", "keep-group", NULL
|
||||
@@ -1704,7 +1705,8 @@ const struct options_table_entry options_table[] = {
|
||||
.choices = options_table_remain_on_exit_list,
|
||||
.default_num = 0,
|
||||
.text = "Whether panes should remain ('on'), remain until a key is "
|
||||
"pressed ('key') or be automatically killed ('off' or "
|
||||
"pressed after any exit ('key') or after a failure "
|
||||
"('failed-key'), or be automatically killed ('off' or "
|
||||
"'failed') when the program inside exits."
|
||||
},
|
||||
|
||||
|
||||
8
prompt.c
8
prompt.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: prompt.c,v 1.6 2026/08/17 06:45:16 nicm Exp $ */
|
||||
/* $OpenBSD: prompt.c,v 1.7 2026/09/09 07:53:03 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -1493,9 +1493,11 @@ append_key:
|
||||
utf8_set(&tmp, key);
|
||||
if (key <= 0x1f || key == 0x7f)
|
||||
tmp.width = 2;
|
||||
} else if (KEYC_IS_UNICODE(key))
|
||||
} else if (KEYC_IS_UNICODE(key)) {
|
||||
utf8_to_data(key, &tmp);
|
||||
else
|
||||
if (tmp.size == 0)
|
||||
return (PROMPT_KEY_HANDLED);
|
||||
} else
|
||||
return (PROMPT_KEY_HANDLED);
|
||||
|
||||
pr->buffer = xreallocarray(pr->buffer, size + 2,
|
||||
|
||||
38
regress/capture-pane-time.sh
Normal file
38
regress/capture-pane-time.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
# capture-pane -I line timestamps
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMP=$(mktemp)
|
||||
trap '$TMUX kill-server 2>/dev/null; rm -f "$TMP"' 0 1 15
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
before=$(date +%s)
|
||||
$TMUX new-session -d -x 40 -y 5 'seq 1 12; sleep 10' || exit 1
|
||||
sleep 1
|
||||
after=$(date +%s)
|
||||
|
||||
$TMUX capture-pane -pILF -S - -E - >"$TMP" || exit 1
|
||||
awk -v before="$before" -v after="$after" '
|
||||
$1 !~ /^-?[0-9]+$/ || $2 !~ /^[0-9]+$/ { exit 1 }
|
||||
$4 == "1" {
|
||||
if ($2 < before || $2 > after)
|
||||
exit 1
|
||||
history = 1
|
||||
}
|
||||
$4 == "9" {
|
||||
if ($2 != 0)
|
||||
exit 1
|
||||
visible = 1
|
||||
}
|
||||
END { if (!history || !visible) exit 1 }
|
||||
' "$TMP" || {
|
||||
cat "$TMP"
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
123
regress/control-client-new-layouts.sh
Executable file
123
regress/control-client-new-layouts.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that control client flags are available in every format expansion path
|
||||
# used to produce layouts. The layout strings themselves are tested separately.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
LANG=C.UTF-8
|
||||
export PATH TERM LC_ALL LANG
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
FIFO=$DIR/input
|
||||
OUT=$DIR/output
|
||||
CFG=$DIR/flags.conf
|
||||
PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&-
|
||||
[ -n "$PID" ] && kill "$PID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
wait_for()
|
||||
{
|
||||
pattern=$1
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt 50 ]; do
|
||||
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
|
||||
if [ -n "$PID" ] && ! kill -0 "$PID" 2>/dev/null; then
|
||||
fail "control client exited waiting for: $pattern"
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "missing: $pattern"
|
||||
}
|
||||
|
||||
send()
|
||||
{
|
||||
printf '%s\n' "$*" >&3
|
||||
}
|
||||
|
||||
check_commands()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
match='#{m:*new-layouts*,#{client_flags}}'
|
||||
|
||||
send "display-message -p 'DISPLAY-$name $match'"
|
||||
wait_for "DISPLAY-$name $expected"
|
||||
send "list-panes -F 'PANES-$name $match'"
|
||||
wait_for "PANES-$name $expected"
|
||||
send "list-windows -F 'WINDOWS-$name $match'"
|
||||
wait_for "WINDOWS-$name $expected"
|
||||
send "list-sessions -F 'SESSIONS-$name $match'"
|
||||
wait_for "SESSIONS-$name $expected"
|
||||
send "list-clients -F 'CLIENTS-$name $match'"
|
||||
wait_for "CLIENTS-$name $expected"
|
||||
}
|
||||
|
||||
check_config()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
send "source-file '$CFG'"
|
||||
send "display-message -p 'CONFIG-$name #{@config-new-layouts}'"
|
||||
wait_for "CONFIG-$name $expected"
|
||||
}
|
||||
|
||||
cat >"$CFG" <<'EOF'
|
||||
%if #{m:*new-layouts*,#{client_flags}}
|
||||
set-option -g @config-new-layouts 1
|
||||
%else
|
||||
set-option -g @config-new-layouts 0
|
||||
%endif
|
||||
EOF
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s layouts -x 80 -y 24 || exit 1
|
||||
$TMUX split-window -h -t layouts: || exit 1
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
: >"$OUT"
|
||||
$TMUX -C attach-session -t layouts <"$FIFO" >"$OUT" 2>&1 &
|
||||
PID=$!
|
||||
exec 3>"$FIFO"
|
||||
|
||||
send 'display-message -p READY'
|
||||
wait_for READY
|
||||
|
||||
check_commands OFF 0
|
||||
check_config OFF 0
|
||||
|
||||
send 'refresh-client -f new-layouts'
|
||||
check_commands ON 1
|
||||
check_config ON 1
|
||||
|
||||
send 'refresh-client -f !new-layouts'
|
||||
check_commands OFF-AGAIN 0
|
||||
check_config OFF-AGAIN 0
|
||||
|
||||
# The list commands also run for unattached command clients. Supplying that
|
||||
# client to formats must not make the session loop dereference a NULL session.
|
||||
$TMUX list-sessions -F '#{S:all,active}' >/dev/null ||
|
||||
fail "session loop failed for unattached command client"
|
||||
$TMUX has-session -t layouts || fail "server exited"
|
||||
|
||||
exit 0
|
||||
@@ -30,8 +30,11 @@ killw
|
||||
EOF
|
||||
sleep 1
|
||||
$TMUX has || exit 1
|
||||
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
|
||||
cat <<EOF|cmp -s $TMP - || exit 1
|
||||
# Use a control client to request legacy layouts, keeping only pane lines
|
||||
# from the control protocol output.
|
||||
$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]
|
||||
%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]
|
||||
|
||||
@@ -268,5 +268,149 @@ $TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$right" || 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
|
||||
exit 0
|
||||
|
||||
137
regress/json.sh
Executable file
137
regress/json.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test parsing and printing JSON with display-message -j.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
check()
|
||||
{
|
||||
actual=$($TMUX display-message -plj "$1" 2>&1) ||
|
||||
fail "JSON was rejected: $1: $actual"
|
||||
[ "$actual" = "$2" ] ||
|
||||
fail "JSON output mismatch: $1: expected $2, got $actual"
|
||||
}
|
||||
|
||||
check_fail()
|
||||
{
|
||||
if $TMUX display-message -plj "$1" >/dev/null 2>&1; then
|
||||
fail "invalid JSON was accepted: $1"
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX new-session -d || exit 1
|
||||
|
||||
check '{}' '{}'
|
||||
check ' { "z":true, "a":-9223372036854775808, "m":[{}, {"x":false}] } ' \
|
||||
'{"a":-9223372036854775808,"m":[{},{"x":false}],"z":true}'
|
||||
check '{"max":9223372036854775807,"min":-9223372036854775808}' \
|
||||
'{"max":9223372036854775807,"min":-9223372036854775808}'
|
||||
check '{" key":" value","{key":"[value"}' \
|
||||
'{" key":" value","{key":"[value"}'
|
||||
check '{"brace":"{value","bracket":"[value","colon":":value",'\
|
||||
'"comma":",value","space":" value"}' \
|
||||
'{"brace":"{value","bracket":"[value","colon":":value",'\
|
||||
'"comma":",value","space":" value"}'
|
||||
check '{"esc":"\"\\\/\b\f\n\r\t\u0041"}' \
|
||||
'{"esc":"\"\\\/\b\f\n\r\t\u0041"}'
|
||||
check '{"unicode":"\u0123\uabcd\uABCD"}' \
|
||||
'{"unicode":"\u0123\uabcd\uABCD"}'
|
||||
|
||||
actual=$($TMUX display-message -palj '{}' 2>&1) ||
|
||||
fail "display-message -aj rejected JSON: $actual"
|
||||
[ "$actual" = '{}' ] ||
|
||||
fail "display-message -aj did not ignore -a: got $actual"
|
||||
actual=$($TMUX display-message -pIlj '{}' 2>&1) ||
|
||||
fail "display-message -Ij rejected JSON: $actual"
|
||||
[ "$actual" = '{}' ] ||
|
||||
fail "display-message -Ij did not ignore -I: got $actual"
|
||||
|
||||
check_fail ''
|
||||
check_fail ' '
|
||||
check_fail '[]'
|
||||
check_fail 'true'
|
||||
check_fail '1'
|
||||
check_fail '"string"'
|
||||
check_fail '{}{}'
|
||||
|
||||
check_fail '{'
|
||||
check_fail '{x:1}'
|
||||
check_fail '{"":1}'
|
||||
check_fail '{"x" 1}'
|
||||
check_fail '{"x",1}'
|
||||
check_fail '{"x":}'
|
||||
check_fail '{"x":,}'
|
||||
check_fail '{"x":1'
|
||||
check_fail '{"x":1 "y":2}'
|
||||
check_fail '{"x":1,,"y":2}'
|
||||
check_fail '{"x":1,}'
|
||||
check_fail '{"x":1,"x":2}'
|
||||
check_fail '{"x":{}}{}'
|
||||
check_fail '{"x":{}} trailing'
|
||||
check_fail '{"x":{'
|
||||
check_fail '{"x":{}'
|
||||
|
||||
check_fail '{"x":['
|
||||
check_fail '{"x":[}'
|
||||
check_fail '{"x":[{}'
|
||||
check_fail '{"x":[{},}'
|
||||
check_fail '{"x":[{},]}'
|
||||
check_fail '{"x":[{}{}]}'
|
||||
check_fail '{"x":[{},,{}]}'
|
||||
check_fail '{"x":[{"y":}]}'
|
||||
check_fail '{"x":""}'
|
||||
check_fail '{"x":null}'
|
||||
check_fail '{"x":[null]}'
|
||||
check_fail '{"x":[true]}'
|
||||
check_fail '{"x":["string"]}'
|
||||
check_fail '{"x":[[]]}'
|
||||
check_fail '{"x":1.0}'
|
||||
check_fail '{"x":1e2}'
|
||||
check_fail '{"x":+1}'
|
||||
check_fail '{"x":-}'
|
||||
check_fail '{"x":0x10}'
|
||||
check_fail '{"x":01}'
|
||||
check_fail '{"x":-01}'
|
||||
check_fail '{"x":[1]}'
|
||||
check_fail '{"x":9223372036854775808}'
|
||||
check_fail '{"x":-9223372036854775809}'
|
||||
check_fail '{"x":tru}'
|
||||
check_fail '{"x":True}'
|
||||
check_fail '{"x":falsee}'
|
||||
check_fail '{"x":"bad\q"}'
|
||||
check_fail '{"x":"bad\x20"}'
|
||||
check_fail '{"x":"bad\u123"}'
|
||||
check_fail '{"x":"bad\u12x4"}'
|
||||
check_fail '{"x":"unterminated}'
|
||||
check_fail '{"x":"escaped quote\"}'
|
||||
check_fail '{"x":"line
|
||||
break"}'
|
||||
|
||||
# The maximum object nesting depth is 200.
|
||||
json='{}'
|
||||
n=1
|
||||
while [ "$n" -lt 200 ]; do
|
||||
json='{"x":'"$json"'}'
|
||||
n=$((n + 1))
|
||||
done
|
||||
check "$json" "$json"
|
||||
check_fail '{"x":'"$json"'}'
|
||||
|
||||
if $TMUX display-message -pj >/dev/null 2>&1; then
|
||||
fail "display-message -j accepted a missing message"
|
||||
fi
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
986
regress/layout-custom.sh
Normal file
986
regress/layout-custom.sh
Normal file
@@ -0,0 +1,986 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the custom layout dumper and evaluator in layout-custom.c, and of
|
||||
# the JSON tokenizer and parser in json.c that the current layout format is
|
||||
# built on.
|
||||
#
|
||||
# layout_dump is reached through the #{window_layout} and
|
||||
# #{window_visible_layout} formats and layout_parse through
|
||||
# "select-layout <layout>". json.c has no command of its own either:
|
||||
# layout_construct sniffs the first non-blank character and hands anything
|
||||
# starting with '{' to json_parse, so select-layout is the only way into it
|
||||
# from the shell as well.
|
||||
#
|
||||
# Both layout formats are covered:
|
||||
# - the current (v2) JSON format, which is what every client except an old
|
||||
# control client sees;
|
||||
# - the legacy (v1) format, which is still produced for a control client that
|
||||
# has not asked for the "new-layouts" flag, and which is still accepted by
|
||||
# the parser (the version is sniffed from the first character).
|
||||
#
|
||||
# This exercises:
|
||||
# - dumping a single pane, a split, the "a" (active) and "l" (last pane) keys
|
||||
# and the "z" key of a floating pane;
|
||||
# - #{window_visible_layout} agreeing with #{window_layout};
|
||||
# - the JSON syntax itself: insignificant whitespace, backslash escapes inside
|
||||
# strings, the number and boolean forms, and one failure for each way json.c
|
||||
# can reject an input that a layout string can carry;
|
||||
# - a dump being parsed back to exactly the same layout (round trip), after
|
||||
# another layout has been applied in between, and the same for a layout with
|
||||
# two floating panes in it;
|
||||
# - parsing a hand-written v2 layout;
|
||||
# - "i" deciding which pane goes in which cell, checked with a layout whose
|
||||
# cells are written in a different order from their indexes;
|
||||
# - the same layouts with their fields in reversed and scrambled orders,
|
||||
# including "c" before "t" and "V" after "L", neither of which changes the
|
||||
# order the fields are read in;
|
||||
# - a layout with more cells than the window has panes having the bottom right
|
||||
# cells dropped, in both formats;
|
||||
# - a layout naming no active or last pane leaving the active pane where it was
|
||||
# and emptying the last pane stack, whether it leaves "a" out or gives it as
|
||||
# false;
|
||||
# - parsing a v1 layout and dumping it back as v1 through a control client,
|
||||
# with the checksum computed here independently of layout_checksum(), and a
|
||||
# v1 layout leaving the active pane and last pane stack untouched;
|
||||
# - the legacy format meeting the floating panes it cannot represent: a v1 dump
|
||||
# dropping the floating cells, both where that leaves the node they were in
|
||||
# with one child so that it collapses, where it does not, and where adjacent
|
||||
# nested floating-only subtrees are dropped, and a v1 layout being applied to
|
||||
# a window that has floating panes without disturbing them, whether the tiled
|
||||
# layout it names is a single cell or a split;
|
||||
# - a window whose only tiled pane has been killed, which leaves it with a
|
||||
# floating cell as its layout root or with a root node holding nothing but
|
||||
# floating cells, producing no v1 dump at all, and being parsed as v1;
|
||||
# - the %layout-change notification, in both formats at once: two control
|
||||
# clients watching one layout change, only one of which has asked for new
|
||||
# layouts, and the number of notifications a change produces in each format;
|
||||
# - failures: a bad v1 header, checksum or body, a wrong version, a missing or
|
||||
# duplicated root cell, missing sizes, sizes out of range, bad cell types, a
|
||||
# pane cell missing "i", leaf cells with children and node cells with fewer
|
||||
# than two, more than one active pane, too few cells for the panes and
|
||||
# inconsistent sizes.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LANG=C.UTF-8
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LANG LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
# must_equal $what $got $expected
|
||||
must_equal()
|
||||
{
|
||||
if [ "$2" != "$3" ]; then
|
||||
echo "$1 wrong." >&2
|
||||
echo "Expected: '$3'" >&2
|
||||
echo "But got: '$2'" >&2
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# must_differ $what $got $unwanted
|
||||
must_differ()
|
||||
{
|
||||
[ "$2" != "$3" ] || fail "$1 unchanged: '$2'"
|
||||
}
|
||||
|
||||
# must_contain $what $got $wanted
|
||||
must_contain()
|
||||
{
|
||||
case "$2" in
|
||||
*"$3"*) ;;
|
||||
*) fail "$1: '$2' does not contain '$3'";;
|
||||
esac
|
||||
}
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
# Run a command and require that it succeeds.
|
||||
check_ok()
|
||||
{
|
||||
out=$($TMUX "$@" 2>&1) || fail "Command failed (expected success): $* ($out)"
|
||||
}
|
||||
|
||||
# check_fail $cmd...
|
||||
#
|
||||
# Run a command and require that it fails. The error text itself is never
|
||||
# checked anywhere in this test: the wording of a message is not part of what
|
||||
# the layout formats promise, so matching on it only makes the test fail when a
|
||||
# message is reworded.
|
||||
check_fail()
|
||||
{
|
||||
$TMUX "$@" >/dev/null 2>&1 &&
|
||||
fail "Command succeeded (expected failure): $*"
|
||||
}
|
||||
|
||||
# layout $target
|
||||
#
|
||||
# The layout of a window with pane ids replaced by %N, so that the expected
|
||||
# strings do not depend on which ids the server handed out.
|
||||
layout()
|
||||
{
|
||||
$TMUX display-message -p -t "$1" '#{window_layout}' |
|
||||
sed 's/%[0-9][0-9]*/%N/g'
|
||||
}
|
||||
|
||||
# visible_layout $target
|
||||
#
|
||||
# As layout(), but the visible (zoomed) layout.
|
||||
visible_layout()
|
||||
{
|
||||
$TMUX display-message -p -t "$1" '#{window_visible_layout}' |
|
||||
sed 's/%[0-9][0-9]*/%N/g'
|
||||
}
|
||||
|
||||
# raw_layout $target
|
||||
#
|
||||
# The layout of a window with the real pane ids left in place.
|
||||
raw_layout()
|
||||
{
|
||||
$TMUX display-message -p -t "$1" '#{window_layout}'
|
||||
}
|
||||
|
||||
# v1_layout $target
|
||||
#
|
||||
# The legacy (v1) dump of a window, which is what a control client that has not
|
||||
# asked for the "new-layouts" flag is sent. A control client wraps its output in
|
||||
# %begin/%end guard lines, which are dropped here.
|
||||
v1_layout()
|
||||
{
|
||||
$TMUX -C display-message -p -t "$1" '#{window_layout}' | grep -v '^%'
|
||||
}
|
||||
|
||||
# v1 $body
|
||||
#
|
||||
# Prefix a legacy (v1) layout body with its checksum. This is a separate
|
||||
# implementation of layout_checksum(): a 16 bit rotate right then add, so a
|
||||
# mistake in either one shows up as a mismatch.
|
||||
v1()
|
||||
{
|
||||
awk -v s="$1" 'BEGIN {
|
||||
for (i = 32; i < 127; i++)
|
||||
ord[sprintf("%c", i)] = i
|
||||
csum = 0
|
||||
for (i = 1; i <= length(s); i++) {
|
||||
csum = int(csum / 2) + (csum % 2) * 32768
|
||||
csum = (csum + ord[substr(s, i, 1)]) % 65536
|
||||
}
|
||||
printf "%04x,%s\n", csum, s
|
||||
}'
|
||||
}
|
||||
|
||||
# A pane cell is dumped as its geometry, then "a" if it is the active pane or
|
||||
# "l" with its position on the last pane stack if it is on it, then "i" with
|
||||
# its pane index, then "z" if it is floating, then "I" with its pane id.
|
||||
ONE='{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"a":true,"i":0,"I":"%N"}}'
|
||||
|
||||
check_ok new-session -d -s L -x 80 -y 24 -n one
|
||||
|
||||
p0=$($TMUX display-message -p -t L:one.0 '#{pane_id}')
|
||||
|
||||
# A single leaf cell filling the window. A pane cell must carry "i", its pane
|
||||
# index; "I", its pane id, is written by the dumper and is here so that the cell
|
||||
# is the same shape as a dumped one. The JSON checks below care about the syntax
|
||||
# around the cell rather than the cell itself.
|
||||
LEAF='{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$p0"'"}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dumping a single pane.
|
||||
|
||||
# The root cell of a new window is the pane itself, and it is the active pane
|
||||
# so it has "a" rather than "l".
|
||||
must_equal 'Single pane layout' "$(layout L:one)" "$ONE"
|
||||
|
||||
# Nothing is zoomed, so the visible layout is the same.
|
||||
must_equal 'Single pane visible layout' "$(visible_layout L:one)" "$ONE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# More cells than panes.
|
||||
|
||||
# The bottom right cells are closed until as many are left as there are panes,
|
||||
# so a two cell layout applied to a one pane window collapses back to the
|
||||
# single pane filling the window: the cell that is left takes the space of the
|
||||
# one that was closed. The window has one pane to name, so the cell that is
|
||||
# closed carries an id belonging to no pane of it.
|
||||
check_ok select-layout -t L:one \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$p0"'"},{"t":"p","w":80,"h":12,"x":0,"y":12,"i":1,"I":"%999"}]}}'
|
||||
must_equal 'Trimmed layout' "$(layout L:one)" "$ONE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The JSON syntax.
|
||||
#
|
||||
# These run on the one pane window and are written so that what they prove
|
||||
# depends on json.c rather than on the layout evaluation in layout-custom.c:
|
||||
# an accepted layout is only required to leave the window as its single pane,
|
||||
# and values that are not part of the layout format are carried on keys
|
||||
# layout-custom.c never looks at ("n", "b" and so on), which it skips, so
|
||||
# numbers, booleans and escapes can be exercised on their own.
|
||||
#
|
||||
# Objects nested in an array nested in an object are not checked here: every
|
||||
# split layout below is one.
|
||||
#
|
||||
# Two of json.c's rejections cannot be reached from the shell and so are not
|
||||
# covered: json_parse_tokens() refusing a top level that is not an object,
|
||||
# because layout_construct() only calls json_parse() once the string already
|
||||
# starts with '{'; and the maximum object depth, which needs a layout built by a
|
||||
# program rather than one written out here.
|
||||
|
||||
# check_json_ok $what $layout
|
||||
#
|
||||
# select-layout must parse $layout and leave the window as its single pane.
|
||||
check_json_ok()
|
||||
{
|
||||
check_ok select-layout -t L:one "$2"
|
||||
must_equal "Layout after '$1'" "$(layout L:one)" "$ONE"
|
||||
}
|
||||
|
||||
# check_json_fail $what $layout
|
||||
#
|
||||
# select-layout must reject $layout.
|
||||
check_json_fail()
|
||||
{
|
||||
$TMUX select-layout -t L:one "$2" >/dev/null 2>&1 &&
|
||||
fail "$1: select-layout succeeded (expected failure)"
|
||||
}
|
||||
|
||||
# Whitespace between tokens is skipped. A number is scanned up to the ',', ']',
|
||||
# '}' or whitespace that ends it, so a space after a number is fine but one
|
||||
# inside it is not.
|
||||
check_json_ok 'Spaces between tokens' \
|
||||
'{ "V" : 2 , "L" : { "t" : "p" , "w" : 80 , "h" : 24 , "x" : 0 , "y" : 0 , "i" : 0 , "I" : "'"$p0"'" } }'
|
||||
|
||||
check_json_ok 'Newlines and tabs between tokens' "$(printf '{
|
||||
\t"V": 2,
|
||||
\t"L": {
|
||||
\t\t"t": "p",
|
||||
\t\t"w": 80,
|
||||
\t\t"h": 24,
|
||||
\t\t"x": 0,
|
||||
\t\t"y": 0,
|
||||
\t\t"i": 0,
|
||||
\t\t"I": "%s"
|
||||
\t}
|
||||
}' "$p0")"
|
||||
|
||||
check_json_ok 'Carriage returns between tokens' \
|
||||
"$(printf '{\r"V":2,\r"L":%s\r}' "$LEAF")"
|
||||
|
||||
# A backslash makes the tokenizer consume the next character whatever it is, so
|
||||
# an escaped quote does not end the string. The key is not one that
|
||||
# layout-custom.c looks at, so all that is being checked is that the string
|
||||
# ended in the right place and the object still parsed.
|
||||
check_json_ok 'Escaped quote in a string' \
|
||||
'{"V":2,"a\"b":0,"L":'"$LEAF"'}'
|
||||
|
||||
# An escaped backslash immediately before the closing quote: the escape has to
|
||||
# be cleared again so that the quote after it does end the string.
|
||||
check_json_ok 'Escaped backslash before the closing quote' \
|
||||
'{"V":2,"a\\":0,"L":'"$LEAF"'}'
|
||||
|
||||
# Numbers and booleans, again on keys layout-custom.c ignores, so only json.c
|
||||
# decides whether they are accepted.
|
||||
check_json_ok 'Zero' '{"V":2,"n":0,"L":'"$LEAF"'}'
|
||||
check_json_ok 'Several digits' '{"V":2,"n":1234567,"L":'"$LEAF"'}'
|
||||
check_json_ok 'Negative number' '{"V":2,"n":-42,"L":'"$LEAF"'}'
|
||||
check_json_ok 'Booleans' '{"V":2,"b":true,"d":false,"L":'"$LEAF"'}'
|
||||
|
||||
# Tokenizer failures. A value that runs to the end of the input has no
|
||||
# terminator, so it is the tokenizer rather than the parser that gives up. Both
|
||||
# the number scan and the string scan have to notice this, and with the closing
|
||||
# quote escaped there is no terminator left either.
|
||||
check_json_fail 'Unterminated number' '{"V":2'
|
||||
check_json_fail 'Unterminated string' '{"V":"x'
|
||||
check_json_fail 'Escaped closing quote' '{"V":2,"L":{"t":"p\"}}'
|
||||
|
||||
# Something that is not a quoted string where a key belongs.
|
||||
check_json_fail 'Missing key' '{"V":2,,"L":'"$LEAF"'}'
|
||||
|
||||
# A key not followed by ':'.
|
||||
check_json_fail 'Missing colon' '{"V","L":2}'
|
||||
|
||||
# A bare word that is neither "true", "false" nor a number. This is where
|
||||
# "null" ends up.
|
||||
check_json_fail 'Unknown literal' '{"V":null,"L":'"$LEAF"'}'
|
||||
|
||||
# A ':' with no value after it, so the token where the value belongs is one the
|
||||
# object parser has no case for.
|
||||
check_json_fail 'Missing value' '{"V":}'
|
||||
|
||||
# A ',' with nothing after it, and a value with no ',' before the next key.
|
||||
check_json_fail 'Trailing comma in an object' '{"V":2,"L":'"$LEAF"',}'
|
||||
check_json_fail 'Missing comma in an object' '{"V":2 "L":'"$LEAF"'}'
|
||||
|
||||
# Arrays hold objects and nothing else.
|
||||
check_json_fail 'Non-object in an array' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":["x"]}}'
|
||||
check_json_fail 'Trailing comma in an array' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":['"$LEAF"',]}}'
|
||||
|
||||
# An empty string is two adjacent quotes with no value token between them,
|
||||
# which the string parser does not accept.
|
||||
check_json_fail 'Empty string' '{"V":2,"L":""}'
|
||||
|
||||
# A number token that strtoll does not consume all of.
|
||||
check_json_fail 'Number with trailing characters' '{"V":8a,"L":'"$LEAF"'}'
|
||||
|
||||
# Anything after the top level object.
|
||||
check_json_fail 'Data after the top level object' '{"V":2,"L":'"$LEAF"'}{}'
|
||||
|
||||
# None of the rejections touched the layout.
|
||||
must_equal 'Layout after rejected parses' "$(layout L:one)" "$ONE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dumping a split.
|
||||
|
||||
check_ok new-window -d -t L:2 -n two
|
||||
q0=$($TMUX display-message -p -t L:two.0 '#{pane_id}')
|
||||
|
||||
# -l 12 gives the new (bottom) pane 12 lines, leaving 11 for the top pane and
|
||||
# one for the border between them. With -d the top pane stays active.
|
||||
check_ok split-window -d -v -l 12 -t L:two.0
|
||||
q1=$($TMUX display-message -p -t L:two.1 '#{pane_id}')
|
||||
|
||||
# Nothing has changed the active pane, so the last pane stack is still empty
|
||||
# and the bottom pane has neither "a" nor "l".
|
||||
must_equal 'Split layout' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The active and last pane keys.
|
||||
|
||||
# Selecting the bottom pane makes it active and pushes the top pane onto the
|
||||
# last pane stack, where it is at index 0.
|
||||
check_ok select-pane -t "$q1"
|
||||
must_equal 'Layout after select-pane' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"l":0,"i":0,"I":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"a":true,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# Selecting the top pane again swaps the two keys over. "i" and "I" do not
|
||||
# move: they are the pane's position in the window and its id.
|
||||
check_ok select-pane -t "$q0"
|
||||
SPLIT='{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"l":0,"i":1,"I":"%N"}]}}'
|
||||
must_equal 'Layout after select-pane back' "$(layout L:two)" "$SPLIT"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The visible layout.
|
||||
|
||||
# With nothing zoomed the two layout formats agree.
|
||||
#
|
||||
# The zoomed case is deliberately not covered here. While a pane is zoomed
|
||||
# #{window_layout} dumps the saved (unzoomed) layout and
|
||||
# #{window_visible_layout} the zoomed one, but that depends on how zooming
|
||||
# stashes the layout root rather than on anything in layout-custom.c.
|
||||
must_equal 'Visible layout' "$(visible_layout L:two)" "$SPLIT"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Round trip.
|
||||
|
||||
# Make the two panes obviously uneven so that the layout applied in between
|
||||
# cannot be mistaken for the saved one. A resize shows up in the dump as the
|
||||
# new cell sizes and offsets.
|
||||
check_ok resize-pane -t "$q0" -y 5
|
||||
saved=$(raw_layout L:two)
|
||||
must_equal 'Resized layout' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":5,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":18,"x":0,"y":6,"l":0,"i":1,"I":"%N"}]}}'
|
||||
|
||||
check_ok select-layout -t L:two even-vertical
|
||||
must_differ 'Layout after even-vertical' "$(raw_layout L:two)" "$saved"
|
||||
|
||||
# Parsing a dump gives back exactly the same dump, pane ids included. The panes
|
||||
# go back into the cells that named them: the cells are ordered by "i" and then
|
||||
# given the window's panes in order, so a cell dumped with "i":k must come back
|
||||
# the k'th.
|
||||
check_ok select-layout -t L:two "$saved"
|
||||
must_equal 'Round tripped layout' "$(raw_layout L:two)" "$saved"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parsing a hand-written layout.
|
||||
|
||||
# Laid out over several lines to keep it readable; that the whitespace is
|
||||
# skipped at all is json.c's business, what matters here is that the cells come
|
||||
# out of it in the right shape.
|
||||
#
|
||||
# "a" and "l" are given on the cells so that the active pane and the last pane
|
||||
# stack are pinned by the layout rather than left to whatever a layout that
|
||||
# names neither happens to produce.
|
||||
check_ok select-layout -t L:two "$(printf '{
|
||||
"V": 2,
|
||||
"L": {
|
||||
"t": "h",
|
||||
"w": 80,
|
||||
"h": 24,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"c": [
|
||||
{"t": "p", "w": 30, "h": 24, "x": 0, "y": 0, "a": true, "i": 0, "I": "%s"},
|
||||
{"t": "p", "w": 49, "h": 24, "x": 31, "y": 0, "l": 0, "i": 1, "I": "%s"}
|
||||
]
|
||||
}
|
||||
}' "$q0" "$q1")"
|
||||
must_equal 'Hand-written layout' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"h","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":30,"h":24,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":49,"h":24,"x":31,"y":0,"l":0,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# The panes are assigned to the cells in order.
|
||||
must_equal 'First pane width' \
|
||||
"$($TMUX display-message -p -t "$q0" '#{pane_width}')" '30'
|
||||
must_equal 'Second pane width' \
|
||||
"$($TMUX display-message -p -t "$q1" '#{pane_width}')" '49'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Field order.
|
||||
|
||||
# Fields are looked up by key once the object has been parsed, so the order
|
||||
# they are written in must give the same layout. Here every object has its keys
|
||||
# reversed: "c" comes before "t" and "V" comes after "L", neither of which
|
||||
# changes the order they are read in - the cell type is always read before the
|
||||
# children and the version before the layout.
|
||||
check_ok select-layout -t L:two \
|
||||
'{"L":{"c":[{"I":"'"$q0"'","i":0,"a":true,"y":0,"x":0,"h":8,"w":80,"t":"p"},{"I":"'"$q1"'","i":1,"l":0,"y":9,"x":0,"h":15,"w":80,"t":"p"}],"y":0,"x":0,"h":24,"w":80,"t":"v"},"V":2}'
|
||||
must_equal 'Reversed field order' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":8,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":15,"x":0,"y":9,"l":0,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# Keys interleaved rather than simply reversed, with "c" in the middle. This
|
||||
# time "a" is on the second cell, so the second pane becomes the active one:
|
||||
# which pane is active comes from the layout, while "i" and "I" still come from
|
||||
# the window. The first cell names neither "a" nor "l", so its pane is neither
|
||||
# active nor on the last pane stack and the dump gives it neither key.
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"h":24,"c":[{"w":40,"t":"p","y":0,"i":0,"h":24,"I":"'"$q0"'","x":0},{"a":true,"h":24,"I":"'"$q1"'","w":39,"y":0,"t":"p","i":1,"x":41}],"w":80,"y":0,"t":"h","x":0}}'
|
||||
must_equal 'Scrambled field order' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"h","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":40,"h":24,"x":0,"y":0,"i":0,"I":"%N"},{"t":"p","w":39,"h":24,"x":41,"y":0,"a":true,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The legacy (v1) format.
|
||||
|
||||
# The layout just applied, in v1: a left/right cell is written with braces and
|
||||
# a top/bottom cell with brackets, and each leaf carries its pane id without
|
||||
# the leading %.
|
||||
v1body="80x24,0,0{40x24,0,0,${q0#%},39x24,41,0,${q1#%}}"
|
||||
|
||||
# A control client that has not asked for new layouts is dumped v1.
|
||||
must_equal 'v1 dump' "$(v1_layout L:two)" "$(v1 "$v1body")"
|
||||
|
||||
# With the new-layouts flag the same client is dumped v2 instead. The flag is
|
||||
# set with "attach -f" rather than refresh-client because refresh-client needs
|
||||
# a current client, which a control client that has not attached has not got.
|
||||
got=$(printf "display-message -p -t L:two '#{window_layout}'\n" |
|
||||
$TMUX -C attach -f new-layouts -t L 2>&1 | grep -v '^%')
|
||||
must_contain 'v2 dump for control client' "$got" '{"V":2,"L":'
|
||||
|
||||
# A v1 layout with a correct checksum is parsed, and dumping v1 again gives
|
||||
# back the same string. That is the whole of what v1 carries: the cells take
|
||||
# the sizes and offsets from the body, and the panes are assigned to them in
|
||||
# order, which is what puts the same two ids back in the same two places. It is
|
||||
# checked in v1 rather than against a v2 dump so that nothing v1 has no opinion
|
||||
# on - the active pane, the last pane stack, the pane index - comes into it.
|
||||
v1vsplit="80x24,0,0[80x11,0,0,${q0#%},80x12,0,12,${q1#%}]"
|
||||
check_ok select-layout -t L:two "$(v1 "$v1vsplit")"
|
||||
must_equal 'v1 round trip' "$(v1_layout L:two)" "$(v1 "$v1vsplit")"
|
||||
|
||||
# v1 names no active pane, last pane or z-index and must disturb none of them.
|
||||
# Applying the v1 form of the layout the window already has therefore leaves
|
||||
# even the v2 dump the same byte for byte, last pane stack included.
|
||||
check_ok select-pane -t "$q1"
|
||||
check_ok select-pane -t "$q0"
|
||||
before=$(raw_layout L:two)
|
||||
check_ok select-layout -t L:two "$(v1 "$v1vsplit")"
|
||||
must_equal 'v1 leaves the active and last panes alone' \
|
||||
"$(raw_layout L:two)" "$before"
|
||||
|
||||
# A v1 layout with more cells than the window has panes is trimmed like any
|
||||
# other: the bottom right cell is closed and the cell above it takes its eight
|
||||
# rows and the border between them, leaving 16. Pane ids in a v1 body are not
|
||||
# used to place panes, so the third cell can carry any id.
|
||||
v1three="80x24,0,0[80x7,0,0,${q0#%},80x7,0,8,${q1#%},80x8,0,16,999]"
|
||||
check_ok select-layout -t L:two "$(v1 "$v1three")"
|
||||
must_equal 'v1 layout trimmed' "$(v1_layout L:two)" \
|
||||
"$(v1 "80x24,0,0[80x7,0,0,${q0#%},80x16,0,8,${q1#%}]")"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pane assignment order.
|
||||
|
||||
# "i" is what decides which pane goes into which cell: the cells are ordered by
|
||||
# it and then handed the window's panes in order, so the cell with "i":0 takes
|
||||
# the first pane of the window wherever that cell sits in the layout. Here the
|
||||
# cells are written the other way round from their indexes - the first cell in
|
||||
# the string is "i":1 and the second "i":0 - so the first pane of the window
|
||||
# has to come out in the second cell.
|
||||
#
|
||||
# Every other layout above lists its cells in the same order as their indexes,
|
||||
# which is the order the tree is walked in, so this is the only check that can
|
||||
# tell the two apart.
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":8,"x":0,"y":0,"i":1,"I":"'"$q1"'"},{"t":"p","w":80,"h":15,"x":0,"y":9,"i":0,"I":"'"$q0"'"}]}}'
|
||||
must_equal 'First pane height' \
|
||||
"$($TMUX display-message -p -t "$q0" '#{pane_height}')" '15'
|
||||
must_equal 'Second pane height' \
|
||||
"$($TMUX display-message -p -t "$q1" '#{pane_height}')" '8'
|
||||
|
||||
# So the dump carries the two ids the other way round from every dump above,
|
||||
# and with them their indexes, which are the panes' positions in the window and
|
||||
# have not moved. Neither cell named an active or last pane, so the pane that
|
||||
# was active still is - it is now the one in the second cell.
|
||||
swapped='{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":8,"x":0,"y":0,"i":1,"I":"'"$q1"'"},{"t":"p","w":80,"h":15,"x":0,"y":9,"a":true,"i":0,"I":"'"$q0"'"}]}}'
|
||||
must_equal 'Layout with the panes swapped' "$(raw_layout L:two)" "$swapped"
|
||||
|
||||
# And that dump round trips, indexes out of order and all.
|
||||
check_ok select-layout -t L:two "$swapped"
|
||||
must_equal 'Round tripped swapped layout' "$(raw_layout L:two)" "$swapped"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cells that name no active or last pane.
|
||||
|
||||
# "a" and "l" are the only things that decide which pane is active and what is
|
||||
# on the last pane stack, so a layout naming neither leaves the active pane
|
||||
# where it was and empties the stack. Here the first pane of the window is
|
||||
# active and the second is at index 0 of the stack beforehand; afterwards the
|
||||
# first pane is still active and the stack is empty, so the second pane has no
|
||||
# "l".
|
||||
check_ok select-pane -t "$q1"
|
||||
check_ok select-pane -t "$q0"
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":14,"x":0,"y":10,"i":1,"I":"'"$q1"'"}]}}'
|
||||
must_equal 'Layout naming no active pane' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":14,"x":0,"y":10,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# "a" may be given as false, which says the same as leaving it out: this pane
|
||||
# is not the active one. A layout where every cell says so names no active pane
|
||||
# at all and so leaves the active pane alone, exactly as the layout above did.
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":10,"x":0,"y":0,"a":false,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":13,"x":0,"y":11,"a":false,"i":1,"I":"'"$q1"'"}]}}'
|
||||
must_equal 'Layout with only false active panes' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":10,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":13,"x":0,"y":11,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Failures.
|
||||
#
|
||||
# Each of these is a different reason for a layout to be rejected, but only the
|
||||
# rejection itself is checked; the message that comes back with it is not.
|
||||
|
||||
# check_layout_fail $layout
|
||||
#
|
||||
# select-layout must reject $layout.
|
||||
check_layout_fail()
|
||||
{
|
||||
check_fail select-layout -t L:two "$1"
|
||||
}
|
||||
|
||||
# A rejected layout must leave the window alone, whatever it was.
|
||||
unchanged=$(raw_layout L:two)
|
||||
|
||||
# Not JSON and not a checksum.
|
||||
check_layout_fail 'garbage'
|
||||
|
||||
# A v1 body with its checksum left off, and a string of nothing but hex digits.
|
||||
# A v1 header is four hex digits and a comma; neither of these has one, so there
|
||||
# is no header and nothing to check a body against.
|
||||
check_layout_fail '80x24,0,0'
|
||||
check_layout_fail 'ab'
|
||||
|
||||
# A v1 header with the checksum of a different body.
|
||||
good=$(v1 '80x24,0,0')
|
||||
check_layout_fail "${good%%,*},80x24,0,1"
|
||||
|
||||
# A correct checksum over a body that is not a layout: a cell with no offsets,
|
||||
# and a top to bottom cell closed with '}' instead of ']'. layout_construct_v1
|
||||
# returns NULL for both.
|
||||
check_layout_fail "$(v1 '80x24')"
|
||||
check_layout_fail "$(v1 '80x24,0,0[80x11,0,0,80x12,0,12}')"
|
||||
|
||||
# Fewer cells than the window has panes; unlike the other way around this
|
||||
# cannot be fixed up.
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# The children of a top to bottom cell must all be the width of their parent.
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":40,"h":12,"x":0,"y":12,"i":1,"I":"'"$q1"'"}]}}'
|
||||
|
||||
# The rest are valid JSON, so it is layout_parse_json() and
|
||||
# layout_parse_json_layout() doing the rejecting rather than json.c. Each of
|
||||
# them is a layout that would be applied but for the one thing being checked.
|
||||
|
||||
# Two root cells.
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"},"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# A missing "y". A cell needs all four of "w", "h", "x" and "y".
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"i":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# Cell sizes are bounded below by one column or row and above by 10000 of
|
||||
# either. Both cases are otherwise complete two cell layouts, so the size is
|
||||
# the only thing wrong with them.
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":0,"h":12,"x":0,"y":12,"i":1,"I":"'"$q1"'"}]}}'
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":10001,"x":0,"y":12,"i":1,"I":"'"$q1"'"}]}}'
|
||||
|
||||
# An unknown cell type: only "h", "v" and "p" exist.
|
||||
check_layout_fail '{"V":2,"L":{"t":"q","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# A pane cell needs "i", its pane index. It is "i" that says which pane goes in
|
||||
# the cell; "I" is the pane id the cell was dumped with and is not read back.
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# A node cell must have more than one child and a leaf cell must have none. A
|
||||
# node is written with no "c" at all, with an empty one and with a single child.
|
||||
check_layout_fail '{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail '{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[]}}'
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}]}}'
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'","c":[{"t":"p","w":80,"h":24,"x":0,"y":0,"i":1,"I":"'"$q1"'"}]}}'
|
||||
|
||||
# Only one cell may be the active pane.
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"a":true,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":12,"x":0,"y":12,"a":true,"i":1,"I":"'"$q1"'"}]}}'
|
||||
|
||||
# The same rejections apply whatever order the fields are written in: a leaf
|
||||
# with children when "c" comes first, and a node with no children and a bad cell
|
||||
# type when "t" comes last.
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"c":[{"t":"p","w":80,"h":24,"x":0,"y":0,"i":1,"I":"'"$q1"'"}],"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
check_layout_fail '{"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"v"}}'
|
||||
check_layout_fail '{"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"q"}}'
|
||||
|
||||
# A child that fails after a sibling has already been parsed and added to the
|
||||
# parent. This is the case the cleanup at the end of layout_parse_json_layout
|
||||
# exists for: the children built so far have to be freed along with the parent
|
||||
# that is never returned. The second child has no "y".
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":12,"x":0,"i":1,"I":"'"$q1"'"}],"t":"v","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# No root cell at all. Every other rejection above comes from a cell that
|
||||
# failed to parse; this one is the check for "L" itself.
|
||||
check_layout_fail '{"V":2}'
|
||||
|
||||
# The wrong version, with "V" before and after "L". Fields are looked up by
|
||||
# key, so the version is read before the layout either way and the position of
|
||||
# "V" in the string makes no difference.
|
||||
check_layout_fail '{"V":1,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
check_layout_fail '{"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"},"V":1}'
|
||||
|
||||
# None of that touched the layout.
|
||||
must_equal 'Layout after failures' "$(raw_layout L:two)" "$unchanged"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Floating panes.
|
||||
|
||||
check_ok new-window -d -t L:3 -n float
|
||||
check_ok select-window -t L:float
|
||||
check_ok new-pane -d -x 20 -y 6 -X 8 -Y 3 'sleep 100'
|
||||
check_ok new-pane -d -x 30 -y 8 -X 30 -Y 10 'sleep 100'
|
||||
|
||||
# The tiled pane and the two floating ones. A floating pane goes on the end of
|
||||
# the window's pane list, so the pane indexes are in the order the panes were
|
||||
# made whatever order their cells end up in.
|
||||
f0=$($TMUX display-message -p -t L:float.0 '#{pane_id}')
|
||||
fa=$($TMUX display-message -p -t L:float.1 '#{pane_id}')
|
||||
fb=$($TMUX display-message -p -t L:float.2 '#{pane_id}')
|
||||
|
||||
# A floating cell is dumped with its z-index, which is what marks it as
|
||||
# floating when the layout is parsed back. Two of them, so that there is an
|
||||
# order between them to get wrong: the newer floating pane is in front, and a
|
||||
# cell's "z" is its place in that order counting from the front.
|
||||
floating=$(raw_layout L:float)
|
||||
must_contain 'Floating layout front z-index' "$floating" '"z":0'
|
||||
must_contain 'Floating layout back z-index' "$floating" '"z":1'
|
||||
|
||||
# Each floating cell goes in after the cell of the pane that was current when
|
||||
# it was made, which is the tiled pane both times, so the newer floating cell
|
||||
# is written before the older one while its pane comes after in the window.
|
||||
# The dump therefore has its cells in one order and their indexes in another,
|
||||
# and only comes back the same if the panes go by index.
|
||||
check_ok select-layout -t L:float "$floating"
|
||||
must_equal 'Floating layout after round trip' "$(raw_layout L:float)" "$floating"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Floating panes and the legacy (v1) format.
|
||||
#
|
||||
# v1 has no way to write a floating pane down, so the two formats cannot say the
|
||||
# same thing about a window that has one. Dumping v1 takes a copy of the layout,
|
||||
# deletes the floating cells from the copy and dumps what is left; parsing v1
|
||||
# rearranges the tiled panes and leaves the floating ones where they are. None
|
||||
# of this is reached above: every v1 check so far runs on a window that has no
|
||||
# floating panes, and every floating pane check so far is in v2.
|
||||
|
||||
# float_state $target
|
||||
#
|
||||
# Everything about a floating pane that a v1 layout has no way to carry, so that
|
||||
# applying one can be checked against all of it at once.
|
||||
float_state()
|
||||
{
|
||||
$TMUX display-message -p -t "$1" \
|
||||
'#{pane_floating_flag} #{pane_width}x#{pane_height} #{pane_left},#{pane_top} #{pane_z}'
|
||||
}
|
||||
|
||||
# Deleting both floating cells from the copy leaves the root node with a single
|
||||
# child, and a node with a single child collapses into it, so the root of the
|
||||
# copy is the tiled cell and the dump is that cell on its own filling the
|
||||
# window.
|
||||
must_equal 'v1 dump with floating panes' "$(v1_layout L:float)" \
|
||||
"$(v1 "80x24,0,0,${f0#%}")"
|
||||
|
||||
# The cells are deleted from the copy, so the window itself comes through a v1
|
||||
# dump untouched - floating panes, z-indexes and all.
|
||||
must_equal 'Layout after a v1 dump' "$(raw_layout L:float)" "$floating"
|
||||
|
||||
# The same with a split, where deleting the floating cell still leaves two
|
||||
# children behind and the node it was in does not collapse.
|
||||
check_ok new-window -d -t L:4 -n mixed
|
||||
m0=$($TMUX display-message -p -t L:mixed.0 '#{pane_id}')
|
||||
check_ok split-window -d -v -l 12 -t L:mixed.0
|
||||
m1=$($TMUX display-message -p -t L:mixed.1 '#{pane_id}')
|
||||
check_ok new-pane -d -x 20 -y 6 -X 8 -Y 3 -t L:mixed.0 'sleep 100'
|
||||
mf=$($TMUX display-message -p -t L:mixed.2 '#{pane_id}')
|
||||
|
||||
# A floating pane takes no space from the tiled layout, so the two tiled cells
|
||||
# are the same 11 and 12 rows the split gave them.
|
||||
must_equal 'v1 dump with a split and a floating pane' "$(v1_layout L:mixed)" \
|
||||
"$(v1 "80x24,0,0[80x11,0,0,${m0#%},80x12,0,12,${m1#%}]")"
|
||||
|
||||
# A v1 layout applied to a window that has a floating pane rearranges the tiled
|
||||
# panes and must leave the floating one exactly as it was: v1 names no floating
|
||||
# pane, so there is nothing in it for one to be changed by. The top pane goes
|
||||
# from 11 rows to 7 and the bottom one from 12 to 16.
|
||||
v1mixed="80x24,0,0[80x7,0,0,${m0#%},80x16,0,8,${m1#%}]"
|
||||
mfbefore=$(float_state "$mf")
|
||||
check_ok select-layout -t L:mixed "$(v1 "$v1mixed")"
|
||||
must_equal 'v1 layout with a floating pane' "$(v1_layout L:mixed)" \
|
||||
"$(v1 "$v1mixed")"
|
||||
must_equal 'Floating pane after a v1 layout' "$(float_state "$mf")" "$mfbefore"
|
||||
must_equal 'Panes after a v1 layout' \
|
||||
"$($TMUX display-message -p -t L:mixed '#{window_panes}')" '3'
|
||||
|
||||
# When the tiled layout a v1 string names is a single cell there is no node in
|
||||
# the new layout for the floating cells to go back into, so one is made: the
|
||||
# root cell is replaced by a top to bottom node holding it and the floating
|
||||
# cells go on the end. Nothing else here reaches that.
|
||||
fabefore=$(float_state "$fa")
|
||||
fbbefore=$(float_state "$fb")
|
||||
check_ok select-layout -t L:float "$(v1 "80x24,0,0,${f0#%}")"
|
||||
must_equal 'v1 single cell layout with floating panes' "$(v1_layout L:float)" \
|
||||
"$(v1 "80x24,0,0,${f0#%}")"
|
||||
must_equal 'Front floating pane after a v1 layout' "$(float_state "$fb")" \
|
||||
"$fbbefore"
|
||||
must_equal 'Back floating pane after a v1 layout' "$(float_state "$fa")" \
|
||||
"$fabefore"
|
||||
must_equal 'Panes after a v1 single cell layout' \
|
||||
"$($TMUX display-message -p -t L:float '#{window_panes}')" '3'
|
||||
|
||||
# Adjacent subtrees containing only floating panes used to be a distinct case:
|
||||
# dumping v1 made a copy of the v2 tree and deleted floating cells from the
|
||||
# copy, but deleting the last floating cell in the first subtree collapsed the
|
||||
# parent and could leave the outer traversal holding a stale pointer to the
|
||||
# second subtree.
|
||||
check_ok new-window -d -t L:5 -n nested
|
||||
n0=$($TMUX display-message -p -t L:nested.0 '#{pane_id}')
|
||||
check_ok split-window -d -v -l 12 -t L:nested.0
|
||||
n1=$($TMUX display-message -p -t L:nested.1 '#{pane_id}')
|
||||
check_ok split-window -d -v -l 6 -t L:nested.1
|
||||
n2=$($TMUX display-message -p -t L:nested.2 '#{pane_id}')
|
||||
check_ok split-window -d -v -l 3 -t L:nested.2
|
||||
n3=$($TMUX display-message -p -t L:nested.3 '#{pane_id}')
|
||||
check_ok split-window -d -v -l 2 -t L:nested.3
|
||||
n4=$($TMUX display-message -p -t L:nested.4 '#{pane_id}')
|
||||
check_ok select-layout -t L:nested \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"h","w":30,"h":10,"x":0,"y":0,"c":[{"t":"p","w":10,"h":5,"x":2,"y":2,"i":0,"z":0},{"t":"p","w":12,"h":6,"x":5,"y":5,"i":1,"z":1}]},{"t":"h","w":30,"h":10,"x":0,"y":0,"c":[{"t":"p","w":14,"h":7,"x":8,"y":8,"i":2,"z":2},{"t":"p","w":16,"h":8,"x":11,"y":11,"i":3,"z":3}]},{"t":"p","w":80,"h":24,"x":0,"y":0,"i":4}]}}'
|
||||
must_equal 'v1 dump with nested floating-only subtrees' \
|
||||
"$(v1_layout L:nested)" "$(v1 "80x24,0,0,${n4#%}")"
|
||||
check_ok kill-window -t L:nested
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# A window with no tiled panes.
|
||||
#
|
||||
# Killing the last tiled pane of a window that has floating panes does not kill
|
||||
# the window: that only happens when the pane being killed is the last one
|
||||
# counting the floating ones. What is left is a window whose layout root is
|
||||
# either a floating cell on its own, or a node holding nothing but floating
|
||||
# cells, depending on how many are left. v1 has no way to write either down, so
|
||||
# it must not try: a layout with no tiled panes in it produces no v1 dump at
|
||||
# all, and #{window_layout} comes back empty for a client being sent v1. What v2
|
||||
# makes of such a window is a separate question and is not checked here.
|
||||
#
|
||||
# A dead server dumps nothing either, so the checks below have to establish that
|
||||
# the server is still there before an empty dump is allowed to mean anything.
|
||||
|
||||
# no_hang $cmd...
|
||||
#
|
||||
# Run a command whose result is not being checked, but which has to come back:
|
||||
# only the server surviving it is checked afterwards, and a server wedged rather
|
||||
# than killed would otherwise show up as the test never finishing.
|
||||
no_hang()
|
||||
{
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout 10 "$@" >/dev/null 2>&1
|
||||
else
|
||||
"$@" >/dev/null 2>&1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# One floating pane left. The node it and the tiled cell were in is down to a
|
||||
# single child, so it collapses and the floating cell becomes the root.
|
||||
check_ok new-window -d -t L:5 -n gone1
|
||||
g0=$($TMUX display-message -p -t L:gone1.0 '#{pane_id}')
|
||||
check_ok new-pane -d -x 20 -y 6 -X 8 -Y 3 -t L:gone1.0 'sleep 100'
|
||||
check_ok kill-pane -t "$g0"
|
||||
must_equal 'Panes left with one floating pane' \
|
||||
"$($TMUX display-message -p -t L:gone1 '#{window_panes}')" '1'
|
||||
|
||||
# The floating cell is the root and there is nothing tiled under it, so there is
|
||||
# no v1 dump to make. In particular the floating cell must not be written out on
|
||||
# its own, which would be a layout claiming the window is the size and position
|
||||
# of the floating pane with no pane in it at all.
|
||||
got=$(v1_layout L:gone1)
|
||||
check_ok display-message -p alive
|
||||
must_equal 'v1 dump with one floating pane and no tiled panes' "$got" ''
|
||||
|
||||
# Two floating panes left, so the node keeps two children, does not collapse,
|
||||
# and stays the root with nothing but floating cells in it.
|
||||
check_ok new-window -d -t L:6 -n gone2
|
||||
h0=$($TMUX display-message -p -t L:gone2.0 '#{pane_id}')
|
||||
check_ok new-pane -d -x 20 -y 6 -X 8 -Y 3 -t L:gone2.0 'sleep 100'
|
||||
check_ok new-pane -d -x 30 -y 8 -X 30 -Y 10 -t L:gone2.0 'sleep 100'
|
||||
check_ok kill-pane -t "$h0"
|
||||
must_equal 'Panes left with two floating panes' \
|
||||
"$($TMUX display-message -p -t L:gone2 '#{window_panes}')" '2'
|
||||
|
||||
# The node is the root this time rather than the floating cell, but it has no
|
||||
# tiled cell anywhere under it either, so there is still no v1 dump to make -
|
||||
# and making one must not take the server with it.
|
||||
got=$(v1_layout L:gone2)
|
||||
check_ok display-message -p alive
|
||||
must_equal 'v1 dump with two floating panes and no tiled panes' "$got" ''
|
||||
|
||||
# Nor must parsing a v1 layout against it. There is no tiled pane for the
|
||||
# layout to name, so whether it is applied or rejected is the format's business;
|
||||
# it just has to be one of the two.
|
||||
no_hang $TMUX select-layout -t L:gone2 "$(v1 '80x24,0,0,999')"
|
||||
check_ok display-message -p alive
|
||||
|
||||
check_ok kill-window -t L:gone1
|
||||
check_ok kill-window -t L:gone2
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Control mode notifications.
|
||||
#
|
||||
# %layout-change is what a control client actually reads a layout from, and it
|
||||
# carries both #{window_layout} and #{window_visible_layout}. Its template is
|
||||
# expanded once per client (control-notify.c), so two clients watching the same
|
||||
# window must be told about the same change in different formats: v1 for the
|
||||
# one that has not asked for new layouts, v2 for the one that has.
|
||||
#
|
||||
# The dumps above go through "-C display-message", which only ever reaches the
|
||||
# format callbacks for the client asking. This needs clients that stay
|
||||
# attached while something else changes the layout, so they go on the end of
|
||||
# fifos and the change is made from outside.
|
||||
|
||||
DIR=$(mktemp -d) || fail 'Could not make a temporary directory'
|
||||
OLDIN="$DIR/old-in"
|
||||
OLDOUT="$DIR/old-out"
|
||||
NEWIN="$DIR/new-in"
|
||||
NEWOUT="$DIR/new-out"
|
||||
OLDPID=
|
||||
NEWPID=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$OLDPID" ] && kill "$OLDPID" 2>/dev/null
|
||||
[ -n "$NEWPID" ] && kill "$NEWPID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# wait_for $file $text
|
||||
#
|
||||
# Wait for $text to appear in a control client's output.
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 6 ]; do
|
||||
grep -F -- "$2" "$1" >/dev/null 2>&1 && return 0
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo "missing from $1: $2" >&2
|
||||
cat "$1" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
mkfifo "$OLDIN" "$NEWIN" || fail 'Could not make the control client fifos'
|
||||
: >"$OLDOUT"
|
||||
: >"$NEWOUT"
|
||||
|
||||
$TMUX -C attach -t L <"$OLDIN" >"$OLDOUT" 2>&1 &
|
||||
OLDPID=$!
|
||||
exec 4>"$OLDIN"
|
||||
$TMUX -C attach -f new-layouts -t L <"$NEWIN" >"$NEWOUT" 2>&1 &
|
||||
NEWPID=$!
|
||||
exec 5>"$NEWIN"
|
||||
|
||||
# Both clients have to be attached before the layout changes, or they miss the
|
||||
# notification entirely.
|
||||
printf 'display-message -p ready\n' >&4
|
||||
printf 'display-message -p ready\n' >&5
|
||||
wait_for "$OLDOUT" ready || fail 'Control client without new-layouts did not attach'
|
||||
wait_for "$NEWOUT" ready || fail 'Control client with new-layouts did not attach'
|
||||
|
||||
wid=$($TMUX display-message -p -t L:two '#{window_id}')
|
||||
|
||||
# One layout change, made by a third client so that neither of the two is the
|
||||
# one running the command. 8 lines for the top pane leaves 15 for the bottom
|
||||
# and one for the border.
|
||||
check_ok resize-pane -t "$q0" -y 8
|
||||
|
||||
# Nothing is zoomed, so both fields of the notification carry the same layout.
|
||||
# The v2 one is compared against the dump rather than a literal so that it is
|
||||
# the two formats being checked and not the geometry again.
|
||||
v2now=$(raw_layout L:two)
|
||||
v1now=$(v1 "80x24,0,0[80x8,0,0,${q0#%},80x15,0,9,${q1#%}]")
|
||||
wait_for "$NEWOUT" "%layout-change $wid $v2now $v2now " ||
|
||||
fail 'No v2 %layout-change for the client with new-layouts'
|
||||
wait_for "$OLDOUT" "%layout-change $wid $v1now $v1now " ||
|
||||
fail 'No v1 %layout-change for the client without new-layouts'
|
||||
|
||||
# How many notifications one layout change produces, which differs by format
|
||||
# on purpose. cmd_select_layout_exec() fires window-layout-changed for any
|
||||
# layout it applies, and layout_parse() fires it again for a v1 one, so v1
|
||||
# arrives twice - which is what master does for every layout, and what control
|
||||
# clients written against it expect. v2 is new and has no such clients, so it
|
||||
# gets the single notification. Counting the delta rather than the total, with
|
||||
# a settle in between, keeps this independent of what has already been sent.
|
||||
n1=$(grep -c "%layout-change $wid " "$OLDOUT")
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":14,"x":0,"y":10,"i":1,"I":"'"$q1"'"}]}}'
|
||||
sleep 2
|
||||
n2=$(grep -c "%layout-change $wid " "$OLDOUT")
|
||||
must_equal 'Notifications for a v2 layout' "$((n2 - n1))" '1'
|
||||
|
||||
check_ok select-layout -t L:two "$(v1 "$v1vsplit")"
|
||||
sleep 2
|
||||
n3=$(grep -c "%layout-change $wid " "$OLDOUT")
|
||||
must_equal 'Notifications for a v1 layout' "$((n3 - n2))" '2'
|
||||
|
||||
# And the client that did not ask for new layouts must never have been sent
|
||||
# one, in that notification or any other.
|
||||
grep -F '{"V":2,' "$OLDOUT" >/dev/null 2>&1 &&
|
||||
fail 'Control client without new-layouts was sent a v2 layout'
|
||||
|
||||
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
|
||||
echo "Server died." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
77
regress/layout-nesting-limit.sh
Normal file
77
regress/layout-nesting-limit.sh
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Version 1 layouts must reject excessive nesting without losing the session or
|
||||
# changing its layout. Check both split types and the depth limit boundary.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX="$TEST_TMUX -S$DIR/socket -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0
|
||||
trap 'exit 1' 1 2 15
|
||||
|
||||
# Generate a checksum-valid v1 layout with one child in every nested group.
|
||||
layout()
|
||||
{
|
||||
awk -v depth="$1" -v open="$2" 'BEGIN {
|
||||
closing = (open == "{" ? "}" : "]")
|
||||
body = ""
|
||||
for (i = 0; i < depth; i++)
|
||||
body = body "1x1,0,0" open
|
||||
body = body "1x1,0,0"
|
||||
for (i = 0; i < depth; i++)
|
||||
body = body closing
|
||||
|
||||
ord["0"] = 48; ord["1"] = 49; ord["x"] = 120
|
||||
ord[","] = 44; ord["{"] = 123; ord["}"] = 125
|
||||
ord["["] = 91; ord["]"] = 93
|
||||
csum = 0
|
||||
for (i = 1; i <= length(body); i++) {
|
||||
c = substr(body, i, 1)
|
||||
bit = csum % 2
|
||||
csum = int(csum / 2) + bit * 32768
|
||||
csum = (csum + ord[c]) % 65536
|
||||
}
|
||||
printf "%04x,%s", csum, body
|
||||
}'
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s deep -x80 -y24 'exec sleep 100' || exit 1
|
||||
|
||||
for open in '{' '['; do
|
||||
# A valid checksum and nesting up to the limit must still be accepted.
|
||||
value=$(layout 1000 "$open") || fail "could not generate layout"
|
||||
$TMUX select-layout -t deep "$value" ||
|
||||
fail "layout at the depth limit was rejected ($open)"
|
||||
before=$($TMUX display-message -p -t deep \
|
||||
'#{pane_id} #{pane_width} #{pane_height} #{window_layout}') || exit 1
|
||||
|
||||
for depth in 1001 1500; do
|
||||
value=$(layout "$depth" "$open") || fail "could not generate layout"
|
||||
$TMUX select-layout -t deep "$value" >/dev/null 2>&1 &&
|
||||
fail "excessive nesting was accepted ($open, $depth)"
|
||||
$TMUX has-session -t deep ||
|
||||
fail "server died on excessive nesting ($open, $depth)"
|
||||
after=$($TMUX display-message -p -t deep \
|
||||
'#{pane_id} #{pane_width} #{pane_height} #{window_layout}') || exit 1
|
||||
[ "$before" = "$after" ] ||
|
||||
fail "pane or layout changed after rejection ($open, $depth)"
|
||||
done
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no
|
||||
$TMUX set -g @modal-root no
|
||||
$TMUX bind -n z set -g @modal-root yes
|
||||
|
||||
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
|
||||
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "new-pane -OK failed"
|
||||
fail "new-pane -ODK failed"
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||
sleep 1
|
||||
@@ -356,9 +356,50 @@ new_left=$(fmt "$modal" '#{pane_left}')
|
||||
new_top=$(fmt "$modal" '#{pane_top}')
|
||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||
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"
|
||||
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
|
||||
# the zoomed tiled pane must not unzoom the window.
|
||||
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
||||
|
||||
@@ -86,6 +86,11 @@ check_value "-gv status-keys" "vi"
|
||||
check_fail "unknown value: bogus" set -g status-keys bogus
|
||||
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 ---------------------------------------------------------
|
||||
#
|
||||
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
||||
|
||||
97
regress/output-commands.sh
Normal file → Executable file
97
regress/output-commands.sh
Normal file → Executable file
@@ -19,7 +19,7 @@ $TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\ntwo\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007broken\\n\\033]133;C\\007unfinished\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
|
||||
@@ -37,43 +37,30 @@ $TMUX send-keys -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X select-output || exit 1
|
||||
$TMUX send-keys -X pipe-selection "wc -c >$OUT" || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 10 ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward one || exit 1
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
|
||||
$TMUX send-keys -X pipe-output "wc -l >$OUT" || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 2 ] || exit 1
|
||||
|
||||
$TMUX send-keys -X copy-pipe-output "wc -l >$OUT" output || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 2 ] || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward echo || exit 1
|
||||
$TMUX send-keys -X select-output || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
selected=$($TMUX show-buffer)
|
||||
[ "$selected" = "$EXPECTED" ] || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX set-buffer -b keep unchanged || exit 1
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward unfinished || exit 1
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X search-backward one || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n plain "printf 'alpha\\nbeta\\n'; exec sleep 100" || exit 1
|
||||
@@ -93,17 +80,6 @@ $TMUX send-keys -t :plain.0 -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer -b keep)" = unchanged ] || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X select-output || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X select-output -a || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-selection || exit 1
|
||||
all=$($TMUX show-buffer)
|
||||
case "$all" in
|
||||
*alpha*beta*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n empty "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007true\\033]133;C\\033]133;D;0\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :empty || exit 1
|
||||
@@ -113,56 +89,43 @@ $TMUX send-keys -t :empty.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n prompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -U -t :prompt || exit 1
|
||||
$TMUX copy-mode -t :prompt || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -c -t :prompt || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X expand-output || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X -N 10 cursor-down || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X -N 100 cursor-down || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X select-output || exit 1
|
||||
[ "$($TMUX display-message -p -t :prompt.0 '#{selection_present}')" = 1 ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n below "printf '\\033]133;A\\007p\\$ \\033]133;B\\007old\\n\\033]133;C\\007old1\\nold2\\nold3\\nold4\\nold5\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007ps\\n\\033]133;C\\007PID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -c -t :below || exit 1
|
||||
$TMUX send-keys -t :below.0 -X search-backward ps || exit 1
|
||||
$TMUX send-keys -t :below.0 -X expand-output || exit 1
|
||||
$TMUX send-keys -t :below.0 -X -N 100 cursor-down || exit 1
|
||||
$TMUX send-keys -t :below.0 C-o || exit 1
|
||||
[ "$($TMUX display-message -p -t :below.0 '#{selection_present}')" = 1 ] || exit 1
|
||||
$TMUX send-keys -t :below.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\\n1 pts/0')" ] || exit 1
|
||||
$TMUX send-keys -t :below.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n clear "printf 'old1\\nold2\\nold3\\nold4\\nold5\\nold6\\n\\033]133;A\\007p\\$ \\033]133;B\\007echo 1; clear; ps\\n\\033]133;C\\0071\\n\\033[H\\033[2JPID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :clear || exit 1
|
||||
$TMUX send-keys -t :clear.0 C-o || exit 1
|
||||
$TMUX send-keys -t :clear.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\\n1 pts/0')" ] || exit 1
|
||||
$TMUX send-keys -t :clear.0 -X cancel || exit 1
|
||||
$TMUX copy-mode -t :prompt || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X -N 100 cursor-down || exit 1
|
||||
$TMUX send-keys -t :prompt.0 C-o || exit 1
|
||||
[ "$($TMUX display-message -p -t :prompt.0 '#{selection_present}')" = 1 ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n same "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -U -t :same || exit 1
|
||||
$TMUX copy-mode -t :same || exit 1
|
||||
$TMUX send-keys -t :same.0 -X search-backward one || exit 1
|
||||
$TMUX send-keys -t :same.0 -X select-output || exit 1
|
||||
$TMUX send-keys -t :same.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :same.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1
|
||||
all=$($TMUX show-buffer)
|
||||
case "$all" in
|
||||
*alpha*beta*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
$TMUX new-window -d -n clear "printf 'old1\\nold2\\nold3\\nold4\\nold5\\nold6\\n\\033]133;A\\007p\\$ \\033]133;B\\007echo 1; clear; ps\\n\\033]133;C\\0071\\n\\033[H\\033[2JPID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :clear || exit 1
|
||||
$TMUX send-keys -t :clear.0 C-o || exit 1
|
||||
$TMUX send-keys -t :clear.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\n1 pts/0')" ] || exit 1
|
||||
$TMUX send-keys -t :clear.0 -X cancel || exit 1
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -104,6 +104,25 @@ assert_alive()
|
||||
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.
|
||||
|
||||
@@ -309,12 +328,12 @@ check_ok kill-pane -t "$p6"
|
||||
|
||||
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
||||
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
||||
layout=$($TMUX display-message -p -t P:0 '#{window_layout}')
|
||||
layout=$(pane_layout P:0)
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
@@ -322,7 +341,7 @@ check_ok select-pane -Z -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# Directional selection temporarily restores the full layout to find its
|
||||
# neighbour, then follows the same unzoom or -Z transfer rules.
|
||||
@@ -340,7 +359,7 @@ check_ok select-pane -D -Z -t "$p0"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# The last-pane path has separate zoom handling, both with and without -Z.
|
||||
check_ok select-pane -t "$p0"
|
||||
@@ -357,7 +376,7 @@ check_ok select-pane -l -Z -t P:0
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
||||
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
||||
|
||||
@@ -217,7 +217,36 @@ $OUT send-keys M-r || exit 1
|
||||
settle
|
||||
status_line | grep -q '>' || fail "status-line prompt not drawn on the status line"
|
||||
|
||||
# --- 11. emacs cursor-marker edit, accept recovers the exact buffer. ---
|
||||
# --- 11. Invalid UTF-8 input must not desynchronize the prompt buffer. ---
|
||||
$OUT send-keys -H e6 85 5f || exit 1 # invalid UTF-8, then "_"
|
||||
settle
|
||||
$IN display-message -p '#{version}' >/dev/null 2>&1 || \
|
||||
fail "invalid UTF-8 append killed inner tmux"
|
||||
status_line | grep -qF "> _" || \
|
||||
fail "invalid UTF-8 append did not keep prompt usable (got '$(status_line)')"
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$($IN show -gv @r)" = "_" ] || \
|
||||
fail "invalid UTF-8 append recovered '$($IN show -gv @r)', wanted '_'"
|
||||
|
||||
$IN set -g @r "SENTINEL" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
$OUT send-keys -H e6 85 04 || exit 1 # invalid UTF-8, then C-d
|
||||
settle
|
||||
$IN display-message -p '#{version}' >/dev/null 2>&1 || \
|
||||
fail "invalid UTF-8 delete killed inner tmux"
|
||||
status_line | grep -q '>' || \
|
||||
fail "invalid UTF-8 delete closed the prompt (got '$(status_line)')"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
[ "$($IN show -gv @r)" = "SENTINEL" ] || \
|
||||
fail "invalid UTF-8 delete accepted the prompt"
|
||||
|
||||
# --- 12. emacs cursor-marker edit, accept recovers the exact buffer. ---
|
||||
$IN set -g @r "" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "abc" || exit 1
|
||||
$OUT send-keys Home || exit 1
|
||||
$OUT send-keys -l "X" || exit 1
|
||||
@@ -229,7 +258,7 @@ settle
|
||||
[ "$($IN show -gv @r)" = "Xabc" ] || \
|
||||
fail "status-line accept recovered '$($IN show -gv @r)', wanted 'Xabc'"
|
||||
|
||||
# --- 12. Unicode on the status line: insert, move, delete wide char. ---
|
||||
# --- 13. Unicode on the status line: insert, move, delete wide char. ---
|
||||
$IN set -g @r "" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
@@ -249,7 +278,7 @@ settle
|
||||
[ "$($IN show -gv @r)" = "Za" ] || \
|
||||
fail "status-line wide edit recovered '$($IN show -gv @r)', wanted 'Za'"
|
||||
|
||||
# --- 13. Overflow: more text than fits stays within the line and is kept. ---
|
||||
# --- 14. Overflow: more text than fits stays within the line and is kept. ---
|
||||
big="0123456789012345678901234567890123456789012345678901234567890123456789ABCDEFGHIJ"
|
||||
$IN set -g @r "" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
@@ -264,7 +293,7 @@ settle
|
||||
# The whole buffer was kept despite only part being visible.
|
||||
[ "$($IN show -gv @r)" = "$big" ] || fail "overflowing prompt lost buffer content"
|
||||
|
||||
# --- 14. Escape closes the status-line prompt cleanly. ---
|
||||
# --- 15. Escape closes the status-line prompt cleanly. ---
|
||||
$IN set -g @r "SENTINEL" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
|
||||
@@ -107,6 +107,12 @@ $TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
|
||||
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
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
|
||||
# the float has no border at all, only its (clipped) content over the base pane.
|
||||
new_scene 40 12
|
||||
|
||||
@@ -93,6 +93,15 @@ $TMUX2 splitw -v "$C" || exit 1
|
||||
$TMUX2 select-layout tiled || exit 1
|
||||
compare outside-both-2x2
|
||||
|
||||
# Top pane status supplies internal horizontal borders, but there is no pane
|
||||
# below the window to supply its bottom edge.
|
||||
new_scene 28 9
|
||||
$TMUX2 setw pane-border-status top || exit 1
|
||||
$TMUX2 setw pane-border-format "" || exit 1
|
||||
compare outside-both-status-top-single
|
||||
$TMUX2 splitw -v "$C" || exit 1
|
||||
compare outside-both-status-top-split
|
||||
|
||||
# Window BIGGER than the client: only part of the window is viewed and the view
|
||||
# can be panned (refresh-client). This exercises a non-zero scene offset.
|
||||
# A 2x2 grid in a 60x20 window viewed through the 40x14 client.
|
||||
|
||||
12
regress/screen-redraw-results/floating-border-rounded.result
Normal file
12
regress/screen-redraw-results/floating-border-rounded.result
Normal file
@@ -0,0 +1,12 @@
|
||||
base
|
||||
╭──────────────────────────╮
|
||||
│FLOAT │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
────────────────────────────┐···········
|
||||
OUT01 abcdefghij │···········
|
||||
OUT02 abcdefghij │···········
|
||||
OUT03 abcdefghij │···········
|
||||
OUT04 abcdefghij │···········
|
||||
OUT05 abcdefghij │···········
|
||||
OUT06 abcdefghij │···········
|
||||
OUT07 abcdefghij │···········
|
||||
│···········
|
||||
────────────────────────────┘···········
|
||||
········································
|
||||
········································
|
||||
········································
|
||||
········································
|
||||
@@ -0,0 +1,14 @@
|
||||
────────────────────────────┐···········
|
||||
OUT06 abcdefghij │···········
|
||||
OUT07 abcdefghij │···········
|
||||
│···········
|
||||
────────────────────────────┤···········
|
||||
PAN05 abcdefghij │···········
|
||||
PAN06 abcdefghij │···········
|
||||
PAN07 abcdefghij │···········
|
||||
│···········
|
||||
────────────────────────────┘···········
|
||||
········································
|
||||
········································
|
||||
········································
|
||||
········································
|
||||
@@ -97,6 +97,10 @@ check "p:.{top-left}" "%0"
|
||||
check_ok select-pane -t p:0.%0
|
||||
check "p:0.+" "1" '#{pane_index}' # next pane
|
||||
check "p:0.-" "3" '#{pane_index}' # previous pane (wraps)
|
||||
check_fail "can't find pane: +0" "p:0.+0"
|
||||
check_fail "can't find pane: +foo" "p:0.+foo"
|
||||
check_fail "can't find pane: -0" "p:0.-0"
|
||||
check_fail "can't find pane: -foo" "p:0.-foo"
|
||||
|
||||
# --- last pane (!) --------------------------------------------------------
|
||||
check_ok select-pane -t p:0.%2
|
||||
|
||||
@@ -154,6 +154,13 @@ check "alpha:{previous}" "3"
|
||||
check "alpha:!" "2" # last window
|
||||
check "alpha:{last}" "2"
|
||||
|
||||
# Malformed relative offsets must fail rather than silently selecting the
|
||||
# current window.
|
||||
check_fail "can't find window: +0" "alpha:+0"
|
||||
check_fail "can't find window: +foo" "alpha:+foo"
|
||||
check_fail "can't find window: -0" "alpha:-0"
|
||||
check_fail "can't find window: -foo" "alpha:-foo"
|
||||
|
||||
# --- combined and empty forms ---------------------------------------------
|
||||
#
|
||||
# Empty targets use the current pane from TMUX_PANE when there is no client.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: screen-redraw.c,v 1.158 2026/09/01 19:50:58 nicm Exp $ */
|
||||
/* $OpenBSD: screen-redraw.c,v 1.159 2026/09/09 08:31:42 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -713,7 +713,7 @@ redraw_mark_pane_borders(struct redraw_build_ctx *bctx, struct window_pane *wp,
|
||||
} else {
|
||||
mark_right = (right <= (int)bctx->w->sx);
|
||||
mark_bottom = (bottom <= (int)bctx->w->sy);
|
||||
if (pane_status == PANE_STATUS_TOP)
|
||||
if (pane_status == PANE_STATUS_TOP && bottom < (int)bctx->w->sy)
|
||||
mark_bottom = 0;
|
||||
else if (pane_status == PANE_STATUS_BOTTOM)
|
||||
mark_top = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-client.c,v 1.510 2026/09/01 19:50:58 nicm Exp $ */
|
||||
/* $OpenBSD: server-client.c,v 1.512 2026/09/10 11:02:18 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -1391,15 +1391,19 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
|
||||
return (repeat);
|
||||
}
|
||||
|
||||
/* Handle a key press on a dead pane waiting for a key. */
|
||||
/* Handle a key press which closes a dead pane. */
|
||||
static int
|
||||
server_client_handle_dead_key(struct window_pane *wp, key_code key)
|
||||
{
|
||||
int remain_on_exit;
|
||||
|
||||
if (wp == NULL ||
|
||||
(~wp->flags & PANE_EXITED) ||
|
||||
KEYC_IS_MOUSE(key) ||
|
||||
KEYC_IS_PASTE(key) ||
|
||||
options_get_number(wp->options, "remain-on-exit") != 3)
|
||||
KEYC_IS_PASTE(key))
|
||||
return (0);
|
||||
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
|
||||
if (remain_on_exit != 3 && remain_on_exit != 4)
|
||||
return (0);
|
||||
options_set_number(wp->options, "remain-on-exit", 0);
|
||||
server_destroy_pane(wp, 0);
|
||||
@@ -1756,9 +1760,10 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
||||
}
|
||||
|
||||
/*
|
||||
* Key presses in overlay mode, for panes capturing all keys and in the
|
||||
* command prompt are a special case. The queue might be blocked so they
|
||||
* need to be processed immediately rather than queued.
|
||||
* Key presses in overlay mode, dead panes waiting for a key, modal cancel
|
||||
* keys, panes capturing all keys and the command prompt are special cases.
|
||||
* The queue might be blocked so they need to be processed immediately
|
||||
* rather than queued.
|
||||
*/
|
||||
if (~c->flags & CLIENT_READONLY) {
|
||||
if (c->message_string != NULL) {
|
||||
@@ -1780,12 +1785,18 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
||||
server_client_clear_overlay(c);
|
||||
|
||||
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 &&
|
||||
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
||||
TAILQ_EMPTY(&wp->modes) &&
|
||||
!KEYC_IS_MOUSE(event->key)) {
|
||||
if (server_client_handle_dead_key(wp, event->key))
|
||||
return (0);
|
||||
if (~wp->flags & PANE_EXITED) {
|
||||
window_pane_key(wp, c, s, s->curw, event->key,
|
||||
&event->m);
|
||||
@@ -3084,6 +3095,8 @@ server_client_control_flags(struct client *c, const char *next)
|
||||
return (CLIENT_CONTROL_NOOUTPUT);
|
||||
if (strcmp(next, "wait-exit") == 0)
|
||||
return (CLIENT_CONTROL_WAITEXIT);
|
||||
if (strcmp(next, "new-layouts") == 0)
|
||||
return (CLIENT_CONTROL_NEWLAYOUTS);
|
||||
return (0);
|
||||
}
|
||||
|
||||
@@ -3150,6 +3163,8 @@ server_client_get_flags(struct client *c)
|
||||
strlcat(s, "no-output,", sizeof s);
|
||||
if (c->flags & CLIENT_CONTROL_WAITEXIT)
|
||||
strlcat(s, "wait-exit,", sizeof s);
|
||||
if (c->flags & CLIENT_CONTROL_NEWLAYOUTS)
|
||||
strlcat(s, "new-layouts,", sizeof s);
|
||||
if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
|
||||
xsnprintf(tmp, sizeof tmp, "pause-after=%u,",
|
||||
c->pause_age / 1000);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-fn.c,v 1.152 2026/09/03 19:12:36 nicm Exp $ */
|
||||
/* $OpenBSD: server-fn.c,v 1.153 2026/09/10 11:02:18 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -386,6 +386,7 @@ server_destroy_pane(struct window_pane *wp, int notify)
|
||||
case 0:
|
||||
break;
|
||||
case 2:
|
||||
case 4:
|
||||
if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
|
||||
break;
|
||||
/* FALLTHROUGH */
|
||||
|
||||
147
tmux.1
147
tmux.1
@@ -1,4 +1,4 @@
|
||||
.\" $OpenBSD: tmux.1,v 1.1164 2026/09/03 21:04:11 nicm Exp $
|
||||
.\" $OpenBSD: tmux.1,v 1.1170 2026/09/11 10:17:16 nicm Exp $
|
||||
.\"
|
||||
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
.\"
|
||||
@@ -14,7 +14,7 @@
|
||||
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
.\"
|
||||
.Dd $Mdocdate: September 3 2026 $
|
||||
.Dd $Mdocdate: September 11 2026 $
|
||||
.Dt TMUX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -1116,6 +1116,8 @@ The flags are:
|
||||
.Bl -tag -width Ds
|
||||
.It ignore\-size
|
||||
the client does not affect the size of other clients
|
||||
.It new\-layouts
|
||||
use the new layout string format
|
||||
.It no\-detach\-on\-destroy
|
||||
do not detach the client when the session it is attached to is destroyed if
|
||||
there are any other sessions
|
||||
@@ -2336,6 +2338,13 @@ Pipe the current OSC 133 command output to
|
||||
.Ar command
|
||||
and clear the selection.
|
||||
.It Xo
|
||||
.Ic pipe\-selection
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Pipe the selected text to
|
||||
.Ar command
|
||||
and clear the selection.
|
||||
.It Xo
|
||||
.Ic previous\-matching\-bracket
|
||||
(emacs: M\-C\-b)
|
||||
.Xc
|
||||
@@ -2525,21 +2534,20 @@ Search forward for the specified plain text.
|
||||
Repeat the last search in the reverse direction (forward becomes backward and
|
||||
backward becomes forward).
|
||||
.It Xo
|
||||
.Ic select\-line
|
||||
(vi: V)
|
||||
.Xc
|
||||
Select the current line.
|
||||
.It Xo
|
||||
.Ic select\-output
|
||||
.Op Fl a
|
||||
.Xc
|
||||
Select the current OSC 133 command output.
|
||||
If it is collapsed, it is expanded first.
|
||||
The default emacs binding is
|
||||
.Ql C\-o
|
||||
and the default vi binding is
|
||||
.Ql M\-o .
|
||||
.It Xo
|
||||
.Ic select\-line
|
||||
(vi: V)
|
||||
.Xc
|
||||
Select the current line.
|
||||
.It Xo
|
||||
.Ic select\-word
|
||||
.Xc
|
||||
Select the current word.
|
||||
@@ -2578,11 +2586,19 @@ Toggle the visibility of the position indicator in the top right.
|
||||
Move to the top line.
|
||||
.El
|
||||
.Pp
|
||||
The output commands use OSC 133 output text from the C marker to the D marker.
|
||||
If there is no D marker, the output ends at the end of the copy-mode buffer.
|
||||
The output commands use OSC 133 output text from the
|
||||
.Ql C
|
||||
marker up to its matching
|
||||
.Ql D
|
||||
marker.
|
||||
If there is no
|
||||
.Ql D
|
||||
marker, the output ends at the end of the copy-mode buffer.
|
||||
At the following prompt or below it, they use the most recent nonempty output.
|
||||
With
|
||||
.Fl a ,
|
||||
they use the entire copy-mode buffer instead.
|
||||
If no output range can be found, they do nothing.
|
||||
.Pp
|
||||
The search commands come in several varieties:
|
||||
.Ql search\-forward
|
||||
@@ -2629,15 +2645,6 @@ Finding the beginning of command output requires the shell to emit an escape
|
||||
sequence (\e033]133;C\e033\e\e) to tell tmux where the output begins.
|
||||
If the shell does not send these escape sequences, these commands do nothing.
|
||||
.Pp
|
||||
The output commands operate on text from an OSC 133
|
||||
.Ql C
|
||||
marker up to its matching
|
||||
.Ql D
|
||||
marker, or the current end of the buffer if there is no
|
||||
.Ql D
|
||||
marker.
|
||||
They do nothing if the cursor is not associated with an output range.
|
||||
.Pp
|
||||
Copy commands may take an optional buffer prefix argument which is used
|
||||
to generate the buffer name (the default is
|
||||
.Ql buffer
|
||||
@@ -2753,60 +2760,6 @@ bind PageDown copy\-mode \-ed
|
||||
kills the pane when the mode is exited.
|
||||
.El
|
||||
.Pp
|
||||
The following commands operate on OSC 133 command output. In copy mode, they
|
||||
use the output on the cursor line; otherwise, they use the most recent output
|
||||
in the target pane. If an output has a C marker but no D marker, it ends at the
|
||||
end of the pane buffer. If there is no OSC 133 output, they do nothing unless
|
||||
.Fl a
|
||||
is given, which uses the entire buffer.
|
||||
.Bl -tag -width Ds
|
||||
.It Xo Ic select\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Xc
|
||||
Enter copy mode if necessary and select the output.
|
||||
.It Xo Ic copy\-output
|
||||
.Op Fl aCP
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the output to a paste buffer.
|
||||
.It Xo Ic copy\-pipe\-output
|
||||
.Op Fl aCP
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the output to a paste buffer and pipe it to
|
||||
.Ar command .
|
||||
.It Xo Ic pipe\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Pipe the output to
|
||||
.Ar command .
|
||||
.It Xo Ic open\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the output in an editor. If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise the
|
||||
.Ic editor
|
||||
option is used.
|
||||
.It Xo Ic open\-selection
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the current copy-mode selection in an editor. If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise the
|
||||
.Ic editor
|
||||
option is used.
|
||||
.El
|
||||
.Pp
|
||||
A number of preset arrangements of panes are available, these are called
|
||||
layouts.
|
||||
These may be selected with the
|
||||
@@ -2859,8 +2812,8 @@ For example:
|
||||
.Bd -literal -offset indent
|
||||
$ tmux list\-windows
|
||||
0: ksh [159x48]
|
||||
layout: bb62,159x48,0,0{79x48,0,0,79x48,80,0}
|
||||
$ tmux select\-layout \[aq]bb62,159x48,0,0{79x48,0,0,79x48,80,0}\[aq]
|
||||
layout: {"V":2,"L":{"t":"h","w":159,"h":48,"x":0,"y":0,"c":[{"t":"p","w":79,"h":48,"x":0,"y":0,"l":0,"i":0,"I":"%0"},{"t":"p","w":79,"h":48,"x":80,"y":0,"a":true,"i":1,"I":"%2"}]}}
|
||||
$ tmux select\-layout \[aq]{"V":2,"L":{"t":"h","w":159,"h":48,"x":0,"y":0,"c":[{"t":"p","w":79,"h":48,"x":0,"y":0,"l":0,"i":0,"I":"%0"},{"t":"p","w":79,"h":48,"x":80,"y":0,"a":true,"i":1,"I":"%2"}]}}\[aq]
|
||||
.Ed
|
||||
.Pp
|
||||
.Nm
|
||||
@@ -2948,7 +2901,7 @@ The pane must not already be floating or hidden, and the window must not
|
||||
be zoomed.
|
||||
.Tg capturep
|
||||
.It Xo Ic capture\-pane
|
||||
.Op Fl aeFHLpPRqCJMN
|
||||
.Op Fl aeFHILpPRqCJMN
|
||||
.Op Fl b Ar buffer\-name
|
||||
.Op Fl E Ar end\-line
|
||||
.Op Fl S Ar start\-line
|
||||
@@ -2990,6 +2943,8 @@ captures only any output that the pane has received that is the beginning of an
|
||||
as-yet incomplete escape sequence.
|
||||
.Fl L
|
||||
includes the line number at the start of each line and
|
||||
.Fl I
|
||||
includes the time each line entered the history, or zero if not available.
|
||||
.Fl F
|
||||
includes the flags (where
|
||||
.Ql -
|
||||
@@ -3811,7 +3766,7 @@ but a different format may be specified with
|
||||
.Fl F .
|
||||
.Tg newp
|
||||
.It Xo Ic new\-pane
|
||||
.Op Fl AbCdefhIkKLMOPvWZ
|
||||
.Op Fl AbCDefhIkKLMOPvWZ
|
||||
.Op Fl B Ar border\-lines
|
||||
.Op Fl c Ar start\-directory
|
||||
.Op Fl e Ar environment
|
||||
@@ -3873,6 +3828,13 @@ all keys, including the prefix key, are passed directly to the modal pane.
|
||||
With
|
||||
.Fl C ,
|
||||
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
|
||||
The
|
||||
.Fl L
|
||||
@@ -6143,6 +6105,8 @@ single lines using ACS or UTF\-8 characters
|
||||
double lines using UTF\-8 characters
|
||||
.It heavy
|
||||
heavy lines using UTF\-8 characters
|
||||
.It rounded
|
||||
single lines with rounded corners using UTF\-8 characters
|
||||
.It simple
|
||||
simple ASCII characters
|
||||
.It number
|
||||
@@ -6498,7 +6462,7 @@ uses when the colour with that index is requested.
|
||||
The index may be from zero to 255.
|
||||
.Pp
|
||||
.It Xo Ic remain\-on\-exit
|
||||
.Op Ic on | off | failed | key
|
||||
.Op Ic on | off | failed | key | failed\-key
|
||||
.Xc
|
||||
A pane with this flag set is not destroyed when the program running in it
|
||||
exits.
|
||||
@@ -6508,6 +6472,10 @@ then only when the program exit status is not zero.
|
||||
If set to
|
||||
.Ic key ,
|
||||
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
|
||||
.Ic respawn\-pane
|
||||
command.
|
||||
@@ -8451,7 +8419,7 @@ The following keys are available in menus:
|
||||
.El
|
||||
.Tg display
|
||||
.It Xo Ic display\-message
|
||||
.Op Fl aCIlNpv
|
||||
.Op Fl aCIjlNpv
|
||||
.Op Fl c Ar target\-client
|
||||
.Op Fl d Ar delay
|
||||
.Op Fl t Ar target\-pane
|
||||
@@ -8491,6 +8459,12 @@ if
|
||||
.Fl t
|
||||
is given, otherwise the active pane.
|
||||
.Pp
|
||||
If
|
||||
.Fl j
|
||||
is given,
|
||||
.Ar message
|
||||
is parsed as JSON and printed.
|
||||
.Pp
|
||||
.Fl v
|
||||
prints verbose logging as the format is parsed and
|
||||
.Fl a
|
||||
@@ -9236,7 +9210,7 @@ and flags (currently not used).
|
||||
For example:
|
||||
.Bd -literal -offset indent
|
||||
%begin 1363006971 2 1
|
||||
0: ksh* (1 panes) [80x24] [layout b25f,80x24,0,0,2] @2 (active)
|
||||
0: ksh* (1 panes) [80x24] [layout {"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"a":true,"i":0,"I":"%2"}}] @2 (active)
|
||||
%end 1363006971 2 1
|
||||
.Ed
|
||||
.Pp
|
||||
@@ -9297,10 +9271,15 @@ The layout of a window with ID
|
||||
.Ar window\-id
|
||||
changed.
|
||||
The new layout is
|
||||
.Ar window\-layout .
|
||||
The window's visible layout is
|
||||
.Ar window\-visible\-layout
|
||||
and the window flags are
|
||||
.Ar window\-layout
|
||||
and the window's visible layout is
|
||||
.Ar window\-visible\-layout .
|
||||
If the
|
||||
.Ar new\-layouts
|
||||
flag is set, both layout fields use the new format string; see
|
||||
.Ic refresh\-client
|
||||
.Fl f .
|
||||
The window flags are
|
||||
.Ar window\-flags .
|
||||
.It Ic %message Ar message
|
||||
A message sent with the
|
||||
|
||||
43
tmux.h
43
tmux.h
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tmux.h,v 1.1433 2026/09/01 12:49:49 nicm Exp $ */
|
||||
/* $OpenBSD: tmux.h,v 1.1440 2026/09/11 10:17:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -60,6 +60,7 @@ struct input_ctx;
|
||||
struct input_request;
|
||||
struct input_requests;
|
||||
struct job;
|
||||
struct json_node;
|
||||
struct menu_data;
|
||||
struct mode_tree_data;
|
||||
struct mouse_event;
|
||||
@@ -1151,7 +1152,8 @@ enum pane_lines {
|
||||
PANE_LINES_SIMPLE,
|
||||
PANE_LINES_NUMBER,
|
||||
PANE_LINES_SPACES,
|
||||
PANE_LINES_NONE
|
||||
PANE_LINES_NONE,
|
||||
PANE_LINES_ROUNDED
|
||||
};
|
||||
|
||||
/* Pane border indicator option. */
|
||||
@@ -1340,6 +1342,7 @@ struct window_pane {
|
||||
#define PANE_CLOSEONCLICK 0x80000
|
||||
#define PANE_CAPTUREALLKEYS 0x100000
|
||||
#define PANE_FLOATOVERZOOM 0x200000
|
||||
#define PANE_CLOSEONCANCEL 0x400000
|
||||
|
||||
bitstr_t *sync_dirty;
|
||||
u_int sync_dirty_size;
|
||||
@@ -2298,11 +2301,12 @@ struct client {
|
||||
#define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL
|
||||
#define CLIENT_CONTROL_WAITEXIT 0x200000000ULL
|
||||
#define CLIENT_WINDOWSIZECHANGED 0x400000000ULL
|
||||
/* 0x800000000ULL unused */
|
||||
#define CLIENT_CONTROL_NEWLAYOUTS 0x800000000ULL
|
||||
#define CLIENT_BRACKETPASTING 0x1000000000ULL
|
||||
#define CLIENT_ASSUMEPASTING 0x2000000000ULL
|
||||
#define CLIENT_WRITE_ACK 0x4000000000ULL
|
||||
#define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL
|
||||
#define CLIENT_CONTROL_DISCARD 0x1000000000ULL
|
||||
#define CLIENT_ALLREDRAWFLAGS \
|
||||
(CLIENT_REDRAWWINDOW| \
|
||||
CLIENT_REDRAWSTATUS| \
|
||||
@@ -3751,6 +3755,7 @@ struct window_pane *window_pane_previous_by_number(struct window *,
|
||||
struct window_pane *, u_int);
|
||||
int window_pane_index(struct window_pane *, u_int *);
|
||||
int window_pane_zindex(struct window_pane *, u_int *);
|
||||
int window_pane_last_index(struct window_pane *, u_int *);
|
||||
u_int window_count_panes(struct window *, int);
|
||||
void window_destroy_panes(struct window *);
|
||||
struct window_pane *window_pane_find_by_id_str(const char *);
|
||||
@@ -3850,7 +3855,7 @@ struct visible_ranges *window_visible_ranges(struct window_pane *, int, int,
|
||||
u_int, struct visible_ranges *);
|
||||
|
||||
/* layout.c */
|
||||
u_int layout_count_cells(struct layout_cell *);
|
||||
u_int layout_count_cells(struct layout_cell *, int);
|
||||
struct layout_cell *layout_create_cell(struct layout_cell *);
|
||||
void layout_free_cell(struct layout_cell *, int);
|
||||
void layout_print_cell(struct layout_cell *, const char *, u_int);
|
||||
@@ -3862,8 +3867,8 @@ struct layout_cell *layout_search_by_border(struct layout_cell *, u_int, u_int);
|
||||
void layout_set_size(struct layout_cell *, u_int, u_int, int, int);
|
||||
void layout_make_leaf(struct layout_cell *, struct window_pane *);
|
||||
void layout_make_node(struct layout_cell *, enum layout_type);
|
||||
void layout_fix_zindexes(struct window *, struct layout_cell *);
|
||||
int layout_cell_is_tiled(struct layout_cell *);
|
||||
int layout_cell_has_tiled_child(struct layout_cell *);
|
||||
int layout_add_horizontal_border(struct layout_cell *,
|
||||
struct layout_cell *, int);
|
||||
void layout_fix_offsets(struct window *);
|
||||
@@ -3914,7 +3919,8 @@ int layout_remove_tile(struct window *, struct layout_cell *);
|
||||
int layout_insert_tile(struct window *, struct layout_cell *);
|
||||
|
||||
/* layout-custom.c */
|
||||
char *layout_dump(struct window *, struct layout_cell *);
|
||||
#define LAYOUT_CUSTOM_OLD_FORMAT 0x1
|
||||
char *layout_dump(struct window *, struct layout_cell *, int);
|
||||
int layout_parse(struct window *, const char *, char **);
|
||||
|
||||
/* layout-set.c */
|
||||
@@ -4274,4 +4280,29 @@ struct hyperlinks *hyperlinks_copy(struct hyperlinks *);
|
||||
void hyperlinks_reset(struct hyperlinks *);
|
||||
void hyperlinks_free(struct hyperlinks *);
|
||||
|
||||
/* json.c */
|
||||
struct json_node *json_parse(const char *, char **);
|
||||
void json_destroy_node(struct json_node *);
|
||||
char *json_to_string(struct json_node *);
|
||||
struct json_node *json_find(struct json_node *, const char *);
|
||||
struct json_node *json_array_first(struct json_node *);
|
||||
struct json_node *json_array_next(struct json_node *);
|
||||
int json_get_string(struct json_node *, const char **);
|
||||
int json_get_number(struct json_node *, int64_t *);
|
||||
int json_get_boolean(struct json_node *, int *);
|
||||
int json_get_object(struct json_node *,
|
||||
struct json_node **);
|
||||
int json_get_array(struct json_node *,
|
||||
struct json_node **);
|
||||
int json_find_string(struct json_node *, const char *,
|
||||
const char **, char **);
|
||||
int json_find_number(struct json_node *, const char *,
|
||||
int64_t *, char **);
|
||||
int json_find_boolean(struct json_node *, const char *,
|
||||
int *, char **);
|
||||
int json_find_object(struct json_node *, const char *,
|
||||
struct json_node **, char **);
|
||||
int json_find_array(struct json_node *, const char *,
|
||||
struct json_node **, char **);
|
||||
|
||||
#endif /* TMUX_H */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-border.c,v 1.3 2026/07/23 09:38:27 nicm Exp $ */
|
||||
/* $OpenBSD: window-border.c,v 1.4 2026/09/11 10:17:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -120,6 +120,10 @@ window_get_border_cell(struct window_pane *wp, enum pane_lines pane_lines,
|
||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||
utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
|
||||
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:
|
||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||
utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
|
||||
|
||||
@@ -606,8 +606,8 @@ window_buffer_start_edit(struct window_buffer_modedata *data,
|
||||
ed->name = xstrdup(paste_buffer_name(pb));
|
||||
ed->pb = pb;
|
||||
|
||||
ed->editor = spawn_editor(c, buf, len, NULL,
|
||||
window_buffer_edit_close_cb, ed);
|
||||
ed->editor = spawn_editor(c, buf, len, NULL, window_buffer_edit_close_cb,
|
||||
ed);
|
||||
if (ed->editor == NULL)
|
||||
window_buffer_finish_edit(ed);
|
||||
else {
|
||||
|
||||
397
window-copy.c
397
window-copy.c
@@ -86,11 +86,6 @@ static int window_copy_set_fold(struct window_mode_entry *, int, int,
|
||||
u_int, int);
|
||||
static int window_copy_find_fold(struct window_mode_entry *, u_int,
|
||||
u_int *, u_int *);
|
||||
static int window_copy_find_output_range(struct window_mode_entry *,
|
||||
u_int *, u_int *, u_int *, u_int *);
|
||||
static int window_copy_find_previous_output_range(struct screen *, u_int,
|
||||
u_int, u_int *, u_int *, u_int *, u_int *);
|
||||
static void window_copy_output_end(struct screen *, u_int *, u_int *);
|
||||
static void window_copy_cursor_source(struct window_mode_entry *, u_int *,
|
||||
u_int *);
|
||||
static int window_copy_source_position(struct window_mode_entry *, u_int,
|
||||
@@ -144,11 +139,16 @@ static int window_copy_set_selection(struct window_mode_entry *, int, int);
|
||||
static int window_copy_update_selection(struct window_mode_entry *, int,
|
||||
int);
|
||||
static void window_copy_synchronize_cursor(struct window_mode_entry *, int);
|
||||
static int window_copy_find_output_range(struct window_mode_entry *,
|
||||
u_int *, u_int *, u_int *, u_int *);
|
||||
static int window_copy_find_previous_output_range(struct screen *,
|
||||
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
|
||||
static void window_copy_output_end(struct screen *, u_int *, u_int *);
|
||||
static void *window_copy_get_selection(struct window_mode_entry *, size_t *);
|
||||
static void *window_copy_get_output(struct window_mode_entry *, size_t *,
|
||||
int);
|
||||
static void *window_copy_get_grid_range(struct grid *, u_int, u_int, u_int,
|
||||
u_int, size_t *);
|
||||
static void *window_copy_get_grid_range(struct grid *, u_int,
|
||||
u_int, u_int, u_int, size_t *);
|
||||
static void window_copy_copy_buffer(struct window_mode_entry *,
|
||||
const char *, void *, size_t, int, int);
|
||||
static void window_copy_pipe_buffer(struct session *, const char *, void *,
|
||||
@@ -2144,7 +2144,8 @@ window_copy_cmd_select_output(struct window_copy_cmd_state *cs)
|
||||
struct grid_reader gr;
|
||||
void *buf;
|
||||
size_t len;
|
||||
u_int sx, sy, ex, ey, bsx, bsy, bex, bey, total;
|
||||
u_int sx, sy, ex, ey, bsx, bsy, bex, bey;
|
||||
u_int total;
|
||||
int all = args_has(cs->wargs, 'a');
|
||||
|
||||
if (all) {
|
||||
@@ -4130,137 +4131,6 @@ window_copy_find_fold(struct window_mode_entry *wme, u_int target,
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Find the output range for the command at the cursor. */
|
||||
static int
|
||||
window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx,
|
||||
u_int *sy, u_int *ex, u_int *ey)
|
||||
{
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
struct grid *gd;
|
||||
struct grid_line *gl;
|
||||
u_int cursor_x, cursor_y, prompt_x = 0;
|
||||
u_int prompt_y = UINT_MAX, y, total;
|
||||
int found_start = 0, found_end = 0;
|
||||
|
||||
if (data->source == NULL)
|
||||
return (0);
|
||||
window_copy_cursor_source(wme, &cursor_x, &cursor_y);
|
||||
log_debug("%s: cursor at %u,%u", __func__, cursor_x, cursor_y);
|
||||
|
||||
gd = data->source->grid;
|
||||
total = gd->hsize + gd->sy;
|
||||
for (y = 0; y < total; y++) {
|
||||
gl = grid_get_line(gd, y);
|
||||
if (~gl->flags & GRID_LINE_START_PROMPT)
|
||||
continue;
|
||||
if (y > cursor_y ||
|
||||
(y == cursor_y && gl->osc133_data.prompt_col > cursor_x))
|
||||
break;
|
||||
prompt_y = y;
|
||||
prompt_x = gl->osc133_data.prompt_col;
|
||||
}
|
||||
if (prompt_y == UINT_MAX) {
|
||||
log_debug("%s: no osc133 prompt before cursor", __func__);
|
||||
return (0);
|
||||
}
|
||||
log_debug("%s: prompt at %u,%u", __func__, prompt_x, prompt_y);
|
||||
|
||||
for (y = prompt_y; y < total; y++) {
|
||||
gl = grid_get_line(gd, y);
|
||||
/* An output may end on the next prompt's line. */
|
||||
if (found_start && gl->flags & GRID_LINE_END_OUTPUT &&
|
||||
(y != prompt_y || gl->osc133_data.out_end_col >= prompt_x)) {
|
||||
*ex = gl->osc133_data.out_end_col;
|
||||
*ey = y;
|
||||
found_end = 1;
|
||||
break;
|
||||
}
|
||||
if (y != prompt_y && gl->flags & GRID_LINE_START_PROMPT)
|
||||
break;
|
||||
if (gl->flags & GRID_LINE_START_OUTPUT &&
|
||||
(y != prompt_y || gl->osc133_data.out_start_col >= prompt_x)) {
|
||||
*sx = gl->osc133_data.out_start_col;
|
||||
*sy = y;
|
||||
found_start = 1;
|
||||
}
|
||||
}
|
||||
if (!found_start) {
|
||||
log_debug("%s: no output after prompt", __func__);
|
||||
return (window_copy_find_previous_output_range(data->source,
|
||||
cursor_x, cursor_y, sx, sy, ex, ey));
|
||||
}
|
||||
if (!found_end) {
|
||||
if (y != total) {
|
||||
log_debug("%s: output interrupted by next prompt", __func__);
|
||||
return (0);
|
||||
}
|
||||
window_copy_output_end(data->source, ex, ey);
|
||||
}
|
||||
log_debug("%s: output from %u,%u to %u,%u", __func__, *sx, *sy,
|
||||
*ex, *ey);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Find the most recent complete output at or before a position. */
|
||||
static int
|
||||
window_copy_find_previous_output_range(struct screen *s, u_int cursor_x,
|
||||
u_int cursor_y, u_int *sx, u_int *sy, u_int *ex, u_int *ey)
|
||||
{
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_line *gl;
|
||||
void *buf;
|
||||
u_int start_x, start_y, end_x, end_y, y, total;
|
||||
size_t len;
|
||||
int found = 0, have_prompt = 0, pending = 0;
|
||||
|
||||
total = gd->hsize + gd->sy;
|
||||
for (y = 0; y < total && y <= cursor_y; y++) {
|
||||
gl = grid_get_line(gd, y);
|
||||
if (gl->flags & GRID_LINE_START_OUTPUT &&
|
||||
(y != cursor_y || gl->osc133_data.out_start_col <= cursor_x)) {
|
||||
start_x = gl->osc133_data.out_start_col;
|
||||
start_y = y;
|
||||
pending = 1;
|
||||
}
|
||||
/* The output may have cleared its C marker from the screen. */
|
||||
if (!pending && !have_prompt &&
|
||||
gl->flags & GRID_LINE_END_OUTPUT &&
|
||||
(~gl->flags & GRID_LINE_START_PROMPT ||
|
||||
gl->osc133_data.out_end_col <= gl->osc133_data.prompt_col)) {
|
||||
start_x = start_y = 0;
|
||||
pending = 1;
|
||||
}
|
||||
if (pending && gl->flags & GRID_LINE_END_OUTPUT &&
|
||||
(y != cursor_y || gl->osc133_data.out_end_col <= cursor_x)) {
|
||||
end_x = gl->osc133_data.out_end_col;
|
||||
end_y = y;
|
||||
buf = window_copy_get_grid_range(gd, start_x, start_y,
|
||||
end_x, end_y, &len);
|
||||
if (buf != NULL) {
|
||||
free(buf);
|
||||
*sx = start_x;
|
||||
*sy = start_y;
|
||||
*ex = end_x;
|
||||
*ey = end_y;
|
||||
found = 1;
|
||||
}
|
||||
pending = 0;
|
||||
}
|
||||
if (gl->flags & GRID_LINE_START_PROMPT) {
|
||||
pending = 0;
|
||||
have_prompt = 1;
|
||||
}
|
||||
}
|
||||
return (found);
|
||||
}
|
||||
|
||||
static void
|
||||
window_copy_output_end(struct screen *s, u_int *x, u_int *y)
|
||||
{
|
||||
*x = s->cx;
|
||||
*y = screen_hsize(s) + s->cy;
|
||||
}
|
||||
|
||||
static void
|
||||
window_copy_cursor_source(struct window_mode_entry *wme, u_int *source_x,
|
||||
u_int *source_y)
|
||||
@@ -4277,7 +4147,8 @@ window_copy_cursor_source(struct window_mode_entry *wme, u_int *source_x,
|
||||
else if (line->source_line == UINT_MAX) {
|
||||
*source_x = UINT_MAX;
|
||||
*source_y = data->source_line_count - 1;
|
||||
} else
|
||||
}
|
||||
else
|
||||
*source_y = line->source_line;
|
||||
}
|
||||
|
||||
@@ -4996,6 +4867,12 @@ static const struct {
|
||||
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
|
||||
.f = window_copy_cmd_pipe_output
|
||||
},
|
||||
{ .command = "pipe-selection",
|
||||
.args = { "", 0, 1, NULL },
|
||||
.flags = 0,
|
||||
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
|
||||
.f = window_copy_cmd_pipe
|
||||
},
|
||||
{ .command = "previous-matching-bracket",
|
||||
.args = { "", 0, 0, NULL },
|
||||
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
|
||||
@@ -5173,18 +5050,18 @@ static const struct {
|
||||
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
|
||||
.f = window_copy_cmd_search_reverse
|
||||
},
|
||||
{ .command = "select-line",
|
||||
.args = { "", 0, 0, NULL },
|
||||
.flags = 0,
|
||||
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
|
||||
.f = window_copy_cmd_select_line
|
||||
},
|
||||
{ .command = "select-output",
|
||||
.args = { "a", 0, 0, NULL },
|
||||
.flags = 0,
|
||||
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
|
||||
.f = window_copy_cmd_select_output
|
||||
},
|
||||
{ .command = "select-line",
|
||||
.args = { "", 0, 0, NULL },
|
||||
.flags = 0,
|
||||
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
|
||||
.f = window_copy_cmd_select_line
|
||||
},
|
||||
{ .command = "select-word",
|
||||
.args = { "", 0, 0, NULL },
|
||||
.flags = 0,
|
||||
@@ -7402,6 +7279,188 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Find the output range for the command at the cursor. */
|
||||
static int
|
||||
window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx,
|
||||
u_int *sy, u_int *ex, u_int *ey)
|
||||
{
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
struct grid *gd;
|
||||
struct grid_line *gl;
|
||||
u_int cursor_x, cursor_y, prompt_x = 0;
|
||||
u_int prompt_y = UINT_MAX, y, total;
|
||||
int found_start = 0, found_end = 0;
|
||||
|
||||
if (data->source == NULL)
|
||||
return (0);
|
||||
window_copy_cursor_source(wme, &cursor_x, &cursor_y);
|
||||
log_debug("%s: cursor at %u,%u", __func__, cursor_x, cursor_y);
|
||||
|
||||
gd = data->source->grid;
|
||||
total = gd->hsize + gd->sy;
|
||||
for (y = 0; y < total; y++) {
|
||||
gl = grid_get_line(gd, y);
|
||||
if (~gl->flags & GRID_LINE_START_PROMPT)
|
||||
continue;
|
||||
if (y > cursor_y ||
|
||||
(y == cursor_y && gl->osc133_data.prompt_col > cursor_x))
|
||||
break;
|
||||
prompt_y = y;
|
||||
prompt_x = gl->osc133_data.prompt_col;
|
||||
}
|
||||
if (prompt_y == UINT_MAX) {
|
||||
log_debug("%s: no osc133 prompt before cursor", __func__);
|
||||
return (0);
|
||||
}
|
||||
log_debug("%s: prompt at %u,%u", __func__, prompt_x, prompt_y);
|
||||
|
||||
for (y = prompt_y; y < total; y++) {
|
||||
gl = grid_get_line(gd, y);
|
||||
/* An output may end on the next prompt's line. */
|
||||
if (found_start && gl->flags & GRID_LINE_END_OUTPUT &&
|
||||
(y != prompt_y || gl->osc133_data.out_end_col >= prompt_x)) {
|
||||
*ex = gl->osc133_data.out_end_col;
|
||||
*ey = y;
|
||||
found_end = 1;
|
||||
break;
|
||||
}
|
||||
if (y != prompt_y && gl->flags & GRID_LINE_START_PROMPT)
|
||||
break;
|
||||
if (gl->flags & GRID_LINE_START_OUTPUT &&
|
||||
(y != prompt_y || gl->osc133_data.out_start_col >= prompt_x)) {
|
||||
*sx = gl->osc133_data.out_start_col;
|
||||
*sy = y;
|
||||
found_start = 1;
|
||||
}
|
||||
}
|
||||
if (!found_start) {
|
||||
log_debug("%s: no output after prompt", __func__);
|
||||
return (window_copy_find_previous_output_range(data->source, cursor_x,
|
||||
cursor_y, sx, sy, ex, ey));
|
||||
}
|
||||
if (!found_end) {
|
||||
if (y != total) {
|
||||
log_debug("%s: output interrupted by next prompt", __func__);
|
||||
return (0);
|
||||
}
|
||||
window_copy_output_end(data->source, ex, ey);
|
||||
}
|
||||
log_debug("%s: output from %u,%u to %u,%u", __func__, *sx, *sy,
|
||||
*ex, *ey);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Find the most recent complete output at or before a position. */
|
||||
static int
|
||||
window_copy_find_previous_output_range(struct screen *s, u_int cursor_x,
|
||||
u_int cursor_y, u_int *sx, u_int *sy, u_int *ex, u_int *ey)
|
||||
{
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_line *gl;
|
||||
void *buf;
|
||||
u_int start_x, start_y, end_x, end_y;
|
||||
u_int y, total;
|
||||
size_t len;
|
||||
int found = 0, have_prompt = 0;
|
||||
int pending = 0;
|
||||
|
||||
total = gd->hsize + gd->sy;
|
||||
for (y = 0; y < total && y <= cursor_y; y++) {
|
||||
gl = grid_get_line(gd, y);
|
||||
if (gl->flags & GRID_LINE_START_OUTPUT &&
|
||||
(y != cursor_y || gl->osc133_data.out_start_col <= cursor_x)) {
|
||||
start_x = gl->osc133_data.out_start_col;
|
||||
start_y = y;
|
||||
pending = 1;
|
||||
}
|
||||
/* The output may have cleared its C marker from the screen. */
|
||||
if (!pending && !have_prompt &&
|
||||
gl->flags & GRID_LINE_END_OUTPUT &&
|
||||
(~gl->flags & GRID_LINE_START_PROMPT ||
|
||||
gl->osc133_data.out_end_col <= gl->osc133_data.prompt_col)) {
|
||||
start_x = start_y = 0;
|
||||
pending = 1;
|
||||
}
|
||||
if (pending && gl->flags & GRID_LINE_END_OUTPUT &&
|
||||
(y != cursor_y || gl->osc133_data.out_end_col <= cursor_x)) {
|
||||
end_x = gl->osc133_data.out_end_col;
|
||||
end_y = y;
|
||||
buf = window_copy_get_grid_range(gd, start_x, start_y,
|
||||
end_x, end_y, &len);
|
||||
if (buf != NULL) {
|
||||
free(buf);
|
||||
*sx = start_x;
|
||||
*sy = start_y;
|
||||
*ex = end_x;
|
||||
*ey = end_y;
|
||||
found = 1;
|
||||
}
|
||||
pending = 0;
|
||||
}
|
||||
if (gl->flags & GRID_LINE_START_PROMPT) {
|
||||
pending = 0;
|
||||
have_prompt = 1;
|
||||
}
|
||||
}
|
||||
return (found);
|
||||
}
|
||||
|
||||
static void
|
||||
window_copy_output_end(struct screen *s, u_int *x, u_int *y)
|
||||
{
|
||||
*x = s->cx;
|
||||
*y = screen_hsize(s) + s->cy;
|
||||
}
|
||||
|
||||
static void *
|
||||
window_copy_get_output(struct window_mode_entry *wme, size_t *len, int all)
|
||||
{
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
struct grid *gd;
|
||||
u_int sx, sy, ex, ey, total;
|
||||
|
||||
if (data->source == NULL) {
|
||||
*len = 0;
|
||||
return (NULL);
|
||||
}
|
||||
gd = data->source->grid;
|
||||
total = gd->hsize + gd->sy;
|
||||
if (all) {
|
||||
sx = sy = 0;
|
||||
ey = total - 1;
|
||||
ex = grid_get_line(gd, ey)->cellused;
|
||||
} else if (!window_copy_find_output_range(wme, &sx, &sy, &ex, &ey)) {
|
||||
*len = 0;
|
||||
return (NULL);
|
||||
}
|
||||
return (window_copy_get_grid_range(gd, sx, sy, ex, ey, len));
|
||||
}
|
||||
|
||||
static void *
|
||||
window_copy_get_grid_range(struct grid *gd, u_int sx, u_int sy,
|
||||
u_int ex, u_int ey, size_t *len)
|
||||
{
|
||||
char *buf;
|
||||
size_t off;
|
||||
u_int i;
|
||||
|
||||
buf = xmalloc(1);
|
||||
off = 0;
|
||||
for (i = sy; i <= ey; i++) {
|
||||
window_copy_copy_grid_line(gd, &buf, &off, i, i == sy ? sx : 0,
|
||||
i == ey ? ex : gd->sx);
|
||||
}
|
||||
if (off != 0 && buf[off - 1] == '\n')
|
||||
off--;
|
||||
if (off == 0) {
|
||||
free(buf);
|
||||
*len = 0;
|
||||
return (NULL);
|
||||
}
|
||||
*len = off;
|
||||
return (buf);
|
||||
}
|
||||
|
||||
static void *
|
||||
window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
|
||||
{
|
||||
@@ -7524,52 +7583,6 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
|
||||
return (buf);
|
||||
}
|
||||
|
||||
static void *
|
||||
window_copy_get_output(struct window_mode_entry *wme, size_t *len, int all)
|
||||
{
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
struct grid *gd;
|
||||
u_int sx, sy, ex, ey, total;
|
||||
|
||||
gd = data->source->grid;
|
||||
total = gd->hsize + gd->sy;
|
||||
if (all) {
|
||||
sx = sy = 0;
|
||||
ey = total - 1;
|
||||
ex = grid_get_line(gd, ey)->cellused;
|
||||
} else if (!window_copy_find_output_range(wme, &sx, &sy, &ex, &ey)) {
|
||||
*len = 0;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
return (window_copy_get_grid_range(gd, sx, sy, ex, ey, len));
|
||||
}
|
||||
|
||||
static void *
|
||||
window_copy_get_grid_range(struct grid *gd, u_int sx, u_int sy, u_int ex,
|
||||
u_int ey, size_t *len)
|
||||
{
|
||||
char *buf;
|
||||
size_t off;
|
||||
u_int i;
|
||||
|
||||
buf = xmalloc(1);
|
||||
off = 0;
|
||||
for (i = sy; i <= ey; i++) {
|
||||
window_copy_copy_grid_line(gd, &buf, &off, i,
|
||||
i == sy ? sx : 0, i == ey ? ex : gd->sx);
|
||||
}
|
||||
if (off != 0 && buf[off - 1] == '\n')
|
||||
off--;
|
||||
if (off == 0) {
|
||||
free(buf);
|
||||
*len = 0;
|
||||
return (NULL);
|
||||
}
|
||||
*len = off;
|
||||
return (buf);
|
||||
}
|
||||
|
||||
static void
|
||||
window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix,
|
||||
void *buf, size_t len, int set_paste, int set_clip)
|
||||
|
||||
17
window.c
17
window.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window.c,v 1.373 2026/08/24 21:17:19 nicm Exp $ */
|
||||
/* $OpenBSD: window.c,v 1.374 2026/09/08 08:37:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -1255,6 +1255,21 @@ window_pane_zindex(struct window_pane *wp, u_int *i)
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
window_pane_last_index(struct window_pane *wp, u_int *i)
|
||||
{
|
||||
struct window *w = wp->window;
|
||||
struct window_pane *wq;
|
||||
|
||||
*i = 0;
|
||||
TAILQ_FOREACH(wq, &w->last_panes, sentry) {
|
||||
if (wq == wp)
|
||||
return (0);
|
||||
(*i)++;
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
|
||||
u_int
|
||||
window_count_panes(struct window *w, int with_floating)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user