mirror of
https://github.com/tmux/tmux.git
synced 2026-09-18 02:54:45 +00:00
Compare commits
63 Commits
redraw-dam
...
command_pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c624de9f3c | ||
|
|
cf3ab3cad9 | ||
|
|
c6f62289b0 | ||
|
|
b114b7e089 | ||
|
|
f85b015dc1 | ||
|
|
899b4b5471 | ||
|
|
16064a410e | ||
|
|
fcc7366e86 | ||
|
|
5d2b6542b6 | ||
|
|
66e6b2e1c3 | ||
|
|
baf71480d0 | ||
|
|
5838b45315 | ||
|
|
cfa755a961 | ||
|
|
82683707bc | ||
|
|
3ee24bde1a | ||
|
|
4f7bef5083 | ||
|
|
80b1f20e7d | ||
|
|
111a11ac6a | ||
|
|
1853c220a4 | ||
|
|
115002748b | ||
|
|
db8f9b4c52 | ||
|
|
4951eac7fa | ||
|
|
185ae656e0 | ||
|
|
ab3da84e34 | ||
|
|
bce30eefd3 | ||
|
|
1302bbde07 | ||
|
|
e935342702 | ||
|
|
6d56dd9e7b | ||
|
|
8ca7434d65 | ||
|
|
a9cff084c0 | ||
|
|
186f51b7ee | ||
|
|
298b35c8a9 | ||
|
|
7a1cc1d443 | ||
|
|
fefb926291 | ||
|
|
36273fde4f | ||
|
|
8ac29f6587 | ||
|
|
db9b54f232 | ||
|
|
54f5c82500 | ||
|
|
b3bb8bbd9f | ||
|
|
c92720e3b0 | ||
|
|
08500f844d | ||
|
|
a97912a4f8 | ||
|
|
91ea9e4beb | ||
|
|
b30f5f82e0 | ||
|
|
d4eca8d586 | ||
|
|
8908f50524 | ||
|
|
eb2e26f4cd | ||
|
|
9569c57fc3 | ||
|
|
b48e2ea8e9 | ||
|
|
f7cd6ab547 | ||
|
|
401793eba6 | ||
|
|
fcfa0b6db4 | ||
|
|
5f7a4b0f52 | ||
|
|
bcc2b7ad1e | ||
|
|
9e8cf6f0e2 | ||
|
|
b8a62979ac | ||
|
|
45f4000017 | ||
|
|
572b32766c | ||
|
|
e6ad44c8a3 | ||
|
|
96cd98560b | ||
|
|
ac23a47a5f | ||
|
|
22b6ad6fce | ||
|
|
efe2453eac |
58
CHANGES
58
CHANGES
@@ -9,12 +9,7 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
- modal panes may be created with new-pane -O; a window can have one modal
|
- modal panes may be created with new-pane -O; a window can have one modal
|
||||||
pane and it prevents interaction with the other panes while it is active.
|
pane and it prevents interaction with the other panes while it is active.
|
||||||
-C closes the pane when the mouse is clicked outside it and -K sends all
|
A modal pane is now used for the editor in buffer mode;
|
||||||
keys, including the prefix key, to the pane. A modal pane is now used for
|
|
||||||
the editor in buffer mode;
|
|
||||||
|
|
||||||
- new-pane -A creates a floating pane which remains above a zoomed pane;
|
|
||||||
modal panes do this by default;
|
|
||||||
|
|
||||||
- move-pane and resize-pane can now move and resize floating panes, including
|
- move-pane and resize-pane can now move and resize floating panes, including
|
||||||
mouse dragging;
|
mouse dragging;
|
||||||
@@ -29,15 +24,10 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
-k for choose commands kills the pane when the mode exits, which is useful
|
-k for choose commands kills the pane when the mode exits, which is useful
|
||||||
for modes in floating panes;
|
for modes in floating panes;
|
||||||
|
|
||||||
- command-prompt -P opens a prompt inside a pane rather than on the status
|
|
||||||
line;
|
|
||||||
|
|
||||||
- pane-border-status has top-floating and bottom-floating, and there are new
|
- 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;
|
||||||
|
|
||||||
* Layout strings now use a JSON subset format which includes floating panes.
|
- floating panes are unzoomed before creation to avoid a crash.
|
||||||
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:
|
* Add support for themes and improve default colours:
|
||||||
|
|
||||||
@@ -84,7 +74,7 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
- new hooks to cover areas including client create and destroy, pane
|
- new hooks to cover areas including client create and destroy, pane
|
||||||
activity, pane mode and prompt changes, pane movement and resizing, session
|
activity, pane mode and prompt changes, pane movement and resizing, session
|
||||||
group changes and window creation, close, swap and zoom;
|
group changes and window creation, close and zoom;
|
||||||
|
|
||||||
- OSC 133 escape sequences now trigger events: pane-command-started,
|
- OSC 133 escape sequences now trigger events: pane-command-started,
|
||||||
pane-command-finished and pane-shell-prompt.
|
pane-command-finished and pane-shell-prompt.
|
||||||
@@ -97,23 +87,10 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
while scrolling or while hovered and disappears after pane-scrollbars-timeout
|
while scrolling or while hovered and disappears after pane-scrollbars-timeout
|
||||||
(Michael Grant).
|
(Michael Grant).
|
||||||
|
|
||||||
* Add a clear-on-attach server option. When disabled, tmux does not enter the
|
|
||||||
alternate screen on attach and instead scrolls the existing terminal content
|
|
||||||
into the scrollback buffer so it remains accessible (issue 5508).
|
|
||||||
|
|
||||||
* Copy mode can now automatically refresh as pane content changes with the
|
|
||||||
refresh-on, refresh-off and refresh-toggle commands. Automatic refresh is off
|
|
||||||
by default; refresh-now refreshes once and is bound to r. These replace
|
|
||||||
refresh-from-pane (issue 5165).
|
|
||||||
|
|
||||||
* Extend the fill-character option so both inside and outside the window can be
|
* Extend the fill-character option so both inside and outside the window can be
|
||||||
changed, with a new default for unused areas inside the window.
|
changed.
|
||||||
|
|
||||||
* Change show-options and show-hooks to use formats and add a -F flag to each.
|
* Change set-option and set-hooks to use formats and add a -F flag to each.
|
||||||
|
|
||||||
* Allow individual terminal features to be disabled by adding @ to their names
|
|
||||||
in terminal-features, and add a utf8 feature for terminals which support
|
|
||||||
UTF-8 output.
|
|
||||||
|
|
||||||
* Add a #{A/count:frames} modifier to show a series of frames as an animation
|
* Add a #{A/count:frames} modifier to show a series of frames as an animation
|
||||||
in the status line (issue 5412 from Fernando Daciuk).
|
in the status line (issue 5412 from Fernando Daciuk).
|
||||||
@@ -128,10 +105,6 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient
|
* 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).
|
methods to create an empty pane (rather than using '' for the command).
|
||||||
|
|
||||||
* Add capture-pane -I to include the time each line entered history.
|
|
||||||
|
|
||||||
* Add a default C-b T binding to change the current pane title.
|
|
||||||
|
|
||||||
* Menus now belong to the window, so appear on all clients.
|
* Menus now belong to the window, so appear on all clients.
|
||||||
|
|
||||||
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
|
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
|
||||||
@@ -145,13 +118,6 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
* Copy mode no longer exits at the bottom while a selection is in progress
|
* Copy mode no longer exits at the bottom while a selection is in progress
|
||||||
(issue 5349).
|
(issue 5349).
|
||||||
|
|
||||||
* Add copy-mode-current-line-style to set the style of the line containing the
|
|
||||||
cursor in copy mode (issue 5391).
|
|
||||||
|
|
||||||
* Copy mode word commands now recognise all Unicode whitespace characters; a
|
|
||||||
space in word-separators matches any Unicode whitespace character (issue
|
|
||||||
5562).
|
|
||||||
|
|
||||||
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
|
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
|
||||||
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
||||||
|
|
||||||
@@ -162,9 +128,7 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
* Add or improve format variables including client_colours,
|
* Add or improve format variables including client_colours,
|
||||||
pane_start_command_list, window_manual_width, window_manual_height,
|
pane_start_command_list, window_manual_width, window_manual_height,
|
||||||
pane_last_output_time, pane_modal_flag, window_modal_pane, pane_private_modes,
|
pane_last_output_time, pane_modal_flag and window_modal_pane.
|
||||||
pane_output_generation, history_added, history_collected, history_generation,
|
|
||||||
hook_fire_count and hook_fire_time.
|
|
||||||
|
|
||||||
* Add additional pane sort orders, and a z sort order for choose-tree so
|
* Add additional pane sort orders, and a z sort order for choose-tree so
|
||||||
floating panes can be sorted by z-index.
|
floating panes can be sorted by z-index.
|
||||||
@@ -176,18 +140,12 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
* The mouse option now defaults to on.
|
* 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
|
* Fix send-keys -K so keys are inserted in the correct place in the input
|
||||||
queue, like keys from key bindings (issue 3476).
|
queue, like keys from key bindings (issue 3476).
|
||||||
|
|
||||||
* Fix control mode clients hanging on exit if pty data was still queued (Ben
|
* Fix control mode clients hanging on exit if pty data was still queued (Ben
|
||||||
Maurer, issue 5356), and avoid sending notifications to clients which are
|
Maurer, issue 5356), and avoid sending notifications to clients which are
|
||||||
already exiting (Ben Maurer, issue 5357). Queue notifications so they are not
|
already exiting (Ben Maurer, issue 5357).
|
||||||
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). 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
|
* Fix grouped sessions sometimes being left as unusable command targets while
|
||||||
they are being killed (Bryce Miller, issue 5180).
|
they are being killed (Bryce Miller, issue 5180).
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ dist_tmux_SOURCES = \
|
|||||||
cmd-find-window.c \
|
cmd-find-window.c \
|
||||||
cmd-find.c \
|
cmd-find.c \
|
||||||
cmd-if-shell.c \
|
cmd-if-shell.c \
|
||||||
|
cmd-invoke.c \
|
||||||
cmd-join-pane.c \
|
cmd-join-pane.c \
|
||||||
cmd-kill-pane.c \
|
cmd-kill-pane.c \
|
||||||
cmd-kill-server.c \
|
cmd-kill-server.c \
|
||||||
@@ -174,7 +175,6 @@ dist_tmux_SOURCES = \
|
|||||||
input-keys.c \
|
input-keys.c \
|
||||||
input.c \
|
input.c \
|
||||||
job.c \
|
job.c \
|
||||||
json.c \
|
|
||||||
key-bindings.c \
|
key-bindings.c \
|
||||||
key-string.c \
|
key-string.c \
|
||||||
layout-custom.c \
|
layout-custom.c \
|
||||||
|
|||||||
185
arguments.c
185
arguments.c
@@ -51,13 +51,6 @@ struct args {
|
|||||||
struct args_value *values;
|
struct args_value *values;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Prepared command state. */
|
|
||||||
struct args_command_state {
|
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
char *cmd;
|
|
||||||
struct cmd_parse_input pi;
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct args_entry *args_find(struct args *, u_char);
|
static struct args_entry *args_find(struct args *, u_char);
|
||||||
|
|
||||||
static int args_cmp(struct args_entry *, struct args_entry *);
|
static int args_cmp(struct args_entry *, struct args_entry *);
|
||||||
@@ -89,8 +82,7 @@ args_copy_value(struct args_value *to, struct args_value *from)
|
|||||||
case ARGS_NONE:
|
case ARGS_NONE:
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
to->cmdlist = from->cmdlist;
|
to->cmd = cmd_parse_add_ref(from->cmd);
|
||||||
to->cmdlist->references++;
|
|
||||||
break;
|
break;
|
||||||
case ARGS_STRING:
|
case ARGS_STRING:
|
||||||
to->string = xstrdup(from->string);
|
to->string = xstrdup(from->string);
|
||||||
@@ -123,7 +115,7 @@ args_value_as_string(struct args_value *value)
|
|||||||
return ("");
|
return ("");
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
if (value->cached == NULL)
|
if (value->cached == NULL)
|
||||||
value->cached = cmd_list_print(value->cmdlist, 0);
|
value->cached = cmd_parse_print(value->cmd, 0);
|
||||||
return (value->cached);
|
return (value->cached);
|
||||||
case ARGS_STRING:
|
case ARGS_STRING:
|
||||||
return (value->string);
|
return (value->string);
|
||||||
@@ -367,7 +359,7 @@ args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
|
|||||||
to->string = expanded;
|
to->string = expanded;
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv);
|
to->cmd = cmd_parse_add_ref(from->cmd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,7 +410,7 @@ args_free_value(struct args_value *value)
|
|||||||
free(value->string);
|
free(value->string);
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
cmd_list_free(value->cmdlist);
|
cmd_parse_free(value->cmd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
free(value->cached);
|
free(value->cached);
|
||||||
@@ -477,7 +469,7 @@ args_to_vector(struct args *args, int *argc, char ***argv)
|
|||||||
cmd_append_argv(argc, argv, args->values[i].string);
|
cmd_append_argv(argc, argv, args->values[i].string);
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
s = cmd_list_print(args->values[i].cmdlist, 0);
|
s = cmd_parse_print(args->values[i].cmd, 0);
|
||||||
cmd_append_argv(argc, argv, s);
|
cmd_append_argv(argc, argv, s);
|
||||||
free(s);
|
free(s);
|
||||||
break;
|
break;
|
||||||
@@ -532,7 +524,7 @@ args_print_add_value(char **buf, size_t *len, struct args_value *value)
|
|||||||
case ARGS_NONE:
|
case ARGS_NONE:
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
expanded = cmd_list_print(value->cmdlist, 0);
|
expanded = cmd_parse_print(value->cmd, 0);
|
||||||
args_print_add(buf, len, "{ %s }", expanded);
|
args_print_add(buf, len, "{ %s }", expanded);
|
||||||
break;
|
break;
|
||||||
case ARGS_STRING:
|
case ARGS_STRING:
|
||||||
@@ -747,144 +739,53 @@ args_string(struct args *args, u_int idx)
|
|||||||
return (args_value_as_string(&args->values[idx]));
|
return (args_value_as_string(&args->values[idx]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make a command now. */
|
/*
|
||||||
struct cmd_list *
|
* Convert one command argument into a parse tree. A string value is parsed;
|
||||||
args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
|
* existing commands are referenced.
|
||||||
int expand)
|
*/
|
||||||
|
struct cmd_parse_tree *
|
||||||
|
args_to_commands(struct cmd *self, struct cmdq_item *item, u_int idx,
|
||||||
|
const char *default_command, int expand, char **cause)
|
||||||
{
|
{
|
||||||
struct args_command_state *state;
|
struct args *args = cmd_get_args(self);
|
||||||
char *error;
|
struct args_value *value;
|
||||||
struct cmd_list *cmdlist;
|
struct cmd_parse_input pi = { 0 };
|
||||||
|
const char *cmd = NULL, *file;
|
||||||
|
char *expanded;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
|
||||||
state = args_make_commands_prepare(self, item, idx, NULL, 0, expand);
|
*cause = NULL;
|
||||||
cmdlist = args_make_commands(state, 0, NULL, &error);
|
|
||||||
if (cmdlist == NULL) {
|
|
||||||
cmdq_error(item, "%s", error);
|
|
||||||
free(error);
|
|
||||||
}
|
|
||||||
args_make_commands_free(state);
|
|
||||||
return (cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save bits to make a command later. */
|
if (idx >= args->count)
|
||||||
struct args_command_state *
|
|
||||||
args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
|
|
||||||
const char *default_command, int wait, int expand)
|
|
||||||
{
|
|
||||||
struct args *args = cmd_get_args(self);
|
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
|
||||||
struct args_value *value;
|
|
||||||
struct args_command_state *state;
|
|
||||||
const char *cmd;
|
|
||||||
const char *file;
|
|
||||||
|
|
||||||
state = xcalloc(1, sizeof *state);
|
|
||||||
|
|
||||||
if (idx < args->count) {
|
|
||||||
value = &args->values[idx];
|
|
||||||
if (value->type == ARGS_COMMANDS) {
|
|
||||||
state->cmdlist = value->cmdlist;
|
|
||||||
state->cmdlist->references++;
|
|
||||||
return (state);
|
|
||||||
}
|
|
||||||
cmd = value->string;
|
|
||||||
} else {
|
|
||||||
if (default_command == NULL)
|
|
||||||
fatalx("argument out of range");
|
|
||||||
cmd = default_command;
|
cmd = default_command;
|
||||||
|
else {
|
||||||
|
value = &args->values[idx];
|
||||||
|
switch (value->type) {
|
||||||
|
case ARGS_COMMANDS:
|
||||||
|
return (cmd_parse_add_ref(value->cmd));
|
||||||
|
case ARGS_STRING:
|
||||||
|
cmd = value->string;
|
||||||
|
break;
|
||||||
|
case ARGS_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (cmd == NULL)
|
||||||
|
fatalx("argument out of range");
|
||||||
|
|
||||||
if (expand)
|
if (expand)
|
||||||
state->cmd = format_single_from_target(item, cmd);
|
expanded = format_single_from_target(item, cmd);
|
||||||
else
|
else
|
||||||
state->cmd = xstrdup(cmd);
|
expanded = xstrdup(cmd);
|
||||||
log_debug("%s: %s", __func__, state->cmd);
|
log_debug("%s: %s", __func__, expanded);
|
||||||
|
|
||||||
if (wait)
|
cmd_get_source(self, &file, &pi.line);
|
||||||
state->pi.item = item;
|
|
||||||
cmd_get_source(self, &file, &state->pi.line);
|
|
||||||
if (file != NULL)
|
if (file != NULL)
|
||||||
state->pi.file = xstrdup(file);
|
pi.file = file;
|
||||||
state->pi.c = tc;
|
pi.flags = cmd_get_parse_flags(self);
|
||||||
if (state->pi.c != NULL)
|
tree = cmd_parse_from_string(expanded, &pi, cause);
|
||||||
state->pi.c->references++;
|
free(expanded);
|
||||||
cmd_find_copy_state(&state->pi.fs, target);
|
return (tree);
|
||||||
|
|
||||||
return (state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return argument as command. */
|
|
||||||
struct cmd_list *
|
|
||||||
args_make_commands(struct args_command_state *state, int argc, char **argv,
|
|
||||||
char **error)
|
|
||||||
{
|
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
char *cmd, *new_cmd;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (state->cmdlist != NULL) {
|
|
||||||
if (argc == 0) {
|
|
||||||
state->cmdlist->references++;
|
|
||||||
return (state->cmdlist);
|
|
||||||
}
|
|
||||||
return (cmd_list_copy(state->cmdlist, argc, argv));
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = xstrdup(state->cmd);
|
|
||||||
log_debug("%s: %s", __func__, cmd);
|
|
||||||
cmd_log_argv(argc, argv, __func__);
|
|
||||||
for (i = 0; i < argc; i++) {
|
|
||||||
new_cmd = cmd_template_replace(cmd, argv[i], i + 1);
|
|
||||||
log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd);
|
|
||||||
free(cmd);
|
|
||||||
cmd = new_cmd;
|
|
||||||
}
|
|
||||||
log_debug("%s: %s", __func__, cmd);
|
|
||||||
|
|
||||||
pr = cmd_parse_from_string(cmd, &state->pi);
|
|
||||||
free(cmd);
|
|
||||||
switch (pr->status) {
|
|
||||||
case CMD_PARSE_ERROR:
|
|
||||||
*error = pr->error;
|
|
||||||
return (NULL);
|
|
||||||
case CMD_PARSE_SUCCESS:
|
|
||||||
return (pr->cmdlist);
|
|
||||||
}
|
|
||||||
fatalx("invalid parse return state");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free commands state. */
|
|
||||||
void
|
|
||||||
args_make_commands_free(struct args_command_state *state)
|
|
||||||
{
|
|
||||||
if (state->cmdlist != NULL)
|
|
||||||
cmd_list_free(state->cmdlist);
|
|
||||||
if (state->pi.c != NULL)
|
|
||||||
server_client_unref(state->pi.c);
|
|
||||||
free((void *)state->pi.file);
|
|
||||||
free(state->cmd);
|
|
||||||
free(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get prepared command. */
|
|
||||||
char *
|
|
||||||
args_make_commands_get_command(struct args_command_state *state)
|
|
||||||
{
|
|
||||||
struct cmd *first;
|
|
||||||
int n;
|
|
||||||
char *s;
|
|
||||||
|
|
||||||
if (state->cmdlist != NULL) {
|
|
||||||
first = cmd_list_first(state->cmdlist);
|
|
||||||
if (first == NULL)
|
|
||||||
return (xstrdup(""));
|
|
||||||
return (xstrdup(cmd_get_entry(first)->name));
|
|
||||||
}
|
|
||||||
n = strcspn(state->cmd, " ,");
|
|
||||||
xasprintf(&s, "%.*s", n, state->cmd);
|
|
||||||
return (s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get first value in argument. */
|
/* Get first value in argument. */
|
||||||
|
|||||||
50
cfg.c
50
cfg.c
@@ -87,20 +87,21 @@ start_cfg(void)
|
|||||||
if (cfg_quiet)
|
if (cfg_quiet)
|
||||||
flags = CMD_PARSE_QUIET;
|
flags = CMD_PARSE_QUIET;
|
||||||
for (i = 0; i < cfg_nfiles; i++)
|
for (i = 0; i < cfg_nfiles; i++)
|
||||||
load_cfg(cfg_files[i], c, NULL, NULL, flags, NULL);
|
load_cfg(cfg_files[i], NULL, NULL, flags, NULL);
|
||||||
|
|
||||||
cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
|
cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
load_cfg(const char *path, struct client *c, struct cmdq_item *item,
|
load_cfg(const char *path, struct cmdq_item *item,
|
||||||
struct cmd_find_state *current, int flags, struct cmdq_item **new_item)
|
struct cmd_find_state *current, int flags, struct cmdq_item **new_item)
|
||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
struct cmd_parse_input pi;
|
struct cmd_parse_input pi;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
struct cmdq_item *new_item0;
|
struct cmdq_item *new_item0;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
|
char *cause = NULL;
|
||||||
|
|
||||||
if (new_item != NULL)
|
if (new_item != NULL)
|
||||||
*new_item = NULL;
|
*new_item = NULL;
|
||||||
@@ -117,18 +118,16 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
|
|||||||
pi.flags = flags;
|
pi.flags = flags;
|
||||||
pi.file = path;
|
pi.file = path;
|
||||||
pi.line = 1;
|
pi.line = 1;
|
||||||
pi.item = item;
|
tree = cmd_parse_from_file(f, &pi, &cause);
|
||||||
pi.c = c;
|
|
||||||
|
|
||||||
pr = cmd_parse_from_file(f, &pi);
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
if (pr->status == CMD_PARSE_ERROR) {
|
if (tree == NULL) {
|
||||||
cfg_add_cause("%s", pr->error);
|
cfg_add_cause("%s", cause);
|
||||||
free(pr->error);
|
free(cause);
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
cmd_parse_log(__func__, tree);
|
||||||
if (flags & CMD_PARSE_PARSEONLY) {
|
if (flags & CMD_PARSE_PARSEONLY) {
|
||||||
cmd_list_free(pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +137,12 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
|
|||||||
state = cmdq_new_state(NULL, NULL, 0);
|
state = cmdq_new_state(NULL, NULL, 0);
|
||||||
cmdq_add_format(state, "current_file", "%s", pi.file);
|
cmdq_add_format(state, "current_file", "%s", pi.file);
|
||||||
|
|
||||||
new_item0 = cmdq_get_command(pr->cmdlist, state);
|
new_item0 = cmd_invoke_get(tree, state, NULL);
|
||||||
if (item != NULL)
|
if (item != NULL)
|
||||||
new_item0 = cmdq_insert_after(item, new_item0);
|
new_item0 = cmdq_insert_after(item, new_item0);
|
||||||
else
|
else
|
||||||
new_item0 = cmdq_append(NULL, new_item0);
|
new_item0 = cmdq_append(NULL, new_item0);
|
||||||
cmd_list_free(pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
|
|
||||||
if (new_item != NULL)
|
if (new_item != NULL)
|
||||||
@@ -153,13 +152,14 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
|
|||||||
|
|
||||||
int
|
int
|
||||||
load_cfg_from_buffer(const void *buf, size_t len, const char *path,
|
load_cfg_from_buffer(const void *buf, size_t len, const char *path,
|
||||||
struct client *c, struct cmdq_item *item, struct cmd_find_state *current,
|
struct cmdq_item *item, struct cmd_find_state *current, int flags,
|
||||||
int flags, struct cmdq_item **new_item)
|
struct cmdq_item **new_item)
|
||||||
{
|
{
|
||||||
struct cmd_parse_input pi;
|
struct cmd_parse_input pi;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
struct cmdq_item *new_item0;
|
struct cmdq_item *new_item0;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
|
char *cause = NULL;
|
||||||
|
|
||||||
if (new_item != NULL)
|
if (new_item != NULL)
|
||||||
*new_item = NULL;
|
*new_item = NULL;
|
||||||
@@ -170,17 +170,15 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
|
|||||||
pi.flags = flags;
|
pi.flags = flags;
|
||||||
pi.file = path;
|
pi.file = path;
|
||||||
pi.line = 1;
|
pi.line = 1;
|
||||||
pi.item = item;
|
tree = cmd_parse_from_buffer(buf, len, &pi, &cause);
|
||||||
pi.c = c;
|
if (tree == NULL) {
|
||||||
|
cfg_add_cause("%s", cause);
|
||||||
pr = cmd_parse_from_buffer(buf, len, &pi);
|
free(cause);
|
||||||
if (pr->status == CMD_PARSE_ERROR) {
|
|
||||||
cfg_add_cause("%s", pr->error);
|
|
||||||
free(pr->error);
|
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
cmd_parse_log(__func__, tree);
|
||||||
if (flags & CMD_PARSE_PARSEONLY) {
|
if (flags & CMD_PARSE_PARSEONLY) {
|
||||||
cmd_list_free(pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,12 +188,12 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
|
|||||||
state = cmdq_new_state(NULL, NULL, 0);
|
state = cmdq_new_state(NULL, NULL, 0);
|
||||||
cmdq_add_format(state, "current_file", "%s", pi.file);
|
cmdq_add_format(state, "current_file", "%s", pi.file);
|
||||||
|
|
||||||
new_item0 = cmdq_get_command(pr->cmdlist, state);
|
new_item0 = cmd_invoke_get(tree, state, NULL);
|
||||||
if (item != NULL)
|
if (item != NULL)
|
||||||
new_item0 = cmdq_insert_after(item, new_item0);
|
new_item0 = cmdq_insert_after(item, new_item0);
|
||||||
else
|
else
|
||||||
new_item0 = cmdq_append(NULL, new_item0);
|
new_item0 = cmdq_append(NULL, new_item0);
|
||||||
cmd_list_free(pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
|
|
||||||
if (new_item != NULL)
|
if (new_item != NULL)
|
||||||
|
|||||||
39
client.c
39
client.c
@@ -69,6 +69,25 @@ static void client_dispatch_attached(struct imsg *);
|
|||||||
static void client_dispatch_wait(struct imsg *);
|
static void client_dispatch_wait(struct imsg *);
|
||||||
static const char *client_exit_message(void);
|
static const char *client_exit_message(void);
|
||||||
|
|
||||||
|
/* Does any command have a flag? */
|
||||||
|
static int
|
||||||
|
client_command_has_flag(int argc, char **argv, int flag)
|
||||||
|
{
|
||||||
|
struct args_value *values;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
int found;
|
||||||
|
|
||||||
|
values = args_from_vector(argc, argv);
|
||||||
|
tree = cmd_parse_from_arguments(values, argc, NULL);
|
||||||
|
found = cmd_parse_any_have(tree, flag);
|
||||||
|
|
||||||
|
cmd_parse_free(tree);
|
||||||
|
args_free_values(values, argc);
|
||||||
|
free(values);
|
||||||
|
|
||||||
|
return (found);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get server create lock. If already held then server start is happening in
|
* Get server create lock. If already held then server start is happening in
|
||||||
* another client, so block until the lock is released and return -2 to
|
* another client, so block until the lock is released and return -2 to
|
||||||
@@ -232,7 +251,6 @@ int
|
|||||||
client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
||||||
int feat)
|
int feat)
|
||||||
{
|
{
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
struct msg_command *data;
|
struct msg_command *data;
|
||||||
int fd, i;
|
int fd, i;
|
||||||
const char *ttynam, *termname, *cwd;
|
const char *ttynam, *termname, *cwd;
|
||||||
@@ -242,7 +260,6 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
|||||||
size_t size;
|
size_t size;
|
||||||
char **caps = NULL, *cause;
|
char **caps = NULL, *cause;
|
||||||
u_int ncaps = 0;
|
u_int ncaps = 0;
|
||||||
struct args_value *values;
|
|
||||||
|
|
||||||
/* Set up the initial command. */
|
/* Set up the initial command. */
|
||||||
if (shell_command != NULL) {
|
if (shell_command != NULL) {
|
||||||
@@ -253,22 +270,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
|||||||
flags |= CLIENT_STARTSERVER;
|
flags |= CLIENT_STARTSERVER;
|
||||||
} else {
|
} else {
|
||||||
msg = MSG_COMMAND;
|
msg = MSG_COMMAND;
|
||||||
|
if (client_command_has_flag(argc, argv, CMD_STARTSERVER))
|
||||||
/*
|
flags |= CLIENT_STARTSERVER;
|
||||||
* It's annoying parsing the command string twice (in client
|
|
||||||
* and later in server) but it is necessary to get the start
|
|
||||||
* server flag.
|
|
||||||
*/
|
|
||||||
values = args_from_vector(argc, argv);
|
|
||||||
pr = cmd_parse_from_arguments(values, argc, NULL);
|
|
||||||
if (pr->status == CMD_PARSE_SUCCESS) {
|
|
||||||
if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER))
|
|
||||||
flags |= CLIENT_STARTSERVER;
|
|
||||||
cmd_list_free(pr->cmdlist);
|
|
||||||
} else
|
|
||||||
free(pr->error);
|
|
||||||
args_free_values(values, argc);
|
|
||||||
free(values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create client process structure (starts logging). */
|
/* Create client process structure (starts logging). */
|
||||||
|
|||||||
@@ -91,10 +91,8 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
|
|||||||
wp = target.wp;
|
wp = target.wp;
|
||||||
|
|
||||||
if (wl != NULL) {
|
if (wl != NULL) {
|
||||||
if (wp != NULL) {
|
if (wp != NULL)
|
||||||
window_redraw_active_switch(wp->window, wp);
|
|
||||||
window_set_active_pane(wp->window, wp, 1);
|
window_set_active_pane(wp->window, wp, 1);
|
||||||
}
|
|
||||||
session_set_current(s, wl);
|
session_set_current(s, wl);
|
||||||
if (wp != NULL)
|
if (wp != NULL)
|
||||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ cmd_bind_key_args_parse(__unused struct args *args, __unused u_int idx,
|
|||||||
static enum cmd_retval
|
static enum cmd_retval
|
||||||
cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
|
cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
|
||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
key_code key;
|
key_code key;
|
||||||
const char *tablename, *note = args_get(args, 'N');
|
const char *tablename, *note = args_get(args, 'N'), *file;
|
||||||
struct cmd_parse_result *pr;
|
int repeat;
|
||||||
int repeat;
|
struct args_value *value;
|
||||||
struct args_value *value;
|
u_int count = args_count(args);
|
||||||
u_int count = args_count(args);
|
struct cmd_parse_tree *cmd;
|
||||||
|
struct cmd_parse_input pi;
|
||||||
|
|
||||||
key = key_string_lookup_string(args_string(args, 0));
|
key = key_string_lookup_string(args_string(args, 0));
|
||||||
if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
|
if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
|
||||||
@@ -83,25 +84,18 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
|
|
||||||
value = args_value(args, 1);
|
value = args_value(args, 1);
|
||||||
if (count == 2 && value->type == ARGS_COMMANDS) {
|
if (count == 2 && value->type == ARGS_COMMANDS) {
|
||||||
key_bindings_add(tablename, key, note, repeat, value->cmdlist);
|
cmd = cmd_parse_add_ref(value->cmd);
|
||||||
value->cmdlist->references++;
|
key_bindings_add(tablename, key, note, repeat, cmd);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == 2)
|
memset(&pi, 0, sizeof pi);
|
||||||
pr = cmd_parse_from_string(args_string(args, 1), NULL);
|
cmd_get_source(self, &file, &pi.line);
|
||||||
else {
|
if (file != NULL)
|
||||||
pr = cmd_parse_from_arguments(args_values(args) + 1, count - 1,
|
pi.file = file;
|
||||||
NULL);
|
pi.flags = cmd_get_parse_flags(self);
|
||||||
}
|
|
||||||
switch (pr->status) {
|
cmd = cmd_parse_from_arguments(args_values(args) + 1, count - 1, &pi);
|
||||||
case CMD_PARSE_ERROR:
|
key_bindings_add(tablename, key, note, repeat, cmd);
|
||||||
cmdq_error(item, "%s", pr->error);
|
|
||||||
free(pr->error);
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
case CMD_PARSE_SUCCESS:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
key_bindings_add(tablename, key, note, repeat, pr->cmdlist);
|
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-capture-pane.c,v 1.69 2026/09/08 07:31:59 nicm Exp $ */
|
/* $OpenBSD: cmd-capture-pane.c,v 1.68 2026/07/20 11:16:33 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
|
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
|
||||||
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
|
|||||||
.name = "capture-pane",
|
.name = "capture-pane",
|
||||||
.alias = "capturep",
|
.alias = "capturep",
|
||||||
|
|
||||||
.args = { "ab:CeE:FHIJLMNpPqRS:Tt:", 0, 0, NULL },
|
.args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
|
||||||
.usage = "[-aCeFHIJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
|
.usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||||
"[-S start-line] " CMD_TARGET_PANE_USAGE,
|
"[-S start-line] " CMD_TARGET_PANE_USAGE,
|
||||||
|
|
||||||
.target = { 't', CMD_FIND_PANE, 0 },
|
.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[0] = line[i];
|
||||||
tmp[1] = '\0';
|
tmp[1] = '\0';
|
||||||
} else
|
} else
|
||||||
snprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
|
xsnprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
|
||||||
buf = cmd_capture_pane_append(buf, len, tmp,
|
buf = cmd_capture_pane_append(buf, len, tmp,
|
||||||
strlen(tmp));
|
strlen(tmp));
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
struct grid_cell *gc = NULL;
|
struct grid_cell *gc = NULL;
|
||||||
struct window_mode_entry *wme;
|
struct window_mode_entry *wme;
|
||||||
int n, join_lines, number_lines, flags = 0;
|
int n, join_lines, number_lines, flags = 0;
|
||||||
int show_flags, show_time, hyperlinks;
|
int show_flags, hyperlinks;
|
||||||
u_int *links = NULL, nlinks = 0;
|
u_int *links = NULL, nlinks = 0;
|
||||||
u_int i, sx, top, bottom, tmp;
|
u_int i, sx, top, bottom, tmp;
|
||||||
char *cause, *buf = NULL, *line, b[64], *cp;
|
char *cause, *buf = NULL, *line, b[64], *cp;
|
||||||
@@ -342,7 +342,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
flags |= GRID_STRING_TRIM_SPACES;
|
flags |= GRID_STRING_TRIM_SPACES;
|
||||||
number_lines = args_has(args, 'L');
|
number_lines = args_has(args, 'L');
|
||||||
show_flags = args_has(args, 'F');
|
show_flags = args_has(args, 'F');
|
||||||
show_time = args_has(args, 'I');
|
|
||||||
hyperlinks = args_has(args, 'H');
|
hyperlinks = args_has(args, 'H');
|
||||||
if (hyperlinks)
|
if (hyperlinks)
|
||||||
links = xreallocarray(NULL, gd->sx, sizeof *links);
|
links = xreallocarray(NULL, gd->sx, sizeof *links);
|
||||||
@@ -359,7 +358,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
free(line);
|
free(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
gl = grid_peek_line(gd, i);
|
|
||||||
|
|
||||||
if (number_lines) {
|
if (number_lines) {
|
||||||
if (i >= gd->hsize)
|
if (i >= gd->hsize)
|
||||||
@@ -370,16 +368,11 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
if (n >= 0)
|
if (n >= 0)
|
||||||
buf = cmd_capture_pane_append(buf, len, b, n);
|
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) {
|
if (show_flags) {
|
||||||
cp = b;
|
cp = b;
|
||||||
*cp = '\0';
|
*cp = '\0';
|
||||||
|
|
||||||
|
gl = grid_peek_line(gd, i);
|
||||||
if (gl->flags & GRID_LINE_DEAD)
|
if (gl->flags & GRID_LINE_DEAD)
|
||||||
*cp++ = 'D';
|
*cp++ = 'D';
|
||||||
if (gl->flags & GRID_LINE_HYPERLINK)
|
if (gl->flags & GRID_LINE_HYPERLINK)
|
||||||
@@ -400,6 +393,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
}
|
}
|
||||||
buf = cmd_capture_pane_append(buf, len, line, linelen);
|
buf = cmd_capture_pane_append(buf, len, line, linelen);
|
||||||
|
|
||||||
|
gl = grid_peek_line(gd, i);
|
||||||
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
|
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
|
||||||
buf[(*len)++] = '\n';
|
buf[(*len)++] = '\n';
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ struct cmd_command_prompt_prompt {
|
|||||||
|
|
||||||
struct cmd_command_prompt_cdata {
|
struct cmd_command_prompt_cdata {
|
||||||
struct cmdq_item *item;
|
struct cmdq_item *item;
|
||||||
struct args_command_state *state;
|
struct cmd_parse_tree *tree;
|
||||||
|
|
||||||
int flags;
|
int flags;
|
||||||
enum prompt_type prompt_type;
|
enum prompt_type prompt_type;
|
||||||
@@ -85,13 +85,13 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
const char *type, *s, *input;
|
const char *type, *s, *input, *cmd;
|
||||||
struct cmd_command_prompt_cdata *cdata;
|
struct cmd_command_prompt_cdata *cdata;
|
||||||
char *tmp, *prompts, *prompt, *next_prompt;
|
char *tmp, *prompts, *prompt, *next_prompt;
|
||||||
char *inputs = NULL, *next_input;
|
char *inputs = NULL, *next_input, *cause = NULL;
|
||||||
struct window_pane *wp = target->wp;
|
struct window_pane *wp = target->wp;
|
||||||
u_int count = args_count(args);
|
u_int count = args_count(args);
|
||||||
int wait = !args_has(args, 'b'), space = 1;
|
int wait = !args_has(args, 'b'), space = 1, n;
|
||||||
int pane = args_has(args, 'P');
|
int pane = args_has(args, 'P');
|
||||||
|
|
||||||
if (pane) {
|
if (pane) {
|
||||||
@@ -107,14 +107,22 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cdata->item = item;
|
cdata->item = item;
|
||||||
if (pane)
|
if (pane)
|
||||||
cdata->wp = wp;
|
cdata->wp = wp;
|
||||||
cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait,
|
if (count != 0) {
|
||||||
args_has(args, 'F'));
|
cdata->tree = args_to_commands(self, item, 0, NULL,
|
||||||
|
args_has(args, 'F'), &cause);
|
||||||
|
if (cdata->tree == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
|
free(cdata);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((s = args_get(args, 'p')) == NULL) {
|
if ((s = args_get(args, 'p')) == NULL) {
|
||||||
if (count != 0) {
|
if (count != 0) {
|
||||||
tmp = args_make_commands_get_command(cdata->state);
|
cmd = args_string(args, 0);
|
||||||
xasprintf(&prompts, "(%s)", tmp);
|
n = strcspn(cmd, " ,");
|
||||||
free(tmp);
|
xasprintf(&prompts, "(%.*s)", n, cmd);
|
||||||
} else {
|
} else {
|
||||||
prompts = xstrdup(":");
|
prompts = xstrdup(":");
|
||||||
space = 0;
|
space = 0;
|
||||||
@@ -201,8 +209,11 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
|||||||
struct cmd_command_prompt_cdata *cdata = data;
|
struct cmd_command_prompt_cdata *cdata = data;
|
||||||
char *error;
|
char *error;
|
||||||
struct cmdq_item *item = cdata->item, *new_item;
|
struct cmdq_item *item = cdata->item, *new_item;
|
||||||
struct cmd_list *cmdlist;
|
struct cmdq_state *cs = NULL;
|
||||||
struct cmd_command_prompt_prompt *prompt;
|
struct cmd_command_prompt_prompt *prompt;
|
||||||
|
struct cmd_invoke_input ci = { 0 };
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmd_parse_input pi = { 0 };
|
||||||
int argc = 0;
|
int argc = 0;
|
||||||
char **argv = NULL;
|
char **argv = NULL;
|
||||||
|
|
||||||
@@ -235,18 +246,35 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
|||||||
cdata->argv = cmd_copy_argv(argc, argv);
|
cdata->argv = cmd_copy_argv(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdlist = args_make_commands(cdata->state, argc, argv, &error);
|
if (item != NULL)
|
||||||
if (cmdlist == NULL) {
|
cs = cmdq_get_state(item);
|
||||||
cmdq_append(c, cmdq_get_error(error));
|
|
||||||
free(error);
|
if (cdata->tree != NULL) {
|
||||||
} else if (item == NULL) {
|
/* Explicit body: prompt inputs become the template argv. */
|
||||||
new_item = cmdq_get_command(cmdlist, NULL);
|
ci.argc = argc;
|
||||||
cmdq_append(c, new_item);
|
ci.argv = argv;
|
||||||
cmd_list_free(cmdlist);
|
new_item = cmd_invoke_get(cdata->tree, cs, &ci);
|
||||||
} else {
|
if (item == NULL)
|
||||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
cmdq_append(c, new_item);
|
||||||
cmdq_insert_after(item, new_item);
|
else
|
||||||
cmd_list_free(cmdlist);
|
cmdq_insert_after(item, new_item);
|
||||||
|
} else if (argc > 0 && argv[0] != NULL) {
|
||||||
|
/* No body: parse the submitted text as command language. */
|
||||||
|
tree = cmd_parse_from_string(argv[0], &pi, &error);
|
||||||
|
if (tree == NULL) {
|
||||||
|
if (item == NULL)
|
||||||
|
cmdq_append(c, cmdq_get_error(error));
|
||||||
|
else
|
||||||
|
cmdq_error(item, "%s", error);
|
||||||
|
free(error);
|
||||||
|
} else {
|
||||||
|
new_item = cmd_invoke_get(tree, cs, NULL);
|
||||||
|
cmd_parse_free(tree);
|
||||||
|
if (item == NULL)
|
||||||
|
cmdq_append(c, new_item);
|
||||||
|
else
|
||||||
|
cmdq_insert_after(item, new_item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cmd_free_argv(argc, argv);
|
cmd_free_argv(argc, argv);
|
||||||
|
|
||||||
@@ -283,6 +311,6 @@ cmd_command_prompt_free(void *data)
|
|||||||
}
|
}
|
||||||
free(cdata->prompts);
|
free(cdata->prompts);
|
||||||
cmd_free_argv(cdata->argc, cdata->argv);
|
cmd_free_argv(cdata->argc, cdata->argv);
|
||||||
args_make_commands_free(cdata->state);
|
cmd_parse_free(cdata->tree);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const struct cmd_entry cmd_confirm_before_entry = {
|
|||||||
|
|
||||||
struct cmd_confirm_before_data {
|
struct cmd_confirm_before_data {
|
||||||
struct cmdq_item *item;
|
struct cmdq_item *item;
|
||||||
struct cmd_list *cmdlist;
|
struct cmd_parse_tree *tree;
|
||||||
u_char confirm_key;
|
u_char confirm_key;
|
||||||
int default_yes;
|
int default_yes;
|
||||||
};
|
};
|
||||||
@@ -70,13 +70,15 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct cmd_confirm_before_data *cdata;
|
struct cmd_confirm_before_data *cdata;
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
char *new_prompt;
|
char *new_prompt, *cause = NULL;
|
||||||
const char *confirm_key, *prompt, *cmd;
|
const char *confirm_key, *prompt, *cmd;
|
||||||
int wait = !args_has(args, 'b');
|
int wait = !args_has(args, 'b'), n;
|
||||||
|
|
||||||
cdata = xcalloc(1, sizeof *cdata);
|
cdata = xcalloc(1, sizeof *cdata);
|
||||||
cdata->cmdlist = args_make_commands_now(self, item, 0, 1);
|
cdata->tree = args_to_commands(self, item, 0, NULL, 1, &cause);
|
||||||
if (cdata->cmdlist == NULL) {
|
if (cdata->tree == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
@@ -92,8 +94,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cdata->confirm_key = confirm_key[0];
|
cdata->confirm_key = confirm_key[0];
|
||||||
else {
|
else {
|
||||||
cmdq_error(item, "invalid confirm key");
|
cmdq_error(item, "invalid confirm key");
|
||||||
cmd_list_free(cdata->cmdlist);
|
cmd_confirm_before_free(cdata);
|
||||||
free(cdata);
|
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +104,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
if ((prompt = args_get(args, 'p')) != NULL)
|
if ((prompt = args_get(args, 'p')) != NULL)
|
||||||
xasprintf(&new_prompt, "%s ", prompt);
|
xasprintf(&new_prompt, "%s ", prompt);
|
||||||
else {
|
else {
|
||||||
cmd = cmd_get_entry(cmd_list_first(cdata->cmdlist))->name;
|
cmd = args_string(args, 0);
|
||||||
xasprintf(&new_prompt, "Confirm '%s'? (%c/n) ", cmd,
|
n = strcspn(cmd, " ,");
|
||||||
|
xasprintf(&new_prompt, "Confirm '%.*s'? (%c/n) ", n, cmd,
|
||||||
cdata->confirm_key);
|
cdata->confirm_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +126,7 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
|
|||||||
{
|
{
|
||||||
struct cmd_confirm_before_data *cdata = data;
|
struct cmd_confirm_before_data *cdata = data;
|
||||||
struct cmdq_item *item = cdata->item, *new_item;
|
struct cmdq_item *item = cdata->item, *new_item;
|
||||||
|
struct cmdq_state *cs;
|
||||||
int retcode = 1;
|
int retcode = 1;
|
||||||
|
|
||||||
if (c->flags & CLIENT_DEAD)
|
if (c->flags & CLIENT_DEAD)
|
||||||
@@ -136,11 +139,11 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
|
|||||||
retcode = 0;
|
retcode = 0;
|
||||||
|
|
||||||
if (item == NULL) {
|
if (item == NULL) {
|
||||||
new_item = cmdq_get_command(cdata->cmdlist, NULL);
|
new_item = cmd_invoke_get(cdata->tree, NULL, NULL);
|
||||||
cmdq_append(c, new_item);
|
cmdq_append(c, new_item);
|
||||||
} else {
|
} else {
|
||||||
new_item = cmdq_get_command(cdata->cmdlist,
|
cs = cmdq_get_state(item);
|
||||||
cmdq_get_state(item));
|
new_item = cmd_invoke_get(cdata->tree, cs, NULL);
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +162,6 @@ cmd_confirm_before_free(void *data)
|
|||||||
{
|
{
|
||||||
struct cmd_confirm_before_data *cdata = data;
|
struct cmd_confirm_before_data *cdata = data;
|
||||||
|
|
||||||
cmd_list_free(cdata->cmdlist);
|
cmd_parse_free(cdata->tree);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-display-message.c,v 1.66 2026/09/08 08:33:10 nicm Exp $ */
|
/* $OpenBSD: cmd-display-message.c,v 1.65 2026/02/23 08:46:57 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||||
@@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
|
|||||||
.name = "display-message",
|
.name = "display-message",
|
||||||
.alias = "display",
|
.alias = "display",
|
||||||
|
|
||||||
.args = { "aCc:d:jlINpt:F:v", 0, 1, NULL },
|
.args = { "aCc:d:lINpt:F:v", 0, 1, NULL },
|
||||||
.usage = "[-aCIjlNpv] [-c target-client] [-d delay] [-F format] "
|
.usage = "[-aCIlNpv] [-c target-client] [-d delay] [-F format] "
|
||||||
CMD_TARGET_PANE_USAGE " [message]",
|
CMD_TARGET_PANE_USAGE " [message]",
|
||||||
|
|
||||||
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
||||||
@@ -67,15 +67,14 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct winlink *wl = target->wl;
|
struct winlink *wl = target->wl;
|
||||||
struct window_pane *wp = target->wp;
|
struct window_pane *wp = target->wp;
|
||||||
const char *template;
|
const char *template;
|
||||||
char *msg, *cause = NULL;
|
char *msg, *cause;
|
||||||
int delay = -1, flags, Nflag = args_has(args, 'N');
|
int delay = -1, flags, Nflag = args_has(args, 'N');
|
||||||
int Cflag = args_has(args, 'C');
|
int Cflag = args_has(args, 'C');
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
u_int count = args_count(args);
|
u_int count = args_count(args);
|
||||||
struct evbuffer *evb;
|
struct evbuffer *evb;
|
||||||
struct json_node *jn;
|
|
||||||
|
|
||||||
if (args_has(args, 'I') && !args_has(args, 'j')) {
|
if (args_has(args, 'I')) {
|
||||||
if (wp == NULL)
|
if (wp == NULL)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
switch (window_pane_start_input(wp, item, &cause)) {
|
switch (window_pane_start_input(wp, item, &cause)) {
|
||||||
@@ -108,9 +107,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
template = args_string(args, 0);
|
template = args_string(args, 0);
|
||||||
else
|
else
|
||||||
template = args_get(args, 'F');
|
template = args_get(args, 'F');
|
||||||
if (args_has(args, 'j') && template == NULL)
|
if (template == NULL)
|
||||||
template = "";
|
|
||||||
else if (template == NULL)
|
|
||||||
template = DISPLAY_MESSAGE_TEMPLATE;
|
template = DISPLAY_MESSAGE_TEMPLATE;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -132,7 +129,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
|
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
|
||||||
format_defaults(ft, c, s, wl, wp);
|
format_defaults(ft, c, s, wl, wp);
|
||||||
|
|
||||||
if (args_has(args, 'a') && !args_has(args, 'j')) {
|
if (args_has(args, 'a')) {
|
||||||
format_each(ft, cmd_display_message_each, item);
|
format_each(ft, cmd_display_message_each, item);
|
||||||
format_free(ft);
|
format_free(ft);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
@@ -142,19 +139,6 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
msg = xstrdup(template);
|
msg = xstrdup(template);
|
||||||
else
|
else
|
||||||
msg = format_expand_time(ft, template);
|
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)
|
if (cmdq_get_client(item) == NULL)
|
||||||
cmdq_error(item, "%s", msg);
|
cmdq_error(item, "%s", msg);
|
||||||
|
|||||||
18
cmd-find.c
18
cmd-find.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-find.c,v 1.88 2026/09/07 12:05:12 nicm Exp $ */
|
/* $OpenBSD: cmd-find.c,v 1.87 2026/07/17 12:42:51 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -388,11 +388,9 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
|
|||||||
|
|
||||||
/* Try as an offset. */
|
/* Try as an offset. */
|
||||||
if (!exact && (window[0] == '+' || window[0] == '-')) {
|
if (!exact && (window[0] == '+' || window[0] == '-')) {
|
||||||
if (window[1] != '\0') {
|
if (window[1] != '\0')
|
||||||
n = strtonum(window + 1, 1, INT_MAX, &errstr);
|
n = strtonum(window + 1, 1, INT_MAX, NULL);
|
||||||
if (errstr != NULL)
|
else
|
||||||
return (-1);
|
|
||||||
} else
|
|
||||||
n = 1;
|
n = 1;
|
||||||
s = fs->s;
|
s = fs->s;
|
||||||
if (fs->flags & CMD_FIND_WINDOW_INDEX) {
|
if (fs->flags & CMD_FIND_WINDOW_INDEX) {
|
||||||
@@ -628,11 +626,9 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
|
|||||||
|
|
||||||
/* Try as an offset. */
|
/* Try as an offset. */
|
||||||
if (pane[0] == '+' || pane[0] == '-') {
|
if (pane[0] == '+' || pane[0] == '-') {
|
||||||
if (pane[1] != '\0') {
|
if (pane[1] != '\0')
|
||||||
n = strtonum(pane + 1, 1, INT_MAX, &errstr);
|
n = strtonum(pane + 1, 1, INT_MAX, NULL);
|
||||||
if (errstr != NULL)
|
else
|
||||||
return (-1);
|
|
||||||
} else
|
|
||||||
n = 1;
|
n = 1;
|
||||||
wp = fs->w->active;
|
wp = fs->w->active;
|
||||||
if (pane[0] == '+')
|
if (pane[0] == '+')
|
||||||
|
|||||||
@@ -53,11 +53,12 @@ const struct cmd_entry cmd_if_shell_entry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct cmd_if_shell_data {
|
struct cmd_if_shell_data {
|
||||||
struct args_command_state *cmd_if;
|
struct cmd_parse_tree *cmd_if;
|
||||||
struct args_command_state *cmd_else;
|
struct cmd_parse_tree *cmd_else;
|
||||||
|
|
||||||
struct client *client;
|
|
||||||
struct cmdq_item *item;
|
struct client *client;
|
||||||
|
struct cmdq_item *item;
|
||||||
};
|
};
|
||||||
|
|
||||||
static enum args_parse_type
|
static enum args_parse_type
|
||||||
@@ -76,46 +77,58 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
struct cmd_if_shell_data *cdata;
|
struct cmd_if_shell_data *cdata;
|
||||||
struct cmdq_item *new_item;
|
struct cmdq_item *new_item;
|
||||||
char *shellcmd;
|
struct cmd_parse_tree *tree;
|
||||||
|
char *shellcmd, *cause = NULL;
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct session *s = target->s;
|
struct session *s = target->s;
|
||||||
struct cmd_list *cmdlist;
|
u_int count = args_count(args), idx;
|
||||||
u_int count = args_count(args);
|
|
||||||
int wait = !args_has(args, 'b');
|
int wait = !args_has(args, 'b');
|
||||||
|
|
||||||
shellcmd = format_single_from_target(item, args_string(args, 0));
|
shellcmd = format_single_from_target(item, args_string(args, 0));
|
||||||
if (args_has(args, 'F')) {
|
if (args_has(args, 'F')) {
|
||||||
if (*shellcmd != '0' && *shellcmd != '\0')
|
if (*shellcmd != '0' && *shellcmd != '\0')
|
||||||
cmdlist = args_make_commands_now(self, item, 1, 0);
|
idx = 1;
|
||||||
else if (count == 3)
|
else if (count == 3)
|
||||||
cmdlist = args_make_commands_now(self, item, 2, 0);
|
idx = 2;
|
||||||
else {
|
else {
|
||||||
free(shellcmd);
|
free(shellcmd);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
free(shellcmd);
|
free(shellcmd);
|
||||||
if (cmdlist == NULL)
|
|
||||||
|
tree = args_to_commands(self, item, idx, NULL, 0, &cause);
|
||||||
|
if (tree == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
}
|
||||||
|
new_item = cmd_invoke_get(tree, cmdq_get_state(item), NULL);
|
||||||
|
cmd_parse_free(tree);
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
cmd_list_free(cmdlist);
|
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
cdata = xcalloc(1, sizeof *cdata);
|
cdata = xcalloc(1, sizeof *cdata);
|
||||||
|
cdata->cmd_if = args_to_commands(self, item, 1, NULL, 0, &cause);
|
||||||
cdata->cmd_if = args_make_commands_prepare(self, item, 1, NULL, wait,
|
if (cdata->cmd_if == NULL) {
|
||||||
0);
|
cmdq_error(item, "%s", cause);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
if (count == 3) {
|
if (count == 3) {
|
||||||
cdata->cmd_else = args_make_commands_prepare(self, item, 2,
|
cdata->cmd_else = args_to_commands(self, item, 2, NULL, 0,
|
||||||
NULL, wait, 0);
|
&cause);
|
||||||
|
if (cdata->cmd_else == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wait) {
|
if (!wait)
|
||||||
|
cdata->client = tc;
|
||||||
|
else {
|
||||||
cdata->client = cmdq_get_client(item);
|
cdata->client = cmdq_get_client(item);
|
||||||
cdata->item = item;
|
cdata->item = item;
|
||||||
} else
|
}
|
||||||
cdata->client = tc;
|
|
||||||
if (cdata->client != NULL)
|
if (cdata->client != NULL)
|
||||||
cdata->client->references++;
|
cdata->client->references++;
|
||||||
|
|
||||||
@@ -124,15 +137,19 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1,
|
cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1,
|
||||||
-1) == NULL) {
|
-1) == NULL) {
|
||||||
cmdq_error(item, "failed to run command: %s", shellcmd);
|
cmdq_error(item, "failed to run command: %s", shellcmd);
|
||||||
free(shellcmd);
|
goto fail;
|
||||||
cmd_if_shell_free(cdata);
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
}
|
}
|
||||||
free(shellcmd);
|
free(shellcmd);
|
||||||
|
|
||||||
if (!wait)
|
if (!wait)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
return (CMD_RETURN_WAIT);
|
return (CMD_RETURN_WAIT);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
free(cause);
|
||||||
|
free(shellcmd);
|
||||||
|
cmd_if_shell_free(cdata);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -141,35 +158,23 @@ cmd_if_shell_callback(struct job *job)
|
|||||||
struct cmd_if_shell_data *cdata = job_get_data(job);
|
struct cmd_if_shell_data *cdata = job_get_data(job);
|
||||||
struct client *c = cdata->client;
|
struct client *c = cdata->client;
|
||||||
struct cmdq_item *item = cdata->item, *new_item;
|
struct cmdq_item *item = cdata->item, *new_item;
|
||||||
struct args_command_state *state;
|
struct cmd_parse_tree *tree;
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
char *error;
|
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
status = job_get_status(job);
|
status = job_get_status(job);
|
||||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
||||||
state = cdata->cmd_else;
|
tree = cdata->cmd_else;
|
||||||
else
|
else
|
||||||
state = cdata->cmd_if;
|
tree = cdata->cmd_if;
|
||||||
if (state == NULL)
|
if (tree == NULL)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
cmdlist = args_make_commands(state, 0, NULL, &error);
|
if (item == NULL) {
|
||||||
if (cmdlist == NULL) {
|
new_item = cmd_invoke_get(tree, NULL, NULL);
|
||||||
if (cdata->item == NULL) {
|
|
||||||
*error = toupper((u_char)*error);
|
|
||||||
status_message_set(c, -1, 1, 0, 0, "%s", error);
|
|
||||||
} else
|
|
||||||
cmdq_error(cdata->item, "%s", error);
|
|
||||||
free(error);
|
|
||||||
} else if (item == NULL) {
|
|
||||||
new_item = cmdq_get_command(cmdlist, NULL);
|
|
||||||
cmdq_append(c, new_item);
|
cmdq_append(c, new_item);
|
||||||
cmd_list_free(cmdlist);
|
|
||||||
} else {
|
} else {
|
||||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
new_item = cmd_invoke_get(tree, cmdq_get_state(item), NULL);
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
cmd_list_free(cmdlist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@@ -186,8 +191,8 @@ cmd_if_shell_free(void *data)
|
|||||||
server_client_unref(cdata->client);
|
server_client_unref(cdata->client);
|
||||||
|
|
||||||
if (cdata->cmd_else != NULL)
|
if (cdata->cmd_else != NULL)
|
||||||
args_make_commands_free(cdata->cmd_else);
|
cmd_parse_free(cdata->cmd_else);
|
||||||
args_make_commands_free(cdata->cmd_if);
|
if (cdata->cmd_if != NULL)
|
||||||
|
cmd_parse_free(cdata->cmd_if);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
}
|
}
|
||||||
|
|||||||
594
cmd-invoke.c
Normal file
594
cmd-invoke.c
Normal file
@@ -0,0 +1,594 @@
|
|||||||
|
/* $OpenBSD$ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Nicholas Marriott <nicm@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "tmux.h"
|
||||||
|
#include "tmux-parser.h"
|
||||||
|
|
||||||
|
/* One frame in the parse tree stack. */
|
||||||
|
struct cmd_invoke_frame {
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmd_parse_node *node;
|
||||||
|
struct cmd_parse_node *next;
|
||||||
|
struct cmd_parse_node *end;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Shared state for a parsed command tree invocation. */
|
||||||
|
struct cmd_invoke_state {
|
||||||
|
u_int references;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmd_parse_tree *current_tree;
|
||||||
|
|
||||||
|
struct cmd_invoke_frame *stack;
|
||||||
|
u_int nstack;
|
||||||
|
|
||||||
|
enum cmd_retval last;
|
||||||
|
int have_last;
|
||||||
|
|
||||||
|
int argc;
|
||||||
|
char **argv;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void cmd_invoke_push(struct cmd_invoke_state *,
|
||||||
|
struct cmd_parse_tree *, struct cmd_parse_node *,
|
||||||
|
struct cmd_parse_node *, struct cmd_parse_node *);
|
||||||
|
static struct cmd_parse_tree *cmd_invoke_tree(struct cmd_invoke_state *);
|
||||||
|
static struct cmd_parse_node *cmd_invoke_next(struct cmd_invoke_state *);
|
||||||
|
static void cmd_invoke_skip_sequence(struct cmd_invoke_state *);
|
||||||
|
static int cmd_invoke_expand_string(struct cmdq_item *,
|
||||||
|
struct cmd_invoke_state *, struct cmd_parse_node *,
|
||||||
|
char **);
|
||||||
|
static int cmd_invoke_assignment(struct cmdq_item *,
|
||||||
|
struct cmd_invoke_state *, struct cmd_parse_node *);
|
||||||
|
static int cmd_invoke_if(struct cmdq_item *, struct cmd_invoke_state *,
|
||||||
|
struct cmd_parse_node *);
|
||||||
|
static struct cmd *cmd_invoke_build_command(struct cmdq_item *,
|
||||||
|
struct cmd_invoke_state *, struct cmd_parse_node *);
|
||||||
|
static int cmd_invoke_read_only(struct cmdq_item *, struct cmd *);
|
||||||
|
|
||||||
|
/* Push a node's child range onto the traversal stack. */
|
||||||
|
static void
|
||||||
|
cmd_invoke_push(struct cmd_invoke_state *is, struct cmd_parse_tree *tree,
|
||||||
|
struct cmd_parse_node *node, struct cmd_parse_node *first,
|
||||||
|
struct cmd_parse_node *end)
|
||||||
|
{
|
||||||
|
u_int n = is->nstack + 1;
|
||||||
|
|
||||||
|
is->stack = xreallocarray(is->stack, n, sizeof *is->stack);
|
||||||
|
is->stack[is->nstack].tree = cmd_parse_add_ref(tree);
|
||||||
|
is->stack[is->nstack].node = node;
|
||||||
|
is->stack[is->nstack].next = first;
|
||||||
|
is->stack[is->nstack].end = end;
|
||||||
|
is->nstack = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct cmd_parse_tree *
|
||||||
|
cmd_invoke_tree(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
if (is->current_tree != NULL)
|
||||||
|
return (is->current_tree);
|
||||||
|
return (is->tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the next node to execute from the traversal stack. */
|
||||||
|
static struct cmd_parse_node *
|
||||||
|
cmd_invoke_next(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
struct cmd_invoke_frame *frame;
|
||||||
|
struct cmd_parse_node *node;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (is->nstack == 0)
|
||||||
|
return (NULL);
|
||||||
|
frame = &is->stack[is->nstack - 1];
|
||||||
|
if (frame->next != NULL && frame->next != frame->end)
|
||||||
|
break;
|
||||||
|
cmd_parse_free(frame->tree);
|
||||||
|
is->nstack--;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = frame->next;
|
||||||
|
frame->next = cmd_parse_node_next(node);
|
||||||
|
is->current_tree = frame->tree;
|
||||||
|
return (node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip the rest of the active sequence after a command failure. Failure scope
|
||||||
|
* is the active CMD_PARSE_SEQUENCE. Discard nested frames and advance only as
|
||||||
|
* far as the end of that sequence.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
cmd_invoke_skip_sequence(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
enum cmd_parse_node_type type;
|
||||||
|
u_int i;
|
||||||
|
|
||||||
|
for (i = is->nstack; i > 0; i--) {
|
||||||
|
type = cmd_parse_node_type(is->stack[i - 1].node);
|
||||||
|
if (type == CMD_PARSE_SEQUENCE) {
|
||||||
|
is->stack[i - 1].next = is->stack[i - 1].end;
|
||||||
|
while (is->nstack > i)
|
||||||
|
cmd_parse_free(is->stack[--is->nstack].tree);
|
||||||
|
is->nstack = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is->have_last = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append a string to a dynamically allocated buffer. */
|
||||||
|
static void
|
||||||
|
cmd_invoke_append(char **buf, size_t *len, const char *s)
|
||||||
|
{
|
||||||
|
size_t slen;
|
||||||
|
|
||||||
|
if (s != NULL) {
|
||||||
|
slen = strlen(s);
|
||||||
|
*buf = xrealloc(*buf, *len + slen + 1);
|
||||||
|
memcpy(*buf + *len, s, slen + 1);
|
||||||
|
*len += slen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look up an environment variable. */
|
||||||
|
static const char *
|
||||||
|
cmd_invoke_getenv(struct cmdq_item *item, const char *name)
|
||||||
|
{
|
||||||
|
struct client *c = cmdq_get_client(item);
|
||||||
|
struct environ_entry *envent;
|
||||||
|
|
||||||
|
if (c != NULL && c->environ != NULL) {
|
||||||
|
envent = environ_find(c->environ, name);
|
||||||
|
if (envent != NULL && envent->value != NULL)
|
||||||
|
return (envent->value);
|
||||||
|
}
|
||||||
|
envent = environ_find(global_environ, name);
|
||||||
|
if (envent != NULL && envent->value != NULL)
|
||||||
|
return (envent->value);
|
||||||
|
return ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolve a tilde expansion to a home directory. */
|
||||||
|
static const char *
|
||||||
|
cmd_invoke_tilde(const char *name)
|
||||||
|
{
|
||||||
|
struct passwd *pw;
|
||||||
|
|
||||||
|
if (name == NULL || *name == '\0')
|
||||||
|
return (find_home());
|
||||||
|
if ((pw = getpwnam(name)) == NULL)
|
||||||
|
return ("");
|
||||||
|
return (pw->pw_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand a parsed string node into an argv string. */
|
||||||
|
static int
|
||||||
|
cmd_invoke_expand_string(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node, char **out)
|
||||||
|
{
|
||||||
|
struct cmd_parse_node *child;
|
||||||
|
const char *s, *value;
|
||||||
|
char *buf = NULL, *new;
|
||||||
|
size_t len = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
child = cmd_parse_node_first_child(node);
|
||||||
|
while (child != NULL) {
|
||||||
|
value = cmd_parse_node_value(child);
|
||||||
|
switch (cmd_parse_node_type(child)) {
|
||||||
|
case CMD_PARSE_TEXT:
|
||||||
|
s = value;
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_ENVIRONMENT:
|
||||||
|
s = cmd_invoke_getenv(item, value);
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_TILDE:
|
||||||
|
s = cmd_invoke_tilde(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalx("unexpected node type in string");
|
||||||
|
}
|
||||||
|
cmd_invoke_append(&buf, &len, s);
|
||||||
|
child = cmd_parse_node_next(child);
|
||||||
|
}
|
||||||
|
if (buf == NULL)
|
||||||
|
buf = xstrdup("");
|
||||||
|
for (i = 0; i < is->argc; i++) {
|
||||||
|
new = cmd_template_replace(buf, is->argv[i], i + 1);
|
||||||
|
free(buf);
|
||||||
|
buf = new;
|
||||||
|
}
|
||||||
|
*out = buf;
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Execute an assignment node by updating the environment. */
|
||||||
|
static int
|
||||||
|
cmd_invoke_assignment(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node)
|
||||||
|
{
|
||||||
|
struct cmd_parse_node *value;
|
||||||
|
const char *name;
|
||||||
|
char *expanded;
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
value = cmd_parse_node_first_child(node);
|
||||||
|
if (value == NULL)
|
||||||
|
return (-1);
|
||||||
|
if (cmd_invoke_expand_string(item, is, value, &expanded) != 0)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
name = cmd_parse_node_value(node);
|
||||||
|
if (cmd_parse_node_type(node) == CMD_PARSE_HIDDEN_ASSIGN)
|
||||||
|
flags |= ENVIRON_HIDDEN;
|
||||||
|
environ_set(global_environ, name, flags, "%s", expanded);
|
||||||
|
free(expanded);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand and evaluate a conditional expression. */
|
||||||
|
static int
|
||||||
|
cmd_invoke_is_true(struct cmdq_item *item,
|
||||||
|
struct cmd_invoke_state *is, struct cmd_parse_node *node, int *result)
|
||||||
|
{
|
||||||
|
struct format_tree *ft;
|
||||||
|
char *s, *expanded;
|
||||||
|
|
||||||
|
if (cmd_invoke_expand_string(item, is, node, &s) != 0)
|
||||||
|
return (-1);
|
||||||
|
ft = format_create_from_target(item);
|
||||||
|
|
||||||
|
expanded = format_expand(ft, s);
|
||||||
|
*result = format_true(expanded);
|
||||||
|
free(expanded);
|
||||||
|
|
||||||
|
format_free(ft);
|
||||||
|
free(s);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip else branches. */
|
||||||
|
static struct cmd_parse_node *
|
||||||
|
cmd_invoke_if_branch_end(struct cmd_parse_node *first)
|
||||||
|
{
|
||||||
|
struct cmd_parse_node *node;
|
||||||
|
|
||||||
|
for (node = first; node != NULL; node = cmd_parse_node_next(node)) {
|
||||||
|
switch (cmd_parse_node_type(node)) {
|
||||||
|
case CMD_PARSE_ELIF:
|
||||||
|
case CMD_PARSE_ELSE:
|
||||||
|
return (node);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select and queue the branch for a conditional node. */
|
||||||
|
static int
|
||||||
|
cmd_invoke_if(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node)
|
||||||
|
{
|
||||||
|
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
|
||||||
|
struct cmd_parse_node *child, *first, *next;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
next = cmd_parse_node_first_child(node);
|
||||||
|
if (next == NULL)
|
||||||
|
return (0);
|
||||||
|
first = cmd_parse_node_next(next);
|
||||||
|
if (cmd_invoke_is_true(item, is, next, &r) != 0)
|
||||||
|
return (-1);
|
||||||
|
if (r) {
|
||||||
|
next = cmd_invoke_if_branch_end(first);
|
||||||
|
cmd_invoke_push(is, tree, node, first, next);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (child = first; child != NULL; child = cmd_parse_node_next(child)) {
|
||||||
|
switch (cmd_parse_node_type(child)) {
|
||||||
|
case CMD_PARSE_ELIF:
|
||||||
|
next = cmd_parse_node_first_child(child);
|
||||||
|
if (next == NULL)
|
||||||
|
break;
|
||||||
|
if (cmd_invoke_is_true(item, is, next, &r) != 0)
|
||||||
|
return (-1);
|
||||||
|
if (r) {
|
||||||
|
next = cmd_parse_node_next(next);
|
||||||
|
cmd_invoke_push(is, tree, child, next, NULL);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_ELSE:
|
||||||
|
next = cmd_parse_node_first_child(child);
|
||||||
|
cmd_invoke_push(is, tree, child, next, NULL);
|
||||||
|
return (0);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Report an error from an invoke item. */
|
||||||
|
static void
|
||||||
|
cmd_invoke_error(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node, const char *cause)
|
||||||
|
{
|
||||||
|
struct client *c = cmdq_get_client(item);
|
||||||
|
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
|
||||||
|
const char *file = cmd_parse_file(tree);
|
||||||
|
u_int line = cmd_parse_node_line(node);
|
||||||
|
|
||||||
|
if (c != NULL && (~c->flags & CLIENT_CONTROL || file == NULL)) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file != NULL)
|
||||||
|
cfg_add_cause("%s:%u: %s", file, line, cause);
|
||||||
|
else
|
||||||
|
cfg_add_cause("%s", cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build one command from a parsed command node. */
|
||||||
|
static struct cmd *
|
||||||
|
cmd_invoke_build_command(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node)
|
||||||
|
{
|
||||||
|
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
|
||||||
|
struct cmd_parse_node *child;
|
||||||
|
struct args_value *values = NULL;
|
||||||
|
struct cmd *cmd;
|
||||||
|
const char *file = cmd_parse_file(tree);
|
||||||
|
u_int line = cmd_parse_node_line(node);
|
||||||
|
int flags = cmd_parse_flags(tree);
|
||||||
|
char *cause = NULL;
|
||||||
|
u_int count = 0;
|
||||||
|
|
||||||
|
child = cmd_parse_node_first_child(node);
|
||||||
|
while (child != NULL) {
|
||||||
|
values = xreallocarray(values, count + 1, sizeof *values);
|
||||||
|
memset(&values[count], 0, sizeof values[count]);
|
||||||
|
switch (cmd_parse_node_type(child)) {
|
||||||
|
case CMD_PARSE_STRING:
|
||||||
|
values[count].type = ARGS_STRING;
|
||||||
|
if (cmd_invoke_expand_string(item, is, child,
|
||||||
|
&values[count].string) != 0)
|
||||||
|
goto fail;
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_COMMANDS:
|
||||||
|
values[count].type = ARGS_COMMANDS;
|
||||||
|
values[count].cmd = cmd_parse_from_node(tree, child);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalx("unexpected node type in command");
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
child = cmd_parse_node_next(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = cmd_parse(values, count, file, line, flags, &cause);
|
||||||
|
if (cmd == NULL) {
|
||||||
|
cmd_invoke_error(item, is, node, cause);
|
||||||
|
free(cause);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
args_free_values(values, count);
|
||||||
|
free(values);
|
||||||
|
return (cmd);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
args_free_values(values, count);
|
||||||
|
free(values);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cmd_invoke_read_only(struct cmdq_item *item, struct cmd *cmd)
|
||||||
|
{
|
||||||
|
struct client *c = cmdq_get_client(item);
|
||||||
|
const struct cmd_entry *entry = cmd_get_entry(cmd);
|
||||||
|
|
||||||
|
if (c == NULL || (~c->flags & CLIENT_READONLY))
|
||||||
|
return (0);
|
||||||
|
if (entry->flags & CMD_READONLY)
|
||||||
|
return (0);
|
||||||
|
cmdq_error(item, "client is read-only");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the first invoke queue item for a parsed tree. */
|
||||||
|
struct cmdq_item *
|
||||||
|
cmd_invoke_get(struct cmd_parse_tree *tree, struct cmdq_state *state,
|
||||||
|
const struct cmd_invoke_input *input)
|
||||||
|
{
|
||||||
|
struct cmd_invoke_state *is;
|
||||||
|
struct cmd_parse_node *root = cmd_parse_root(tree), *first;
|
||||||
|
struct cmdq_item *item;
|
||||||
|
struct cmd_invoke_input new_input = { 0 };
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (input == NULL)
|
||||||
|
input = &new_input;
|
||||||
|
|
||||||
|
is = xcalloc(1, sizeof *is);
|
||||||
|
is->references = 1;
|
||||||
|
is->tree = cmd_parse_add_ref(tree);
|
||||||
|
|
||||||
|
is->argc = input->argc;
|
||||||
|
if (input->argc != 0) {
|
||||||
|
is->argv = xreallocarray(NULL, input->argc, sizeof *is->argv);
|
||||||
|
for (i = 0; i < input->argc; i++)
|
||||||
|
is->argv[i] = xstrdup(input->argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
first = cmd_parse_node_first_child(root);
|
||||||
|
cmd_invoke_push(is, tree, root, first, NULL);
|
||||||
|
|
||||||
|
item = cmdq_get_invoke(is, state);
|
||||||
|
cmd_invoke_state_free(is);
|
||||||
|
return (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a reference to shared invoke state. */
|
||||||
|
struct cmd_invoke_state *
|
||||||
|
cmd_invoke_state_add_ref(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
is->references++;
|
||||||
|
return (is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release a reference to shared invoke state. */
|
||||||
|
void
|
||||||
|
cmd_invoke_state_free(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u_int j;
|
||||||
|
|
||||||
|
if (is == NULL)
|
||||||
|
return;
|
||||||
|
if (--is->references != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < is->argc; i++)
|
||||||
|
free(is->argv[i]);
|
||||||
|
free(is->argv);
|
||||||
|
for (j = 0; j < is->nstack; j++)
|
||||||
|
cmd_parse_free(is->stack[j].tree);
|
||||||
|
|
||||||
|
cmd_parse_free(is->tree);
|
||||||
|
free(is->stack);
|
||||||
|
free(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Record the result from the last command item. */
|
||||||
|
void
|
||||||
|
cmd_invoke_result(struct cmd_invoke_state *is, enum cmd_retval retval)
|
||||||
|
{
|
||||||
|
is->last = retval;
|
||||||
|
is->have_last = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fire an invoke item and queue the next command plus continuation. */
|
||||||
|
enum cmd_retval
|
||||||
|
cmd_invoke_fire(struct cmdq_item *item, struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
struct cmd_parse_node *node, *first;
|
||||||
|
struct cmd_parse_tree *tree, *alias;
|
||||||
|
struct cmdq_item *new_item, *next;
|
||||||
|
struct cmdq_state *state;
|
||||||
|
struct client *c;
|
||||||
|
struct cmd *cmd;
|
||||||
|
const char *file;
|
||||||
|
char *cause;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (is->have_last && is->last == CMD_RETURN_ERROR)
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
else
|
||||||
|
is->have_last = 0;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
node = cmd_invoke_next(is);
|
||||||
|
if (node == NULL)
|
||||||
|
return (CMD_RETURN_NORMAL);
|
||||||
|
cmd_parse_log_node(__func__, node);
|
||||||
|
|
||||||
|
tree = cmd_invoke_tree(is);
|
||||||
|
switch (cmd_parse_node_type(node)) {
|
||||||
|
case CMD_PARSE_ROOT:
|
||||||
|
case CMD_PARSE_SEQUENCE:
|
||||||
|
first = cmd_parse_node_first_child(node);
|
||||||
|
cmd_invoke_push(is, tree, node, first, NULL);
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_ASSIGN:
|
||||||
|
case CMD_PARSE_HIDDEN_ASSIGN:
|
||||||
|
if (cmd_invoke_assignment(item, is, node) == 0)
|
||||||
|
break;
|
||||||
|
cmd_invoke_error(item, is, node, "bad assignment");
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_IF:
|
||||||
|
if (cmd_invoke_if(item, is, node) == 0)
|
||||||
|
break;
|
||||||
|
cmd_invoke_error(item, is, node, "bad condition");
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_ELIF:
|
||||||
|
case CMD_PARSE_ELSE:
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_COMMAND:
|
||||||
|
r = cmd_parse_expand_alias(tree, node, &alias, &cause);
|
||||||
|
if (r == 1) {
|
||||||
|
node = cmd_parse_root(alias);
|
||||||
|
first = cmd_parse_node_first_child(node);
|
||||||
|
cmd_invoke_push(is, alias, node, first, NULL);
|
||||||
|
cmd_parse_free(alias);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r == -1) {
|
||||||
|
c = cmdq_get_client(item);
|
||||||
|
file = cmd_parse_file(tree);
|
||||||
|
if (c != NULL &&
|
||||||
|
(~c->flags & CLIENT_CONTROL || file == NULL))
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
else
|
||||||
|
cfg_add_cause("%s", cause);
|
||||||
|
free(cause);
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = cmd_invoke_build_command(item, is, node);
|
||||||
|
if (cmd == NULL) {
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cmd_invoke_read_only(item, cmd) != 0) {
|
||||||
|
cmd_free(cmd);
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Queue one command followed by this walker. WAIT and
|
||||||
|
* command-inserted items therefore run before resume.
|
||||||
|
*/
|
||||||
|
state = cmdq_get_state(item);
|
||||||
|
new_item = cmdq_get_one_command(cmd, state, is);
|
||||||
|
next = cmdq_get_invoke(is, state);
|
||||||
|
cmdq_insert_after(item, next);
|
||||||
|
cmdq_insert_after(item, new_item);
|
||||||
|
return (CMD_RETURN_NORMAL);
|
||||||
|
case CMD_PARSE_STRING:
|
||||||
|
case CMD_PARSE_COMMANDS:
|
||||||
|
case CMD_PARSE_TEXT:
|
||||||
|
case CMD_PARSE_ENVIRONMENT:
|
||||||
|
case CMD_PARSE_TILDE:
|
||||||
|
fatalx("unexpected node type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -300,7 +300,6 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
|||||||
struct window_pane *wp;
|
struct window_pane *wp;
|
||||||
struct layout_cell *lc;
|
struct layout_cell *lc;
|
||||||
int y, ly, x, lx;
|
int y, ly, x, lx;
|
||||||
int old_xoff, old_yoff, old_sx, old_sy;
|
|
||||||
|
|
||||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
@@ -322,17 +321,10 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
|||||||
ly = m->statusat - 1;
|
ly = m->statusat - 1;
|
||||||
|
|
||||||
if (x != lx || y != ly) {
|
if (x != lx || y != ly) {
|
||||||
old_xoff = wp->xoff;
|
|
||||||
old_yoff = wp->yoff;
|
|
||||||
old_sx = wp->sx;
|
|
||||||
old_sy = wp->sy;
|
|
||||||
|
|
||||||
lc->g.xoff += x - lx;
|
lc->g.xoff += x - lx;
|
||||||
lc->g.yoff += y - ly;
|
lc->g.yoff += y - ly;
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
|
server_redraw_window(w);
|
||||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
|
||||||
old_sy);
|
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,16 @@
|
|||||||
"-T #{p|#{key_table_width}:key_table} " \
|
"-T #{p|#{key_table_width}:key_table} " \
|
||||||
"#{p|#{key_string_width}:#{q|a:key_string}} " \
|
"#{p|#{key_string_width}:#{q|a:key_string}} " \
|
||||||
"#{key_command}}"
|
"#{key_command}}"
|
||||||
|
#define LIST_KEYS_TEMPLATE_PRETTY \
|
||||||
|
"#{?notes_only," \
|
||||||
|
"#{key_prefix} " \
|
||||||
|
"#{key_string} " \
|
||||||
|
"#{?key_note,#{key_note},#{key_command}}" \
|
||||||
|
"," \
|
||||||
|
"bind-key #{?key_repeat,-r ,}" \
|
||||||
|
"-T #{key_table} " \
|
||||||
|
"#{q|a:key_string} " \
|
||||||
|
"#{key_command}}"
|
||||||
|
|
||||||
static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *);
|
static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *);
|
||||||
|
|
||||||
@@ -44,8 +54,8 @@ const struct cmd_entry cmd_list_keys_entry = {
|
|||||||
.name = "list-keys",
|
.name = "list-keys",
|
||||||
.alias = "lsk",
|
.alias = "lsk",
|
||||||
|
|
||||||
.args = { "1aF:NO:P:rT:", 0, 1, NULL },
|
.args = { "1aF:NO:P:prT:", 0, 1, NULL },
|
||||||
.usage = "[-1aNr] [-F format] [-O order] [-P prefix-string]"
|
.usage = "[-1aNpr] [-F format] [-O order] [-P prefix-string]"
|
||||||
"[-T key-table] [key]",
|
"[-T key-table] [key]",
|
||||||
|
|
||||||
.flags = CMD_STARTSERVER|CMD_AFTERHOOK,
|
.flags = CMD_STARTSERVER|CMD_AFTERHOOK,
|
||||||
@@ -119,7 +129,7 @@ cmd_list_keys_get_root_and_prefix(u_int *n, struct sort_criteria *sort_crit)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cmd_list_keys_filter_key_list(int filter_notes, int filter_key, key_code only,
|
cmd_list_keys_do_filter(int filter_notes, int filter_key, key_code only,
|
||||||
struct key_binding **l, u_int *n)
|
struct key_binding **l, u_int *n)
|
||||||
{
|
{
|
||||||
key_code key;
|
key_code key;
|
||||||
@@ -137,8 +147,8 @@ cmd_list_keys_filter_key_list(int filter_notes, int filter_key, key_code only,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cmd_list_keys_format_add_key_binding(struct format_tree *ft,
|
cmd_list_keys_format_key_binding(struct format_tree *ft,
|
||||||
const struct key_binding *bd, const char *prefix)
|
const struct key_binding *bd, const char *prefix, int flags)
|
||||||
{
|
{
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
@@ -157,8 +167,7 @@ cmd_list_keys_format_add_key_binding(struct format_tree *ft,
|
|||||||
|
|
||||||
format_add(ft, "key_string", "%s", key_string_lookup_key(bd->key, 0));
|
format_add(ft, "key_string", "%s", key_string_lookup_key(bd->key, 0));
|
||||||
|
|
||||||
s = cmd_list_print(bd->cmdlist, CMD_LIST_PRINT_ESCAPED|
|
s = cmd_parse_print(bd->cmd, flags);
|
||||||
CMD_LIST_PRINT_NO_GROUPS);
|
|
||||||
format_add(ft, "key_command", "%s", s);
|
format_add(ft, "key_command", "%s", s);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
@@ -177,6 +186,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
char *prefix = NULL;
|
char *prefix = NULL;
|
||||||
u_int i, n;
|
u_int i, n;
|
||||||
int single, notes_only, filter_notes, filter_key;
|
int single, notes_only, filter_notes, filter_key;
|
||||||
|
int print_flags = 0;
|
||||||
struct sort_criteria sort_crit;
|
struct sort_criteria sort_crit;
|
||||||
|
|
||||||
if ((keystr = args_string(args, 0)) != NULL) {
|
if ((keystr = args_string(args, 0)) != NULL) {
|
||||||
@@ -206,9 +216,15 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
prefix = cmd_list_keys_get_prefix(args);
|
prefix = cmd_list_keys_get_prefix(args);
|
||||||
single = args_has(args, '1');
|
single = args_has(args, '1');
|
||||||
notes_only = args_has(args, 'N');
|
notes_only = args_has(args, 'N');
|
||||||
|
if (args_has(args, 'p'))
|
||||||
|
print_flags |= CMD_PARSE_PRINT_MULTILINE;
|
||||||
|
|
||||||
if ((template = args_get(args, 'F')) == NULL)
|
if ((template = args_get(args, 'F')) == NULL) {
|
||||||
template = LIST_KEYS_TEMPLATE;
|
if (print_flags & CMD_PARSE_PRINT_MULTILINE)
|
||||||
|
template = LIST_KEYS_TEMPLATE_PRETTY;
|
||||||
|
else
|
||||||
|
template = LIST_KEYS_TEMPLATE;
|
||||||
|
}
|
||||||
|
|
||||||
if (table)
|
if (table)
|
||||||
l = sort_get_key_bindings_table(table, &n, &sort_crit);
|
l = sort_get_key_bindings_table(table, &n, &sort_crit);
|
||||||
@@ -219,10 +235,8 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
|
|
||||||
filter_notes = notes_only && !args_has(args, 'a');
|
filter_notes = notes_only && !args_has(args, 'a');
|
||||||
filter_key = only != KEYC_UNKNOWN;
|
filter_key = only != KEYC_UNKNOWN;
|
||||||
if (filter_notes || filter_key) {
|
if (filter_notes || filter_key)
|
||||||
cmd_list_keys_filter_key_list(filter_notes, filter_key, only, l,
|
cmd_list_keys_do_filter(filter_notes, filter_key, only, l, &n);
|
||||||
&n);
|
|
||||||
}
|
|
||||||
if (filter_key && n == 0) {
|
if (filter_key && n == 0) {
|
||||||
cmdq_error(item, "unknown key: %s", keystr);
|
cmdq_error(item, "unknown key: %s", keystr);
|
||||||
free(prefix);
|
free(prefix);
|
||||||
@@ -239,7 +253,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
format_add(ft, "key_table_width", "%u",
|
format_add(ft, "key_table_width", "%u",
|
||||||
cmd_list_keys_get_table_width(l, n));
|
cmd_list_keys_get_table_width(l, n));
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
cmd_list_keys_format_add_key_binding(ft, l[i], prefix);
|
cmd_list_keys_format_key_binding(ft, l[i], prefix, print_flags);
|
||||||
|
|
||||||
line = format_expand(ft, template);
|
line = format_expand(ft, template);
|
||||||
if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL))
|
if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-list-panes.c,v 1.41 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: cmd-list-panes.c,v 1.40 2026/06/01 14:01:09 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -97,7 +97,6 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
|
|||||||
struct cmdq_item *item, int type)
|
struct cmdq_item *item, int type)
|
||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct client *c = cmdq_get_client(item);
|
|
||||||
struct window_pane *wp, **l;
|
struct window_pane *wp, **l;
|
||||||
u_int i, n;
|
u_int i, n;
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
@@ -148,7 +147,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
|
|||||||
wp = l[i];
|
wp = l[i];
|
||||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||||
format_add(ft, "line", "%u", n);
|
format_add(ft, "line", "%u", n);
|
||||||
format_defaults(ft, c, s, wl, wp);
|
format_defaults(ft, NULL, s, wl, wp);
|
||||||
|
|
||||||
if (filter != NULL) {
|
if (filter != NULL) {
|
||||||
expanded = format_expand(ft, filter);
|
expanded = format_expand(ft, filter);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-list-sessions.c,v 1.37 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: cmd-list-sessions.c,v 1.36 2026/02/27 08:25:12 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -53,7 +53,6 @@ static enum cmd_retval
|
|||||||
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct client *c = cmdq_get_client(item);
|
|
||||||
struct session **l;
|
struct session **l;
|
||||||
u_int n, i;
|
u_int n, i;
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
@@ -77,7 +76,7 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||||
format_add(ft, "line", "%u", i);
|
format_add(ft, "line", "%u", i);
|
||||||
format_defaults(ft, c, l[i], NULL, NULL);
|
format_defaults(ft, NULL, l[i], NULL, NULL);
|
||||||
|
|
||||||
if (filter != NULL) {
|
if (filter != NULL) {
|
||||||
expanded = format_expand(ft, filter);
|
expanded = format_expand(ft, filter);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-list-windows.c,v 1.51 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: cmd-list-windows.c,v 1.50 2026/02/27 08:25:12 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -60,7 +60,6 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
struct client *c = cmdq_get_client(item);
|
|
||||||
struct winlink *wl, **l;
|
struct winlink *wl, **l;
|
||||||
struct session *s;
|
struct session *s;
|
||||||
u_int i, n;
|
u_int i, n;
|
||||||
@@ -95,7 +94,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
s = wl->session;
|
s = wl->session;
|
||||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||||
format_add(ft, "line", "%u", n);
|
format_add(ft, "line", "%u", n);
|
||||||
format_defaults(ft, c, s, wl, NULL);
|
format_defaults(ft, NULL, s, wl, NULL);
|
||||||
|
|
||||||
if (filter != NULL) {
|
if (filter != NULL) {
|
||||||
expanded = format_expand(ft, filter);
|
expanded = format_expand(ft, filter);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-new-window.c,v 1.104 2026/09/03 21:04:11 nicm Exp $ */
|
/* $OpenBSD: cmd-new-window.c,v 1.103 2026/07/08 08:07:42 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -60,8 +60,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct session *s = target->s;
|
struct session *s = target->s;
|
||||||
struct winlink *wl = target->wl, *new_wl = NULL;
|
struct winlink *wl = target->wl, *new_wl = NULL;
|
||||||
int idx = target->idx, before;
|
int idx = target->idx, before, count = args_count(args);
|
||||||
int count = args_count(args);
|
|
||||||
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
||||||
const char *template, *name;
|
const char *template, *name;
|
||||||
struct cmd_find_state fs;
|
struct cmd_find_state fs;
|
||||||
@@ -75,10 +74,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If -S is given, select an existing window instead of creating a
|
* If -S and -n are given and -t is not and a single window with this
|
||||||
* new one: preferring -t if it already points at a window, or
|
* name already exists, select it.
|
||||||
* otherwise -n if one exists with that name. If neither matches,
|
|
||||||
* fall through and create a window as normal.
|
|
||||||
*/
|
*/
|
||||||
name = args_get(args, 'n');
|
name = args_get(args, 'n');
|
||||||
if (name != NULL) {
|
if (name != NULL) {
|
||||||
@@ -91,39 +88,32 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
wname = clean_name(expanded, 0);
|
wname = clean_name(expanded, 0);
|
||||||
free(expanded);
|
free(expanded);
|
||||||
}
|
}
|
||||||
if (args_has(args, 'S')) {
|
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
|
||||||
if (idx != -1)
|
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||||
new_wl = winlink_find_by_index(&s->windows, idx);
|
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||||
else if (wname != NULL) {
|
if (strcmp(wl->window->name, expanded) != 0)
|
||||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
continue;
|
||||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
if (new_wl == NULL) {
|
||||||
if (strcmp(wl->window->name, expanded) != 0)
|
new_wl = wl;
|
||||||
continue;
|
continue;
|
||||||
if (new_wl == NULL) {
|
|
||||||
new_wl = wl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cmdq_error(item, "multiple windows named %s",
|
|
||||||
wname);
|
|
||||||
free(wname);
|
|
||||||
free(expanded);
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
}
|
}
|
||||||
|
cmdq_error(item, "multiple windows named %s", wname);
|
||||||
|
free(wname);
|
||||||
free(expanded);
|
free(expanded);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
}
|
free(expanded);
|
||||||
|
if (new_wl != NULL) {
|
||||||
/* Found an existing window to select instead of creating a new one. */
|
free(wname);
|
||||||
if (new_wl != NULL) {
|
if (args_has(args, 'd'))
|
||||||
free(wname);
|
return (CMD_RETURN_NORMAL);
|
||||||
if (args_has(args, 'd'))
|
if (session_set_current(s, new_wl) == 0)
|
||||||
|
server_redraw_session(s);
|
||||||
|
if (c != NULL && c->session != NULL)
|
||||||
|
s->curw->window->latest = c;
|
||||||
|
recalculate_sizes();
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
if (session_set_current(s, new_wl) == 0)
|
}
|
||||||
server_redraw_session(s);
|
|
||||||
if (c != NULL && c->session != NULL)
|
|
||||||
s->curw->window->latest = c;
|
|
||||||
recalculate_sizes();
|
|
||||||
return (CMD_RETURN_NORMAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
before = args_has(args, 'b');
|
before = args_has(args, 'b');
|
||||||
|
|||||||
2843
cmd-parse.y
2843
cmd-parse.y
File diff suppressed because it is too large
Load Diff
115
cmd-queue.c
115
cmd-queue.c
@@ -35,6 +35,7 @@
|
|||||||
enum cmdq_type {
|
enum cmdq_type {
|
||||||
CMDQ_COMMAND,
|
CMDQ_COMMAND,
|
||||||
CMDQ_CALLBACK,
|
CMDQ_CALLBACK,
|
||||||
|
CMDQ_INVOKE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Command queue item. */
|
/* Command queue item. */
|
||||||
@@ -47,7 +48,6 @@ struct cmdq_item {
|
|||||||
struct client *target_client;
|
struct client *target_client;
|
||||||
|
|
||||||
enum cmdq_type type;
|
enum cmdq_type type;
|
||||||
u_int group;
|
|
||||||
|
|
||||||
u_int number;
|
u_int number;
|
||||||
time_t time;
|
time_t time;
|
||||||
@@ -58,8 +58,8 @@ struct cmdq_item {
|
|||||||
struct cmd_find_state source;
|
struct cmd_find_state source;
|
||||||
struct cmd_find_state target;
|
struct cmd_find_state target;
|
||||||
|
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
struct cmd *cmd;
|
struct cmd *cmd;
|
||||||
|
struct cmd_invoke_state *invoke_state;
|
||||||
|
|
||||||
cmdq_cb cb;
|
cmdq_cb cb;
|
||||||
void *data;
|
void *data;
|
||||||
@@ -428,8 +428,10 @@ cmdq_remove(struct cmdq_item *item)
|
|||||||
{
|
{
|
||||||
if (item->client != NULL)
|
if (item->client != NULL)
|
||||||
server_client_unref(item->client);
|
server_client_unref(item->client);
|
||||||
if (item->cmdlist != NULL)
|
if (item->cmd != NULL)
|
||||||
cmd_list_free(item->cmdlist);
|
cmd_free(item->cmd);
|
||||||
|
if (item->invoke_state != NULL)
|
||||||
|
cmd_invoke_state_free(item->invoke_state);
|
||||||
cmdq_free_state(item->state);
|
cmdq_free_state(item->state);
|
||||||
|
|
||||||
TAILQ_REMOVE(&item->queue->list, item, entry);
|
TAILQ_REMOVE(&item->queue->list, item, entry);
|
||||||
@@ -438,75 +440,46 @@ cmdq_remove(struct cmdq_item *item)
|
|||||||
free(item);
|
free(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove all subsequent items that match this item's group. */
|
/* Get a single command for command queue with invoke state. */
|
||||||
static void
|
|
||||||
cmdq_remove_group(struct cmdq_item *item)
|
|
||||||
{
|
|
||||||
struct cmdq_item *this, *next;
|
|
||||||
|
|
||||||
if (item->group == 0)
|
|
||||||
return;
|
|
||||||
this = TAILQ_NEXT(item, entry);
|
|
||||||
while (this != NULL) {
|
|
||||||
next = TAILQ_NEXT(this, entry);
|
|
||||||
if (this->group == item->group)
|
|
||||||
cmdq_remove(this);
|
|
||||||
this = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Empty command callback. */
|
|
||||||
static enum cmd_retval
|
|
||||||
cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data)
|
|
||||||
{
|
|
||||||
return (CMD_RETURN_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get a command for the command queue. */
|
|
||||||
struct cmdq_item *
|
struct cmdq_item *
|
||||||
cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
|
cmdq_get_one_command(struct cmd *cmd, struct cmdq_state *state,
|
||||||
|
struct cmd_invoke_state *invoke_state)
|
||||||
{
|
{
|
||||||
struct cmdq_item *item, *first = NULL, *last = NULL;
|
struct cmdq_item *item;
|
||||||
struct cmd *cmd;
|
const struct cmd_entry *entry = cmd_get_entry(cmd);
|
||||||
const struct cmd_entry *entry;
|
|
||||||
int created = 0;
|
|
||||||
|
|
||||||
if ((cmd = cmd_list_first(cmdlist)) == NULL)
|
item = xcalloc(1, sizeof *item);
|
||||||
return (cmdq_get_callback(cmdq_empty_command, NULL));
|
xasprintf(&item->name, "[%s/%p]", entry->name, item);
|
||||||
|
item->type = CMDQ_COMMAND;
|
||||||
|
item->cmd = cmd;
|
||||||
|
|
||||||
if (state == NULL) {
|
if (state == NULL)
|
||||||
state = cmdq_new_state(NULL, NULL, 0);
|
item->state = cmdq_new_state(NULL, NULL, 0);
|
||||||
created = 1;
|
else
|
||||||
}
|
|
||||||
|
|
||||||
while (cmd != NULL) {
|
|
||||||
entry = cmd_get_entry(cmd);
|
|
||||||
|
|
||||||
item = xcalloc(1, sizeof *item);
|
|
||||||
xasprintf(&item->name, "[%s/%p]", entry->name, item);
|
|
||||||
item->type = CMDQ_COMMAND;
|
|
||||||
|
|
||||||
item->group = cmd_get_group(cmd);
|
|
||||||
item->state = cmdq_link_state(state);
|
item->state = cmdq_link_state(state);
|
||||||
|
|
||||||
item->cmdlist = cmdlist;
|
if (invoke_state != NULL)
|
||||||
item->cmd = cmd;
|
item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
|
||||||
|
|
||||||
cmdlist->references++;
|
log_debug("%s: %s", __func__, item->name);
|
||||||
log_debug("%s: %s group %u", __func__, item->name, item->group);
|
return (item);
|
||||||
|
}
|
||||||
|
|
||||||
if (first == NULL)
|
/* Get an invoke continuation for command queue. */
|
||||||
first = item;
|
struct cmdq_item *
|
||||||
if (last != NULL)
|
cmdq_get_invoke(struct cmd_invoke_state *invoke_state, struct cmdq_state *state)
|
||||||
last->next = item;
|
{
|
||||||
last = item;
|
struct cmdq_item *item;
|
||||||
|
|
||||||
cmd = cmd_list_next(cmd);
|
item = xcalloc(1, sizeof *item);
|
||||||
}
|
xasprintf(&item->name, "[invoke/%p]", item);
|
||||||
|
item->type = CMDQ_INVOKE;
|
||||||
if (created)
|
if (state == NULL)
|
||||||
cmdq_free_state(state);
|
item->state = cmdq_new_state(NULL, NULL, 0);
|
||||||
return (first);
|
else
|
||||||
|
item->state = cmdq_link_state(state);
|
||||||
|
item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
|
||||||
|
return (item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fill in flag for a command. */
|
/* Fill in flag for a command. */
|
||||||
@@ -584,7 +557,7 @@ cmdq_fire_command(struct cmdq_item *item)
|
|||||||
cmdq_add_message(item);
|
cmdq_add_message(item);
|
||||||
if (log_get_level() > 1) {
|
if (log_get_level() > 1) {
|
||||||
tmp = cmd_print(cmd);
|
tmp = cmd_print(cmd);
|
||||||
log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
|
log_debug("%s %s: %s", __func__, name, tmp);
|
||||||
free(tmp);
|
free(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,7 +636,6 @@ cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
|
|||||||
xasprintf(&item->name, "[%s/%p]", name, item);
|
xasprintf(&item->name, "[%s/%p]", name, item);
|
||||||
item->type = CMDQ_CALLBACK;
|
item->type = CMDQ_CALLBACK;
|
||||||
|
|
||||||
item->group = 0;
|
|
||||||
item->state = cmdq_new_state(NULL, NULL, 0);
|
item->state = cmdq_new_state(NULL, NULL, 0);
|
||||||
|
|
||||||
item->cb = cb;
|
item->cb = cb;
|
||||||
@@ -745,17 +717,16 @@ cmdq_next(struct client *c)
|
|||||||
switch (item->type) {
|
switch (item->type) {
|
||||||
case CMDQ_COMMAND:
|
case CMDQ_COMMAND:
|
||||||
retval = cmdq_fire_command(item);
|
retval = cmdq_fire_command(item);
|
||||||
|
if (item->invoke_state != NULL)
|
||||||
|
cmd_invoke_result(item->invoke_state, retval);
|
||||||
|
|
||||||
/*
|
|
||||||
* If a command returns an error, remove any
|
|
||||||
* subsequent commands in the same group.
|
|
||||||
*/
|
|
||||||
if (retval == CMD_RETURN_ERROR)
|
|
||||||
cmdq_remove_group(item);
|
|
||||||
break;
|
break;
|
||||||
case CMDQ_CALLBACK:
|
case CMDQ_CALLBACK:
|
||||||
retval = cmdq_fire_callback(item);
|
retval = cmdq_fire_callback(item);
|
||||||
break;
|
break;
|
||||||
|
case CMDQ_INVOKE:
|
||||||
|
retval = cmd_invoke_fire(item, item->invoke_state);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
retval = CMD_RETURN_ERROR;
|
retval = CMD_RETURN_ERROR;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -238,7 +238,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
||||||
int left, right;
|
int left, right;
|
||||||
int new_xoff, new_yoff, resizes = 0;
|
int new_xoff, new_yoff, resizes = 0;
|
||||||
int old_xoff, old_yoff, old_sx, old_sy;
|
|
||||||
|
|
||||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
@@ -249,10 +248,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
lc = wp->layout_cell;
|
lc = wp->layout_cell;
|
||||||
sx = wp->sx;
|
sx = wp->sx;
|
||||||
sy = wp->sy;
|
sy = wp->sy;
|
||||||
old_xoff = wp->xoff;
|
|
||||||
old_yoff = wp->yoff;
|
|
||||||
old_sx = (int)wp->sx;
|
|
||||||
old_sy = (int)wp->sy;
|
|
||||||
left = wp->xoff - 1;
|
left = wp->xoff - 1;
|
||||||
right = wp->xoff + sx;
|
right = wp->xoff + sx;
|
||||||
if (window_pane_scrollbar_reserve(wp) &&
|
if (window_pane_scrollbar_reserve(wp) &&
|
||||||
@@ -352,8 +347,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
}
|
}
|
||||||
if (resizes != 0) {
|
if (resizes != 0) {
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
server_redraw_window(w);
|
||||||
old_sy);
|
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,15 +55,15 @@ const struct cmd_entry cmd_run_shell_entry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct cmd_run_shell_data {
|
struct cmd_run_shell_data {
|
||||||
struct client *client;
|
struct client *client;
|
||||||
char *cmd;
|
char *cmd;
|
||||||
struct args_command_state *state;
|
struct cmd_parse_tree *tree;
|
||||||
char *cwd;
|
char *cwd;
|
||||||
struct cmdq_item *item;
|
struct cmdq_item *item;
|
||||||
struct session *s;
|
struct session *s;
|
||||||
int wp_id;
|
int wp_id;
|
||||||
struct event timer;
|
struct event timer;
|
||||||
int flags;
|
int flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
static enum args_parse_type
|
static enum args_parse_type
|
||||||
@@ -119,7 +119,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
double d;
|
double d;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
char *end, key[16];
|
char *end, key[16], *cause = NULL;
|
||||||
u_int i;
|
u_int i;
|
||||||
int wait = !args_has(args, 'b');
|
int wait = !args_has(args, 'b');
|
||||||
|
|
||||||
@@ -129,8 +129,10 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmdq_error(item, "invalid delay time: %s", delay);
|
cmdq_error(item, "invalid delay time: %s", delay);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
} else if (args_count(args) == 0)
|
} else {
|
||||||
return (CMD_RETURN_NORMAL);
|
if (args_count(args) == 0)
|
||||||
|
return (CMD_RETURN_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
cdata = xcalloc(1, sizeof *cdata);
|
cdata = xcalloc(1, sizeof *cdata);
|
||||||
if (!args_has(args, 'C')) {
|
if (!args_has(args, 'C')) {
|
||||||
@@ -145,8 +147,13 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
format_free(ft);
|
format_free(ft);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cdata->state = args_make_commands_prepare(self, item, 0, NULL,
|
cdata->tree = args_to_commands(self, item, 0, NULL, 1, &cause);
|
||||||
wait, 1);
|
if (cdata->tree == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
|
cmd_run_shell_free(cdata);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args_has(args, 't') && wp != NULL)
|
if (args_has(args, 't') && wp != NULL)
|
||||||
@@ -163,6 +170,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
}
|
}
|
||||||
if (cdata->client != NULL)
|
if (cdata->client != NULL)
|
||||||
cdata->client->references++;
|
cdata->client->references++;
|
||||||
|
|
||||||
if (args_has(args, 'c'))
|
if (args_has(args, 'c'))
|
||||||
cdata->cwd = xstrdup(args_get(args, 'c'));
|
cdata->cwd = xstrdup(args_get(args, 'c'));
|
||||||
else
|
else
|
||||||
@@ -171,18 +179,20 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
if (args_has(args, 'E'))
|
if (args_has(args, 'E'))
|
||||||
cdata->flags |= JOB_SHOWSTDERR;
|
cdata->flags |= JOB_SHOWSTDERR;
|
||||||
|
|
||||||
cdata->s = s;
|
if (s != NULL) {
|
||||||
if (s != NULL)
|
cdata->s = s;
|
||||||
session_add_ref(s, __func__);
|
session_add_ref(s, __func__);
|
||||||
|
}
|
||||||
|
|
||||||
evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
|
evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
|
||||||
if (delay != NULL) {
|
if (delay == NULL)
|
||||||
|
event_active(&cdata->timer, EV_TIMEOUT, 1);
|
||||||
|
else {
|
||||||
timerclear(&tv);
|
timerclear(&tv);
|
||||||
tv.tv_sec = (time_t)d;
|
tv.tv_sec = (time_t)d;
|
||||||
tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
|
tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
|
||||||
evtimer_add(&cdata->timer, &tv);
|
evtimer_add(&cdata->timer, &tv);
|
||||||
} else
|
}
|
||||||
event_active(&cdata->timer, EV_TIMEOUT, 1);
|
|
||||||
|
|
||||||
if (!wait)
|
if (!wait)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
@@ -196,10 +206,9 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
|
|||||||
struct client *c = cdata->client;
|
struct client *c = cdata->client;
|
||||||
const char *cmd = cdata->cmd;
|
const char *cmd = cdata->cmd;
|
||||||
struct cmdq_item *item = cdata->item, *new_item;
|
struct cmdq_item *item = cdata->item, *new_item;
|
||||||
struct cmd_list *cmdlist;
|
struct cmdq_state *cs = NULL;
|
||||||
char *error;
|
|
||||||
|
|
||||||
if (cdata->state == NULL) {
|
if (cdata->tree == NULL) {
|
||||||
if (cmd == NULL) {
|
if (cmd == NULL) {
|
||||||
if (cdata->item != NULL)
|
if (cdata->item != NULL)
|
||||||
cmdq_continue(cdata->item);
|
cmdq_continue(cdata->item);
|
||||||
@@ -222,23 +231,13 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdlist = args_make_commands(cdata->state, 0, NULL, &error);
|
if (item != NULL)
|
||||||
if (cmdlist == NULL) {
|
cs = cmdq_get_state(item);
|
||||||
if (cdata->item == NULL) {
|
new_item = cmd_invoke_get(cdata->tree, cs, NULL);
|
||||||
*error = toupper((u_char)*error);
|
if (item == NULL)
|
||||||
status_message_set(c, -1, 1, 0, 0, "%s", error);
|
|
||||||
} else
|
|
||||||
cmdq_error(cdata->item, "%s", error);
|
|
||||||
free(error);
|
|
||||||
} else if (item == NULL) {
|
|
||||||
new_item = cmdq_get_command(cmdlist, NULL);
|
|
||||||
cmdq_append(c, new_item);
|
cmdq_append(c, new_item);
|
||||||
cmd_list_free(cmdlist);
|
else
|
||||||
} else {
|
|
||||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
cmd_list_free(cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cdata->item != NULL)
|
if (cdata->item != NULL)
|
||||||
cmdq_continue(cdata->item);
|
cmdq_continue(cdata->item);
|
||||||
@@ -306,8 +305,7 @@ cmd_run_shell_free(void *data)
|
|||||||
session_remove_ref(cdata->s, __func__);
|
session_remove_ref(cdata->s, __func__);
|
||||||
if (cdata->client != NULL)
|
if (cdata->client != NULL)
|
||||||
server_client_unref(cdata->client);
|
server_client_unref(cdata->client);
|
||||||
if (cdata->state != NULL)
|
cmd_parse_free(cdata->tree);
|
||||||
args_make_commands_free(cdata->state);
|
|
||||||
free(cdata->cwd);
|
free(cdata->cwd);
|
||||||
free(cdata->cmd);
|
free(cdata->cmd);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-select-layout.c,v 1.45 2026/09/09 07:03:39 nicm Exp $ */
|
/* $OpenBSD: cmd-select-layout.c,v 1.43 2026/07/10 13:38:45 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -73,13 +73,12 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
struct client *c = cmdq_get_target_client(item);
|
|
||||||
struct winlink *wl = target->wl;
|
struct winlink *wl = target->wl;
|
||||||
struct window *w = wl->window;
|
struct window *w = wl->window;
|
||||||
struct window_pane *wp = target->wp;
|
struct window_pane *wp = target->wp;
|
||||||
const char *layoutname;
|
const char *layoutname;
|
||||||
char *oldlayout, *cause = NULL;
|
char *oldlayout, *cause;
|
||||||
int next, previous, layout, flags = 0;
|
int next, previous, layout;
|
||||||
|
|
||||||
server_unzoom_window(w);
|
server_unzoom_window(w);
|
||||||
|
|
||||||
@@ -90,12 +89,8 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
if (args_has(args, 'p'))
|
if (args_has(args, 'p'))
|
||||||
previous = 1;
|
previous = 1;
|
||||||
|
|
||||||
if (c != NULL &&
|
|
||||||
(c->flags & CLIENT_CONTROL) &&
|
|
||||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
|
||||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
|
||||||
oldlayout = w->old_layout;
|
oldlayout = w->old_layout;
|
||||||
w->old_layout = layout_dump(w, w->layout_root, flags);
|
w->old_layout = layout_dump(w, w->layout_root);
|
||||||
|
|
||||||
if (next || previous) {
|
if (next || previous) {
|
||||||
if (next)
|
if (next)
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ cmd_source_file_done(__unused struct client *oc, const char *path,
|
|||||||
if (error != 0)
|
if (error != 0)
|
||||||
cmdq_error(item, "%s: %s", strerror(error), path);
|
cmdq_error(item, "%s: %s", strerror(error), path);
|
||||||
else if (bsize != 0) {
|
else if (bsize != 0) {
|
||||||
if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after,
|
if (load_cfg_from_buffer(bdata, bsize, path, cdata->after,
|
||||||
target, cdata->flags, &new_item) < 0)
|
target, cdata->flags, &new_item) < 0)
|
||||||
cdata->retval = CMD_RETURN_ERROR;
|
cdata->retval = CMD_RETURN_ERROR;
|
||||||
else if (new_item != NULL)
|
else if (new_item != NULL)
|
||||||
|
|||||||
@@ -360,7 +360,6 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
|||||||
enum pane_lines lines;
|
enum pane_lines lines;
|
||||||
u_int sx, sy;
|
u_int sx, sy;
|
||||||
int x, y, xoff, yoff, border;
|
int x, y, xoff, yoff, border;
|
||||||
int old_xoff, old_yoff, old_sx, old_sy;
|
|
||||||
|
|
||||||
if (c->tty.mouse_last_pane == -1)
|
if (c->tty.mouse_last_pane == -1)
|
||||||
return;
|
return;
|
||||||
@@ -416,15 +415,8 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
|||||||
if (sy < PANE_MINIMUM)
|
if (sy < PANE_MINIMUM)
|
||||||
sy = PANE_MINIMUM;
|
sy = PANE_MINIMUM;
|
||||||
|
|
||||||
old_xoff = wp->xoff;
|
|
||||||
old_yoff = wp->yoff;
|
|
||||||
old_sx = wp->sx;
|
|
||||||
old_sy = wp->sy;
|
|
||||||
|
|
||||||
layout_set_size(lc, sx, sy, xoff, yoff);
|
layout_set_size(lc, sx, sy, xoff, yoff);
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
|
server_redraw_window(w);
|
||||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
|
||||||
old_sy);
|
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
|
|||||||
199
cmd.c
199
cmd.c
@@ -220,18 +220,11 @@ const struct cmd_entry *cmd_table[] = {
|
|||||||
struct cmd {
|
struct cmd {
|
||||||
const struct cmd_entry *entry;
|
const struct cmd_entry *entry;
|
||||||
struct args *args;
|
struct args *args;
|
||||||
u_int group;
|
|
||||||
|
|
||||||
char *file;
|
char *file;
|
||||||
u_int line;
|
u_int line;
|
||||||
int parse_flags;
|
int parse_flags;
|
||||||
|
|
||||||
TAILQ_ENTRY(cmd) qentry;
|
|
||||||
};
|
};
|
||||||
TAILQ_HEAD(cmds, cmd);
|
|
||||||
|
|
||||||
/* Next group number for new command list. */
|
|
||||||
static u_int cmd_list_next_group = 1;
|
|
||||||
|
|
||||||
/* Log an argument vector. */
|
/* Log an argument vector. */
|
||||||
void printflike(3, 4)
|
void printflike(3, 4)
|
||||||
@@ -402,13 +395,6 @@ cmd_get_args(struct cmd *cmd)
|
|||||||
return (cmd->args);
|
return (cmd->args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get group for command. */
|
|
||||||
u_int
|
|
||||||
cmd_get_group(struct cmd *cmd)
|
|
||||||
{
|
|
||||||
return (cmd->group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get file and line for command. */
|
/* Get file and line for command. */
|
||||||
void
|
void
|
||||||
cmd_get_source(struct cmd *cmd, const char **file, u_int *line)
|
cmd_get_source(struct cmd *cmd, const char **file, u_int *line)
|
||||||
@@ -558,23 +544,6 @@ cmd_free(struct cmd *cmd)
|
|||||||
free(cmd);
|
free(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy a command. */
|
|
||||||
struct cmd *
|
|
||||||
cmd_copy(struct cmd *cmd, int argc, char **argv)
|
|
||||||
{
|
|
||||||
struct cmd *new_cmd;
|
|
||||||
|
|
||||||
new_cmd = xcalloc(1, sizeof *new_cmd);
|
|
||||||
new_cmd->entry = cmd->entry;
|
|
||||||
new_cmd->args = args_copy(cmd->args, argc, argv);
|
|
||||||
|
|
||||||
if (cmd->file != NULL)
|
|
||||||
new_cmd->file = xstrdup(cmd->file);
|
|
||||||
new_cmd->line = cmd->line;
|
|
||||||
|
|
||||||
return (new_cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get a command as a string. */
|
/* Get a command as a string. */
|
||||||
char *
|
char *
|
||||||
cmd_print(struct cmd *cmd)
|
cmd_print(struct cmd *cmd)
|
||||||
@@ -591,174 +560,6 @@ cmd_print(struct cmd *cmd)
|
|||||||
return (out);
|
return (out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a new command list. */
|
|
||||||
struct cmd_list *
|
|
||||||
cmd_list_new(void)
|
|
||||||
{
|
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
|
|
||||||
cmdlist = xcalloc(1, sizeof *cmdlist);
|
|
||||||
cmdlist->references = 1;
|
|
||||||
cmdlist->group = cmd_list_next_group++;
|
|
||||||
cmdlist->list = xcalloc(1, sizeof *cmdlist->list);
|
|
||||||
TAILQ_INIT(cmdlist->list);
|
|
||||||
return (cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Append a command to a command list. */
|
|
||||||
void
|
|
||||||
cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd)
|
|
||||||
{
|
|
||||||
cmd->group = cmdlist->group;
|
|
||||||
TAILQ_INSERT_TAIL(cmdlist->list, cmd, qentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Append all commands from one list to another. */
|
|
||||||
void
|
|
||||||
cmd_list_append_all(struct cmd_list *cmdlist, struct cmd_list *from)
|
|
||||||
{
|
|
||||||
struct cmd *cmd;
|
|
||||||
|
|
||||||
TAILQ_FOREACH(cmd, from->list, qentry)
|
|
||||||
cmd->group = cmdlist->group;
|
|
||||||
TAILQ_CONCAT(cmdlist->list, from->list, qentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Move all commands from one command list to another. */
|
|
||||||
void
|
|
||||||
cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from)
|
|
||||||
{
|
|
||||||
TAILQ_CONCAT(cmdlist->list, from->list, qentry);
|
|
||||||
cmdlist->group = cmd_list_next_group++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free a command list. */
|
|
||||||
void
|
|
||||||
cmd_list_free(struct cmd_list *cmdlist)
|
|
||||||
{
|
|
||||||
struct cmd *cmd, *cmd1;
|
|
||||||
|
|
||||||
if (--cmdlist->references != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
TAILQ_FOREACH_SAFE(cmd, cmdlist->list, qentry, cmd1) {
|
|
||||||
TAILQ_REMOVE(cmdlist->list, cmd, qentry);
|
|
||||||
cmd_free(cmd);
|
|
||||||
}
|
|
||||||
free(cmdlist->list);
|
|
||||||
free(cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy a command list, expanding %s in arguments. */
|
|
||||||
struct cmd_list *
|
|
||||||
cmd_list_copy(const struct cmd_list *cmdlist, int argc, char **argv)
|
|
||||||
{
|
|
||||||
struct cmd *cmd;
|
|
||||||
struct cmd_list *new_cmdlist;
|
|
||||||
struct cmd *new_cmd;
|
|
||||||
u_int group = cmdlist->group;
|
|
||||||
char *s;
|
|
||||||
|
|
||||||
s = cmd_list_print(cmdlist, 0);
|
|
||||||
log_debug("%s: %s", __func__, s);
|
|
||||||
free(s);
|
|
||||||
|
|
||||||
new_cmdlist = cmd_list_new();
|
|
||||||
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
|
|
||||||
if (cmd->group != group) {
|
|
||||||
new_cmdlist->group = cmd_list_next_group++;
|
|
||||||
group = cmd->group;
|
|
||||||
}
|
|
||||||
new_cmd = cmd_copy(cmd, argc, argv);
|
|
||||||
cmd_list_append(new_cmdlist, new_cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
s = cmd_list_print(new_cmdlist, 0);
|
|
||||||
log_debug("%s: %s", __func__, s);
|
|
||||||
free(s);
|
|
||||||
|
|
||||||
return (new_cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get a command list as a string. */
|
|
||||||
char *
|
|
||||||
cmd_list_print(const struct cmd_list *cmdlist, int flags)
|
|
||||||
{
|
|
||||||
struct cmd *cmd, *next;
|
|
||||||
char *buf, *this;
|
|
||||||
size_t len;
|
|
||||||
const char *separator;
|
|
||||||
int escaped = flags & CMD_LIST_PRINT_ESCAPED;
|
|
||||||
int no_groups = flags & CMD_LIST_PRINT_NO_GROUPS;
|
|
||||||
const char *single_separator = escaped ? " \\; " : " ; ";
|
|
||||||
const char *double_separator = escaped ? " \\;\\; " : " ;; ";
|
|
||||||
|
|
||||||
len = 1;
|
|
||||||
buf = xcalloc(1, len);
|
|
||||||
|
|
||||||
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
|
|
||||||
this = cmd_print(cmd);
|
|
||||||
|
|
||||||
len += strlen(this) + 6;
|
|
||||||
buf = xrealloc(buf, len);
|
|
||||||
|
|
||||||
strlcat(buf, this, len);
|
|
||||||
|
|
||||||
next = TAILQ_NEXT(cmd, qentry);
|
|
||||||
if (next != NULL) {
|
|
||||||
if (!no_groups && cmd->group != next->group)
|
|
||||||
separator = double_separator;
|
|
||||||
else
|
|
||||||
separator = single_separator;
|
|
||||||
strlcat(buf, separator, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get first command in list. */
|
|
||||||
struct cmd *
|
|
||||||
cmd_list_first(struct cmd_list *cmdlist)
|
|
||||||
{
|
|
||||||
return (TAILQ_FIRST(cmdlist->list));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get next command in list. */
|
|
||||||
struct cmd *
|
|
||||||
cmd_list_next(struct cmd *cmd)
|
|
||||||
{
|
|
||||||
return (TAILQ_NEXT(cmd, qentry));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Do all of the commands in this command list have this flag? */
|
|
||||||
int
|
|
||||||
cmd_list_all_have(struct cmd_list *cmdlist, int flag)
|
|
||||||
{
|
|
||||||
struct cmd *cmd;
|
|
||||||
|
|
||||||
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
|
|
||||||
if (~cmd->entry->flags & flag)
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Do any of the commands in this command list have this flag? */
|
|
||||||
int
|
|
||||||
cmd_list_any_have(struct cmd_list *cmdlist, int flag)
|
|
||||||
{
|
|
||||||
struct cmd *cmd;
|
|
||||||
|
|
||||||
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
|
|
||||||
if (cmd->entry->flags & flag)
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adjust current mouse position for a pane. */
|
/* Adjust current mouse position for a pane. */
|
||||||
int
|
int
|
||||||
cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
|
cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# configure.ac
|
# configure.ac
|
||||||
|
|
||||||
AC_INIT([tmux], next-3.9)
|
AC_INIT([tmux], next-3.8)
|
||||||
AC_PREREQ([2.60])
|
AC_PREREQ([2.60])
|
||||||
|
|
||||||
AC_CONFIG_AUX_DIR(etc)
|
AC_CONFIG_AUX_DIR(etc)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: control-notify.c,v 1.39 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: control-notify.c,v 1.38 2026/08/03 13:38:42 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -70,7 +70,6 @@ control_window_layout_changed_cb(__unused const char *name,
|
|||||||
struct session *s;
|
struct session *s;
|
||||||
struct winlink *wl;
|
struct winlink *wl;
|
||||||
struct window *w = event_payload_get_window(ep, "window");
|
struct window *w = event_payload_get_window(ep, "window");
|
||||||
struct format_tree *ft;
|
|
||||||
const char *template;
|
const char *template;
|
||||||
char *cp;
|
char *cp;
|
||||||
|
|
||||||
@@ -85,25 +84,19 @@ control_window_layout_changed_cb(__unused const char *name,
|
|||||||
* and we don't need to inform the client about the layout change
|
* and we don't need to inform the client about the layout change
|
||||||
* because the whole window will go away soon.
|
* because the whole window will go away soon.
|
||||||
*/
|
*/
|
||||||
if (TAILQ_FIRST(&w->winlinks) == NULL || w->layout_root == NULL)
|
wl = TAILQ_FIRST(&w->winlinks);
|
||||||
|
if (wl == NULL || w->layout_root == NULL)
|
||||||
return;
|
return;
|
||||||
|
cp = format_single(NULL, template, NULL, NULL, wl, NULL);
|
||||||
|
|
||||||
TAILQ_FOREACH(c, &clients, entry) {
|
TAILQ_FOREACH(c, &clients, entry) {
|
||||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
|
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
|
||||||
continue;
|
continue;
|
||||||
s = c->session;
|
s = c->session;
|
||||||
wl = winlink_find_by_window_id(&s->windows, w->id);
|
if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
|
||||||
if (wl == NULL)
|
control_notify_write(c, "%s", cp);
|
||||||
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. */
|
/* Notify control clients that window pane changed. */
|
||||||
|
|||||||
81
control.c
81
control.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: control.c,v 1.68 2026/09/09 08:30:05 nicm Exp $ */
|
/* $OpenBSD: control.c,v 1.66 2026/08/18 07:43:44 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -108,7 +108,6 @@ struct control_state {
|
|||||||
u_int pending_count;
|
u_int pending_count;
|
||||||
|
|
||||||
TAILQ_HEAD(, control_block) all_blocks;
|
TAILQ_HEAD(, control_block) all_blocks;
|
||||||
size_t queued_reply_bytes;
|
|
||||||
|
|
||||||
struct bufferevent *read_event;
|
struct bufferevent *read_event;
|
||||||
struct bufferevent *write_event;
|
struct bufferevent *write_event;
|
||||||
@@ -133,9 +132,6 @@ struct control_state {
|
|||||||
/* Maximum age for clients that are not using pause mode. */
|
/* Maximum age for clients that are not using pause mode. */
|
||||||
#define CONTROL_MAXIMUM_AGE 300000
|
#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. */
|
/* Flags to ignore client. */
|
||||||
#define CONTROL_IGNORE_FLAGS \
|
#define CONTROL_IGNORE_FLAGS \
|
||||||
(CLIENT_CONTROL_NOOUTPUT| \
|
(CLIENT_CONTROL_NOOUTPUT| \
|
||||||
@@ -169,15 +165,6 @@ RB_GENERATE_STATIC(control_windows, control_window, entry, control_window_cmp);
|
|||||||
static void
|
static void
|
||||||
control_free_block(struct control_state *cs, struct control_block *cb)
|
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);
|
free(cb->line);
|
||||||
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
|
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
|
||||||
free(cb);
|
free(cb);
|
||||||
@@ -420,52 +407,16 @@ control_reset_pane(struct client *c, struct window_pane *wp)
|
|||||||
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
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. */
|
/* Write an already-formatted line, queueing it behind %output if needed. */
|
||||||
static void
|
static void
|
||||||
control_write_line(struct client *c, char *line)
|
control_write_line(struct client *c, char *line)
|
||||||
{
|
{
|
||||||
struct control_state *cs = c->control_state;
|
struct control_state *cs = c->control_state;
|
||||||
struct control_block *cb;
|
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)) {
|
if (TAILQ_EMPTY(&cs->all_blocks)) {
|
||||||
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
|
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
|
||||||
bufferevent_write(cs->write_event, line, size - 1);
|
bufferevent_write(cs->write_event, line, strlen(line));
|
||||||
bufferevent_write(cs->write_event, "\n", 1);
|
bufferevent_write(cs->write_event, "\n", 1);
|
||||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||||
free(line);
|
free(line);
|
||||||
@@ -475,7 +426,6 @@ control_write_line(struct client *c, char *line)
|
|||||||
cb = xcalloc(1, sizeof *cb);
|
cb = xcalloc(1, sizeof *cb);
|
||||||
cb->line = line;
|
cb->line = line;
|
||||||
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
||||||
cs->queued_reply_bytes += size;
|
|
||||||
cb->t = get_timer();
|
cb->t = get_timer();
|
||||||
|
|
||||||
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
||||||
@@ -505,12 +455,8 @@ control_flush_deferred(struct client *c)
|
|||||||
void
|
void
|
||||||
control_write(struct client *c, const char *fmt, ...)
|
control_write(struct client *c, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
struct control_state *cs = c->control_state;
|
va_list ap;
|
||||||
char *line;
|
char *line;
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
if (cs == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
xvasprintf(&line, fmt, ap);
|
xvasprintf(&line, fmt, ap);
|
||||||
@@ -533,9 +479,6 @@ control_write_guard(struct client *c, const char *guard, long t, u_int number,
|
|||||||
struct control_state *cs = c->control_state;
|
struct control_state *cs = c->control_state;
|
||||||
char *line;
|
char *line;
|
||||||
|
|
||||||
if (cs == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (strcmp(guard, "begin") == 0)
|
if (strcmp(guard, "begin") == 0)
|
||||||
cs->guard_depth++;
|
cs->guard_depth++;
|
||||||
|
|
||||||
@@ -560,9 +503,6 @@ control_notify_write(struct client *c, const char *fmt, ...)
|
|||||||
va_list ap;
|
va_list ap;
|
||||||
char *line;
|
char *line;
|
||||||
|
|
||||||
if (cs == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
xvasprintf(&line, fmt, ap);
|
xvasprintf(&line, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
@@ -701,8 +641,9 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
|
|||||||
struct control_state *cs = c->control_state;
|
struct control_state *cs = c->control_state;
|
||||||
struct evbuffer *buffer = cs->read_event->input;
|
struct evbuffer *buffer = cs->read_event->input;
|
||||||
char *line, *error;
|
char *line, *error;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmdq_item *new_item;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
enum cmd_parse_status status;
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
|
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
|
||||||
@@ -716,9 +657,14 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
|
state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
|
||||||
status = cmd_parse_and_append(line, NULL, c, state, &error);
|
tree = cmd_parse_from_string(line, NULL, &error);
|
||||||
if (status == CMD_PARSE_ERROR)
|
if (tree == NULL)
|
||||||
cmdq_append(c, cmdq_get_callback(control_error, error));
|
cmdq_append(c, cmdq_get_callback(control_error, error));
|
||||||
|
else {
|
||||||
|
new_item = cmd_invoke_get(tree, state, NULL);
|
||||||
|
cmdq_append(c, new_item);
|
||||||
|
cmd_parse_free(tree);
|
||||||
|
}
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
|
|
||||||
free(line);
|
free(line);
|
||||||
@@ -1057,7 +1003,6 @@ control_discard_all(struct client *c)
|
|||||||
control_discard(c);
|
control_discard(c);
|
||||||
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
||||||
control_free_block(cs, cb);
|
control_free_block(cs, cb);
|
||||||
cs->queued_reply_bytes = 0;
|
|
||||||
bufferevent_disable(cs->write_event, EV_WRITE);
|
bufferevent_disable(cs->write_event, EV_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
format.c
27
format.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: format.c,v 1.417 2026/09/08 15:42:26 nicm Exp $ */
|
/* $OpenBSD: format.c,v 1.415 2026/08/31 19:34:09 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -855,42 +855,26 @@ format_cb_window_active_clients_list(struct format_tree *ft)
|
|||||||
static void *
|
static void *
|
||||||
format_cb_window_layout(struct format_tree *ft)
|
format_cb_window_layout(struct format_tree *ft)
|
||||||
{
|
{
|
||||||
struct client *c = ft->client;
|
struct window *w = ft->w;
|
||||||
struct window *w = ft->w;
|
|
||||||
struct layout_cell *lcroot;
|
|
||||||
int flags = 0;
|
|
||||||
|
|
||||||
if (w == NULL)
|
if (w == NULL)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|
||||||
if (w->saved_layout_root != NULL)
|
if (w->saved_layout_root != NULL)
|
||||||
lcroot = w->saved_layout_root;
|
return (layout_dump(w, w->saved_layout_root));
|
||||||
else
|
return (layout_dump(w, w->layout_root));
|
||||||
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. */
|
/* Callback for window_visible_layout. */
|
||||||
static void *
|
static void *
|
||||||
format_cb_window_visible_layout(struct format_tree *ft)
|
format_cb_window_visible_layout(struct format_tree *ft)
|
||||||
{
|
{
|
||||||
struct client *c = ft->client;
|
|
||||||
struct window *w = ft->w;
|
struct window *w = ft->w;
|
||||||
int flags = 0;
|
|
||||||
|
|
||||||
if (w == NULL)
|
if (w == NULL)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|
||||||
if (c != NULL &&
|
return (layout_dump(w, w->layout_root));
|
||||||
(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. */
|
/* Callback for pane_start_command. */
|
||||||
@@ -5250,7 +5234,6 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
|
|||||||
format_log(es, "session loop: $%u", s->id);
|
format_log(es, "session loop: $%u", s->id);
|
||||||
if (active != NULL &&
|
if (active != NULL &&
|
||||||
ft->c != NULL &&
|
ft->c != NULL &&
|
||||||
ft->c->session != NULL &&
|
|
||||||
s->id == ft->c->session->id)
|
s->id == ft->c->session->id)
|
||||||
use = active;
|
use = active;
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ int
|
|||||||
LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
||||||
{
|
{
|
||||||
struct cmd_parse_input pi;
|
struct cmd_parse_input pi;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
|
char *cause = NULL;
|
||||||
|
|
||||||
if (size > 2048 || size == 0)
|
if (size > 2048 || size == 0)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -44,17 +45,10 @@ LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
|||||||
memset(&pi, 0, sizeof pi);
|
memset(&pi, 0, sizeof pi);
|
||||||
pi.flags = CMD_PARSE_QUIET;
|
pi.flags = CMD_PARSE_QUIET;
|
||||||
|
|
||||||
pr = cmd_parse_from_buffer(data, size, &pi);
|
tree = cmd_parse_from_buffer(data, size, &pi, &cause);
|
||||||
switch (pr->status) {
|
if (tree != NULL)
|
||||||
case CMD_PARSE_SUCCESS:
|
cmd_parse_free(tree);
|
||||||
cmd_list_free(pr->cmdlist);
|
free(cause);
|
||||||
break;
|
|
||||||
case CMD_PARSE_ERROR:
|
|
||||||
free(pr->error);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
67
hooks.c
67
hooks.c
@@ -59,35 +59,35 @@ static struct hooks_events hooks_events = TAILQ_HEAD_INITIALIZER(hooks_events);
|
|||||||
/* Insert one hook command list. */
|
/* Insert one hook command list. */
|
||||||
static struct cmdq_item *
|
static struct cmdq_item *
|
||||||
hooks_insert_one(struct cmdq_item *item, struct hooks_data *hd,
|
hooks_insert_one(struct cmdq_item *item, struct hooks_data *hd,
|
||||||
struct cmd_list *cmdlist, struct cmdq_state *state)
|
struct cmd_parse_tree *tree, struct cmdq_state *state)
|
||||||
{
|
{
|
||||||
struct cmdq_item *new_item;
|
struct cmdq_item *new_item;
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
if (cmdlist == NULL)
|
if (tree == NULL)
|
||||||
return (item);
|
return (item);
|
||||||
if (log_get_level() != 0) {
|
if (log_get_level() != 0) {
|
||||||
s = cmd_list_print(cmdlist, 0);
|
s = cmd_parse_print(tree, 0);
|
||||||
log_debug("%s: hook %s is: %s", __func__, hd->name, s);
|
log_debug("%s: hook %s is: %s", __func__, hd->name, s);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
new_item = cmdq_get_command(cmdlist, state);
|
new_item = cmd_invoke_get(tree, state, NULL);
|
||||||
if (item != NULL)
|
if (item != NULL)
|
||||||
return (cmdq_insert_after(item, new_item));
|
return (cmdq_insert_after(item, new_item));
|
||||||
return (cmdq_append(NULL, new_item));
|
return (cmdq_append(NULL, new_item));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse a hook command. */
|
/* Parse a hook command. */
|
||||||
static struct cmd_parse_result *
|
static struct cmd_parse_tree *
|
||||||
hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs,
|
hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs,
|
||||||
const char *value)
|
const char *value, char **cause)
|
||||||
{
|
{
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
char *expanded;
|
char *expanded;
|
||||||
|
|
||||||
if (!hd->expand)
|
if (!hd->expand)
|
||||||
return (cmd_parse_from_string(value, NULL));
|
return (cmd_parse_from_string(value, NULL, cause));
|
||||||
|
|
||||||
ft = format_create_defaults(NULL, hd->client, fs->s, fs->wl, fs->wp);
|
ft = format_create_defaults(NULL, hd->client, fs->s, fs->wl, fs->wp);
|
||||||
if (hd->formats != NULL)
|
if (hd->formats != NULL)
|
||||||
@@ -95,9 +95,9 @@ hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs,
|
|||||||
expanded = format_expand(ft, value);
|
expanded = format_expand(ft, value);
|
||||||
format_free(ft);
|
format_free(ft);
|
||||||
|
|
||||||
pr = cmd_parse_from_string(expanded, NULL);
|
tree = cmd_parse_from_string(expanded, NULL, cause);
|
||||||
free(expanded);
|
free(expanded);
|
||||||
return (pr);
|
return (tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Insert commands for a hook. */
|
/* Insert commands for a hook. */
|
||||||
@@ -109,9 +109,9 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd)
|
|||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
struct options_entry *o;
|
struct options_entry *o;
|
||||||
struct options_array_item *a;
|
struct options_array_item *a;
|
||||||
struct cmd_list *cmdlist;
|
struct cmd_parse_tree *tree;
|
||||||
const char *value;
|
const char *value;
|
||||||
struct cmd_parse_result *pr;
|
char *cause = NULL;
|
||||||
|
|
||||||
log_debug("%s: inserting hook %s", __func__, hd->name);
|
log_debug("%s: inserting hook %s", __func__, hd->name);
|
||||||
|
|
||||||
@@ -155,38 +155,35 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd)
|
|||||||
|
|
||||||
if (*hd->name == '@') {
|
if (*hd->name == '@') {
|
||||||
value = options_get_string(oo, hd->name);
|
value = options_get_string(oo, hd->name);
|
||||||
pr = hooks_parse(hd, &fs, value);
|
cause = NULL;
|
||||||
switch (pr->status) {
|
tree = hooks_parse(hd, &fs, value, &cause);
|
||||||
case CMD_PARSE_ERROR:
|
if (tree == NULL) {
|
||||||
log_debug("%s: can't parse hook %s: %s", __func__,
|
log_debug("%s: can't parse hook %s: %s", __func__,
|
||||||
hd->name, pr->error);
|
hd->name, cause);
|
||||||
free(pr->error);
|
free(cause);
|
||||||
break;
|
} else {
|
||||||
case CMD_PARSE_SUCCESS:
|
hooks_insert_one(item, hd, tree, state);
|
||||||
hooks_insert_one(item, hd, pr->cmdlist, state);
|
cmd_parse_free(tree);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
a = options_array_first(o);
|
a = options_array_first(o);
|
||||||
while (a != NULL) {
|
while (a != NULL) {
|
||||||
if (hd->expand) {
|
if (hd->expand) {
|
||||||
value = options_array_item_value(a)->string;
|
value = options_array_item_value(a)->string;
|
||||||
pr = hooks_parse(hd, &fs, value);
|
cause = NULL;
|
||||||
switch (pr->status) {
|
tree = hooks_parse(hd, &fs, value, &cause);
|
||||||
case CMD_PARSE_ERROR:
|
if (tree == NULL) {
|
||||||
if (pr->error != NULL) {
|
if (cause != NULL)
|
||||||
cmdq_error(item, "%s",
|
cmdq_error(item, "%s", cause);
|
||||||
pr->error);
|
free(cause);
|
||||||
}
|
} else {
|
||||||
break;
|
item = hooks_insert_one(item, hd, tree,
|
||||||
case CMD_PARSE_SUCCESS:
|
state);
|
||||||
item = hooks_insert_one(item, hd,
|
cmd_parse_free(tree);
|
||||||
pr->cmdlist, state);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cmdlist = options_array_item_value(a)->cmdlist;
|
tree = options_array_item_value(a)->cmd;
|
||||||
item = hooks_insert_one(item, hd, cmdlist,
|
item = hooks_insert_one(item, hd, tree,
|
||||||
state);
|
state);
|
||||||
}
|
}
|
||||||
a = options_array_next(a);
|
a = options_array_next(a);
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2)
|
|||||||
static void
|
static void
|
||||||
key_bindings_free(struct key_binding *bd)
|
key_bindings_free(struct key_binding *bd)
|
||||||
{
|
{
|
||||||
cmd_list_free(bd->cmdlist);
|
cmd_parse_free(bd->cmd);
|
||||||
free((void *)bd->note);
|
free((void *)bd->note);
|
||||||
free(bd);
|
free(bd);
|
||||||
}
|
}
|
||||||
@@ -222,16 +222,15 @@ key_bindings_next(__unused struct key_table *table, struct key_binding *bd)
|
|||||||
|
|
||||||
void
|
void
|
||||||
key_bindings_add(const char *name, key_code key, const char *note, int repeat,
|
key_bindings_add(const char *name, key_code key, const char *note, int repeat,
|
||||||
struct cmd_list *cmdlist)
|
struct cmd_parse_tree *cmd)
|
||||||
{
|
{
|
||||||
struct key_table *table;
|
struct key_table *table = key_bindings_get_table(name, 1);
|
||||||
struct key_binding *bd;
|
struct key_binding *bd;
|
||||||
|
const char *k;
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
table = key_bindings_get_table(name, 1);
|
|
||||||
|
|
||||||
bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
|
bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
|
||||||
if (cmdlist == NULL) {
|
if (cmd == NULL) {
|
||||||
if (bd != NULL) {
|
if (bd != NULL) {
|
||||||
if (note != NULL) {
|
if (note != NULL) {
|
||||||
free((void *)bd->note);
|
free((void *)bd->note);
|
||||||
@@ -256,11 +255,11 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat,
|
|||||||
|
|
||||||
if (repeat)
|
if (repeat)
|
||||||
bd->flags |= KEY_BINDING_REPEAT;
|
bd->flags |= KEY_BINDING_REPEAT;
|
||||||
bd->cmdlist = cmdlist;
|
bd->cmd = cmd;
|
||||||
|
|
||||||
s = cmd_list_print(bd->cmdlist, 0);
|
k = key_string_lookup_key(bd->key, 1);
|
||||||
log_debug("%s: %#llx %s = %s", __func__, bd->key,
|
s = cmd_parse_print(bd->cmd, 0);
|
||||||
key_string_lookup_key(bd->key, 1), s);
|
log_debug("%s: %#llx %s = %s", __func__, bd->key, k, s);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +268,7 @@ key_bindings_remove(const char *name, key_code key)
|
|||||||
{
|
{
|
||||||
struct key_table *table;
|
struct key_table *table;
|
||||||
struct key_binding *bd;
|
struct key_binding *bd;
|
||||||
|
const char *k;
|
||||||
|
|
||||||
table = key_bindings_get_table(name, 0);
|
table = key_bindings_get_table(name, 0);
|
||||||
if (table == NULL)
|
if (table == NULL)
|
||||||
@@ -278,8 +278,8 @@ key_bindings_remove(const char *name, key_code key)
|
|||||||
if (bd == NULL)
|
if (bd == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
log_debug("%s: %#llx %s", __func__, bd->key,
|
k = key_string_lookup_key(bd->key, 1);
|
||||||
key_string_lookup_key(bd->key, 1));
|
log_debug("%s: %#llx %s", __func__, bd->key, k);
|
||||||
|
|
||||||
RB_REMOVE(key_bindings, &table->key_bindings, bd);
|
RB_REMOVE(key_bindings, &table->key_bindings, bd);
|
||||||
key_bindings_free(bd);
|
key_bindings_free(bd);
|
||||||
@@ -311,9 +311,8 @@ key_bindings_reset(const char *name, key_code key)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_list_free(bd->cmdlist);
|
cmd_parse_free(bd->cmd);
|
||||||
bd->cmdlist = dd->cmdlist;
|
bd->cmd = cmd_parse_add_ref(dd->cmd);
|
||||||
bd->cmdlist->references++;
|
|
||||||
|
|
||||||
free((void *)bd->note);
|
free((void *)bd->note);
|
||||||
if (dd->note != NULL)
|
if (dd->note != NULL)
|
||||||
@@ -370,8 +369,7 @@ key_bindings_init_done(__unused struct cmdq_item *item, __unused void *data)
|
|||||||
if (bd->note != NULL)
|
if (bd->note != NULL)
|
||||||
new_bd->note = xstrdup(bd->note);
|
new_bd->note = xstrdup(bd->note);
|
||||||
new_bd->flags = bd->flags;
|
new_bd->flags = bd->flags;
|
||||||
new_bd->cmdlist = bd->cmdlist;
|
new_bd->cmd = cmd_parse_add_ref(bd->cmd);
|
||||||
new_bd->cmdlist->references++;
|
|
||||||
RB_INSERT(key_bindings, &table->default_key_bindings,
|
RB_INSERT(key_bindings, &table->default_key_bindings,
|
||||||
new_bd);
|
new_bd);
|
||||||
}
|
}
|
||||||
@@ -733,50 +731,39 @@ key_bindings_init(void)
|
|||||||
"bind -Tcopy-mode-vi C-Up { send -X scroll-up }",
|
"bind -Tcopy-mode-vi C-Up { send -X scroll-up }",
|
||||||
"bind -Tcopy-mode-vi C-Down { send -X scroll-down }",
|
"bind -Tcopy-mode-vi C-Down { send -X scroll-down }",
|
||||||
};
|
};
|
||||||
|
|
||||||
u_int i;
|
u_int i;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmdq_item *new_item;
|
||||||
|
char *cause = NULL;
|
||||||
|
|
||||||
for (i = 0; i < nitems(defaults); i++) {
|
for (i = 0; i < nitems(defaults); i++) {
|
||||||
pr = cmd_parse_from_string(defaults[i], NULL);
|
tree = cmd_parse_from_string(defaults[i], NULL, &cause);
|
||||||
if (pr->status != CMD_PARSE_SUCCESS) {
|
if (tree == NULL)
|
||||||
log_debug("%s", pr->error);
|
|
||||||
fatalx("bad default key: %s", defaults[i]);
|
fatalx("bad default key: %s", defaults[i]);
|
||||||
}
|
|
||||||
cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL));
|
new_item = cmd_invoke_get(tree, NULL, NULL);
|
||||||
cmd_list_free(pr->cmdlist);
|
cmdq_append(NULL, new_item);
|
||||||
|
cmd_parse_free(tree);
|
||||||
}
|
}
|
||||||
cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL));
|
cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum cmd_retval
|
|
||||||
key_bindings_read_only(struct cmdq_item *item, __unused void *data)
|
|
||||||
{
|
|
||||||
cmdq_error(item, "client is read-only");
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct cmdq_item *
|
struct cmdq_item *
|
||||||
key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item,
|
key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item,
|
||||||
struct client *c, struct key_event *event, struct cmd_find_state *fs)
|
struct client *c, struct key_event *event, struct cmd_find_state *fs)
|
||||||
{
|
{
|
||||||
struct cmdq_item *new_item;
|
struct cmdq_item *new_item;
|
||||||
struct cmdq_state *new_state;
|
struct cmdq_state *new_state;
|
||||||
int readonly, flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
if (c == NULL || (~c->flags & CLIENT_READONLY))
|
if (bd->flags & KEY_BINDING_REPEAT)
|
||||||
readonly = 1;
|
flags |= CMDQ_STATE_REPEAT;
|
||||||
else
|
new_state = cmdq_new_state(fs, event, flags);
|
||||||
readonly = cmd_list_all_have(bd->cmdlist, CMD_READONLY);
|
|
||||||
|
new_item = cmd_invoke_get(bd->cmd, new_state, NULL);
|
||||||
|
cmdq_free_state(new_state);
|
||||||
|
|
||||||
if (!readonly)
|
|
||||||
new_item = cmdq_get_callback(key_bindings_read_only, NULL);
|
|
||||||
else {
|
|
||||||
if (bd->flags & KEY_BINDING_REPEAT)
|
|
||||||
flags |= CMDQ_STATE_REPEAT;
|
|
||||||
new_state = cmdq_new_state(fs, event, flags);
|
|
||||||
new_item = cmdq_get_command(bd->cmdlist, new_state);
|
|
||||||
cmdq_free_state(new_state);
|
|
||||||
}
|
|
||||||
if (item != NULL)
|
if (item != NULL)
|
||||||
new_item = cmdq_insert_after(item, new_item);
|
new_item = cmdq_insert_after(item, new_item);
|
||||||
else
|
else
|
||||||
|
|||||||
1065
layout-custom.c
1065
layout-custom.c
File diff suppressed because it is too large
Load Diff
43
layout.c
43
layout.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: layout.c,v 1.99 2026/09/09 07:03:39 nicm Exp $ */
|
/* $OpenBSD: layout.c,v 1.98 2026/08/25 18:38:05 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -106,7 +106,7 @@ layout_free_cell(struct layout_cell *lc, int only_nodes)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LAYOUT_WINDOWPANE:
|
case LAYOUT_WINDOWPANE:
|
||||||
if (lc->wp != NULL && lc->wp->layout_cell != NULL) {
|
if (lc->wp != NULL) {
|
||||||
lc->wp->layout_cell->parent = NULL;
|
lc->wp->layout_cell->parent = NULL;
|
||||||
lc->wp->layout_cell = NULL;
|
lc->wp->layout_cell = NULL;
|
||||||
}
|
}
|
||||||
@@ -233,6 +233,29 @@ layout_make_node(struct layout_cell *lc, enum layout_type type)
|
|||||||
lc->wp = NULL;
|
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
|
int
|
||||||
layout_cell_is_tiled(struct layout_cell *lc)
|
layout_cell_is_tiled(struct layout_cell *lc)
|
||||||
{
|
{
|
||||||
@@ -242,7 +265,7 @@ layout_cell_is_tiled(struct layout_cell *lc)
|
|||||||
return is_leaf && !is_floating;
|
return is_leaf && !is_floating;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static int
|
||||||
layout_cell_has_tiled_child(struct layout_cell *lc)
|
layout_cell_has_tiled_child(struct layout_cell *lc)
|
||||||
{
|
{
|
||||||
struct layout_cell *lcchild;
|
struct layout_cell *lcchild;
|
||||||
@@ -469,6 +492,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
sx = PANE_MINIMUM;
|
sx = PANE_MINIMUM;
|
||||||
else
|
else
|
||||||
sx = sx - sb_w - sb_pad;
|
sx = sx - sb_w - sb_pad;
|
||||||
|
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
window_pane_resize(wp, sx, sy);
|
window_pane_resize(wp, sx, sy);
|
||||||
@@ -476,11 +500,8 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
if (wp->xoff != old_xoff ||
|
if (wp->xoff != old_xoff ||
|
||||||
wp->yoff != old_yoff ||
|
wp->yoff != old_yoff ||
|
||||||
wp->sx != old_sx ||
|
wp->sx != old_sx ||
|
||||||
wp->sy != old_sy) {
|
wp->sy != old_sy)
|
||||||
changed = 1;
|
changed = 1;
|
||||||
if (window_pane_scrollbar_reserve(wp))
|
|
||||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (changed)
|
if (changed)
|
||||||
redraw_invalidate_scene(w);
|
redraw_invalidate_scene(w);
|
||||||
@@ -488,20 +509,18 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
|
|
||||||
/* Count the number of available cells in a layout. */
|
/* Count the number of available cells in a layout. */
|
||||||
u_int
|
u_int
|
||||||
layout_count_cells(struct layout_cell *lc, int with_floating)
|
layout_count_cells(struct layout_cell *lc)
|
||||||
{
|
{
|
||||||
struct layout_cell *lcchild;
|
struct layout_cell *lcchild;
|
||||||
u_int count = 0;
|
u_int count = 0;
|
||||||
|
|
||||||
switch (lc->type) {
|
switch (lc->type) {
|
||||||
case LAYOUT_WINDOWPANE:
|
case LAYOUT_WINDOWPANE:
|
||||||
if (lc->flags & LAYOUT_CELL_FLOATING && !with_floating)
|
|
||||||
return 0;
|
|
||||||
return (1);
|
return (1);
|
||||||
case LAYOUT_LEFTRIGHT:
|
case LAYOUT_LEFTRIGHT:
|
||||||
case LAYOUT_TOPBOTTOM:
|
case LAYOUT_TOPBOTTOM:
|
||||||
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
||||||
count += layout_count_cells(lcchild, with_floating);
|
count += layout_count_cells(lcchild);
|
||||||
return (count);
|
return (count);
|
||||||
default:
|
default:
|
||||||
fatalx("bad layout type");
|
fatalx("bad layout type");
|
||||||
@@ -702,7 +721,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
|
|||||||
/* If no parent, this is the last pane in a window. */
|
/* If no parent, this is the last pane in a window. */
|
||||||
lcparent = lc->parent;
|
lcparent = lc->parent;
|
||||||
if (lcparent == NULL) {
|
if (lcparent == NULL) {
|
||||||
if (*lcroot == lc)
|
if (lc->wp != NULL)
|
||||||
*lcroot = NULL;
|
*lcroot = NULL;
|
||||||
layout_free_cell(lc, 0);
|
layout_free_cell(lc, 0);
|
||||||
return;
|
return;
|
||||||
|
|||||||
12
menu.c
12
menu.c
@@ -333,9 +333,10 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
int move;
|
int move;
|
||||||
const char *name = NULL;
|
const char *name = NULL;
|
||||||
const struct menu_item *item;
|
const struct menu_item *item;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmdq_item *new_item;
|
||||||
struct key_event saved_event;
|
struct key_event saved_event;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
enum cmd_parse_status status;
|
|
||||||
char *error;
|
char *error;
|
||||||
key_code key;
|
key_code key;
|
||||||
|
|
||||||
@@ -528,11 +529,14 @@ chosen:
|
|||||||
} else
|
} else
|
||||||
event = NULL;
|
event = NULL;
|
||||||
state = cmdq_new_state(&md->fs, event, 0);
|
state = cmdq_new_state(&md->fs, event, 0);
|
||||||
|
tree = cmd_parse_from_string(item->command, NULL, &error);
|
||||||
status = cmd_parse_and_append(item->command, NULL, c, state, &error);
|
if (tree == NULL) {
|
||||||
if (status == CMD_PARSE_ERROR) {
|
|
||||||
cmdq_append(c, cmdq_get_error(error));
|
cmdq_append(c, cmdq_get_error(error));
|
||||||
free(error);
|
free(error);
|
||||||
|
} else {
|
||||||
|
new_item = cmd_invoke_get(tree, state, NULL);
|
||||||
|
cmdq_append(c, new_item);
|
||||||
|
cmd_parse_free(tree);
|
||||||
}
|
}
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
|
|
||||||
|
|||||||
11
mode-tree.c
11
mode-tree.c
@@ -1815,20 +1815,25 @@ void
|
|||||||
mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
|
mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
|
||||||
const char *template, const char *name)
|
const char *template, const char *name)
|
||||||
{
|
{
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmdq_item *new_item;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
char *command, *error;
|
char *command, *error;
|
||||||
enum cmd_parse_status status;
|
|
||||||
|
|
||||||
command = cmd_template_replace(template, name, 1);
|
command = cmd_template_replace(template, name, 1);
|
||||||
if (command != NULL && *command != '\0') {
|
if (command != NULL && *command != '\0') {
|
||||||
state = cmdq_new_state(fs, NULL, 0);
|
state = cmdq_new_state(fs, NULL, 0);
|
||||||
status = cmd_parse_and_append(command, NULL, c, state, &error);
|
tree = cmd_parse_from_string(command, NULL, &error);
|
||||||
if (status == CMD_PARSE_ERROR) {
|
if (tree == NULL) {
|
||||||
if (c != NULL) {
|
if (c != NULL) {
|
||||||
*error = toupper((u_char)*error);
|
*error = toupper((u_char)*error);
|
||||||
status_message_set(c, -1, 1, 0, 0, "%s", error);
|
status_message_set(c, -1, 1, 0, 0, "%s", error);
|
||||||
}
|
}
|
||||||
free(error);
|
free(error);
|
||||||
|
} else {
|
||||||
|
new_item = cmd_invoke_get(tree, state, NULL);
|
||||||
|
cmdq_append(c, new_item);
|
||||||
|
cmd_parse_free(tree);
|
||||||
}
|
}
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: monitor.c,v 1.8 2026/09/07 10:15:25 nicm Exp $ */
|
/* $OpenBSD: monitor.c,v 1.7 2026/07/27 19:15:58 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -614,10 +614,8 @@ monitor_parse(const char *value, char **name, enum monitor_type *type, int *id,
|
|||||||
*type = MONITOR_ALL_WINDOWS;
|
*type = MONITOR_ALL_WINDOWS;
|
||||||
else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
|
else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
|
||||||
*type = MONITOR_WINDOW;
|
*type = MONITOR_WINDOW;
|
||||||
else if (*what == '\0')
|
|
||||||
*type = MONITOR_SESSION;
|
|
||||||
else
|
else
|
||||||
goto fail;
|
*type = MONITOR_SESSION;
|
||||||
*name = xstrdup(copy);
|
*name = xstrdup(copy);
|
||||||
*format = xstrdup(split);
|
*format = xstrdup(split);
|
||||||
|
|
||||||
|
|||||||
67
options.c
67
options.c
@@ -180,8 +180,8 @@ options_value_free(struct options_entry *o, union options_value *ov)
|
|||||||
{
|
{
|
||||||
if (OPTIONS_IS_STRING(o))
|
if (OPTIONS_IS_STRING(o))
|
||||||
free(ov->string);
|
free(ov->string);
|
||||||
if (OPTIONS_IS_COMMAND(o) && ov->cmdlist != NULL)
|
if (OPTIONS_IS_COMMAND(o))
|
||||||
cmd_list_free(ov->cmdlist);
|
cmd_parse_free(ov->cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
@@ -191,7 +191,7 @@ options_value_to_string(struct options_entry *o, union options_value *ov,
|
|||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
if (OPTIONS_IS_COMMAND(o))
|
if (OPTIONS_IS_COMMAND(o))
|
||||||
return (cmd_list_print(ov->cmdlist, 0));
|
return (cmd_parse_print(ov->cmd, 0));
|
||||||
if (OPTIONS_IS_NUMBER(o)) {
|
if (OPTIONS_IS_NUMBER(o)) {
|
||||||
switch (o->tableentry->type) {
|
switch (o->tableentry->type) {
|
||||||
case OPTIONS_TABLE_NUMBER:
|
case OPTIONS_TABLE_NUMBER:
|
||||||
@@ -315,8 +315,8 @@ options_default(struct options *oo, const struct options_table_entry *oe)
|
|||||||
struct options_entry *o;
|
struct options_entry *o;
|
||||||
union options_value *ov;
|
union options_value *ov;
|
||||||
char key[32];
|
char key[32];
|
||||||
|
char *error = NULL;
|
||||||
u_int i;
|
u_int i;
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
|
|
||||||
o = options_empty(oo, oe);
|
o = options_empty(oo, oe);
|
||||||
ov = &o->value;
|
ov = &o->value;
|
||||||
@@ -338,15 +338,9 @@ options_default(struct options *oo, const struct options_table_entry *oe)
|
|||||||
ov->string = xstrdup(oe->default_str);
|
ov->string = xstrdup(oe->default_str);
|
||||||
break;
|
break;
|
||||||
case OPTIONS_TABLE_COMMAND:
|
case OPTIONS_TABLE_COMMAND:
|
||||||
pr = cmd_parse_from_string(oe->default_str, NULL);
|
ov->cmd = cmd_parse_from_string(oe->default_str, NULL, &error);
|
||||||
switch (pr->status) {
|
if (ov->cmd == NULL)
|
||||||
case CMD_PARSE_ERROR:
|
fatalx("bad default option %s", oe->name);
|
||||||
free(pr->error);
|
|
||||||
break;
|
|
||||||
case CMD_PARSE_SUCCESS:
|
|
||||||
ov->cmdlist = pr->cmdlist;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ov->number = oe->default_num;
|
ov->number = oe->default_num;
|
||||||
@@ -549,8 +543,9 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
|
|||||||
{
|
{
|
||||||
struct options_array_item *a;
|
struct options_array_item *a;
|
||||||
char *new, *new_key;
|
char *new, *new_key;
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
long long number;
|
long long number;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
char *error = NULL;
|
||||||
|
|
||||||
if (!OPTIONS_IS_ARRAY(o)) {
|
if (!OPTIONS_IS_ARRAY(o)) {
|
||||||
if (cause != NULL)
|
if (cause != NULL)
|
||||||
@@ -574,17 +569,14 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (OPTIONS_IS_COMMAND(o)) {
|
if (OPTIONS_IS_COMMAND(o)) {
|
||||||
pr = cmd_parse_from_string(value, NULL);
|
tree = cmd_parse_from_string(value, NULL, &error);
|
||||||
switch (pr->status) {
|
if (tree == NULL) {
|
||||||
case CMD_PARSE_ERROR:
|
|
||||||
if (cause != NULL)
|
if (cause != NULL)
|
||||||
*cause = pr->error;
|
*cause = error;
|
||||||
else
|
else
|
||||||
free(pr->error);
|
free(error);
|
||||||
free(new_key);
|
free(new_key);
|
||||||
return (-1);
|
return (-1);
|
||||||
case CMD_PARSE_SUCCESS:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a = options_array_item(o, new_key);
|
a = options_array_item(o, new_key);
|
||||||
@@ -592,7 +584,7 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
|
|||||||
a = options_array_new(o, new_key);
|
a = options_array_new(o, new_key);
|
||||||
else
|
else
|
||||||
options_value_free(o, &a->value);
|
options_value_free(o, &a->value);
|
||||||
a->value.cmdlist = pr->cmdlist;
|
a->value.cmd = tree;
|
||||||
free(new_key);
|
free(new_key);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
@@ -912,7 +904,7 @@ options_get_number(struct options *oo, const char *name)
|
|||||||
return (o->value.number);
|
return (o->value.number);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct cmd_list *
|
struct cmd_parse_tree *
|
||||||
options_get_command(struct options *oo, const char *name)
|
options_get_command(struct options *oo, const char *name)
|
||||||
{
|
{
|
||||||
struct options_entry *o;
|
struct options_entry *o;
|
||||||
@@ -922,7 +914,7 @@ options_get_command(struct options *oo, const char *name)
|
|||||||
fatalx("missing option %s", name);
|
fatalx("missing option %s", name);
|
||||||
if (!OPTIONS_IS_COMMAND(o))
|
if (!OPTIONS_IS_COMMAND(o))
|
||||||
fatalx("option %s is not a command", name);
|
fatalx("option %s is not a command", name);
|
||||||
return (o->value.cmdlist);
|
return (o->value.cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct options_entry *
|
struct options_entry *
|
||||||
@@ -988,7 +980,7 @@ options_set_number(struct options *oo, const char *name, long long value)
|
|||||||
|
|
||||||
struct options_entry *
|
struct options_entry *
|
||||||
options_set_command(struct options *oo, const char *name,
|
options_set_command(struct options *oo, const char *name,
|
||||||
struct cmd_list *value)
|
struct cmd_parse_tree *value)
|
||||||
{
|
{
|
||||||
struct options_entry *o;
|
struct options_entry *o;
|
||||||
|
|
||||||
@@ -1004,9 +996,8 @@ options_set_command(struct options *oo, const char *name,
|
|||||||
|
|
||||||
if (!OPTIONS_IS_COMMAND(o))
|
if (!OPTIONS_IS_COMMAND(o))
|
||||||
fatalx("option %s is not a command", name);
|
fatalx("option %s is not a command", name);
|
||||||
if (o->value.cmdlist != NULL)
|
cmd_parse_free(o->value.cmd);
|
||||||
cmd_list_free(o->value.cmdlist);
|
o->value.cmd = cmd_parse_add_ref(value);
|
||||||
o->value.cmdlist = value;
|
|
||||||
return (o);
|
return (o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1282,7 +1273,8 @@ options_from_string(struct options *oo, const struct options_table_entry *oe,
|
|||||||
const char *errstr, *new;
|
const char *errstr, *new;
|
||||||
char *old;
|
char *old;
|
||||||
key_code key;
|
key_code key;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmd_parse_input pi = { 0 };
|
||||||
|
|
||||||
if (oe != NULL) {
|
if (oe != NULL) {
|
||||||
if (value == NULL &&
|
if (value == NULL &&
|
||||||
@@ -1340,17 +1332,14 @@ options_from_string(struct options *oo, const struct options_table_entry *oe,
|
|||||||
return (options_from_string_flag(oo, name, value, cause));
|
return (options_from_string_flag(oo, name, value, cause));
|
||||||
case OPTIONS_TABLE_CHOICE:
|
case OPTIONS_TABLE_CHOICE:
|
||||||
return (options_from_string_choice(oe, oo, name, value, cause));
|
return (options_from_string_choice(oe, oo, name, value, cause));
|
||||||
case OPTIONS_TABLE_COMMAND:
|
case OPTIONS_TABLE_COMMAND: {
|
||||||
pr = cmd_parse_from_string(value, NULL);
|
tree = cmd_parse_from_string(value, &pi, cause);
|
||||||
switch (pr->status) {
|
if (tree == NULL)
|
||||||
case CMD_PARSE_ERROR:
|
|
||||||
*cause = pr->error;
|
|
||||||
return (-1);
|
return (-1);
|
||||||
case CMD_PARSE_SUCCESS:
|
options_set_command(oo, name, tree);
|
||||||
options_set_command(oo, name, pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
|||||||
60
popup.c
60
popup.c
@@ -136,8 +136,7 @@ popup_reapply_styles(struct popup_data *pd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
popup_redraw_cb(const struct tty_ctx *ttyctx, __unused u_int py,
|
popup_redraw_cb(const struct tty_ctx *ttyctx)
|
||||||
__unused u_int ny)
|
|
||||||
{
|
{
|
||||||
struct popup_data *pd = ttyctx->arg;
|
struct popup_data *pd = ttyctx->arg;
|
||||||
|
|
||||||
@@ -322,60 +321,11 @@ popup_resize_cb(__unused struct client *c, void *data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Report damage for a popup's rectangle, given in raw client/tty
|
|
||||||
* coordinates. Status-line cells are outside the window scene - they have no
|
|
||||||
* corresponding window content and redraw_damage_window() can't reach them -
|
|
||||||
* so if the popup's rectangle overlaps the status line, force it to redraw
|
|
||||||
* separately. The rest of the rectangle is clipped to the actual pane area
|
|
||||||
* (above or below the status line, whichever side it's on) before being
|
|
||||||
* translated into window coordinates and reported the normal way, the same
|
|
||||||
* way mouse coordinates are translated elsewhere (e.g. cmd-join-pane.c,
|
|
||||||
* cmd-split-window.c).
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
popup_damage(struct client *c, u_int px, u_int py, u_int sx, u_int sy)
|
|
||||||
{
|
|
||||||
struct window *w;
|
|
||||||
int statusat;
|
|
||||||
u_int ox, oy, osx, osy, lines, top, bottom, y0, y1;
|
|
||||||
|
|
||||||
if (c->session == NULL)
|
|
||||||
return;
|
|
||||||
w = c->session->curw->window;
|
|
||||||
|
|
||||||
lines = status_line_size(c);
|
|
||||||
statusat = status_at_line(c);
|
|
||||||
if (statusat >= 0 && py < (u_int)statusat + lines &&
|
|
||||||
py + sy > (u_int)statusat)
|
|
||||||
c->flags |= (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS);
|
|
||||||
|
|
||||||
if (statusat == 0) {
|
|
||||||
top = lines;
|
|
||||||
bottom = c->tty.sy;
|
|
||||||
} else if (statusat > 0) {
|
|
||||||
top = 0;
|
|
||||||
bottom = statusat;
|
|
||||||
} else {
|
|
||||||
top = 0;
|
|
||||||
bottom = c->tty.sy;
|
|
||||||
}
|
|
||||||
y0 = (py > top) ? py : top;
|
|
||||||
y1 = (py + sy < bottom) ? py + sy : bottom;
|
|
||||||
if (y0 >= y1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
tty_window_offset(&c->tty, &ox, &oy, &osx, &osy);
|
|
||||||
redraw_damage_window(w, px + ox, y0 - top + oy, sx, y1 - y0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
popup_handle_drag(struct client *c, struct popup_data *pd,
|
popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||||
struct mouse_event *m)
|
struct mouse_event *m)
|
||||||
{
|
{
|
||||||
u_int px, py;
|
u_int px, py;
|
||||||
u_int old_px = pd->px, old_py = pd->py;
|
|
||||||
u_int old_sx = pd->sx, old_sy = pd->sy;
|
|
||||||
|
|
||||||
if (!MOUSE_DRAG(m->b))
|
if (!MOUSE_DRAG(m->b))
|
||||||
pd->dragging = OFF;
|
pd->dragging = OFF;
|
||||||
@@ -398,9 +348,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
|||||||
pd->dy = m->y - pd->py;
|
pd->dy = m->y - pd->py;
|
||||||
pd->ppx = px;
|
pd->ppx = px;
|
||||||
pd->ppy = py;
|
pd->ppy = py;
|
||||||
|
server_redraw_client(c);
|
||||||
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
|
||||||
c->flags |= CLIENT_REDRAWOVERLAY;
|
|
||||||
} else if (pd->dragging == SIZE) {
|
} else if (pd->dragging == SIZE) {
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
if (m->x < pd->px + 1)
|
if (m->x < pd->px + 1)
|
||||||
@@ -427,9 +375,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
|||||||
if (pd->job != NULL)
|
if (pd->job != NULL)
|
||||||
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
||||||
}
|
}
|
||||||
|
server_redraw_client(c);
|
||||||
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
|
||||||
c->flags |= CLIENT_REDRAWOVERLAY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
prompt.c
8
prompt.c
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: prompt.c,v 1.7 2026/09/09 07:53:03 nicm Exp $ */
|
/* $OpenBSD: prompt.c,v 1.6 2026/08/17 06:45:16 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1493,11 +1493,9 @@ append_key:
|
|||||||
utf8_set(&tmp, key);
|
utf8_set(&tmp, key);
|
||||||
if (key <= 0x1f || key == 0x7f)
|
if (key <= 0x1f || key == 0x7f)
|
||||||
tmp.width = 2;
|
tmp.width = 2;
|
||||||
} else if (KEYC_IS_UNICODE(key)) {
|
} else if (KEYC_IS_UNICODE(key))
|
||||||
utf8_to_data(key, &tmp);
|
utf8_to_data(key, &tmp);
|
||||||
if (tmp.size == 0)
|
else
|
||||||
return (PROMPT_KEY_HANDLED);
|
|
||||||
} else
|
|
||||||
return (PROMPT_KEY_HANDLED);
|
return (PROMPT_KEY_HANDLED);
|
||||||
|
|
||||||
pr->buffer = xreallocarray(pr->buffer, size + 2,
|
pr->buffer = xreallocarray(pr->buffer, size + 2,
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Selecting a pane with attach-session from an already attached client must
|
|
||||||
# redraw pane contents when window-style and window-active-style differ.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lattach-redraw-outer-$$ -f/dev/null"
|
|
||||||
BEFORE=$DIR/before
|
|
||||||
AFTER=$DIR/after
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
LEFT=$($INNER new-session -dPF '#{pane_id}' -s inner -x 40 -y 8 \
|
|
||||||
"printf 'LEFT'; exec sleep 100") || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g default-terminal screen || exit 1
|
|
||||||
$INNER split-window -h -t "$LEFT" "printf 'RIGHT'; exec sleep 100" || exit 1
|
|
||||||
$INNER set-option -w -t "$LEFT" window-style bg=red || exit 1
|
|
||||||
$INNER set-option -w -t "$LEFT" window-active-style bg=blue || exit 1
|
|
||||||
$INNER bind-key -n x attach-session -t "$LEFT" || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
sleep 1
|
|
||||||
$OUTER send-keys -t outer:0.0 x || exit 1
|
|
||||||
sleep 1
|
|
||||||
[ "$($INNER display-message -p -t inner '#{pane_id}')" = "$LEFT" ] ||
|
|
||||||
fail "attach-session did not select the target pane"
|
|
||||||
$OUTER capture-pane -pe -t outer:0.0 >"$BEFORE" || exit 1
|
|
||||||
|
|
||||||
# A forced redraw produces the correct scene. It must be identical to the
|
|
||||||
# scene drawn immediately by attach-session.
|
|
||||||
$INNER refresh-client -t "$CLIENT" || exit 1
|
|
||||||
sleep 1
|
|
||||||
$OUTER capture-pane -pe -t outer:0.0 >"$AFTER" || exit 1
|
|
||||||
cmp -s "$BEFORE" "$AFTER" ||
|
|
||||||
fail "attach-session left stale active/inactive pane styles"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#!/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
|
|
||||||
0
regress/cfg-client-lost.sh
Normal file → Executable file
0
regress/cfg-client-lost.sh
Normal file → Executable file
@@ -290,92 +290,4 @@ wait_count ': G4' 7
|
|||||||
fail "window 1 of zzz not killed"
|
fail "window 1 of zzz not killed"
|
||||||
exit_mode q
|
exit_mode q
|
||||||
|
|
||||||
# --- help and information preview --------------------------------------------
|
|
||||||
# The complete tree help is taller than the 24-line test terminal, where the
|
|
||||||
# help renderer deliberately leaves the tree unchanged.
|
|
||||||
$TMUX2 resize-window -t out:0 -x 80 -y 50 || exit 1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
# The inner session has a one-line status, leaving a 49-line window.
|
|
||||||
[ "$($TMUX display-message -p -t aaa:0 '#{window_height}')" -ge 49 ] &&
|
|
||||||
break
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "attached client did not resize for tree help"
|
|
||||||
$TMUX choose-tree -t aaa:0 -F 'G5' -O index || exit 1
|
|
||||||
wait_count ': G5' 7
|
|
||||||
$TMUX2 send-keys -t out:0 F1 || fail "send-keys F1 failed"
|
|
||||||
wait_for 'Swap current and previous window'
|
|
||||||
# The first key closes help; the second exits the tree.
|
|
||||||
$TMUX send-keys -t aaa:0 q q || fail "send-keys help exit failed"
|
|
||||||
wait_mode aaa:0 0
|
|
||||||
|
|
||||||
$TMUX choose-tree -t aaa:0 -F 'G6' -O index || exit 1
|
|
||||||
wait_count ': G6' 7
|
|
||||||
$TMUX send-keys -t aaa:0 i || fail "send-keys info failed"
|
|
||||||
wait_for 'Session'
|
|
||||||
$TMUX send-keys -t aaa:0 i || fail "send-keys preview failed"
|
|
||||||
exit_mode q
|
|
||||||
|
|
||||||
# --- window swapping and command prompt for current/tagged items -------------
|
|
||||||
# Restrict a window-only tree to two disposable windows in aaa. J (the
|
|
||||||
# Shift-Down equivalent) swaps their window objects while leaving their indexes
|
|
||||||
# in place.
|
|
||||||
$TMUX new-window -d -t aaa: -n swap-a 'cat' || exit 1
|
|
||||||
$TMUX new-window -d -t aaa: -n swap-b 'cat' || exit 1
|
|
||||||
aidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \
|
|
||||||
awk -F: '$2 == "swap-a" {print $1}')
|
|
||||||
bidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \
|
|
||||||
awk -F: '$2 == "swap-b" {print $1}')
|
|
||||||
$TMUX choose-tree -w -t aaa:0 -O index -F 'WT #{window_name}' \
|
|
||||||
-f '#{m:swap-*,#{window_name}}' || exit 1
|
|
||||||
wait_count 'WT swap-' 2
|
|
||||||
$TMUX send-keys -t aaa:0 g j J || fail "window tree swap failed"
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
aname=$($TMUX display-message -p -t "aaa:$aidx" '#{window_name}')
|
|
||||||
bname=$($TMUX display-message -p -t "aaa:$bidx" '#{window_name}')
|
|
||||||
[ "$aname:$bname" = 'swap-b:swap-a' ] && break
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "window tree did not swap the two windows"
|
|
||||||
|
|
||||||
# ':' runs an entered command for the current item and rebuilds the tree when
|
|
||||||
# its queued command completes.
|
|
||||||
$TMUX set-option -g @tree-command '' || exit 1
|
|
||||||
$TMUX send-keys -t aaa:0 : || fail "window tree command prompt failed"
|
|
||||||
wait_for '(current)'
|
|
||||||
$TMUX send-keys -t aaa:0 -l "set-option -g @tree-command '%%'" || exit 1
|
|
||||||
$TMUX send-keys -t aaa:0 Enter || exit 1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
value=$($TMUX show-option -gqv @tree-command)
|
|
||||||
[ -n "$value" ] && break
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "window tree entered command did not run"
|
|
||||||
case "$value" in
|
|
||||||
=aaa:*.) ;;
|
|
||||||
*) fail "window tree command target was '$value'" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Tag both disposable windows and kill them through the tagged confirmation
|
|
||||||
# callback. The mode-hosting window is outside the filter and survives.
|
|
||||||
$TMUX send-keys -t aaa:0 g j t j t X || fail "tagged kill keys failed"
|
|
||||||
wait_for 'Kill 2 tagged?'
|
|
||||||
$TMUX send-keys -t aaa:0 y || fail "tagged kill confirmation failed"
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
left=$($TMUX list-windows -t aaa -F '#{window_name}' | \
|
|
||||||
grep -c '^swap-' || true)
|
|
||||||
[ "$left" -eq 0 ] && break
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "tagged windows were not killed"
|
|
||||||
exit_mode q
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# With clear-on-attach disabled tmux must preserve the terminal contents by
|
|
||||||
# scrolling them away, rather than entering and clearing the alternate screen.
|
|
||||||
# Capture the bytes written by a real attached client for both settings.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
INNER_ON="$TEST_TMUX -LtestIon$$ -f/dev/null"
|
|
||||||
OUTER_ON="$TEST_TMUX -LtestOon$$ -f/dev/null"
|
|
||||||
INNER_OFF="$TEST_TMUX -LtestIoff$$ -f/dev/null"
|
|
||||||
OUTER_OFF="$TEST_TMUX -LtestOoff$$ -f/dev/null"
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER_ON kill-server 2>/dev/null
|
|
||||||
$INNER_ON kill-server 2>/dev/null
|
|
||||||
$OUTER_OFF kill-server 2>/dev/null
|
|
||||||
$INNER_OFF kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$current_inner list-clients 2>/dev/null | grep -q . && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
capture_attach()
|
|
||||||
{
|
|
||||||
setting=$1
|
|
||||||
output=$2
|
|
||||||
go=$DIR/go-$setting
|
|
||||||
if [ "$setting" = on ]; then
|
|
||||||
current_inner=$INNER_ON
|
|
||||||
current_outer=$OUTER_ON
|
|
||||||
else
|
|
||||||
current_inner=$INNER_OFF
|
|
||||||
current_outer=$OUTER_OFF
|
|
||||||
fi
|
|
||||||
|
|
||||||
$current_inner new-session -d -s inner -x40 -y10 \
|
|
||||||
'exec sleep 100' || exit 1
|
|
||||||
$current_inner set-option -g status off || exit 1
|
|
||||||
$current_inner set-option -g clear-on-attach "$setting" || exit 1
|
|
||||||
$current_outer new-session -d -s outer -x40 -y10 \
|
|
||||||
"while [ ! -e '$go' ]; do sleep 0.01; done; exec $current_inner attach-session -t inner" || exit 1
|
|
||||||
$current_outer set-option -g status off || exit 1
|
|
||||||
$current_outer pipe-pane -O "cat >'$output'" || exit 1
|
|
||||||
touch "$go"
|
|
||||||
wait_for_client
|
|
||||||
sleep 0.5
|
|
||||||
$current_outer pipe-pane -O || exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
on=$DIR/on
|
|
||||||
off=$DIR/off
|
|
||||||
capture_attach on "$on"
|
|
||||||
capture_attach off "$off"
|
|
||||||
|
|
||||||
smcup=$(printf '\033[?1049h')
|
|
||||||
grep -Fq "$smcup" "$on" || fail "clear-on-attach on did not use smcup"
|
|
||||||
if grep -Fq "$smcup" "$off"; then
|
|
||||||
fail "clear-on-attach off used smcup"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Some screen terminfo entries have indn (CSI Ps S), while older ones only
|
|
||||||
# have ind (newline). The preserving path uses indn once or repeats ind once
|
|
||||||
# for every line in the terminal plus one.
|
|
||||||
escape=$(printf '\033')
|
|
||||||
if ! LC_ALL=C grep -Eq "${escape}\\[[0-9]+S" "$off"; then
|
|
||||||
hex=$(od -An -tx1 -v "$off" | tr -d ' \n')
|
|
||||||
case "$hex" in
|
|
||||||
*0a0a0a0a0a0a0a0a0a0a0a*) ;;
|
|
||||||
*) fail "clear-on-attach off did not scroll the old contents away" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# send-keys -K injects a key into a client. Commands produced by that key must
|
|
||||||
# be inserted immediately after send-keys, before later commands in the same
|
|
||||||
# command list. Focus events arriving between a prefix and its following key
|
|
||||||
# must be forwarded without cancelling the prefix table.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
client=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$client" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_option()
|
|
||||||
{
|
|
||||||
option=$1
|
|
||||||
expected=$2
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
actual=$($INNER show-option -gv "$option" 2>/dev/null)
|
|
||||||
[ "$actual" = "$expected" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "$option is '$actual', expected '$expected'"
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g focus-events on || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x40 -y10 "$INNER attach -t inner" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
wait_for_client
|
|
||||||
|
|
||||||
# The binding generated by send-keys -K must run before the command which
|
|
||||||
# follows send-keys in this command list. Before the queue-ordering fix this
|
|
||||||
# produces AB rather than BA.
|
|
||||||
$INNER set-option -g @order '' || exit 1
|
|
||||||
$INNER bind-key -n a set-option -gF @order '#{@order}B' || exit 1
|
|
||||||
$INNER send-keys -K -c "$client" a \; \
|
|
||||||
set-option -gF @order '#{@order}A' || exit 1
|
|
||||||
wait_for_option @order BA
|
|
||||||
|
|
||||||
# Put the real attached client in the prefix table, deliver FocusIn, then the
|
|
||||||
# bound key. FocusIn is not bindable and must not reset the prefix table.
|
|
||||||
$INNER bind-key x set-option -g @focus-prefix yes || exit 1
|
|
||||||
$OUTER send-keys C-b || exit 1
|
|
||||||
focus_in=$(printf '\033[I')
|
|
||||||
$OUTER send-keys -l "$focus_in" || exit 1
|
|
||||||
$OUTER send-keys x || exit 1
|
|
||||||
wait_for_option @focus-prefix yes
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise clock-mode initialization, all four styles, its one-second timer,
|
|
||||||
# resize into the compact renderer, arbitrary-key exit, and cleanup.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
capture()
|
|
||||||
{
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_mode()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($INNER display-message -p -t clock:0 '#{pane_mode}' 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "pane mode is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_hashes()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -q '#' && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "large clock did not render"
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s clock -x80 -y24 'exec sleep 100' || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -w clock-mode-colour red || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t clock" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
for style in 12 24 12-with-seconds 24-with-seconds; do
|
|
||||||
$INNER set-option -w -t clock:0 clock-mode-style "$style" || exit 1
|
|
||||||
$INNER clock-mode -t clock:0 || exit 1
|
|
||||||
wait_mode clock-mode
|
|
||||||
wait_hashes
|
|
||||||
# Let at least one timer callback observe a new second.
|
|
||||||
[ "$style" != 12-with-seconds ] || sleep 1.2
|
|
||||||
$INNER send-keys -t clock:0 x || exit 1
|
|
||||||
wait_mode ''
|
|
||||||
done
|
|
||||||
|
|
||||||
# Resizing an active mode below the large-glyph threshold selects the compact
|
|
||||||
# renderer. A visible time contains a colon and no block-clock hash.
|
|
||||||
$INNER clock-mode -t clock:0 || exit 1
|
|
||||||
wait_mode clock-mode
|
|
||||||
$INNER resize-window -t clock:0 -x20 -y5 || exit 1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -Eq '[0-9][0-9]?:[0-9][0-9]' && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "compact clock did not render after resize"
|
|
||||||
printf '%s\n' "$captured" | grep -q '#' &&
|
|
||||||
fail "compact clock still used large glyphs"
|
|
||||||
$INNER send-keys -t clock:0 Enter || exit 1
|
|
||||||
wait_mode ''
|
|
||||||
|
|
||||||
$INNER has-session -t clock || fail "server died during clock-mode tests"
|
|
||||||
exit 0
|
|
||||||
50
regress/cmd-client-argv.sh
Executable file
50
regress/cmd-client-argv.sh
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Client argv parsing and the start-server boundary.
|
||||||
|
#
|
||||||
|
# The command line given to the client is parsed and invoked as one sequence.
|
||||||
|
# This checks: a multi-command argv runs every command and starts the server
|
||||||
|
# (new-session carries CMD_STARTSERVER); a command without start-server fails
|
||||||
|
# cleanly against no server; and a parse error leaves no stray server behind.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
# Multi-command argv: starts the server and runs all commands in order.
|
||||||
|
$TMUX new-session -d -swork -nbase \; set -g @a one \; new-window -dn W2 \; \
|
||||||
|
set -g @b two || exit 1
|
||||||
|
[ "$($TMUX show -gv @a)" = one ] || { echo "argv cmd 2 did not run" >&2; exit 1; }
|
||||||
|
[ "$($TMUX show -gv @b)" = two ] || { echo "argv cmd 4 did not run" >&2; exit 1; }
|
||||||
|
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
|
||||||
|
[ "$got" = "base,W2," ] || { echo "argv windows: got [$got]" >&2; exit 1; }
|
||||||
|
# kill-server is asynchronous; wait for the server to actually exit before the
|
||||||
|
# checks below that require no server to be running.
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
i=0
|
||||||
|
while $TMUX has-session 2>/dev/null; do
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
[ $i -gt 100 ] && break
|
||||||
|
done
|
||||||
|
|
||||||
|
# A command without start-server fails cleanly when no server is running and
|
||||||
|
# does not fork one.
|
||||||
|
$TMUX new-window -dn ZZ 2>/dev/null && { echo "new-window unexpectedly succeeded" >&2; exit 1; }
|
||||||
|
if $TMUX has-session 2>/dev/null; then
|
||||||
|
echo "non-start-server command left a stray server" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A parse error with no server running starts no server.
|
||||||
|
$TMUX 'if-shell true {' 2>/dev/null && { echo "parse error did not fail" >&2; exit 1; }
|
||||||
|
if $TMUX has-session 2>/dev/null; then
|
||||||
|
echo "parse error left a stray server" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
87
regress/cmd-command-alias.sh
Executable file
87
regress/cmd-command-alias.sh
Executable file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# command-alias expansion, against a running server and at server start.
|
||||||
|
#
|
||||||
|
# An alias replaces a command name with parsed command text; arguments after the
|
||||||
|
# alias name are appended to the last command of the expansion. This covers a
|
||||||
|
# single-command alias, a multi-command alias, argument appending, a built-in
|
||||||
|
# default alias, and aliases that are defined and used as the server starts
|
||||||
|
# (both from the startup config and from the client command line that starts
|
||||||
|
# the server).
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
CONF=$(mktemp)
|
||||||
|
trap "rm -f $CONF" 0 1 15
|
||||||
|
|
||||||
|
wait_gone() {
|
||||||
|
i=0
|
||||||
|
while $TMUX has-session 2>/dev/null; do
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
[ $i -gt 100 ] && break
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Against a running server. ---------------------------------------------
|
||||||
|
$TMUX -f/dev/null start \; new-session -d -swork -nbase 2>/dev/null || exit 1
|
||||||
|
|
||||||
|
# Single-command alias.
|
||||||
|
$TMUX split-window -dt work || exit 1
|
||||||
|
$TMUX set -s command-alias[200] 'zoomit=resize-pane -Z' || exit 1
|
||||||
|
$TMUX zoomit -t work:.0 || exit 1
|
||||||
|
[ "$($TMUX display-message -p -t work '#{window_zoomed_flag}')" = 1 ] || {
|
||||||
|
echo "single-command alias did not zoom" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Multi-command alias: both commands run.
|
||||||
|
$TMUX set -s command-alias[201] 'twowin=new-window -dn AA ; new-window -dn BB' || exit 1
|
||||||
|
$TMUX run-shell -C 'twowin' || exit 1
|
||||||
|
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
|
||||||
|
[ "$got" = "base,AA,BB," ] || { echo "multi-command alias: got [$got]" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Arguments after the alias name are appended to the expansion.
|
||||||
|
$TMUX set -s command-alias[202] 'namewin=new-window -d -n' || exit 1
|
||||||
|
$TMUX run-shell -C 'namewin CC' || exit 1
|
||||||
|
$TMUX list-windows -t work -F '#{window_name}' | grep -qx CC || {
|
||||||
|
echo "alias argument append did not create window CC" >&2; exit 1; }
|
||||||
|
|
||||||
|
# A built-in default alias (splitp -> split-window).
|
||||||
|
before=$($TMUX list-panes -t work | wc -l)
|
||||||
|
$TMUX run-shell -C 'splitp -d -t work' || exit 1
|
||||||
|
after=$($TMUX list-panes -t work | wc -l)
|
||||||
|
[ "$after" -gt "$before" ] || { echo "built-in alias splitp did not split" >&2; exit 1; }
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
wait_gone
|
||||||
|
|
||||||
|
# --- At server start: alias defined and used in the startup config. --------
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
set -s command-alias[100] greet='set -g @g started'
|
||||||
|
set -s command-alias[101] mkwins='new-window -dn SW1 ; new-window -dn SW2'
|
||||||
|
new-session -d -swork -nbase
|
||||||
|
greet
|
||||||
|
mkwins
|
||||||
|
EOF
|
||||||
|
$TMUX -f$CONF start 2>/dev/null || exit 1
|
||||||
|
[ "$($TMUX show -gv @g)" = started ] || { echo "startup-config alias did not run" >&2; exit 1; }
|
||||||
|
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
|
||||||
|
[ "$got" = "base,SW1,SW2," ] || { echo "startup-config multi alias: got [$got]" >&2; exit 1; }
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
wait_gone
|
||||||
|
|
||||||
|
# --- At server start: alias from config, used on the command line that starts
|
||||||
|
# the server. ---------------------------------------------------------
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
set -s command-alias[100] greet='set -g @g argv'
|
||||||
|
EOF
|
||||||
|
$TMUX -f$CONF new-session -d -swork \; greet 2>/dev/null || exit 1
|
||||||
|
[ "$($TMUX show -gv @g)" = argv ] || { echo "argv-start alias did not run" >&2; exit 1; }
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
102
regress/cmd-invoke-deferred.sh
Executable file
102
regress/cmd-invoke-deferred.sh
Executable file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Deferred command callbacks.
|
||||||
|
#
|
||||||
|
# Several commands store a parsed tree and invoke it later through
|
||||||
|
# cmd_invoke_get: if-shell (then/else, sync and background), run-shell -C, and -
|
||||||
|
# once a key is pressed by an attached client - a key binding, confirm-before and
|
||||||
|
# command-prompt. The headless cases run directly on the inner server; the
|
||||||
|
# client cases use the nested-tmux pattern from prompt-mechanics.sh: an outer
|
||||||
|
# server hosts the inner client in a pane and keys are injected with send-keys.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
OUT="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
IN="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||||
|
|
||||||
|
$OUT kill-server 2>/dev/null
|
||||||
|
$IN kill-server 2>/dev/null
|
||||||
|
trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" 0 1 15
|
||||||
|
|
||||||
|
fail() { echo "[FAIL] $1" >&2; exit 1; }
|
||||||
|
settle() { sleep 0.5; }
|
||||||
|
|
||||||
|
CONF=$(mktemp)
|
||||||
|
trap "rm -f $CONF; $OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" 0 1 15
|
||||||
|
|
||||||
|
# --- Headless deferred callbacks: no client needed. ------------------------
|
||||||
|
$IN new -d -x80 -y23 -nbase "sh -c 'exec sleep 1000'" || exit 1
|
||||||
|
$IN set -g status on || exit 1
|
||||||
|
$IN set -g status-keys emacs || exit 1
|
||||||
|
$IN set -g window-size manual || exit 1
|
||||||
|
|
||||||
|
$IN if-shell true 'set -g @t then' 'set -g @t else' || exit 1
|
||||||
|
[ "$($IN show -gv @t)" = then ] || fail "if-shell true ran wrong branch"
|
||||||
|
$IN if-shell false 'set -g @f then' 'set -g @f else' || exit 1
|
||||||
|
[ "$($IN show -gv @f)" = else ] || fail "if-shell false ran wrong branch"
|
||||||
|
|
||||||
|
# Background (-b) if-shell evaluates its condition in a shell asynchronously.
|
||||||
|
$IN if-shell -b 'true' 'set -g @b yes' 'set -g @b no' || exit 1
|
||||||
|
settle
|
||||||
|
[ "$($IN show -gv @b)" = yes ] || fail "background if-shell ran wrong branch"
|
||||||
|
|
||||||
|
# A multi-command then-body runs both commands.
|
||||||
|
$IN if-shell true 'new-window -dn IF1 ; new-window -dn IF2' '' || exit 1
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx IF1 || fail "if-shell body cmd1 missing"
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx IF2 || fail "if-shell body cmd2 missing"
|
||||||
|
|
||||||
|
# run-shell -C invokes its argument as tmux commands.
|
||||||
|
$IN run-shell -C 'set -g @rc ran' || exit 1
|
||||||
|
[ "$($IN show -gv @rc)" = ran ] || fail "run-shell -C did not run command"
|
||||||
|
|
||||||
|
# --- Client deferred callbacks: key binding, confirm-before, command-prompt. -
|
||||||
|
$IN bind -n M-c confirm-before -p '(ok) ' 'set -g @cb confirmed' || exit 1
|
||||||
|
$IN bind -n M-d command-prompt -I pre -p '(cmd) ' 'set -g @cp %%' || exit 1
|
||||||
|
# A brace body must come through the lexer, so bind it from a config.
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
bind -n M-k { new-window -dn K1 ; new-window -dn K2 }
|
||||||
|
EOF
|
||||||
|
$IN source-file $CONF || exit 1
|
||||||
|
|
||||||
|
$OUT new -d -x80 -y24 || exit 1
|
||||||
|
$OUT set -g status off || exit 1
|
||||||
|
$OUT set -g window-size manual || exit 1
|
||||||
|
$OUT send-keys -l "$IN attach" || exit 1
|
||||||
|
$OUT send-keys Enter || exit 1
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Key binding with a stored multi-command body fires on keypress.
|
||||||
|
$OUT send-keys M-k || exit 1
|
||||||
|
settle
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx K1 || fail "key binding body cmd1 missing"
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx K2 || fail "key binding body cmd2 missing"
|
||||||
|
|
||||||
|
# confirm-before runs its command only when confirmed.
|
||||||
|
$IN set -g @cb none || exit 1
|
||||||
|
$OUT send-keys M-c || exit 1
|
||||||
|
settle
|
||||||
|
$OUT capture-pane -p | tail -1 | grep -qF '(ok)' || fail "confirm-before prompt not shown"
|
||||||
|
$OUT send-keys n || exit 1
|
||||||
|
settle
|
||||||
|
[ "$($IN show -gv @cb)" = none ] || fail "confirm-before ran command after 'n'"
|
||||||
|
$OUT send-keys M-c || exit 1
|
||||||
|
settle
|
||||||
|
$OUT send-keys y || exit 1
|
||||||
|
settle
|
||||||
|
[ "$($IN show -gv @cb)" = confirmed ] || fail "confirm-before did not run command after 'y'"
|
||||||
|
|
||||||
|
# command-prompt feeds the typed line (with -I prefill) into its template.
|
||||||
|
$IN set -g @cp none || exit 1
|
||||||
|
$OUT send-keys M-d || exit 1
|
||||||
|
settle
|
||||||
|
$OUT capture-pane -p | tail -1 | grep -qF '(cmd)' || fail "command-prompt not shown"
|
||||||
|
$OUT send-keys -l X || exit 1
|
||||||
|
$OUT send-keys Enter || exit 1
|
||||||
|
settle
|
||||||
|
[ "$($IN show -gv @cp)" = preX ] || fail "command-prompt recovered '$($IN show -gv @cp)'"
|
||||||
|
|
||||||
|
$OUT kill-server 2>/dev/null
|
||||||
|
$IN kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
75
regress/cmd-invoke-expand.sh
Executable file
75
regress/cmd-invoke-expand.sh
Executable file
@@ -0,0 +1,75 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Invoke-time expansion and conditionals.
|
||||||
|
#
|
||||||
|
# The parser builds syntax only; environment/tilde expansion, assignments and
|
||||||
|
# %if/%elif/%else are all evaluated when the tree is invoked. This drives every
|
||||||
|
# one of those through a single config and checks the resulting option values:
|
||||||
|
# - FOO=bar assignments set the environment, read back with ${FOO} and $FOO
|
||||||
|
# - %hidden assignments behave the same
|
||||||
|
# - ~ expands to the home directory
|
||||||
|
# - %if / %elif / %else selects the correct branch
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
CONF=$(mktemp)
|
||||||
|
trap "rm -f $CONF" 0 1 15
|
||||||
|
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
FOO=hello
|
||||||
|
set -g @assign "${FOO}"
|
||||||
|
set -g @dollar "$FOO"
|
||||||
|
%hidden BAR=secret
|
||||||
|
set -g @hidden "${BAR}"
|
||||||
|
set -g @undef "x${NOSUCHVAR_ZZZ}y"
|
||||||
|
set -g @tilde ~
|
||||||
|
set -g @baduser ~nosuchuser_zzz9
|
||||||
|
%if 1
|
||||||
|
set -g @iftrue then
|
||||||
|
%else
|
||||||
|
set -g @iftrue else
|
||||||
|
%endif
|
||||||
|
%if 0
|
||||||
|
set -g @iffalse then
|
||||||
|
%else
|
||||||
|
set -g @iffalse else
|
||||||
|
%endif
|
||||||
|
%if 0
|
||||||
|
set -g @elif a
|
||||||
|
%elif 1
|
||||||
|
set -g @elif b
|
||||||
|
%else
|
||||||
|
set -g @elif c
|
||||||
|
%endif
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1
|
||||||
|
$TMUX source-file $CONF || exit 1
|
||||||
|
|
||||||
|
check() {
|
||||||
|
got=$($TMUX show -gv "$1")
|
||||||
|
[ "$got" = "$2" ] || { echo "$1: got [$got] expected [$2]" >&2; exit 1; }
|
||||||
|
}
|
||||||
|
check @assign hello
|
||||||
|
check @dollar hello
|
||||||
|
check @hidden secret
|
||||||
|
check @undef xy # undefined environment variable expands to nothing
|
||||||
|
check @baduser "" # unknown user in ~user expands to nothing
|
||||||
|
check @iftrue then
|
||||||
|
check @iffalse else
|
||||||
|
check @elif b
|
||||||
|
|
||||||
|
# ~ expands to an absolute home directory (value is machine dependent).
|
||||||
|
tilde=$($TMUX show -gv @tilde)
|
||||||
|
case "$tilde" in
|
||||||
|
/*) ;;
|
||||||
|
*) echo "@tilde did not expand to an absolute path: [$tilde]" >&2; exit 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
72
regress/cmd-invoke-failure-scope.sh
Executable file
72
regress/cmd-invoke-failure-scope.sh
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Command failure scope.
|
||||||
|
#
|
||||||
|
# Failure scope is the active sequence (CMD_PARSE_SEQUENCE): commands joined by
|
||||||
|
# ';' share one scope, so a failure skips the rest of that sequence; commands on
|
||||||
|
# separate lines are independent sequences and are not skipped. After a failed
|
||||||
|
# inner brace the enclosing sequence still resumes.
|
||||||
|
#
|
||||||
|
# Each case below creates windows around a deliberately invalid command and the
|
||||||
|
# resulting window list shows exactly which commands ran.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
TMP=$(mktemp)
|
||||||
|
CONF=$(mktemp)
|
||||||
|
trap "rm -f $TMP $CONF" 0 1 15
|
||||||
|
|
||||||
|
# kill-server is asynchronous, so wait for the old server to actually exit
|
||||||
|
# before starting a fresh one (otherwise the next start races a dying server).
|
||||||
|
fresh_server() {
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
i=0
|
||||||
|
while $TMUX list-sessions >/dev/null 2>&1; do
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
[ $i -gt 100 ] && break
|
||||||
|
done
|
||||||
|
$TMUX -f/dev/null start \; new-session -d -swork -nbase || exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1 label, $2 config body, $3 expected comma-separated window list.
|
||||||
|
run() {
|
||||||
|
fresh_server
|
||||||
|
printf '%s\n' "$2" >$CONF
|
||||||
|
$TMUX source-file $CONF >/dev/null 2>&1
|
||||||
|
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
|
||||||
|
if [ "$got" != "$3" ]; then
|
||||||
|
echo "$1: got [$got] expected [$3]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ';' sequence in a brace body: A runs, the bad command skips B (same sequence),
|
||||||
|
# the outer sequence resumes so C runs.
|
||||||
|
run "semicolon body" 'if-shell true { new-window -dn A ; nonexistent_cmd ; new-window -dn B }
|
||||||
|
new-window -dn C' 'base,A,C,'
|
||||||
|
|
||||||
|
# Newlines are independent sequences: the bad command skips only itself, so both
|
||||||
|
# B and C still run.
|
||||||
|
run "newline body" 'if-shell true {
|
||||||
|
new-window -dn A
|
||||||
|
nonexistent_cmd
|
||||||
|
new-window -dn B
|
||||||
|
}
|
||||||
|
new-window -dn C' 'base,A,B,C,'
|
||||||
|
|
||||||
|
# Top-level ';' sequence: failure skips the rest of the line.
|
||||||
|
run "top-level semicolon" 'new-window -dn A ; nonexistent_cmd ; new-window -dn B' 'base,A,'
|
||||||
|
|
||||||
|
# Top-level newlines: independent, both run.
|
||||||
|
run "top-level newline" 'new-window -dn A
|
||||||
|
nonexistent_cmd
|
||||||
|
new-window -dn B' 'base,A,B,'
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
46
regress/cmd-invoke-hooks.sh
Executable file
46
regress/cmd-invoke-hooks.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Hooks run stored command trees.
|
||||||
|
#
|
||||||
|
# A hook stores a parsed command tree that is invoked through cmd_invoke_get when
|
||||||
|
# the hook fires. Covers a single-command hook, a multi-entry hook array (every
|
||||||
|
# entry fires), and that show-hooks prints the stored commands back.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
$TMUX -f/dev/null start \; new-session -d -sfirst -nbase 2>/dev/null || exit 1
|
||||||
|
|
||||||
|
# Single-command hook fires on session creation.
|
||||||
|
$TMUX set-hook -g session-created 'set -g @hook done' || exit 1
|
||||||
|
$TMUX new-session -d -ssecond || exit 1
|
||||||
|
[ "$($TMUX show -gv @hook)" = done ] || {
|
||||||
|
echo "single-command hook did not fire" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# A hook array fires every entry.
|
||||||
|
$TMUX set-hook -g session-created[10] 'new-window -dn H1' || exit 1
|
||||||
|
$TMUX set-hook -g session-created[11] 'set -g @hook2 two' || exit 1
|
||||||
|
$TMUX new-session -d -sthird || exit 1
|
||||||
|
[ "$($TMUX show -gv @hook2)" = two ] || {
|
||||||
|
echo "hook array entry 11 did not fire" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "$($TMUX list-windows -t third -F '#{window_name}')" | grep -qx H1 || {
|
||||||
|
echo "hook array entry 10 did not create window" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# show-hooks prints the stored hook commands.
|
||||||
|
$TMUX show-hooks -g | grep -q 'session-created.* set -g @hook done' || {
|
||||||
|
echo "show-hooks did not print stored hook command" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
56
regress/cmd-invoke-readonly.sh
Executable file
56
regress/cmd-invoke-readonly.sh
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Read-only client enforcement.
|
||||||
|
#
|
||||||
|
# When the invoking client is read-only, cmd_invoke builds each command and
|
||||||
|
# rejects it (without running it) unless the command entry is marked read-only.
|
||||||
|
# A read-only client is created by attaching with -r; the nested-tmux pattern
|
||||||
|
# from cmd-invoke-deferred.sh is used so a real read-only client presses a key.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
OUT="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
IN="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||||
|
|
||||||
|
$OUT kill-server 2>/dev/null
|
||||||
|
$IN kill-server 2>/dev/null
|
||||||
|
trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" 0 1 15
|
||||||
|
|
||||||
|
fail() { echo "[FAIL] $1" >&2; exit 1; }
|
||||||
|
settle() { sleep 0.5; }
|
||||||
|
|
||||||
|
# Inner session: a key bound to a non-read-only command.
|
||||||
|
$IN new -d -x80 -y23 -nbase "sh -c 'exec sleep 1000'" || exit 1
|
||||||
|
$IN set -g status on || exit 1
|
||||||
|
$IN set -g status-keys emacs || exit 1
|
||||||
|
$IN set -g window-size manual || exit 1
|
||||||
|
$IN bind -n M-w new-window -dn ROTRY || exit 1
|
||||||
|
|
||||||
|
# Outer session hosts the inner client, attached read-only with -r.
|
||||||
|
$OUT new -d -x80 -y24 || exit 1
|
||||||
|
$OUT set -g status off || exit 1
|
||||||
|
$OUT set -g window-size manual || exit 1
|
||||||
|
$OUT send-keys -l "$IN attach -r" || exit 1
|
||||||
|
$OUT send-keys Enter || exit 1
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
[ "$($IN list-clients -F '#{client_readonly}')" = 1 ] || fail "client is not read-only"
|
||||||
|
|
||||||
|
# Pressing the key runs new-window through the read-only client: it must be
|
||||||
|
# rejected, leave no window, and report the error.
|
||||||
|
$OUT send-keys M-w || exit 1
|
||||||
|
settle
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx ROTRY && \
|
||||||
|
fail "read-only client was allowed to create a window"
|
||||||
|
$OUT capture-pane -p | grep -qi 'read-only' || fail "no read-only error was shown"
|
||||||
|
|
||||||
|
# Positive control: the same command with no client is not read-only and runs,
|
||||||
|
# proving the command itself is valid and only the read-only client blocked it.
|
||||||
|
$IN new-window -dn OKWIN || exit 1
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx OKWIN || fail "control new-window did not run"
|
||||||
|
|
||||||
|
$OUT kill-server 2>/dev/null
|
||||||
|
$IN kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
51
regress/cmd-parse-errors.sh
Executable file
51
regress/cmd-parse-errors.sh
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Malformed input rejection and crash/leak canary.
|
||||||
|
#
|
||||||
|
# Each input below is a syntax error. For every one we require that:
|
||||||
|
# - parsing reports failure (non-zero exit), and
|
||||||
|
# - the server is still alive afterwards (a follow-up command succeeds).
|
||||||
|
# The second check is the important one: a parser that crashes or corrupts state
|
||||||
|
# on bad input would take the server down here.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1
|
||||||
|
|
||||||
|
n=0
|
||||||
|
check() {
|
||||||
|
n=$((n + 1))
|
||||||
|
# Expect a parse failure.
|
||||||
|
if $TMUX run-shell -C "$1" >/dev/null 2>&1; then
|
||||||
|
echo "case $n: expected failure but succeeded: $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Expect the server to still be responsive.
|
||||||
|
if ! $TMUX list-sessions >/dev/null 2>&1; then
|
||||||
|
echo "case $n: server died after: $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check 'if-shell true {' # unterminated brace body
|
||||||
|
check 'display-message a }' # stray close brace
|
||||||
|
check 'if-shell true { display-message a' # unclosed nested brace
|
||||||
|
check 'if-shell true { ; }' # empty sequence in body
|
||||||
|
check '%elif 1' # %elif with no %if
|
||||||
|
check '%endif' # %endif with no %if
|
||||||
|
check '%zzz' # unknown % directive
|
||||||
|
check '; display-message a' # leading separator
|
||||||
|
input='%if 1
|
||||||
|
display-message a'
|
||||||
|
check "$input" # unterminated %if
|
||||||
|
input='%if 1
|
||||||
|
%endif'
|
||||||
|
check "$input" # empty %if body
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
120
regress/cmd-parse-print.sh
Executable file
120
regress/cmd-parse-print.sh
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Parse/print golden suite for the command parser.
|
||||||
|
#
|
||||||
|
# Binds a spread of command syntax forms into a dedicated key table, then prints
|
||||||
|
# them back with list-keys (which calls cmd_parse_print on the stored tree). A
|
||||||
|
# command-valued option is printed too. Three things are checked:
|
||||||
|
# - the default (single line) form is byte-for-byte as expected: quoting,
|
||||||
|
# escaped \; separators, inline braces, and preservation of unexpanded
|
||||||
|
# ${env}, ~ and #{format} inside stored bodies;
|
||||||
|
# - the -p (multiline) form is as expected;
|
||||||
|
# - the default form round-trips: sourcing it again reproduces the same keys.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
SOCKET=$(mktemp -u testXXXXXX)
|
||||||
|
TMUX="$TEST_TMUX -L$SOCKET -f/dev/null"
|
||||||
|
TMUX2=
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
TMP=$(mktemp)
|
||||||
|
CONF=$(mktemp)
|
||||||
|
EXP=$(mktemp)
|
||||||
|
RT1=$(mktemp)
|
||||||
|
RT2=$(mktemp)
|
||||||
|
trap "[ -n \"\$TMUX\" ] && \$TMUX kill-server 2>/dev/null; [ -n \"\$TMUX2\" ] && \$TMUX2 kill-server 2>/dev/null; rm -f $TMP $CONF $EXP $RT1 $RT2" 0 1 15
|
||||||
|
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
bind -T parsetest a display-message hello
|
||||||
|
bind -T parsetest b display-message "hello world"
|
||||||
|
bind -T parsetest c display-message 'literal $HOME #{p} ~'
|
||||||
|
bind -T parsetest d display-message ""
|
||||||
|
bind -T parsetest e display-message "#{pane_id}"
|
||||||
|
bind -T parsetest f display-message a \; display-message b
|
||||||
|
bind -T parsetest g {
|
||||||
|
display-message one
|
||||||
|
display-message two
|
||||||
|
}
|
||||||
|
bind -T parsetest h if-shell true { display-message yes } { display-message no }
|
||||||
|
bind -T parsetest m if-shell true { display-message "${HOME}" "~" "~root" "#{pane_id}" }
|
||||||
|
bind -T parsetest n if-shell true { display-message "a\nb" "x;y" '#literal' }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Expected output. The default form is single line (round-trippable); the -p
|
||||||
|
# form is multiline, with lines inside braced bodies indented by a single tab.
|
||||||
|
cat <<'EOF' >$EXP
|
||||||
|
bind-key -T parsetest a display-message hello
|
||||||
|
bind-key -T parsetest b display-message 'hello world'
|
||||||
|
bind-key -T parsetest c display-message 'literal $HOME #{p} ~'
|
||||||
|
bind-key -T parsetest d display-message ''
|
||||||
|
bind-key -T parsetest e display-message '#{pane_id}'
|
||||||
|
bind-key -T parsetest f display-message a \; display-message b
|
||||||
|
bind-key -T parsetest g display-message one \; display-message two
|
||||||
|
bind-key -T parsetest h if-shell true { display-message yes } { display-message no }
|
||||||
|
bind-key -T parsetest m if-shell true { display-message ${HOME} ~ ~root '#{pane_id}' }
|
||||||
|
bind-key -T parsetest n if-shell true { display-message a\nb 'x;y' '#literal' }
|
||||||
|
--- options ---
|
||||||
|
display-message 'hi there'
|
||||||
|
--- multiline ---
|
||||||
|
bind-key -T parsetest a display-message hello
|
||||||
|
bind-key -T parsetest b display-message 'hello world'
|
||||||
|
bind-key -T parsetest c display-message 'literal $HOME #{p} ~'
|
||||||
|
bind-key -T parsetest d display-message ''
|
||||||
|
bind-key -T parsetest e display-message '#{pane_id}'
|
||||||
|
bind-key -T parsetest f display-message a \; display-message b
|
||||||
|
bind-key -T parsetest g display-message one
|
||||||
|
display-message two
|
||||||
|
bind-key -T parsetest h if-shell true {
|
||||||
|
display-message yes
|
||||||
|
} {
|
||||||
|
display-message no
|
||||||
|
}
|
||||||
|
bind-key -T parsetest m if-shell true {
|
||||||
|
display-message ${HOME} ~ ~root '#{pane_id}'
|
||||||
|
}
|
||||||
|
bind-key -T parsetest n if-shell true {
|
||||||
|
display-message a\nb 'x;y' '#literal'
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1
|
||||||
|
$TMUX source-file $CONF || exit 1
|
||||||
|
$TMUX set -g default-client-command 'display-message "hi there"' || exit 1
|
||||||
|
|
||||||
|
set +x
|
||||||
|
{
|
||||||
|
$TMUX list-keys -T parsetest
|
||||||
|
echo "--- options ---"
|
||||||
|
$TMUX show -gv default-client-command
|
||||||
|
echo "--- multiline ---"
|
||||||
|
$TMUX list-keys -p -T parsetest
|
||||||
|
} >$TMP 2>&1 || exit 1
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cmp -s $TMP $EXP || {
|
||||||
|
echo "cmd-parse-print: output differs from expected" >&2
|
||||||
|
diff -u $EXP $TMP >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Round-trip: the default form re-sourced into a fresh server must reproduce the
|
||||||
|
# identical key list.
|
||||||
|
$TMUX list-keys -T parsetest >$RT1 || exit 1
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
SOCKET2=$(mktemp -u testXXXXXX)
|
||||||
|
TMUX2="$TEST_TMUX -L$SOCKET2 -f/dev/null"
|
||||||
|
$TMUX2 -f/dev/null start \; new-session -d 2>/dev/null || exit 1
|
||||||
|
$TMUX2 source-file $RT1 || exit 1
|
||||||
|
$TMUX2 list-keys -T parsetest >$RT2 || exit 1
|
||||||
|
$TMUX2 kill-server 2>/dev/null
|
||||||
|
|
||||||
|
cmp -s $RT1 $RT2 || {
|
||||||
|
echo "cmd-parse-print: default form does not round-trip" >&2
|
||||||
|
diff -u $RT1 $RT2 >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise confirm-before's waiting and background callbacks, custom keys and
|
|
||||||
# prompts, default-yes handling and errors. Also cover all three lock command
|
|
||||||
# entry points with a harmless lock-command which leaves a marker.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
capture()
|
|
||||||
{
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_capture()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -Fq "$marker" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "timed out waiting for '$marker'"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_option()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($INNER show-option -gqv @confirmed 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "@confirmed is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_file()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
[ -f "$marker" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "lock command did not create $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s test -x80 -y24 'exec sleep 100' || exit 1
|
|
||||||
$INNER set-option -g status on || exit 1
|
|
||||||
$INNER set-option -g status-position bottom || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t test" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
client=$($INNER list-clients -F '#{client_name}')
|
|
||||||
[ -n "$client" ] || fail "inner client did not attach"
|
|
||||||
|
|
||||||
$INNER bind-key -n M-a confirm-before \
|
|
||||||
'set-option -g @confirmed accepted' || exit 1
|
|
||||||
$INNER bind-key -n M-b confirm-before -b -p 'Background?' \
|
|
||||||
'set-option -g @confirmed background' || exit 1
|
|
||||||
$INNER bind-key -n M-c confirm-before -c x -p 'Custom?' \
|
|
||||||
'set-option -g @confirmed custom' || exit 1
|
|
||||||
$INNER bind-key -n M-y confirm-before -y -p 'Default?' \
|
|
||||||
'set-option -g @confirmed default' || exit 1
|
|
||||||
|
|
||||||
# A non-confirming response closes the waiting prompt without running its
|
|
||||||
# command; reopening and accepting inserts the command after the waiting item.
|
|
||||||
$INNER set-option -g @confirmed sentinel || exit 1
|
|
||||||
$OUTER send-keys M-a || exit 1
|
|
||||||
wait_capture "Confirm 'set-option'? (y/n)"
|
|
||||||
$OUTER send-keys n || exit 1
|
|
||||||
sleep 0.2
|
|
||||||
wait_option sentinel
|
|
||||||
$OUTER send-keys M-a || exit 1
|
|
||||||
wait_capture "Confirm 'set-option'? (y/n)"
|
|
||||||
$OUTER send-keys y || exit 1
|
|
||||||
wait_option accepted
|
|
||||||
|
|
||||||
# -b has no waiting queue item and appends its command to the client queue.
|
|
||||||
$OUTER send-keys M-b || exit 1
|
|
||||||
wait_capture 'Background?'
|
|
||||||
$OUTER send-keys y || exit 1
|
|
||||||
wait_option background
|
|
||||||
|
|
||||||
# A custom printable key is accepted, and -y makes Enter affirmative.
|
|
||||||
$OUTER send-keys M-c || exit 1
|
|
||||||
wait_capture 'Custom?'
|
|
||||||
$OUTER send-keys x || exit 1
|
|
||||||
wait_option custom
|
|
||||||
$OUTER send-keys M-y || exit 1
|
|
||||||
wait_capture 'Default?'
|
|
||||||
$OUTER send-keys Enter || exit 1
|
|
||||||
wait_option default
|
|
||||||
|
|
||||||
# Reject invalid multi-character and control confirmation keys.
|
|
||||||
if $INNER confirm-before -b -t "$client" -c xx 'display-message x' \
|
|
||||||
>/dev/null 2>&1; then
|
|
||||||
fail "multi-character confirm key was accepted"
|
|
||||||
fi
|
|
||||||
if $INNER confirm-before -b -t "$client" -c "$(printf '\001')" \
|
|
||||||
'display-message x' >/dev/null 2>&1; then
|
|
||||||
fail "control confirm key was accepted"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# lock-client, lock-session and lock-server all send lock-command to the real
|
|
||||||
# attached client. The command returns immediately and unlocks the client.
|
|
||||||
for kind in client session server; do
|
|
||||||
marker="$DIR/locked-$kind"
|
|
||||||
$INNER set-option -t test lock-command "printf locked >$marker" || exit 1
|
|
||||||
case "$kind" in
|
|
||||||
client) $INNER lock-client -t "$client" || exit 1 ;;
|
|
||||||
session) $INNER lock-session -t test || exit 1 ;;
|
|
||||||
server) $INNER lock-server || exit 1 ;;
|
|
||||||
esac
|
|
||||||
wait_file "$marker"
|
|
||||||
done
|
|
||||||
|
|
||||||
$INNER has-session -t test || fail "server died during prompt or lock tests"
|
|
||||||
exit 0
|
|
||||||
0
regress/control-client-exit-stalled.sh
Normal file → Executable file
0
regress/control-client-exit-stalled.sh
Normal file → Executable file
@@ -19,7 +19,8 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest"
|
SOCKET=$(mktemp -u testXXXXXX)
|
||||||
|
TMUX="$TEST_TMUX -L$SOCKET"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
DIR=$(mktemp -d)
|
DIR=$(mktemp -d)
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Popups require a tty overlay and cannot be displayed by a control client.
|
|
||||||
# A popup command from control mode must be ignored cleanly, leaving the
|
|
||||||
# client command queue and server usable.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
export PATH TERM
|
|
||||||
|
|
||||||
[ -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
|
|
||||||
RAN=$DIR/popup-ran
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
exec 3>&-
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
mkfifo "$FIFO" || exit 1
|
|
||||||
$TMUX new-session -d -s control -x40 -y10 'exec sleep 100' || exit 1
|
|
||||||
$TMUX -C attach-session -t control <"$FIFO" >"$OUT" 2>&1 &
|
|
||||||
control_pid=$!
|
|
||||||
exec 3>"$FIFO"
|
|
||||||
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$TMUX list-clients -F '#{client_flags}' 2>/dev/null |
|
|
||||||
grep -q 'control-mode' && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "control client did not attach"
|
|
||||||
|
|
||||||
printf '%s\n' "display-popup -E 'touch $RAN'" >&3
|
|
||||||
printf '%s\n' "display-message -p CONTROL-POPUP-ALIVE" >&3
|
|
||||||
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
grep -q 'CONTROL-POPUP-ALIVE' "$OUT" 2>/dev/null && break
|
|
||||||
if ! kill -0 "$control_pid" 2>/dev/null; then
|
|
||||||
fail "control client exited after display-popup"
|
|
||||||
fi
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "command after control-client popup did not run"
|
|
||||||
$TMUX has-session -t control || fail "server exited after control-client popup"
|
|
||||||
[ ! -e "$RAN" ] || fail "control client started a popup command"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# A waited run-shell command can complete after a control client has closed its
|
|
||||||
# input and exited. The command queue still references the client until the job
|
|
||||||
# callback runs, but control_stop has already destroyed c->control_state. Late
|
|
||||||
# command output must be discarded instead of dereferencing the stopped control
|
|
||||||
# state and killing the server.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -Ltest"
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
|
|
||||||
OUT=$(mktemp)
|
|
||||||
trap "$TMUX kill-server 2>/dev/null; rm -f $OUT" 0 1 15
|
|
||||||
|
|
||||||
$TMUX -f/dev/null new-session -d -s main || exit 1
|
|
||||||
|
|
||||||
printf '%s\n' 'run-shell "sleep 0.2; echo TEST"' |
|
|
||||||
$TMUX -f/dev/null -C >"$OUT" 2>&1
|
|
||||||
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
$TMUX has-session -t main 2>/dev/null || {
|
|
||||||
echo "server exited after late run-shell output"
|
|
||||||
cat "$OUT"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
grep -q '^%exit' "$OUT" || {
|
|
||||||
echo "control client did not exit"
|
|
||||||
cat "$OUT"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
0
regress/control-notify-guard.sh
Normal file → Executable file
0
regress/control-notify-guard.sh
Normal file → Executable file
@@ -1,253 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise the broad non-mouse copy-mode command surface: navigation, search,
|
|
||||||
# jumps, state toggles, line/end-of-line copies, append, and pipe variants.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
x()
|
|
||||||
{
|
|
||||||
$TMUX send-keys -t copy:0 -X "$@" || fail "copy command failed: $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_mode()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($TMUX display-message -p -t copy:0 '#{pane_in_mode}' 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "pane_in_mode is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
fresh()
|
|
||||||
{
|
|
||||||
$TMUX copy-mode -t copy:0 || exit 1
|
|
||||||
wait_mode 1
|
|
||||||
x history-top
|
|
||||||
x start-of-line
|
|
||||||
}
|
|
||||||
|
|
||||||
select_text()
|
|
||||||
{
|
|
||||||
fresh
|
|
||||||
x begin-selection
|
|
||||||
x cursor-right
|
|
||||||
x cursor-right
|
|
||||||
}
|
|
||||||
|
|
||||||
$TMUX new-session -d -s copy -x40 -y8 \
|
|
||||||
"sh -c 'i=0; while [ \$i -lt 40 ]; do printf \" line-%02d needle (a[b]c) end\\n\" \$i; i=\$((i + 1)); done; printf \"\\nparagraph two needle\\n\\nparagraph three\\n\"; printf \"\\033]133;A\\007prompt-one\\n\\033]133;C\\007output-one\\n\\033]133;A\\007prompt-two\\n\\033]133;C\\007output-two\\n\"; exec sleep 100'" ||
|
|
||||||
exit 1
|
|
||||||
$TMUX set-option -g status off || exit 1
|
|
||||||
$TMUX set-option -g history-limit 200 || exit 1
|
|
||||||
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
history=$($TMUX display-message -p -t copy:0 '#{history_size}')
|
|
||||||
[ "$history" -ge 30 ] && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "history did not fill"
|
|
||||||
|
|
||||||
fresh
|
|
||||||
|
|
||||||
# Copy-mode-specific format callbacks expose the word, line, hyperlink and
|
|
||||||
# active search match at the cursor.
|
|
||||||
formats=$($TMUX display-message -p -t copy:0 \
|
|
||||||
'#{copy_cursor_word}|#{copy_cursor_line}|#{copy_cursor_hyperlink}|#{search_match}') ||
|
|
||||||
exit 1
|
|
||||||
[ -n "$formats" ] || fail "copy-mode cursor formats were empty"
|
|
||||||
|
|
||||||
# Cursor, viewport and paragraph commands. Each is asserted by successful
|
|
||||||
# dispatch and the final mode-liveness check; detailed word/selection geometry
|
|
||||||
# is covered by the existing copy-mode tests.
|
|
||||||
for command in back-to-indentation bottom-line top-line middle-line \
|
|
||||||
cursor-centre-vertical cursor-centre-horizontal end-of-line history-bottom \
|
|
||||||
history-top halfpage-down halfpage-up page-down page-up next-paragraph \
|
|
||||||
previous-paragraph next-matching-bracket previous-matching-bracket; do
|
|
||||||
x "$command"
|
|
||||||
done
|
|
||||||
x goto-line 15
|
|
||||||
x set-mark
|
|
||||||
x cursor-down
|
|
||||||
x jump-to-mark
|
|
||||||
|
|
||||||
# OSC 133 prompt and output markers support shell-integration navigation.
|
|
||||||
x history-top
|
|
||||||
x next-prompt
|
|
||||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
|
||||||
*prompt-one*) ;;
|
|
||||||
*) fail "next-prompt did not find prompt-one" ;;
|
|
||||||
esac
|
|
||||||
x next-prompt
|
|
||||||
x previous-prompt
|
|
||||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
|
||||||
*prompt-one*) ;;
|
|
||||||
*) fail "previous-prompt did not return to prompt-one" ;;
|
|
||||||
esac
|
|
||||||
x next-prompt -o
|
|
||||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
|
||||||
*output-one*) ;;
|
|
||||||
*) fail "next-prompt -o did not find output-one" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Character jumps remember their direction and character for repeat/reverse.
|
|
||||||
x jump-forward e
|
|
||||||
x jump-again
|
|
||||||
x jump-reverse
|
|
||||||
x jump-backward e
|
|
||||||
x jump-to-forward e
|
|
||||||
x jump-to-backward e
|
|
||||||
|
|
||||||
# Direct, text and incremental searches cover both directions and the saved
|
|
||||||
# search used by again/reverse.
|
|
||||||
x search-forward needle
|
|
||||||
x search-again
|
|
||||||
x search-reverse
|
|
||||||
x search-backward needle
|
|
||||||
x search-forward-text needle
|
|
||||||
x search-backward-text needle
|
|
||||||
x search-forward-incremental '=needle'
|
|
||||||
x search-forward-incremental '+needle'
|
|
||||||
x search-backward-incremental '=needle'
|
|
||||||
x search-backward-incremental '-needle'
|
|
||||||
|
|
||||||
# Stateful display and behaviour toggles.
|
|
||||||
for command in rectangle-on rectangle-off rectangle-toggle \
|
|
||||||
line-numbers-on line-numbers-off line-numbers-toggle \
|
|
||||||
refresh-on refresh-now refresh-off refresh-toggle \
|
|
||||||
scroll-exit-on scroll-exit-off scroll-exit-toggle toggle-position; do
|
|
||||||
x "$command"
|
|
||||||
done
|
|
||||||
wait_mode 1
|
|
||||||
x cancel
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
# Line and end-of-line copy commands work without an explicit selection.
|
|
||||||
fresh
|
|
||||||
x select-line
|
|
||||||
x copy-selection-and-cancel
|
|
||||||
case "$($TMUX show-buffer)" in
|
|
||||||
*line-00*) ;;
|
|
||||||
*) fail "select-line did not select the current line" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
fresh
|
|
||||||
x copy-line
|
|
||||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-line produced an empty buffer"
|
|
||||||
x cancel
|
|
||||||
|
|
||||||
fresh
|
|
||||||
x copy-end-of-line
|
|
||||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-end-of-line produced an empty buffer"
|
|
||||||
x cancel
|
|
||||||
|
|
||||||
# Append variants retain or close the mode as advertised.
|
|
||||||
$TMUX set-buffer -b append-buffer prefix || exit 1
|
|
||||||
select_text
|
|
||||||
x append-selection
|
|
||||||
appended=$($TMUX show-buffer -b append-buffer)
|
|
||||||
case "$appended" in
|
|
||||||
prefix*) ;;
|
|
||||||
*) fail "append-selection produced '$appended'" ;;
|
|
||||||
esac
|
|
||||||
x cancel
|
|
||||||
|
|
||||||
$TMUX set-buffer -b append-cancel-buffer prefix || exit 1
|
|
||||||
select_text
|
|
||||||
x append-selection-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
appended=$($TMUX show-buffer -b append-cancel-buffer)
|
|
||||||
case "$appended" in
|
|
||||||
prefix*) ;;
|
|
||||||
*) fail "append-selection-and-cancel produced '$appended'" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# The explicit cancel variants close copy mode.
|
|
||||||
fresh
|
|
||||||
x copy-line-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x copy-end-of-line-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
select_text
|
|
||||||
x copy-selection-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
# Pipe wrappers have distinct command handlers. /dev/null keeps the test
|
|
||||||
# deterministic while exercising asynchronous pipe job creation and cleanup.
|
|
||||||
fresh
|
|
||||||
x copy-pipe-line 'cat >/dev/null'
|
|
||||||
x cancel
|
|
||||||
fresh
|
|
||||||
x copy-pipe-line-and-cancel 'cat >/dev/null'
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x copy-pipe-end-of-line 'cat >/dev/null'
|
|
||||||
x cancel
|
|
||||||
fresh
|
|
||||||
x copy-pipe-end-of-line-and-cancel 'cat >/dev/null'
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
select_text
|
|
||||||
x copy-pipe-no-clear 'cat >/dev/null'
|
|
||||||
x pipe-no-clear 'cat >/dev/null'
|
|
||||||
x copy-pipe 'cat >/dev/null'
|
|
||||||
x pipe 'cat >/dev/null'
|
|
||||||
x cancel
|
|
||||||
select_text
|
|
||||||
x copy-pipe-and-cancel 'cat >/dev/null'
|
|
||||||
wait_mode 0
|
|
||||||
select_text
|
|
||||||
x pipe-and-cancel 'cat >/dev/null'
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
# Commands which cancel on downward movement have separate handlers.
|
|
||||||
fresh
|
|
||||||
x history-bottom
|
|
||||||
x bottom-line
|
|
||||||
x cursor-down-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x history-bottom
|
|
||||||
x halfpage-down-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x history-bottom
|
|
||||||
x page-down-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x history-bottom
|
|
||||||
x bottom-line
|
|
||||||
x scroll-down-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
$TMUX has-session -t copy || fail "server died during copy-mode commands"
|
|
||||||
exit 0
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Changing an existing character selection to line mode must reset both ends
|
|
||||||
# to complete lines, including when the cursor subsequently crosses the fixed
|
|
||||||
# end. An invalid regular-expression search must also discard marks left by a
|
|
||||||
# previous successful search.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
$TMUX new-session -d -x30 -y6 \
|
|
||||||
"printf 'alpha\nbeta\ngamma\ndelta\n'; exec sleep 100" || exit 1
|
|
||||||
$TMUX set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# A valid regular-expression search creates marks. Replacing it with an
|
|
||||||
# invalid expression, growing the mode, and scrolling must not leave an old
|
|
||||||
# mark array indexed with the new geometry (the original bug was an ASan heap
|
|
||||||
# overflow in this sequence).
|
|
||||||
$TMUX copy-mode || exit 1
|
|
||||||
$TMUX send-keys -X history-top || exit 1
|
|
||||||
$TMUX send-keys -X search-forward beta || exit 1
|
|
||||||
[ "$($TMUX display-message -p '#{search_present}')" = 1 ] ||
|
|
||||||
fail "successful search did not create marks"
|
|
||||||
$TMUX send-keys -X search-forward '[' || exit 1
|
|
||||||
$TMUX resize-window -x40 -y8 || exit 1
|
|
||||||
$TMUX send-keys -X scroll-down || exit 1
|
|
||||||
[ "$($TMUX display-message -p '#{search_present}')" = 0 ] ||
|
|
||||||
fail "invalid regular expression left stale marks after resize"
|
|
||||||
$TMUX send-keys -X cancel || exit 1
|
|
||||||
|
|
||||||
# Select part of alpha through part of beta, then change the existing
|
|
||||||
# selection to line mode. Both lines, and no partial columns, must be copied.
|
|
||||||
$TMUX copy-mode || exit 1
|
|
||||||
$TMUX send-keys -X history-top || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
|
||||||
$TMUX send-keys -X begin-selection || exit 1
|
|
||||||
$TMUX send-keys -X cursor-down || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
|
||||||
$TMUX send-keys -X selection-mode line || exit 1
|
|
||||||
[ "$($TMUX display-message -p '#{selection_mode}')" = line ] ||
|
|
||||||
fail "selection did not change to line mode"
|
|
||||||
$TMUX send-keys -X copy-selection || exit 1
|
|
||||||
expected=$(printf 'alpha\nbeta')
|
|
||||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
|
||||||
fail "line-mode selection did not expand to complete lines"
|
|
||||||
|
|
||||||
# Start with the cursor above the fixed end so selection-mode itself takes the
|
|
||||||
# reverse-direction path, then extend it through another line.
|
|
||||||
$TMUX copy-mode || exit 1
|
|
||||||
$TMUX send-keys -X history-top || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-down || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
|
||||||
$TMUX send-keys -X begin-selection || exit 1
|
|
||||||
$TMUX send-keys -X cursor-up || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
|
||||||
$TMUX send-keys -X selection-mode line || exit 1
|
|
||||||
$TMUX send-keys -X cursor-up || exit 1
|
|
||||||
$TMUX send-keys -X copy-selection || exit 1
|
|
||||||
expected=$(printf 'alpha\nbeta\ngamma')
|
|
||||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
|
||||||
fail "reversed line-mode selection did not keep complete lines"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
0
regress/copy-mode-unicode-whitespace.sh
Normal file → Executable file
0
regress/copy-mode-unicode-whitespace.sh
Normal file → Executable file
@@ -1,118 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise find-window's content, name and title filters in every combination,
|
|
||||||
# glob/regex and case-insensitive suffixes, plus zoom restoration.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
capture()
|
|
||||||
{
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_mode()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($INNER display-message -p -t find:base.0 '#{pane_mode}' 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "pane mode is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_find()
|
|
||||||
{
|
|
||||||
label=$1
|
|
||||||
expected=$2
|
|
||||||
shift 2
|
|
||||||
$INNER find-window -t find:base.0 "$@" || fail "$label failed"
|
|
||||||
wait_mode tree-mode
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -Fq "$expected" && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "$label did not show '$expected'"
|
|
||||||
$INNER send-keys -t find:base.0 q || exit 1
|
|
||||||
wait_mode ''
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s find -n base -x80 -y24 \
|
|
||||||
"sh -c 'printf \"BaseBody\\n\"; exec sleep 100'" || exit 1
|
|
||||||
$INNER split-window -d -h -t find:base \
|
|
||||||
"sh -c 'printf \"OtherBody\\n\"; exec sleep 100'" || exit 1
|
|
||||||
$INNER new-window -d -t find: -n NameNeedle 'exec sleep 100' || exit 1
|
|
||||||
$INNER new-window -d -t find: -n body \
|
|
||||||
"sh -c 'printf \"BodyNeedle\\n\"; exec sleep 100'" || exit 1
|
|
||||||
$INNER new-window -d -t find: -n titled 'exec sleep 100' || exit 1
|
|
||||||
$INNER select-pane -t find:titled.0 -T TitleNeedle || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t find:base" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# Wait until the body text is in the pane history before searching it.
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$INNER capture-pane -p -t find:body.0 | grep -Fq BodyNeedle && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "pane content was not ready"
|
|
||||||
|
|
||||||
# No flags means C+N+T. Then cover all three pairs and all three singletons.
|
|
||||||
run_find default NameNeedle NameNeedle
|
|
||||||
run_find content-name NameNeedle -C -N NameNeedle
|
|
||||||
run_find content-title titled -C -T TitleNeedle
|
|
||||||
run_find name-title titled -N -T TitleNeedle
|
|
||||||
run_find content-only body -C BodyNeedle
|
|
||||||
run_find name-only NameNeedle -N NameNeedle
|
|
||||||
run_find title-only titled -T TitleNeedle
|
|
||||||
|
|
||||||
# Glob case folding and all regular-expression suffix variants.
|
|
||||||
run_find insensitive NameNeedle -N -i nameneedle
|
|
||||||
run_find regex NameNeedle -N -r '^NameNeedle$'
|
|
||||||
run_find regex-insensitive NameNeedle -N -r -i '^nameneedle$'
|
|
||||||
|
|
||||||
# -Z temporarily zooms the target window and restores its prior state on exit.
|
|
||||||
$INNER find-window -Z -t find:base.0 -N NameNeedle || exit 1
|
|
||||||
wait_mode tree-mode
|
|
||||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 1 ] ||
|
|
||||||
fail "find-window -Z did not zoom"
|
|
||||||
$INNER send-keys -t find:base.0 q || exit 1
|
|
||||||
wait_mode ''
|
|
||||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 0 ] ||
|
|
||||||
fail "find-window -Z did not restore zoom"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Regression test for a floating-pane drag bug: cmd_resize_pane_redraw_floating()
|
|
||||||
# (cmd-resize-pane.c) reported damage for just a dragged floating pane's
|
|
||||||
# content rectangle, not the one-cell border frame drawn around it (see the
|
|
||||||
# "floating" case in screen-redraw.c, which draws that frame at
|
|
||||||
# xoff-1/yoff-1 through xoff+sx/yoff+sy - one cell outside the pane's own
|
|
||||||
# content area). Damage scoped to only the content area left the frame's
|
|
||||||
# previous position undrawn as the pane moved, so dragging it left a trail
|
|
||||||
# of un-erased border frames behind - visible as several "corners" stacked
|
|
||||||
# up rather than just the pane's current one.
|
|
||||||
#
|
|
||||||
# This bug has nothing to do with images - it reproduces with a plain
|
|
||||||
# floating pane and no image support required.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server >/dev/null 2>&1
|
|
||||||
$TMUX2 kill-server >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
cleanup
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
|
||||||
#
|
|
||||||
# Write a plain (unmodified) SGR button-1 press, drag update and release at
|
|
||||||
# 1-based positions to the outer pane holding the inner client - this
|
|
||||||
# matches the default MouseDown1Border/MouseDrag1Border bindings used to
|
|
||||||
# move or resize a floating pane by its border.
|
|
||||||
drag()
|
|
||||||
{
|
|
||||||
scol="$1"
|
|
||||||
srow="$2"
|
|
||||||
ecol="$3"
|
|
||||||
erow="$4"
|
|
||||||
|
|
||||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.2
|
|
||||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.2
|
|
||||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
|
|
||||||
TMP=$(mktemp)
|
|
||||||
trap "cleanup; rm -f $TMP" 0 1 15
|
|
||||||
|
|
||||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
|
||||||
$TMUX set -g mouse on
|
|
||||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
|
||||||
|
|
||||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
|
||||||
fail "new-pane -X -Y failed"
|
|
||||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
|
||||||
|
|
||||||
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
|
||||||
sleep 1
|
|
||||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
|
||||||
[ -n "$OUTER" ] || fail "No outer pane."
|
|
||||||
|
|
||||||
# Sanity check: exactly one floating pane, so exactly one top-left corner,
|
|
||||||
# before dragging anything.
|
|
||||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
|
||||||
n=$(grep -o '┌' $TMP | wc -l)
|
|
||||||
[ "$n" -eq 1 ] || fail "sanity: expected 1 corner before drag, found $n"
|
|
||||||
|
|
||||||
# Drag the floating pane by its top border (row FTOP-1, some column within
|
|
||||||
# its width) down several rows in a few separate steps, then release. A
|
|
||||||
# single drag() call already does press/motion/release, so call it several
|
|
||||||
# times in a row to simulate a multi-step real drag.
|
|
||||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
|
||||||
STARTROW=$FTOP
|
|
||||||
i=0
|
|
||||||
while [ $i -lt 6 ]; do
|
|
||||||
newrow=$((STARTROW + i + 1))
|
|
||||||
drag $((GRABCOL + 1)) $((STARTROW + i)) $((GRABCOL + 1)) $newrow
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
|
||||||
|
|
||||||
# Exactly one top-left corner should remain - the pane's current position.
|
|
||||||
# This is expected to fail before the fix: multiple corners (a trail of
|
|
||||||
# un-erased frames) would remain from the intermediate drag positions.
|
|
||||||
n=$(grep -o '┌' $TMP | wc -l)
|
|
||||||
[ "$n" -eq 1 ] || fail "expected exactly 1 corner after drag, found $n (ghost frames left behind)"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Regression test: dragging a floating pane across another pane's ordinary
|
|
||||||
# content must not redraw that other pane's scrollbar, unless the drag
|
|
||||||
# actually crosses the scrollbar's own strip.
|
|
||||||
#
|
|
||||||
# cmd_resize_pane_redraw_floating() (cmd-resize-pane.c) used to flag
|
|
||||||
# PANE_REDRAWSCROLLBAR on any pane whose whole *body* intersected the
|
|
||||||
# floating pane's old or new rectangle, rather than just its narrow
|
|
||||||
# scrollbar strip - so dragging a floating pane back and forth over an
|
|
||||||
# ordinary tiled pane's content (never touching its scrollbar) still
|
|
||||||
# needlessly redrew that pane's scrollbar on every motion step. See
|
|
||||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
|
||||||
#
|
|
||||||
# This is checked by giving the non-dragged pane a distinctive scrollbar
|
|
||||||
# colour and counting how many times its SGR code appears in the client's
|
|
||||||
# raw output while the floating pane is dragged vertically over that pane's
|
|
||||||
# body, well clear of its scrollbar column: with the fix, it should never
|
|
||||||
# reappear after the initial draw.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server >/dev/null 2>&1
|
|
||||||
$TMUX2 kill-server >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
cleanup
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
|
||||||
drag()
|
|
||||||
{
|
|
||||||
scol="$1"
|
|
||||||
srow="$2"
|
|
||||||
ecol="$3"
|
|
||||||
erow="$4"
|
|
||||||
|
|
||||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.2
|
|
||||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.2
|
|
||||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
|
|
||||||
TMP=$(mktemp)
|
|
||||||
trap "cleanup; rm -f $TMP" 0 1 15
|
|
||||||
|
|
||||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
|
||||||
$TMUX set -g mouse on || fail "set mouse failed"
|
|
||||||
$TMUX set -g default-command 'sh -c "sleep 100"' || fail "set default-command failed"
|
|
||||||
$TMUX set -g pane-scrollbars on || fail "set pane-scrollbars failed"
|
|
||||||
|
|
||||||
$TMUX split-window -h -t inner 'sh -c "sleep 100"' || fail "split-window failed"
|
|
||||||
|
|
||||||
PANES=$($TMUX list-panes -t inner -F '#{pane_id} #{pane_left}')
|
|
||||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
|
||||||
[ -n "$LEFT" ] || fail "could not identify left pane"
|
|
||||||
|
|
||||||
# A distinctive scrollbar colour for the non-dragged (left) pane only.
|
|
||||||
$TMUX set -p -t "$LEFT" pane-scrollbars-style 'fg=colour201,bg=colour17' ||
|
|
||||||
fail "set pane-scrollbars-style failed"
|
|
||||||
|
|
||||||
ALEFT=$($TMUX display-message -p -t "$LEFT" '#{pane_left}')
|
|
||||||
ATOP=$($TMUX display-message -p -t "$LEFT" '#{pane_top}')
|
|
||||||
AWIDTH=$($TMUX display-message -p -t "$LEFT" '#{pane_width}')
|
|
||||||
AHEIGHT=$($TMUX display-message -p -t "$LEFT" '#{pane_height}')
|
|
||||||
[ "$AWIDTH" -gt 15 ] || fail "left pane too narrow for this test ($AWIDTH)"
|
|
||||||
|
|
||||||
# A small floating pane placed well inside the left pane's content area,
|
|
||||||
# clear of its (right-hand) scrollbar column by several columns.
|
|
||||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 8 -y 5 \
|
|
||||||
-X $((ALEFT + 2)) -Y $((ATOP + 2))) || fail "new-pane -X -Y failed"
|
|
||||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
|
||||||
[ $((FLEFT + FWIDTH + 3)) -lt $((ALEFT + AWIDTH)) ] ||
|
|
||||||
fail "sanity: floating pane too close to the scrollbar column"
|
|
||||||
|
|
||||||
# Start the outer session with a plain shell, then start capturing before
|
|
||||||
# triggering the attach - starting the attach as the outer pane's initial
|
|
||||||
# command would mean pipe-pane only starts after the attach-driven initial
|
|
||||||
# redraw (which draws the scrollbars) has already happened, missing it.
|
|
||||||
$TMUX2 new-session -d -x 60 -y 20 || exit 1
|
|
||||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
|
||||||
[ -n "$OUTER" ] || fail "No outer pane."
|
|
||||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
|
||||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# Sanity check: the distinctive scrollbar colour reaches the client at all.
|
|
||||||
grep -qa '48;5;201' $TMP || fail "sanity: scrollbar colour never reached the client"
|
|
||||||
: >$TMP
|
|
||||||
|
|
||||||
# Drag the floating pane straight up and down by its top border, staying at
|
|
||||||
# a fixed column the whole time - this never crosses the left pane's
|
|
||||||
# scrollbar strip, only its ordinary content.
|
|
||||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
|
||||||
row=$FTOP
|
|
||||||
i=0
|
|
||||||
while [ $i -lt 6 ]; do
|
|
||||||
newrow=$((row + 1))
|
|
||||||
drag $GRABCOL $row $GRABCOL $newrow
|
|
||||||
row=$newrow
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
i=0
|
|
||||||
while [ $i -lt 6 ]; do
|
|
||||||
newrow=$((row - 1))
|
|
||||||
drag $GRABCOL $row $GRABCOL $newrow
|
|
||||||
row=$newrow
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
# The scrollbar colour should never reappear - its geometry never changed,
|
|
||||||
# and the drag never crossed its column. This is expected to fail before
|
|
||||||
# the fix - see the header comment.
|
|
||||||
n=$(grep -ac '48;5;201' $TMP)
|
|
||||||
[ "$n" -eq 0 ] ||
|
|
||||||
fail "left pane's scrollbar was redrawn $n times while dragging over its body only"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Resizing a floating pane must refresh session status formats which depend on
|
|
||||||
# its geometry, not only the pane scene and borders.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lfloating-status-outer-$$ -f/dev/null"
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has_status()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
tail -1 "$CAPTURE" | grep -q "$marker" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client status did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 50 -y 12 'sleep 100' || exit 1
|
|
||||||
FLOAT=$($INNER new-pane -PF '#{pane_id}' -x 10 -y 5 -X 5 -Y 3 \
|
|
||||||
'sleep 100') || fail "could not create floating pane"
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
$INNER set-option -g status on || exit 1
|
|
||||||
$INNER set-option -g status-position bottom || exit 1
|
|
||||||
$INNER set-option -g status-left 'WIDTH=#{pane_width}' || exit 1
|
|
||||||
$INNER set-option -g status-right '' || exit 1
|
|
||||||
$INNER set-option -g status-interval 0 || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 50 -y 12 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
OLD_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
|
||||||
wait_outer_has_status "WIDTH=$OLD_WIDTH"
|
|
||||||
|
|
||||||
RIGHT=$($INNER display-message -p -t "$FLOAT" '#{pane_right}')
|
|
||||||
TOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
X=$((RIGHT + 2))
|
|
||||||
Y=$((TOP + 2))
|
|
||||||
|
|
||||||
# Grab the right frame and enlarge the floating pane.
|
|
||||||
mouse 0 "$X" "$Y" M
|
|
||||||
mouse 32 "$((X + 1))" "$Y" M
|
|
||||||
mouse 32 "$((X + 8))" "$Y" M
|
|
||||||
mouse 0 "$((X + 8))" "$Y" m
|
|
||||||
|
|
||||||
NEW_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
|
||||||
[ "$NEW_WIDTH" -ne "$OLD_WIDTH" ] || fail "floating pane was not resized"
|
|
||||||
wait_outer_has_status "WIDTH=$NEW_WIDTH"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -71,10 +71,7 @@ cursor_very_visible
|
|||||||
cursor_x
|
cursor_x
|
||||||
cursor_y
|
cursor_y
|
||||||
history_all_bytes
|
history_all_bytes
|
||||||
history_added
|
|
||||||
history_bytes
|
history_bytes
|
||||||
history_collected
|
|
||||||
history_generation
|
|
||||||
history_limit
|
history_limit
|
||||||
history_size
|
history_size
|
||||||
host
|
host
|
||||||
@@ -106,11 +103,6 @@ pane_at_right
|
|||||||
pane_at_top
|
pane_at_top
|
||||||
pane_bg
|
pane_bg
|
||||||
pane_bottom
|
pane_bottom
|
||||||
pane_command_duration
|
|
||||||
pane_command_end_time
|
|
||||||
pane_command_running
|
|
||||||
pane_command_start_time
|
|
||||||
pane_command_status
|
|
||||||
pane_current_command
|
pane_current_command
|
||||||
pane_current_path
|
pane_current_path
|
||||||
pane_dead
|
pane_dead
|
||||||
@@ -128,21 +120,16 @@ pane_index
|
|||||||
pane_input_off
|
pane_input_off
|
||||||
pane_key_mode
|
pane_key_mode
|
||||||
pane_last
|
pane_last
|
||||||
pane_last_output_time
|
|
||||||
pane_last_prompt_time
|
|
||||||
pane_left
|
pane_left
|
||||||
pane_marked
|
pane_marked
|
||||||
pane_marked_set
|
pane_marked_set
|
||||||
pane_modal_flag
|
|
||||||
pane_mode
|
pane_mode
|
||||||
pane_output_generation
|
|
||||||
pane_path
|
pane_path
|
||||||
pane_pb_progress
|
pane_pb_progress
|
||||||
pane_pb_state
|
pane_pb_state
|
||||||
pane_pid
|
pane_pid
|
||||||
pane_pipe
|
pane_pipe
|
||||||
pane_pipe_pid
|
pane_pipe_pid
|
||||||
pane_private_modes
|
|
||||||
pane_right
|
pane_right
|
||||||
pane_search_string
|
pane_search_string
|
||||||
pane_start_command
|
pane_start_command
|
||||||
@@ -154,8 +141,6 @@ pane_title
|
|||||||
pane_top
|
pane_top
|
||||||
pane_tty
|
pane_tty
|
||||||
pane_unseen_changes
|
pane_unseen_changes
|
||||||
pane_unzoomed_height
|
|
||||||
pane_unzoomed_width
|
|
||||||
pane_width
|
pane_width
|
||||||
pane_x
|
pane_x
|
||||||
pane_y
|
pane_y
|
||||||
@@ -221,10 +206,7 @@ window_layout
|
|||||||
window_linked
|
window_linked
|
||||||
window_linked_sessions
|
window_linked_sessions
|
||||||
window_linked_sessions_list
|
window_linked_sessions_list
|
||||||
window_manual_height
|
|
||||||
window_manual_width
|
|
||||||
window_marked_flag
|
window_marked_flag
|
||||||
window_modal_pane
|
|
||||||
window_name
|
window_name
|
||||||
window_offset_x
|
window_offset_x
|
||||||
window_offset_y
|
window_offset_y
|
||||||
|
|||||||
@@ -218,13 +218,11 @@ $TMUX set-hook -gu pane-mode-changed || fail "unset pane-mode-changed failed"
|
|||||||
|
|
||||||
# pane-exited when a pane's command exits with remain-on-exit off.
|
# pane-exited when a pane's command exits with remain-on-exit off.
|
||||||
$TMUX set -g @x 0 || fail "set @x failed"
|
$TMUX set -g @x 0 || fail "set @x failed"
|
||||||
$TMUX set-hook -g pane-exited \
|
$TMUX set-hook -g pane-exited 'set -gF @x "#{hook}:#{hook_pane}"' ||
|
||||||
'set -gF @x "#{hook}:#{hook_pane}:#{hook_exit_status}:#{hook_exit_success}"' ||
|
|
||||||
fail "set-hook pane-exited failed"
|
fail "set-hook pane-exited failed"
|
||||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
|
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') ||
|
||||||
'trap "" HUP; exec </dev/null >/dev/null 2>&1; sleep 1; exit 42') ||
|
fail "split-window true failed"
|
||||||
fail "split-window exit 42 failed"
|
wait_for @x "pane-exited:$pane"
|
||||||
wait_for @x "pane-exited:$pane:42:0"
|
|
||||||
$TMUX set-hook -gu pane-exited || fail "unset pane-exited failed"
|
$TMUX set-hook -gu pane-exited || fail "unset pane-exited failed"
|
||||||
|
|
||||||
# pane-died when a pane's command exits with remain-on-exit on.
|
# pane-died when a pane's command exits with remain-on-exit on.
|
||||||
|
|||||||
@@ -7,24 +7,35 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
SOCKET=$(mktemp -u testAXXXXXX)
|
||||||
|
TMUX="$TEST_TMUX -L$SOCKET -f/dev/null"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
TMP=$(mktemp)
|
TMP=$(mktemp)
|
||||||
OUT=$(mktemp)
|
OUT=$(mktemp)
|
||||||
trap "rm -f $TMP $OUT" 0 1 15
|
cleanup() {
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
rm -f "$TMP" "$OUT"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
cat <<EOF >$TMP
|
cat <<EOF >"$TMP"
|
||||||
if 'true' 'wibble wobble'
|
if 'true' 'wibble wobble'
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$TMUX -f$TMP -C new <<EOF >$OUT
|
$TMUX -f"$TMP" -C new <<EOF >"$OUT"
|
||||||
EOF
|
EOF
|
||||||
grep -q "^%config-error $TMP:1: $TMP:1: unknown command: wibble$" $OUT
|
if ! grep -q "^%config-error $TMP:1: unknown command: wibble$" "$OUT"; then
|
||||||
|
cat "$OUT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
cat <<EOF >$TMP
|
cat <<EOF >"$TMP"
|
||||||
wibble wobble
|
wibble wobble
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "source $TMP" | $TMUX -C new >$OUT
|
echo "source $TMP" | $TMUX -C new >"$OUT"
|
||||||
grep -q "^%config-error $TMP:1: unknown command: wibble$" $OUT
|
if ! grep -q "^%config-error $TMP:1: unknown command: wibble$" "$OUT"; then
|
||||||
|
cat "$OUT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
137
regress/json.sh
137
regress/json.sh
@@ -1,137 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,986 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise every predefined layout, next/previous cycling, lookup and restore
|
|
||||||
# paths, one-pane early returns, minimum-size handling and floating panes.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
export PATH TERM
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
|
||||||
SEEN="$DIR/layouts"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
layout()
|
|
||||||
{
|
|
||||||
$TMUX display-message -p -t layouts:many '#{window_layout}'
|
|
||||||
}
|
|
||||||
|
|
||||||
check_layout()
|
|
||||||
{
|
|
||||||
name=$1
|
|
||||||
$TMUX select-layout -t layouts:many "$name" ||
|
|
||||||
fail "select-layout $name failed"
|
|
||||||
value=$(layout)
|
|
||||||
[ -n "$value" ] || fail "$name produced an empty layout"
|
|
||||||
if grep -Fxq "$value" "$SEEN"; then
|
|
||||||
fail "$name produced the same layout as an earlier preset"
|
|
||||||
fi
|
|
||||||
printf '%s\n' "$value" >>"$SEEN"
|
|
||||||
[ "$($TMUX list-panes -t layouts:many -F x | wc -l)" -eq 5 ] ||
|
|
||||||
fail "$name lost a pane"
|
|
||||||
[ "$($TMUX list-panes -t layouts:many -F '#{pane_floating_flag}' | \
|
|
||||||
grep -c '^1$')" -eq 1 ] || fail "$name did not preserve the floating pane"
|
|
||||||
}
|
|
||||||
|
|
||||||
$TMUX new-session -d -s layouts -n many -x100 -y40 'exec sleep 100' || exit 1
|
|
||||||
$TMUX set-option -g window-size manual || exit 1
|
|
||||||
$TMUX split-window -d -h -t layouts:many 'exec sleep 100' || exit 1
|
|
||||||
$TMUX split-window -d -v -t layouts:many 'exec sleep 100' || exit 1
|
|
||||||
$TMUX split-window -d -h -t layouts:many 'exec sleep 100' || exit 1
|
|
||||||
floating=$($TMUX new-pane -dP -F '#{pane_id}' -t layouts:many \
|
|
||||||
-x20 -y8 -X10 -Y5 'exec sleep 100') || exit 1
|
|
||||||
: >"$SEEN"
|
|
||||||
|
|
||||||
# Options exercise percentage and explicit secondary-size calculations.
|
|
||||||
$TMUX set-option -w -t layouts:many main-pane-height 30% || exit 1
|
|
||||||
$TMUX set-option -w -t layouts:many other-pane-height 12 || exit 1
|
|
||||||
$TMUX set-option -w -t layouts:many main-pane-width 35% || exit 1
|
|
||||||
$TMUX set-option -w -t layouts:many other-pane-width 20 || exit 1
|
|
||||||
|
|
||||||
for name in even-horizontal even-vertical main-horizontal \
|
|
||||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
|
||||||
check_layout "$name"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Exact and unique-prefix lookup work; ambiguous and invalid names fail.
|
|
||||||
$TMUX select-layout -t layouts:many even-h || fail "unique layout prefix failed"
|
|
||||||
$TMUX select-layout -t layouts:many main >/dev/null 2>&1 &&
|
|
||||||
fail "ambiguous layout prefix succeeded"
|
|
||||||
$TMUX select-layout -t layouts:many no-such-layout >/dev/null 2>&1 &&
|
|
||||||
fail "invalid layout name succeeded"
|
|
||||||
|
|
||||||
# Cycle through all layouts in both directions, including wraparound. The
|
|
||||||
# aliases and select-layout flags share layout_set_next/previous.
|
|
||||||
first=$(layout)
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 7 ]; do
|
|
||||||
$TMUX next-layout -t layouts:many || exit 1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$(layout)" = "$first" ] || fail "next-layout did not wrap"
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 7 ]; do
|
|
||||||
$TMUX previous-layout -t layouts:many || exit 1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$(layout)" = "$first" ] || fail "previous-layout did not wrap"
|
|
||||||
$TMUX select-layout -t layouts:many -n || exit 1
|
|
||||||
$TMUX select-layout -t layouts:many -p || exit 1
|
|
||||||
|
|
||||||
# No argument reapplies the last preset; -o restores the previous layout; -E
|
|
||||||
# spreads the current cell without changing the pane set.
|
|
||||||
$TMUX kill-pane -t "$floating" || exit 1
|
|
||||||
$TMUX select-layout -t layouts:many tiled || exit 1
|
|
||||||
tiled=$(layout)
|
|
||||||
$TMUX select-layout -t layouts:many even-horizontal || exit 1
|
|
||||||
$TMUX select-layout -t layouts:many -o || exit 1
|
|
||||||
[ "$(layout)" = "$tiled" ] || fail "select-layout -o did not restore layout"
|
|
||||||
$TMUX select-layout -t layouts:many || exit 1
|
|
||||||
$TMUX select-layout -t layouts:many -E || exit 1
|
|
||||||
|
|
||||||
# Each arranger has a deliberate one-pane early return.
|
|
||||||
$TMUX new-window -d -t layouts: -n one 'exec sleep 100' || exit 1
|
|
||||||
one=$($TMUX display-message -p -t layouts:one '#{window_layout}')
|
|
||||||
for name in even-horizontal even-vertical main-horizontal \
|
|
||||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
|
||||||
$TMUX select-layout -t layouts:one "$name" || exit 1
|
|
||||||
[ "$($TMUX display-message -p -t layouts:one '#{window_layout}')" = "$one" ] ||
|
|
||||||
fail "$name changed a one-pane layout"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Force the minimum-size paths without requiring a terminal client.
|
|
||||||
$TMUX resize-window -t layouts:many -x10 -y6 || exit 1
|
|
||||||
for name in even-horizontal even-vertical main-horizontal \
|
|
||||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
|
||||||
$TMUX select-layout -t layouts:many "$name" || exit 1
|
|
||||||
done
|
|
||||||
|
|
||||||
$TMUX has-session -t layouts || fail "server died during layout tests"
|
|
||||||
exit 0
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise list-commands for the full table, a single command, aliases, custom
|
|
||||||
# and empty formats, and lookup errors.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
export PATH TERM
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
# Keep a server alive across commands. Otherwise a STARTSERVER-only command
|
|
||||||
# may exit between invocations and leave a short socket-cleanup race.
|
|
||||||
$TMUX new-session -d -s list-commands 'exec sleep 100' || exit 1
|
|
||||||
|
|
||||||
all=$($TMUX list-commands) || exit 1
|
|
||||||
printf '%s\n' "$all" | grep -q '^attach-session (attach)' || exit 1
|
|
||||||
printf '%s\n' "$all" | grep -q '^list-commands (lscm)' || exit 1
|
|
||||||
|
|
||||||
one=$($TMUX list-commands -F \
|
|
||||||
'#{command_list_name}|#{command_list_alias}|#{command_list_usage}' \
|
|
||||||
list-commands) || exit 1
|
|
||||||
case "$one" in
|
|
||||||
'list-commands|lscm|'*) ;;
|
|
||||||
*) exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Aliases resolve to their command, while an empty expansion prints no line.
|
|
||||||
[ "$($TMUX list-commands -F '#{command_list_name}' lscm)" = list-commands ] ||
|
|
||||||
exit 1
|
|
||||||
[ -z "$($TMUX list-commands -F '' list-commands)" ] || exit 1
|
|
||||||
|
|
||||||
$TMUX list-commands tmux-no-command-$$ >/dev/null 2>&1 && exit 1
|
|
||||||
$TMUX list-commands list >/dev/null 2>&1 && exit 1
|
|
||||||
exit 0
|
|
||||||
0
regress/mode-kill.sh
Normal file → Executable file
0
regress/mode-kill.sh
Normal file → Executable file
@@ -1,97 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Moving a popup away from a pane prompt must recompose the prompt over the
|
|
||||||
# pane contents restored by damage redraw.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lpopup-prompt-outer-$$ -f/dev/null"
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
POPUP_PID=
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
$INNER command-prompt -b -P -t "$CLIENT" -p 'PROMPT-MARK>' \
|
|
||||||
'display-message -- %1' || exit 1
|
|
||||||
wait_outer_has PROMPT-MARK
|
|
||||||
|
|
||||||
$INNER display-popup -t "$CLIENT" -x 0 -y 10 -w 16 -h 3 -E \
|
|
||||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
|
||||||
POPUP_PID=$!
|
|
||||||
wait_outer_has POPUP-MARKER
|
|
||||||
|
|
||||||
mouse 0 10 8 M
|
|
||||||
mouse 32 11 8 M
|
|
||||||
mouse 32 25 4 M
|
|
||||||
mouse 0 25 4 m
|
|
||||||
wait_outer_has PROMPT-MARK
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Moving and resizing a popup must restore its old rectangle, including pane
|
|
||||||
# border status, on the attached client's terminal.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lpopup-drag-outer-$$ -f/dev/null"
|
|
||||||
BASE=$DIR/base
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
RAW=$DIR/raw
|
|
||||||
POPUP_PID=
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_rows_match()
|
|
||||||
{
|
|
||||||
first=$1
|
|
||||||
last=$2
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
|
||||||
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
|
||||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer rows $first-$last were not restored"
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
# A left-button border drag moves the popup. The first motion starts the drag;
|
|
||||||
# the second changes its position.
|
|
||||||
move_popup()
|
|
||||||
{
|
|
||||||
mouse 0 10 1 M
|
|
||||||
mouse 32 11 1 M
|
|
||||||
mouse 32 26 8 M
|
|
||||||
mouse 0 26 8 m
|
|
||||||
}
|
|
||||||
|
|
||||||
# A right-button bottom-right-border drag resizes the popup.
|
|
||||||
resize_popup()
|
|
||||||
{
|
|
||||||
mouse 2 "$1" "$2" M
|
|
||||||
mouse 34 "$3" "$4" M
|
|
||||||
mouse 34 "$5" "$6" M
|
|
||||||
mouse 2 "$5" "$6" m
|
|
||||||
}
|
|
||||||
|
|
||||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; printf \"\\033[19;45H\\033[38;5;201mSCOPE\\033[0m\"; exec sleep 100'"
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
$INNER set-option -w pane-border-status top || exit 1
|
|
||||||
$INNER set-option -w pane-border-format 'DAMAGE-STATUS-RESTORED' || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
|
||||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
wait_outer_has SCOPE
|
|
||||||
grep -qa '38;5;201' "$RAW" || fail "scope colour was not drawn initially"
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
|
||||||
grep -q DAMAGE-STATUS-RESTORED "$BASE" ||
|
|
||||||
fail "pane border status was not visible before popup"
|
|
||||||
|
|
||||||
$INNER display-popup -t "$CLIENT" -x 0 -y 0 -w 28 -h 6 -E \
|
|
||||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
|
||||||
POPUP_PID=$!
|
|
||||||
wait_outer_has POPUP-MARKER
|
|
||||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
|
||||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
|
||||||
|
|
||||||
move_popup
|
|
||||||
wait_rows_match 1 6
|
|
||||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
|
||||||
grep -qa '38;5;201' "$RAW" &&
|
|
||||||
fail "popup move redrew content outside its old and new rectangles"
|
|
||||||
grep -q DAMAGE-STATUS-RESTORED "$CAPTURE" ||
|
|
||||||
fail "pane border status was not restored after popup move"
|
|
||||||
|
|
||||||
# The moved popup is 28x6 at zero-based 16,7. Shrink it to 21x3, then require
|
|
||||||
# the three rows vacated at the bottom to match the original scene.
|
|
||||||
resize_popup 44 13 43 13 38 11
|
|
||||||
wait_rows_match 11 13
|
|
||||||
|
|
||||||
# Grow it back to 28x6 and require a single, intact popup frame.
|
|
||||||
resize_popup 37 10 36 10 45 14
|
|
||||||
wait_outer_has POPUP-MARKER
|
|
||||||
corners=$(grep -o '┌' "$CAPTURE" | wc -l)
|
|
||||||
[ "$corners" -eq 1 ] || fail "expected one popup frame, found $corners"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Moving a popup away from the tmux status area must restore status and window
|
|
||||||
# cells. Window damage alone cannot describe cells outside the window scene,
|
|
||||||
# and top status lines must be removed before translating to window rows.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
BASE=$DIR/base
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
INNER=
|
|
||||||
OUTER=
|
|
||||||
POPUP_PID=
|
|
||||||
N=0
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup_scene()
|
|
||||||
{
|
|
||||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
|
||||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
|
||||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
|
||||||
POPUP_PID=
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
cleanup_scene
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_rows_restored()
|
|
||||||
{
|
|
||||||
first=$1
|
|
||||||
last=$2
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
|
||||||
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
|
||||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "rows $first-$last under the old popup were not restored"
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
{
|
|
||||||
position=$1
|
|
||||||
lines=$2
|
|
||||||
|
|
||||||
cleanup_scene
|
|
||||||
N=$((N + 1))
|
|
||||||
INNER="$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lpopup-status-outer-$$-$N -f/dev/null"
|
|
||||||
|
|
||||||
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
if [ "$lines" -eq 1 ]; then
|
|
||||||
$INNER set-option -g status on || exit 1
|
|
||||||
else
|
|
||||||
$INNER set-option -g status "$lines" || exit 1
|
|
||||||
fi
|
|
||||||
$INNER set-option -g status-position "$position" || exit 1
|
|
||||||
$INNER set-option -g status-format[0] \
|
|
||||||
'STATUS-LINE-MARK-01234567890123456789012' || exit 1
|
|
||||||
if [ "$lines" -gt 1 ]; then
|
|
||||||
$INNER set-option -g status-format[1] \
|
|
||||||
'SECOND-STATUS-MARK-012345678901234567890' || exit 1
|
|
||||||
fi
|
|
||||||
$INNER set-option -g status-interval 0 || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
wait_outer_has STATUS-LINE-MARK
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
open_popup()
|
|
||||||
{
|
|
||||||
y=$1
|
|
||||||
height=$2
|
|
||||||
$INNER display-popup -t "$CLIENT" -x 0 -y "$y" -w 16 -h "$height" -E \
|
|
||||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
|
||||||
POPUP_PID=$!
|
|
||||||
wait_outer_has POPUP-MARKER
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "${STATUS_CASE:-all}" != top ]; then
|
|
||||||
# A popup clipped against a one-line bottom status must restore that line.
|
|
||||||
setup bottom 1
|
|
||||||
open_popup 10 3
|
|
||||||
mouse 0 10 8 M
|
|
||||||
mouse 32 11 8 M
|
|
||||||
mouse 32 25 4 M
|
|
||||||
mouse 0 25 4 m
|
|
||||||
wait_rows_restored 10 10
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${STATUS_CASE:-all}" != bottom ]; then
|
|
||||||
# A popup beginning in a two-line top status also covers the first window
|
|
||||||
# rows. Restore both coordinate spaces after it moves.
|
|
||||||
setup top 2
|
|
||||||
open_popup 1 4
|
|
||||||
mouse 0 10 1 M
|
|
||||||
mouse 32 11 1 M
|
|
||||||
mouse 32 25 7 M
|
|
||||||
mouse 0 25 7 m
|
|
||||||
wait_rows_restored 1 4
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Damage at a popup edge must redraw complete grid characters. Drawing only a
|
|
||||||
# wide character's base or padding cell leaves a two-cell hole behind.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lpopup-wide-outer-$$ -f/dev/null"
|
|
||||||
EMITTER=$DIR/emitter.pl
|
|
||||||
BASE=$DIR/base
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
POPUP_PID=
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_old_rows_restored()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
sed -n '3,5p' "$BASE" >"$DIR/want"
|
|
||||||
sed -n '3,5p' "$CAPTURE" >"$DIR/got"
|
|
||||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "wide characters under the old popup edge were not restored"
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$EMITTER" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
binmode STDOUT, ':encoding(UTF-8)';
|
|
||||||
$| = 1;
|
|
||||||
for my $row (1 .. 10) {
|
|
||||||
print "\e[$row;1H", chr(0x754c) x 20;
|
|
||||||
}
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
wait_outer_has '界界界'
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
|
||||||
|
|
||||||
$INNER display-popup -t "$CLIENT" -x 5 -y 5 -w 10 -h 3 -E \
|
|
||||||
"sh -c 'printf POPUP; exec sleep 100'" &
|
|
||||||
POPUP_PID=$!
|
|
||||||
wait_outer_has POPUP
|
|
||||||
|
|
||||||
# The first motion starts the drag; the second moves the popup away from its
|
|
||||||
# old rectangle. Its odd x coordinate bisects the underlying double-width
|
|
||||||
# cells at both edges.
|
|
||||||
mouse 0 10 3 M
|
|
||||||
mouse 32 11 3 M
|
|
||||||
mouse 32 28 7 M
|
|
||||||
mouse 0 28 7 m
|
|
||||||
wait_old_rows_restored
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -217,36 +217,7 @@ $OUT send-keys M-r || exit 1
|
|||||||
settle
|
settle
|
||||||
status_line | grep -q '>' || fail "status-line prompt not drawn on the status line"
|
status_line | grep -q '>' || fail "status-line prompt not drawn on the status line"
|
||||||
|
|
||||||
# --- 11. Invalid UTF-8 input must not desynchronize the prompt buffer. ---
|
# --- 11. emacs cursor-marker edit, accept recovers the exact 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 -l "abc" || exit 1
|
||||||
$OUT send-keys Home || exit 1
|
$OUT send-keys Home || exit 1
|
||||||
$OUT send-keys -l "X" || exit 1
|
$OUT send-keys -l "X" || exit 1
|
||||||
@@ -258,7 +229,7 @@ settle
|
|||||||
[ "$($IN show -gv @r)" = "Xabc" ] || \
|
[ "$($IN show -gv @r)" = "Xabc" ] || \
|
||||||
fail "status-line accept recovered '$($IN show -gv @r)', wanted 'Xabc'"
|
fail "status-line accept recovered '$($IN show -gv @r)', wanted 'Xabc'"
|
||||||
|
|
||||||
# --- 13. Unicode on the status line: insert, move, delete wide char. ---
|
# --- 12. Unicode on the status line: insert, move, delete wide char. ---
|
||||||
$IN set -g @r "" || exit 1
|
$IN set -g @r "" || exit 1
|
||||||
$OUT send-keys M-r || exit 1
|
$OUT send-keys M-r || exit 1
|
||||||
settle
|
settle
|
||||||
@@ -278,7 +249,7 @@ settle
|
|||||||
[ "$($IN show -gv @r)" = "Za" ] || \
|
[ "$($IN show -gv @r)" = "Za" ] || \
|
||||||
fail "status-line wide edit recovered '$($IN show -gv @r)', wanted 'Za'"
|
fail "status-line wide edit recovered '$($IN show -gv @r)', wanted 'Za'"
|
||||||
|
|
||||||
# --- 14. Overflow: more text than fits stays within the line and is kept. ---
|
# --- 13. Overflow: more text than fits stays within the line and is kept. ---
|
||||||
big="0123456789012345678901234567890123456789012345678901234567890123456789ABCDEFGHIJ"
|
big="0123456789012345678901234567890123456789012345678901234567890123456789ABCDEFGHIJ"
|
||||||
$IN set -g @r "" || exit 1
|
$IN set -g @r "" || exit 1
|
||||||
$OUT send-keys M-r || exit 1
|
$OUT send-keys M-r || exit 1
|
||||||
@@ -293,7 +264,7 @@ settle
|
|||||||
# The whole buffer was kept despite only part being visible.
|
# The whole buffer was kept despite only part being visible.
|
||||||
[ "$($IN show -gv @r)" = "$big" ] || fail "overflowing prompt lost buffer content"
|
[ "$($IN show -gv @r)" = "$big" ] || fail "overflowing prompt lost buffer content"
|
||||||
|
|
||||||
# --- 15. Escape closes the status-line prompt cleanly. ---
|
# --- 14. Escape closes the status-line prompt cleanly. ---
|
||||||
$IN set -g @r "SENTINEL" || exit 1
|
$IN set -g @r "SENTINEL" || exit 1
|
||||||
$OUT send-keys M-r || exit 1
|
$OUT send-keys M-r || exit 1
|
||||||
settle
|
settle
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Cover prompt word movement in emacs and vi modes, ambiguous inline command
|
|
||||||
# completion, and the show/clear-prompt-history command surface.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
capture()
|
|
||||||
{
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_result()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($INNER show-option -gqv @result 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "prompt result is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
bind_prompt()
|
|
||||||
{
|
|
||||||
initial=$1
|
|
||||||
$INNER bind-key -n M-r command-prompt -I "$initial" -p '(word)' \
|
|
||||||
"set-option -g @result '%%'" || exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
run_prompt()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
shift
|
|
||||||
$INNER set-option -g @result sentinel || exit 1
|
|
||||||
$OUTER send-keys M-r || exit 1
|
|
||||||
sleep 0.2
|
|
||||||
$OUTER send-keys "$@" || exit 1
|
|
||||||
$OUTER send-keys Enter || exit 1
|
|
||||||
wait_result "$want"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_vi_prompt()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
shift
|
|
||||||
$INNER set-option -g @result sentinel || exit 1
|
|
||||||
$OUTER send-keys M-r || exit 1
|
|
||||||
sleep 0.2
|
|
||||||
# Send Escape separately so the terminal's escape-time handling does not
|
|
||||||
# combine it with the first vi command as a Meta key.
|
|
||||||
$OUTER send-keys Escape || exit 1
|
|
||||||
# The inner client must see Escape as a complete key, not the prefix of a
|
|
||||||
# Meta sequence (the default escape-time is 500 milliseconds).
|
|
||||||
sleep 0.7
|
|
||||||
$OUTER send-keys "$@" || exit 1
|
|
||||||
$OUTER send-keys Enter || exit 1
|
|
||||||
wait_result "$want"
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s prompt -x80 -y24 'exec sleep 100' || exit 1
|
|
||||||
$INNER set-option -g status on || exit 1
|
|
||||||
$INNER set-option -g status-position bottom || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t prompt" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# Emacs Meta-f stops after the first word; Meta-b returns to the start of the
|
|
||||||
# previous word. Inserting a marker makes the cursor position observable.
|
|
||||||
$INNER set-option -g status-keys emacs || exit 1
|
|
||||||
bind_prompt 'one two'
|
|
||||||
run_prompt 'oneX two' Home M-f X
|
|
||||||
bind_prompt 'one two'
|
|
||||||
run_prompt 'one Xtwo' M-b X
|
|
||||||
|
|
||||||
# Vi translation and the distinct separator-aware and WORD motions.
|
|
||||||
$INNER set-option -g status-keys vi || exit 1
|
|
||||||
bind_prompt 'one-two three'
|
|
||||||
run_vi_prompt 'one-two Xthree' b i X
|
|
||||||
bind_prompt 'one-two three'
|
|
||||||
run_vi_prompt 'one-two Xthree' B i X
|
|
||||||
bind_prompt 'one-two three'
|
|
||||||
run_vi_prompt 'oneX-two three' 0 w i X
|
|
||||||
bind_prompt 'one-two three'
|
|
||||||
run_vi_prompt 'one-two Xthree' 0 W i X
|
|
||||||
bind_prompt 'one-two three'
|
|
||||||
run_vi_prompt 'oneX-two three' 0 e a X
|
|
||||||
bind_prompt 'one-two three'
|
|
||||||
run_vi_prompt 'one-twoX three' 0 E a X
|
|
||||||
|
|
||||||
# "show-" has several command matches and no longer common prefix. Tab keeps
|
|
||||||
# the input and draws the sorted candidates inline.
|
|
||||||
$INNER set-option -g status-keys emacs || exit 1
|
|
||||||
bind_prompt ''
|
|
||||||
$OUTER send-keys M-r || exit 1
|
|
||||||
sleep 0.2
|
|
||||||
$OUTER send-keys -l 'show-' || exit 1
|
|
||||||
$OUTER send-keys Tab || exit 1
|
|
||||||
sleep 0.2
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -Fq 'show-buffer' ||
|
|
||||||
fail "ambiguous completion list was not drawn"
|
|
||||||
printf '%s\n' "$captured" | grep -Fq 'show-environment' ||
|
|
||||||
fail "ambiguous completion list was incomplete"
|
|
||||||
$OUTER send-keys Escape || exit 1
|
|
||||||
|
|
||||||
# Add entries to both history rings through real prompts.
|
|
||||||
bind_prompt 'history-command'
|
|
||||||
run_prompt 'history-command'
|
|
||||||
$INNER bind-key -n M-s command-prompt -T search -I history-search \
|
|
||||||
-p '(search-history)' "set-option -g @result '%%'" || exit 1
|
|
||||||
$OUTER send-keys M-s || exit 1
|
|
||||||
$OUTER send-keys Enter || exit 1
|
|
||||||
wait_result history-search
|
|
||||||
|
|
||||||
command_history=$($INNER show-prompt-history -T command) || exit 1
|
|
||||||
printf '%s\n' "$command_history" | grep -Fq 'history-command' ||
|
|
||||||
fail "command history entry missing"
|
|
||||||
search_history=$($INNER show-prompt-history -T search) || exit 1
|
|
||||||
printf '%s\n' "$search_history" | grep -Fq 'history-search' ||
|
|
||||||
fail "search history entry missing"
|
|
||||||
all_history=$($INNER show-prompt-history) || exit 1
|
|
||||||
printf '%s\n' "$all_history" | grep -Fq 'History for command:' || exit 1
|
|
||||||
printf '%s\n' "$all_history" | grep -Fq 'History for search:' || exit 1
|
|
||||||
|
|
||||||
$INNER show-prompt-history -T invalid >/dev/null 2>&1 &&
|
|
||||||
fail "invalid show history type succeeded"
|
|
||||||
$INNER clear-prompt-history -T invalid >/dev/null 2>&1 &&
|
|
||||||
fail "invalid clear history type succeeded"
|
|
||||||
$INNER clear-prompt-history -T command || exit 1
|
|
||||||
$INNER show-prompt-history -T command | grep -Fq history-command &&
|
|
||||||
fail "type-specific history clear failed"
|
|
||||||
$INNER clear-prompt-history || exit 1
|
|
||||||
$INNER show-prompt-history | grep -Fq history-search &&
|
|
||||||
fail "all-history clear failed"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Check a redraw callback which has no accompanying client redraw flags. A
|
|
||||||
# wrapped row crossing a panned viewport cannot use the direct tty path.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Ldamage-only-outer-$$ -f/dev/null"
|
|
||||||
EMITTER=$DIR/emitter.pl
|
|
||||||
TRIGGER=$DIR/trigger
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_inner_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
|
||||||
grep -q "$marker" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner pane did not contain $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$EMITTER" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
$| = 1;
|
|
||||||
for my $row (1 .. 12) {
|
|
||||||
print "\e[$row;1H", 'o' x 79;
|
|
||||||
}
|
|
||||||
print "\e[1;1H";
|
|
||||||
while (!-e $ENV{TRIGGER}) {
|
|
||||||
select undef, undef, undef, 0.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $second = ('B' x 24) . 'DAMAGE-ONLY' . ('B' x 45);
|
|
||||||
print "\e[5;1H", ('A' x 80), $second;
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 80 -y 12 \
|
|
||||||
"TRIGGER='$TRIGGER' perl '$EMITTER'" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
$INNER refresh-client -t "$CLIENT" -R 20 || exit 1
|
|
||||||
wait_outer_has oooooooooo
|
|
||||||
|
|
||||||
: >"$TRIGGER"
|
|
||||||
wait_inner_has DAMAGE-ONLY
|
|
||||||
wait_outer_has DAMAGE-ONLY
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Redraw a moved floating pane on both attached clients viewing the same
|
|
||||||
# window. Window redraw work must not be consumed by only one client.
|
|
||||||
#
|
|
||||||
# Uses ASCII pane borders (rather than the default UTF-8 box-drawing) because
|
|
||||||
# this test nests a real tmux client inside another tmux's pane to get a
|
|
||||||
# genuine terminal to capture from; that nested-tmux relay has been observed
|
|
||||||
# to mis-render a cell that previously held a multi-byte UTF-8 border
|
|
||||||
# character being overwritten later by plain content, on the outer instance's
|
|
||||||
# own interpretation, independent of anything the inner tmux sends. That is a
|
|
||||||
# nested-test-harness artifact, not a real tmux bug - confirmed by replaying
|
|
||||||
# the exact same drag sequence against a real terminal (xterm), where it
|
|
||||||
# never reproduces. ASCII borders avoid the artifact entirely.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lredraw-multi-outer-$$ -f/dev/null"
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_clients()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
|
||||||
[ "$count" -eq 2 ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "two inner clients did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
target=$1
|
|
||||||
marker=$2
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer pane $target did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_float_left()
|
|
||||||
{
|
|
||||||
comparison=$1
|
|
||||||
limit=$2
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
left=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
if [ "$comparison" = gt ] && [ "$left" -gt "$limit" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [ "$comparison" = lt ] && [ "$left" -lt "$limit" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "floating pane did not move"
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$2" "$3" "$4" "$5")
|
|
||||||
$OUTER send-keys -t "$1" -l "$sequence" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
drag_float()
|
|
||||||
{
|
|
||||||
target=$1
|
|
||||||
startcol=$2
|
|
||||||
startrow=$3
|
|
||||||
endcol=$4
|
|
||||||
mouse "$target" 0 "$startcol" "$startrow" M
|
|
||||||
mouse "$target" 32 "$endcol" "$startrow" M
|
|
||||||
mouse "$target" 0 "$endcol" "$startrow" m
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_scene()
|
|
||||||
{
|
|
||||||
target=$1
|
|
||||||
base=$2
|
|
||||||
firstcol=$3
|
|
||||||
lastcol=$4
|
|
||||||
|
|
||||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
|
||||||
|
|
||||||
# With ASCII (simple) borders every corner and junction is the same
|
|
||||||
# '+', so one rectangular floating pane always draws exactly 4 of
|
|
||||||
# them; more means a stale frame was left behind somewhere.
|
|
||||||
corners=$(grep -o '+' "$CAPTURE" | wc -l)
|
|
||||||
[ "$corners" -eq 4 ] ||
|
|
||||||
fail "outer pane $target had $corners floating frames"
|
|
||||||
|
|
||||||
sed -n '6,11p' "$base" | cut -c"$firstcol-$lastcol" >"$DIR/want"
|
|
||||||
sed -n '6,11p' "$CAPTURE" | cut -c"$firstcol-$lastcol" >"$DIR/got"
|
|
||||||
cmp -s "$DIR/want" "$DIR/got" ||
|
|
||||||
fail "outer pane $target did not restore the old floating area"
|
|
||||||
}
|
|
||||||
|
|
||||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; exec sleep 100'"
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
$INNER set-option -g default-command 'sleep 100' || exit 1
|
|
||||||
$INNER set-option -g pane-border-lines simple || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen || exit 1
|
|
||||||
$OUTER split-window -h -t outer:0.0 'sleep 100' || exit 1
|
|
||||||
|
|
||||||
PANES=$($OUTER list-panes -t outer:0 -F '#{pane_id} #{pane_left}')
|
|
||||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
|
||||||
RIGHT=$(echo "$PANES" | sort -k2 -n | tail -1 | cut -d' ' -f1)
|
|
||||||
[ -n "$LEFT" ] && [ -n "$RIGHT" ] || fail "could not find outer panes"
|
|
||||||
|
|
||||||
$OUTER respawn-pane -k -t "$LEFT" \
|
|
||||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
$OUTER respawn-pane -k -t "$RIGHT" \
|
|
||||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_clients
|
|
||||||
wait_outer_has "$LEFT" BG-ROW-19
|
|
||||||
wait_outer_has "$RIGHT" BG-ROW-19
|
|
||||||
$OUTER capture-pane -p -t "$LEFT" >"$DIR/base-left" || exit 1
|
|
||||||
$OUTER capture-pane -p -t "$RIGHT" >"$DIR/base-right" || exit 1
|
|
||||||
|
|
||||||
FLOAT=$($INNER new-pane -dPF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
|
||||||
fail "could not create floating pane"
|
|
||||||
wait_outer_has "$LEFT" '+'
|
|
||||||
wait_outer_has "$RIGHT" '+'
|
|
||||||
|
|
||||||
FTOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
FWIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
|
||||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
|
||||||
|
|
||||||
# Move right through one client and require both clients to restore the old
|
|
||||||
# left-hand footprint.
|
|
||||||
drag_float "$LEFT" "$GRABCOL" "$FTOP" $((GRABCOL + 30))
|
|
||||||
wait_float_left gt 30
|
|
||||||
assert_scene "$LEFT" "$DIR/base-left" 1 20
|
|
||||||
assert_scene "$RIGHT" "$DIR/base-right" 1 20
|
|
||||||
|
|
||||||
# Move back through the other client and check the old right-hand footprint.
|
|
||||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
|
||||||
drag_float "$RIGHT" "$GRABCOL" "$FTOP" $((GRABCOL - 30))
|
|
||||||
wait_float_left lt 10
|
|
||||||
assert_scene "$LEFT" "$DIR/base-left" 35 60
|
|
||||||
assert_scene "$RIGHT" "$DIR/base-right" 35 60
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Check that full and region screen-write fallbacks update an attached client,
|
|
||||||
# not only tmux's internal pane grid.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
EMITTER=$DIR/emitter.pl
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
INNER=
|
|
||||||
OUTER=
|
|
||||||
N=0
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
|
||||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_lacks()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" || return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client still showed $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_inner_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
|
||||||
grep -q "$marker" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner pane did not contain $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_inner_lacks()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$INNER capture-pane -p -t inner:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" || return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner pane still contained $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
{
|
|
||||||
mode=$1
|
|
||||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
|
||||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
|
||||||
N=$((N + 1))
|
|
||||||
INNER="$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lredraw-write-outer-$$-$N -f/dev/null"
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 12 \
|
|
||||||
"MODE=$mode READY='$DIR/ready-$N' TRIGGER='$DIR/trigger-$N' perl '$EMITTER'" ||
|
|
||||||
exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
trigger()
|
|
||||||
{
|
|
||||||
: >"$DIR/trigger-$N-${1:-1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$EMITTER" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
$| = 1;
|
|
||||||
my $mode = $ENV{MODE};
|
|
||||||
my $ready = $ENV{READY};
|
|
||||||
my $trigger = $ENV{TRIGGER};
|
|
||||||
|
|
||||||
sub fill_screen {
|
|
||||||
my ($prefix) = @_;
|
|
||||||
print "\e[2J\e[H";
|
|
||||||
for my $row (0 .. 11) {
|
|
||||||
printf "\e[%d;1H%s-ROW-%02d", $row + 1, $prefix, $row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($mode eq 'ris') {
|
|
||||||
fill_screen('RIS');
|
|
||||||
} elsif ($mode eq 'alternate') {
|
|
||||||
fill_screen('BASE');
|
|
||||||
} elsif ($mode eq 'scroll') {
|
|
||||||
fill_screen('SCROLL');
|
|
||||||
} else {
|
|
||||||
die "unknown mode $mode\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
open my $fh, '>', $ready or die "$ready: $!\n";
|
|
||||||
close $fh;
|
|
||||||
while (!-e "$trigger-1") {
|
|
||||||
select undef, undef, undef, 0.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($mode eq 'ris') {
|
|
||||||
print "\ec";
|
|
||||||
} elsif ($mode eq 'alternate') {
|
|
||||||
print "\e[?1049h";
|
|
||||||
fill_screen('ALT');
|
|
||||||
while (!-e "$trigger-2") {
|
|
||||||
select undef, undef, undef, 0.01;
|
|
||||||
}
|
|
||||||
print "\e[?1049l";
|
|
||||||
} else {
|
|
||||||
print "\e[12;1H\r\nSCROLL-NEW";
|
|
||||||
}
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
# RIS clears the complete screen. The source pane and attached client must both
|
|
||||||
# lose every old row.
|
|
||||||
setup ris
|
|
||||||
wait_outer_has RIS-ROW-11
|
|
||||||
trigger
|
|
||||||
wait_inner_lacks RIS-ROW
|
|
||||||
wait_outer_lacks RIS-ROW
|
|
||||||
|
|
||||||
# Leaving the alternate screen restores every row of the base screen.
|
|
||||||
setup alternate
|
|
||||||
wait_outer_has BASE-ROW-11
|
|
||||||
trigger
|
|
||||||
wait_inner_has ALT-ROW-11
|
|
||||||
wait_outer_has ALT-ROW-11
|
|
||||||
wait_outer_lacks BASE-ROW
|
|
||||||
trigger 2
|
|
||||||
wait_inner_has BASE-ROW-11
|
|
||||||
wait_outer_has BASE-ROW-11
|
|
||||||
wait_outer_lacks ALT-ROW
|
|
||||||
|
|
||||||
# Scrolling a pane which is narrower than the terminal redraws its complete
|
|
||||||
# region. Check the physical client row by row after the source grid shifts.
|
|
||||||
setup scroll
|
|
||||||
$INNER split-window -h -t inner:0 'sleep 100' || exit 1
|
|
||||||
wait_outer_has SCROLL-ROW-11
|
|
||||||
trigger
|
|
||||||
wait_inner_has SCROLL-NEW
|
|
||||||
wait_outer_has SCROLL-NEW
|
|
||||||
row=1
|
|
||||||
while [ "$row" -le 11 ]; do
|
|
||||||
expected=$(printf 'SCROLL-ROW-%02d' "$row")
|
|
||||||
actual=$(sed -n "${row}p" "$CAPTURE")
|
|
||||||
case "$actual" in
|
|
||||||
"$expected"*) ;;
|
|
||||||
*) fail "outer row $row was not redrawn as $expected" ;;
|
|
||||||
esac
|
|
||||||
row=$((row + 1))
|
|
||||||
done
|
|
||||||
actual=$(sed -n '12p' "$CAPTURE")
|
|
||||||
case "$actual" in
|
|
||||||
SCROLL-NEW*) ;;
|
|
||||||
*) fail "outer bottom row was not redrawn as SCROLL-NEW" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -93,15 +93,6 @@ $TMUX2 splitw -v "$C" || exit 1
|
|||||||
$TMUX2 select-layout tiled || exit 1
|
$TMUX2 select-layout tiled || exit 1
|
||||||
compare outside-both-2x2
|
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
|
# 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.
|
# can be panned (refresh-client). This exercises a non-zero scene offset.
|
||||||
# A 2x2 grid in a 60x20 window viewed through the 40x14 client.
|
# A 2x2 grid in a 60x20 window viewed through the 40x14 client.
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
────────────────────────────┐···········
|
|
||||||
OUT01 abcdefghij │···········
|
|
||||||
OUT02 abcdefghij │···········
|
|
||||||
OUT03 abcdefghij │···········
|
|
||||||
OUT04 abcdefghij │···········
|
|
||||||
OUT05 abcdefghij │···········
|
|
||||||
OUT06 abcdefghij │···········
|
|
||||||
OUT07 abcdefghij │···········
|
|
||||||
│···········
|
|
||||||
────────────────────────────┘···········
|
|
||||||
········································
|
|
||||||
········································
|
|
||||||
········································
|
|
||||||
········································
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
────────────────────────────┐···········
|
|
||||||
OUT06 abcdefghij │···········
|
|
||||||
OUT07 abcdefghij │···········
|
|
||||||
│···········
|
|
||||||
────────────────────────────┤···········
|
|
||||||
PAN05 abcdefghij │···········
|
|
||||||
PAN06 abcdefghij │···········
|
|
||||||
PAN07 abcdefghij │···········
|
|
||||||
│···········
|
|
||||||
────────────────────────────┘···········
|
|
||||||
········································
|
|
||||||
········································
|
|
||||||
········································
|
|
||||||
········································
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Auto-hide scrollbars appear on activity or pointer hover, then disappear
|
|
||||||
# after pane-scrollbars-timeout. Compare captures of the same copy-mode view so
|
|
||||||
# only the overlay scrollbar changes.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
OUTER="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
INNER="$TEST_TMUX -LtestB$$ -f/dev/null"
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
HIDDEN=$DIR/hidden
|
|
||||||
VISIBLE=$DIR/visible
|
|
||||||
AFTER=$DIR/after
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
capture()
|
|
||||||
{
|
|
||||||
$OUTER capture-pane -pe -t outer:0.0 >"$1" || exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$INNER list-clients 2>/dev/null | grep -q . && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x40 -y12 \
|
|
||||||
"sh -c 'seq 50; exec sleep 100'" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
$INNER set-window-option pane-scrollbars auto-hide || exit 1
|
|
||||||
$INNER set-window-option pane-scrollbars-timeout 3000 || exit 1
|
|
||||||
$INNER set-window-option pane-scrollbars-style \
|
|
||||||
'bg=colour196,fg=colour231,width=1,pad=0' || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x40 -y12 "$INNER attach -t inner" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
wait_for_client
|
|
||||||
|
|
||||||
# Enter copy mode and choose a stable view. The initial show timer is allowed
|
|
||||||
# to expire before taking the hidden reference capture.
|
|
||||||
$INNER copy-mode -H || exit 1
|
|
||||||
$INNER send-keys -X history-top || exit 1
|
|
||||||
sleep 3.5
|
|
||||||
capture "$HIDDEN"
|
|
||||||
|
|
||||||
# A copy-mode page movement shows the overlay and starts its timer. Capture
|
|
||||||
# that view immediately, then again after the timeout without moving it.
|
|
||||||
$INNER send-keys -X page-down || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
capture "$VISIBLE"
|
|
||||||
sleep 3.5
|
|
||||||
capture "$AFTER"
|
|
||||||
cmp -s "$VISIBLE" "$AFTER" && fail "scrollbar did not hide after timeout"
|
|
||||||
|
|
||||||
# Use the post-movement hidden scene as the reference for pointer hover. SGR
|
|
||||||
# mouse column 40 is the right-hand scrollbar; moving to column 5 restarts the
|
|
||||||
# timer without changing the copy-mode view.
|
|
||||||
cp "$AFTER" "$HIDDEN"
|
|
||||||
hover=$(printf '\033[<35;40;5M')
|
|
||||||
$OUTER send-keys -l "$hover" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
capture "$VISIBLE"
|
|
||||||
cmp -s "$HIDDEN" "$VISIBLE" && fail "scrollbar did not appear on hover"
|
|
||||||
away=$(printf '\033[<35;5;5M')
|
|
||||||
$OUTER send-keys -l "$away" || exit 1
|
|
||||||
sleep 3.5
|
|
||||||
capture "$AFTER"
|
|
||||||
cmp -s "$HIDDEN" "$AFTER" || fail "scrollbar did not hide after hover"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise the unprivileged server-access surface: user and group ACL entries,
|
|
||||||
# read-only/writable transitions, listing, removal, implied addition, and
|
|
||||||
# command errors. Actual connections from another UID require privileges and
|
|
||||||
# deliberately do not form part of this test.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
export PATH TERM
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
OUT=$(mktemp) || exit 1
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
rm -f "$OUT"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
expect_fail()
|
|
||||||
{
|
|
||||||
message=$1
|
|
||||||
shift
|
|
||||||
if $TMUX "$@" >"$OUT" 2>&1; then
|
|
||||||
fail "$message: command unexpectedly succeeded"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_entry()
|
|
||||||
{
|
|
||||||
entry=$1
|
|
||||||
$TMUX server-access -l >"$OUT" || exit 1
|
|
||||||
grep -Fxq "$entry" "$OUT" ||
|
|
||||||
fail "missing ACL entry '$entry'"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_no_entry()
|
|
||||||
{
|
|
||||||
entry=$1
|
|
||||||
$TMUX server-access -l >"$OUT" || exit 1
|
|
||||||
if grep -Fxq "$entry" "$OUT"; then
|
|
||||||
fail "unexpected ACL entry '$entry'"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
owner_uid=$(id -u)
|
|
||||||
owner_name=$(id -un)
|
|
||||||
|
|
||||||
# nobody is the non-owner test identity. Derive its primary group rather than
|
|
||||||
# assuming whether the system calls it nobody, nogroup, or something else.
|
|
||||||
acl_user=nobody
|
|
||||||
acl_group=$(id -gn "$acl_user" 2>/dev/null) ||
|
|
||||||
fail "the nobody account is unavailable"
|
|
||||||
[ "$owner_name" != "$acl_user" ] || fail "test cannot run as nobody"
|
|
||||||
|
|
||||||
$TMUX new-session -d -s access 'exec sleep 100' || exit 1
|
|
||||||
|
|
||||||
# The owner is installed as writable at server startup (root is intentionally
|
|
||||||
# omitted from display output).
|
|
||||||
if [ "$owner_uid" != 0 ]; then
|
|
||||||
assert_entry "$owner_name (U,W)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# User ACL lifecycle, including duplicate and missing-entry errors.
|
|
||||||
$TMUX server-access -a "$acl_user" || exit 1
|
|
||||||
assert_entry "$acl_user (U,W)"
|
|
||||||
expect_fail "duplicate user addition" server-access -a "$acl_user"
|
|
||||||
$TMUX server-access -r "$acl_user" || exit 1
|
|
||||||
assert_entry "$acl_user (U,R)"
|
|
||||||
$TMUX server-access -w "$acl_user" || exit 1
|
|
||||||
assert_entry "$acl_user (U,W)"
|
|
||||||
$TMUX server-access -d "$acl_user" || exit 1
|
|
||||||
assert_no_entry "$acl_user (U,W)"
|
|
||||||
expect_fail "removing absent user" server-access -d "$acl_user"
|
|
||||||
|
|
||||||
# -r and -w imply -a when an entry is absent.
|
|
||||||
$TMUX server-access -r "$acl_user" || exit 1
|
|
||||||
assert_entry "$acl_user (U,R)"
|
|
||||||
$TMUX server-access -d "$acl_user" || exit 1
|
|
||||||
|
|
||||||
# The same lifecycle for a group exercises the separate ACL key space.
|
|
||||||
$TMUX server-access -g -a -r "$acl_group" || exit 1
|
|
||||||
assert_entry "$acl_group (G,R)"
|
|
||||||
$TMUX server-access -g -w "$acl_group" || exit 1
|
|
||||||
assert_entry "$acl_group (G,W)"
|
|
||||||
expect_fail "duplicate group addition" server-access -g -a "$acl_group"
|
|
||||||
$TMUX server-access -g -d "$acl_group" || exit 1
|
|
||||||
assert_no_entry "$acl_group (G,W)"
|
|
||||||
expect_fail "removing absent group" server-access -g -d "$acl_group"
|
|
||||||
|
|
||||||
# Parser and lookup errors, plus the immutable owner/root entries.
|
|
||||||
expect_fail "missing ACL subject" server-access -a
|
|
||||||
expect_fail "unknown user" server-access -a "tmux-no-user-$$"
|
|
||||||
expect_fail "unknown group" server-access -g -a "tmux-no-group-$$"
|
|
||||||
expect_fail "conflicting add/delete" server-access -a -d "$acl_user"
|
|
||||||
expect_fail "conflicting read/write" server-access -r -w "$acl_user"
|
|
||||||
expect_fail "changing server owner" server-access -r "$owner_name"
|
|
||||||
if [ "$owner_name" != root ]; then
|
|
||||||
expect_fail "changing root access" server-access -a root
|
|
||||||
fi
|
|
||||||
|
|
||||||
$TMUX has-session -t access || fail "server exited during ACL updates"
|
|
||||||
exit 0
|
|
||||||
0
regress/server-socket-error.sh
Normal file → Executable file
0
regress/server-socket-error.sh
Normal file → Executable file
0
regress/set-hook-E.sh
Normal file → Executable file
0
regress/set-hook-E.sh
Normal file → Executable file
@@ -1,81 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Switching windows in the same session must redraw the attached client. The
|
|
||||||
# session object is shared, so its current winlink cannot be compared after it
|
|
||||||
# has already been changed.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lswitch-redraw-outer-$$ -f/dev/null"
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 8 \
|
|
||||||
"printf '\033[2J\033[HA-WINDOW'; exec sleep 100" || exit 1
|
|
||||||
$INNER new-window -d -t inner:1 \
|
|
||||||
"printf '\033[2J\033[HB-WINDOW'; exec sleep 100" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null attach-session -t inner:0" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
wait_outer_has A-WINDOW
|
|
||||||
|
|
||||||
$INNER switch-client -c "$CLIENT" -t inner:1.0 || exit 1
|
|
||||||
[ "$($INNER display-message -p -t inner '#{window_index}')" -eq 1 ] ||
|
|
||||||
fail "server did not select window 1"
|
|
||||||
wait_outer_has B-WINDOW
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user