mirror of
https://github.com/tmux/tmux.git
synced 2026-09-08 22:47:22 +00:00
Merge branch 'master' into osc133_line_collapsible
This commit is contained in:
4
.github/workflows/lock.yml
vendored
4
.github/workflows/lock.yml
vendored
@@ -16,8 +16,10 @@ concurrency:
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'tmux/tmux'
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v6
|
||||
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '30'
|
||||
|
||||
10
.github/workflows/regress.yml
vendored
10
.github/workflows/regress.yml
vendored
@@ -16,7 +16,8 @@ jobs:
|
||||
regress:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 45
|
||||
if: github.repository == 'tmux/tmux'
|
||||
timeout-minutes: 300
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -37,7 +38,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'Linux'
|
||||
@@ -61,6 +64,7 @@ jobs:
|
||||
automake \
|
||||
bison \
|
||||
libevent \
|
||||
jemalloc \
|
||||
make \
|
||||
ncurses \
|
||||
utf8proc \
|
||||
@@ -79,7 +83,7 @@ jobs:
|
||||
|
||||
- name: logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: regress-logs-${{ matrix.name }}
|
||||
path: regress/logs/*.log
|
||||
|
||||
66
CHANGES
66
CHANGES
@@ -1,4 +1,4 @@
|
||||
CHANGES FROM 3.7b TO 3.8
|
||||
CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* Many improvements to floating panes:
|
||||
|
||||
@@ -9,7 +9,12 @@ CHANGES FROM 3.7b TO 3.8
|
||||
|
||||
- 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.
|
||||
A modal pane is now used for the editor in buffer mode;
|
||||
-C closes the pane when the mouse is clicked outside it and -K sends all
|
||||
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
|
||||
mouse dragging;
|
||||
@@ -24,6 +29,9 @@ CHANGES FROM 3.7b TO 3.8
|
||||
-k for choose commands kills the pane when the mode exits, which is useful
|
||||
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
|
||||
default bindings under C-b g for common move and resize operations;
|
||||
|
||||
@@ -74,7 +82,7 @@ CHANGES FROM 3.7b TO 3.8
|
||||
|
||||
- new hooks to cover areas including client create and destroy, pane
|
||||
activity, pane mode and prompt changes, pane movement and resizing, session
|
||||
group changes and window creation, close and zoom;
|
||||
group changes and window creation, close, swap and zoom;
|
||||
|
||||
- OSC 133 escape sequences now trigger events: pane-command-started,
|
||||
pane-command-finished and pane-shell-prompt.
|
||||
@@ -87,6 +95,27 @@ CHANGES FROM 3.7b TO 3.8
|
||||
while scrolling or while hovered and disappears after pane-scrollbars-timeout
|
||||
(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
|
||||
changed, with a new default for unused areas inside the window.
|
||||
|
||||
* Change show-options and show-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
|
||||
in the status line (issue 5412 from Fernando Daciuk).
|
||||
|
||||
* Change command templates so %% is escaped for single quotes while %1 remains
|
||||
unquoted, preventing single quotes in session or target names from
|
||||
terminating quoted command sections (reported by Aliz Hammond).
|
||||
@@ -97,6 +126,8 @@ CHANGES FROM 3.7b TO 3.8
|
||||
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient
|
||||
methods to create an empty pane (rather than using '' for the command).
|
||||
|
||||
* Add a default C-b T binding to change the current pane title.
|
||||
|
||||
* Menus now belong to the window, so appear on all clients.
|
||||
|
||||
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
|
||||
@@ -110,7 +141,12 @@ CHANGES FROM 3.7b TO 3.8
|
||||
* Copy mode no longer exits at the bottom while a selection is in progress
|
||||
(issue 5349).
|
||||
|
||||
* Fix scrollbar initial state so scrollbars appear on new windows (issue 5339).
|
||||
* 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
|
||||
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
||||
@@ -122,7 +158,9 @@ CHANGES FROM 3.7b TO 3.8
|
||||
|
||||
* Add or improve format variables including client_colours,
|
||||
pane_start_command_list, window_manual_width, window_manual_height,
|
||||
pane_last_output_time, pane_modal_flag and window_modal_pane.
|
||||
pane_last_output_time, pane_modal_flag, window_modal_pane, pane_private_modes,
|
||||
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
|
||||
floating panes can be sorted by z-index.
|
||||
@@ -139,7 +177,10 @@ CHANGES FROM 3.7b TO 3.8
|
||||
|
||||
* 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
|
||||
already exiting (Ben Maurer, issue 5357).
|
||||
already exiting (Ben Maurer, issue 5357). Queue notifications so they are not
|
||||
sent inside %begin/%end (issue 5458), do not let a stuck client prevent the
|
||||
server from exiting (issue 5444), and reset control mode offsets when a pane
|
||||
is respawned (issue 5498).
|
||||
|
||||
* Fix grouped sessions sometimes being left as unusable command targets while
|
||||
they are being killed (Bryce Miller, issue 5180).
|
||||
@@ -158,7 +199,18 @@ CHANGES FROM 3.7b TO 3.8
|
||||
* Do not crash looking for the next or previous session (issue 5344), or when
|
||||
no client is available.
|
||||
|
||||
* Check time periodically in loops rather than for every item (issue 5367).
|
||||
CHANGES FROM 3.7b TO 3.7c
|
||||
|
||||
* Build with jemalloc on macOS to avoid what appears to be a bug in calloc
|
||||
(issue 5385).
|
||||
|
||||
* Fix scrollbar initial state so they appear on new windows (issue 5339).
|
||||
|
||||
* Check time periodically in loops rather than every one (issue 5367).
|
||||
|
||||
* Use message-style again as default for message-format.
|
||||
|
||||
* Unzoom before creating floating panes to avoid a crash.
|
||||
|
||||
CHANGES FROM 3.7a TO 3.7b
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: arguments.c,v 1.66 2026/06/26 09:54:56 nicm Exp $ */
|
||||
/* $OpenBSD: arguments.c,v 1.67 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -762,8 +762,6 @@ args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
|
||||
cmdq_error(item, "%s", error);
|
||||
free(error);
|
||||
}
|
||||
else
|
||||
cmdlist->references++;
|
||||
args_make_commands_free(state);
|
||||
return (cmdlist);
|
||||
}
|
||||
@@ -827,8 +825,10 @@ args_make_commands(struct args_command_state *state, int argc, char **argv,
|
||||
int i;
|
||||
|
||||
if (state->cmdlist != NULL) {
|
||||
if (argc == 0)
|
||||
if (argc == 0) {
|
||||
state->cmdlist->references++;
|
||||
return (state->cmdlist);
|
||||
}
|
||||
return (cmd_list_copy(state->cmdlist, argc, argv));
|
||||
}
|
||||
|
||||
|
||||
16
cfg.c
16
cfg.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cfg.c,v 1.90 2026/07/14 17:17:17 nicm Exp $ */
|
||||
/* $OpenBSD: cfg.c,v 1.91 2026/08/03 13:38:42 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -223,13 +223,15 @@ cfg_print_causes(struct cmdq_item *item)
|
||||
{
|
||||
struct client *c = cmdq_get_client(item);
|
||||
u_int i;
|
||||
char *cause;
|
||||
|
||||
for (i = 0; i < cfg_ncauses; i++) {
|
||||
cause = cfg_causes[i];
|
||||
if (c != NULL && (c->flags & CLIENT_CONTROL))
|
||||
control_write(c, "%%config-error %s", cfg_causes[i]);
|
||||
control_notify_write(c, "%%config-error %s", cause);
|
||||
else
|
||||
cmdq_print(item, "%s", cfg_causes[i]);
|
||||
free(cfg_causes[i]);
|
||||
cmdq_print(item, "%s", cause);
|
||||
free(cause);
|
||||
}
|
||||
|
||||
free(cfg_causes);
|
||||
@@ -244,14 +246,16 @@ cfg_show_causes(struct session *s)
|
||||
struct window_pane *wp;
|
||||
struct window_mode_entry *wme;
|
||||
u_int i;
|
||||
char *cause;
|
||||
|
||||
if (cfg_ncauses == 0)
|
||||
return;
|
||||
|
||||
if (c != NULL && (c->flags & CLIENT_CONTROL)) {
|
||||
for (i = 0; i < cfg_ncauses; i++) {
|
||||
control_write(c, "%%config-error %s", cfg_causes[i]);
|
||||
free(cfg_causes[i]);
|
||||
cause = cfg_causes[i];
|
||||
control_notify_write(c, "%%config-error %s", cause);
|
||||
free(cause);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
10
client.c
10
client.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: client.c,v 1.166 2026/07/10 15:45:11 nicm Exp $ */
|
||||
/* $OpenBSD: client.c,v 1.168 2026/08/17 20:14:31 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -276,7 +276,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
||||
proc_set_signals(client_proc, client_signal);
|
||||
|
||||
/* Save the flags. */
|
||||
client_flags = flags;
|
||||
client_flags = flags|CLIENT_WRITE_ACK;
|
||||
log_debug("flags are %#llx", (unsigned long long)client_flags);
|
||||
|
||||
/* Initialize the client socket and start the server. */
|
||||
@@ -712,6 +712,9 @@ client_dispatch_wait(struct imsg *imsg)
|
||||
fprintf(stderr, "server version is too old for client\n");
|
||||
proc_exit(client_proc);
|
||||
break;
|
||||
default:
|
||||
log_debug("unknown message type %u", imsg->hdr.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,5 +801,8 @@ client_dispatch_attached(struct imsg *imsg)
|
||||
system(data);
|
||||
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
|
||||
break;
|
||||
default:
|
||||
log_debug("unknown message type %u", imsg->hdr.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-break-pane.c,v 1.74 2026/07/15 13:02:33 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-break-pane.c,v 1.76 2026/08/17 07:04:45 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -172,14 +172,16 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
w->name = clean_name(name, 0);
|
||||
options_set_number(w->options, "automatic-rename", 0);
|
||||
}
|
||||
window_set_fill_cells(w);
|
||||
|
||||
if (idx == -1)
|
||||
idx = -1 - options_get_number(dst_s->options, "base-index");
|
||||
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
|
||||
|
||||
layout_init(w, wp);
|
||||
wp->flags |= PANE_CHANGED;
|
||||
colour_palette_from_option(&wp->palette, wp->options);
|
||||
|
||||
if (idx == -1)
|
||||
idx = -1 - options_get_number(dst_s->options, "base-index");
|
||||
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
|
||||
window_remove_ref(w, __func__);
|
||||
events_fire_window("window-created", w);
|
||||
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-command-prompt.c,v 1.75 2026/06/25 11:39:11 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-command-prompt.c,v 1.76 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -242,9 +242,11 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
cmd_free_argv(argc, argv);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-display-menu.c,v 1.52 2026/07/14 19:07:03 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-display-menu.c,v 1.53 2026/08/31 07:46:55 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -591,6 +591,8 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_client_clear_overlay(tc);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (tc->flags & CLIENT_CONTROL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (!modify && tc->overlay_draw != NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-if-shell.c,v 1.86 2025/08/01 09:05:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-if-shell.c,v 1.87 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -98,6 +98,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_ERROR);
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -164,9 +165,11 @@ cmd_if_shell_callback(struct job *job)
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-join-pane.c,v 1.73 2026/07/17 15:24:30 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-join-pane.c,v 1.74 2026/08/03 20:29:52 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 George Nachman <tmux@georgester.com>
|
||||
@@ -431,25 +431,23 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (cmd_get_entry(self) == &cmd_move_pane_entry) {
|
||||
if (args_has(args, 'M'))
|
||||
return (cmd_join_pane_mouse_update(item));
|
||||
if (!window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "pane is not floating");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if ((s = args_get(args, 'P')) != NULL) {
|
||||
server_unzoom_window(dst_w);
|
||||
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
||||
}
|
||||
if ((s = args_get(args, 'z')) != NULL) {
|
||||
server_unzoom_window(dst_w);
|
||||
return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s));
|
||||
}
|
||||
if (args_has(args, 'X') ||
|
||||
if (args_has(args, 'P') ||
|
||||
args_has(args, 'z') ||
|
||||
args_has(args, 'X') ||
|
||||
args_has(args, 'Y') ||
|
||||
args_has(args, 'U') ||
|
||||
args_has(args, 'D') ||
|
||||
args_has(args, 'L') ||
|
||||
args_has(args, 'R')) {
|
||||
if (!window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "pane is not floating");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
server_unzoom_window(dst_w);
|
||||
if ((s = args_get(args, 'P')) != NULL)
|
||||
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
||||
if ((s = args_get(args, 'z')) != NULL)
|
||||
return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s));
|
||||
return (cmd_join_pane_move(item, args, dst_wl, dst_wp));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-new-window.c,v 1.103 2026/07/08 08:07:42 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-new-window.c,v 1.104 2026/09/03 21:04:11 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -60,7 +60,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl = target->wl, *new_wl = NULL;
|
||||
int idx = target->idx, before, count = args_count(args);
|
||||
int idx = target->idx, before;
|
||||
int count = args_count(args);
|
||||
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
||||
const char *template, *name;
|
||||
struct cmd_find_state fs;
|
||||
@@ -74,8 +75,10 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
/*
|
||||
* If -S and -n are given and -t is not and a single window with this
|
||||
* name already exists, select it.
|
||||
* If -S is given, select an existing window instead of creating a
|
||||
* new one: preferring -t if it already points at a window, or
|
||||
* otherwise -n if one exists with that name. If neither matches,
|
||||
* fall through and create a window as normal.
|
||||
*/
|
||||
name = args_get(args, 'n');
|
||||
if (name != NULL) {
|
||||
@@ -88,32 +91,39 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
wname = clean_name(expanded, 0);
|
||||
free(expanded);
|
||||
}
|
||||
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
|
||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
if (strcmp(wl->window->name, expanded) != 0)
|
||||
continue;
|
||||
if (new_wl == NULL) {
|
||||
new_wl = wl;
|
||||
continue;
|
||||
if (args_has(args, 'S')) {
|
||||
if (idx != -1)
|
||||
new_wl = winlink_find_by_index(&s->windows, idx);
|
||||
else if (wname != NULL) {
|
||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
if (strcmp(wl->window->name, expanded) != 0)
|
||||
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);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
free(expanded);
|
||||
if (new_wl != NULL) {
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
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();
|
||||
}
|
||||
|
||||
/* Found an existing window to select instead of creating a new one. */
|
||||
if (new_wl != NULL) {
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
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');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-parse.y,v 1.58 2026/04/27 12:31:11 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-parse.y,v 1.59 2026/08/31 07:51:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -802,6 +802,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
|
||||
if (last == NULL) {
|
||||
pr->status = CMD_PARSE_SUCCESS;
|
||||
pr->cmdlist = cmd_list_new();
|
||||
cmd_parse_free_commands(cmds);
|
||||
return (1);
|
||||
}
|
||||
|
||||
@@ -814,6 +815,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
|
||||
pi->flags |= CMD_PARSE_NOALIAS;
|
||||
cmd_parse_build_commands(cmds, pi, pr);
|
||||
pi->flags &= ~CMD_PARSE_NOALIAS;
|
||||
cmd_parse_free_commands(cmds);
|
||||
return (1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-pipe-pane.c,v 1.63 2026/04/28 08:47:55 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-pipe-pane.c,v 1.64 2026/07/27 14:25:46 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -194,9 +194,13 @@ static void
|
||||
cmd_pipe_pane_read_callback(__unused struct bufferevent *bufev, void *data)
|
||||
{
|
||||
struct window_pane *wp = data;
|
||||
struct evbuffer *evb = wp->pipe_event->input;
|
||||
struct evbuffer *evb;
|
||||
size_t available;
|
||||
|
||||
if (wp->pipe_event == NULL)
|
||||
return;
|
||||
evb = wp->pipe_event->input;
|
||||
|
||||
available = EVBUFFER_LENGTH(evb);
|
||||
log_debug("%%%u pipe read %zu", wp->id, available);
|
||||
|
||||
|
||||
17
cmd-queue.c
17
cmd-queue.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-queue.c,v 1.121 2026/07/14 17:17:17 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-queue.c,v 1.123 2026/08/24 20:34:26 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -802,7 +802,7 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
|
||||
u_int number = item->number;
|
||||
|
||||
if (c != NULL && (c->flags & CLIENT_CONTROL))
|
||||
control_write(c, "%%%s %ld %u %d", guard, t, number, flags);
|
||||
control_write_guard(c, guard, t, number, flags);
|
||||
}
|
||||
|
||||
/* Show message from command. */
|
||||
@@ -850,7 +850,18 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...)
|
||||
|
||||
if (c == NULL) {
|
||||
cmd_get_source(cmd, &file, &line);
|
||||
cfg_add_cause("%s:%u: %s", file, line, msg);
|
||||
if (!cfg_finished) {
|
||||
if (file != NULL)
|
||||
cfg_add_cause("%s:%u: %s", file, line, msg);
|
||||
else
|
||||
cfg_add_cause("%s", msg);
|
||||
} else {
|
||||
if (file != NULL) {
|
||||
server_add_message("message: %s:%u: %s", file,
|
||||
line, msg);
|
||||
} else
|
||||
server_add_message("message: %s", msg);
|
||||
}
|
||||
} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
|
||||
server_add_message("%s message: %s", c->name, msg);
|
||||
if (~c->flags & CLIENT_UTF8) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.67 2026/07/10 13:38:45 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -92,6 +92,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
server_unzoom_window(w);
|
||||
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
||||
|
||||
if (args_has(args, 'x')) {
|
||||
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.93 2026/07/17 12:42:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -233,9 +233,11 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
|
||||
if (cdata->item != NULL)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-select-pane.c,v 1.77 2026/07/17 12:42:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-select-pane.c,v 1.78 2026/08/20 09:19:24 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -160,6 +160,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
char *title;
|
||||
const char *style;
|
||||
struct options_entry *o;
|
||||
int visible, Zflag = args_has(args, 'Z');
|
||||
|
||||
if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
|
||||
/*
|
||||
@@ -185,14 +186,18 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_redraw_window_borders(lastwp->window);
|
||||
server_status_window(lastwp->window);
|
||||
} else {
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
if (w->modal != NULL && lastwp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(lastwp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, lastwp);
|
||||
if (window_set_active_pane(w, lastwp, 1)) {
|
||||
cmd_find_from_winlink(current, wl, 0);
|
||||
cmd_select_pane_redraw(w);
|
||||
}
|
||||
if (window_pop_zoom(w))
|
||||
if (!visible && window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
@@ -268,14 +273,18 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
if (wp == w->active)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(wp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, wp);
|
||||
if (window_set_active_pane(w, wp, 1))
|
||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||
cmdq_insert_hook(s, item, current, "after-select-pane");
|
||||
cmd_select_pane_redraw(w);
|
||||
if (window_pop_zoom(w))
|
||||
if (!visible && window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-set-buffer.c,v 1.37 2026/02/15 17:43:26 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-set-buffer.c,v 1.38 2026/08/24 07:05:23 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -100,6 +100,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmdq_error(item, "%s", cause);
|
||||
goto fail;
|
||||
}
|
||||
free(bufname);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -107,8 +108,10 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmdq_error(item, "no data specified");
|
||||
goto fail;
|
||||
}
|
||||
if ((newsize = strlen(args_string(args, 0))) == 0)
|
||||
if ((newsize = strlen(args_string(args, 0))) == 0) {
|
||||
free(bufname);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
if (args_has(args, 'a') && pb != NULL) {
|
||||
olddata = paste_buffer_data(pb, &bufsize);
|
||||
@@ -127,6 +130,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'w') && tc != NULL)
|
||||
tty_set_selection(&tc->tty, "", bufdata, bufsize);
|
||||
|
||||
free(bufname);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
fail:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-show-options.c,v 1.73 2026/07/10 13:38:45 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-show-options.c,v 1.76 2026/07/27 19:15:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -27,14 +27,23 @@
|
||||
* Show options.
|
||||
*/
|
||||
|
||||
#define SHOW_OPTIONS_TEMPLATE \
|
||||
"#{?option_value_only," \
|
||||
"#{option_value}," \
|
||||
"#{option_name}#{?option_has_array_key," \
|
||||
"[#{option_array_key}],}" \
|
||||
"#{?option_is_parent,*,}" \
|
||||
"#{?option_has_value, " \
|
||||
"#{?option_is_string,#{q/a:option_value},#{option_value}},}}"
|
||||
#define SHOW_HOOKS_MONITOR_TEMPLATE \
|
||||
"#{option_name}:#{hook_monitor_target}:#{hook_monitor_format}"
|
||||
|
||||
static enum cmd_retval cmd_show_options_exec(struct cmd *, struct cmdq_item *);
|
||||
|
||||
static void cmd_show_options_print(struct cmd *, struct cmdq_item *,
|
||||
struct options_entry *, const char *, int);
|
||||
static void cmd_show_hooks_print_monitor(struct cmdq_item *,
|
||||
struct options_entry *);
|
||||
static enum cmd_retval cmd_show_hooks_monitor(struct cmd *, struct cmdq_item *,
|
||||
int, struct options *);
|
||||
static void cmd_show_hooks_print_monitor(struct cmd *,
|
||||
struct cmdq_item *, struct options_entry *);
|
||||
static enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *,
|
||||
int, struct options *);
|
||||
|
||||
@@ -42,8 +51,8 @@ const struct cmd_entry cmd_show_options_entry = {
|
||||
.name = "show-options",
|
||||
.alias = "show",
|
||||
|
||||
.args = { "AgHpqst:vw", 0, 1, NULL },
|
||||
.usage = "[-AgHpqsvw] " CMD_TARGET_PANE_USAGE " [option]",
|
||||
.args = { "AgF:Hpqst:vw", 0, 1, NULL },
|
||||
.usage = "[-AgHpqsvw] [-F format] " CMD_TARGET_PANE_USAGE " [option]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
||||
|
||||
@@ -55,8 +64,8 @@ const struct cmd_entry cmd_show_window_options_entry = {
|
||||
.name = "show-window-options",
|
||||
.alias = "showw",
|
||||
|
||||
.args = { "gvt:", 0, 1, NULL },
|
||||
.usage = "[-gv] " CMD_TARGET_WINDOW_USAGE " [option]",
|
||||
.args = { "F:gvt:", 0, 1, NULL },
|
||||
.usage = "[-gv] [-F format] " CMD_TARGET_WINDOW_USAGE " [option]",
|
||||
|
||||
.target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL },
|
||||
|
||||
@@ -68,8 +77,8 @@ const struct cmd_entry cmd_show_hooks_entry = {
|
||||
.name = "show-hooks",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "Bgpt:w", 0, 1, NULL },
|
||||
.usage = "[-Bgpw] " CMD_TARGET_PANE_USAGE " [hook]",
|
||||
.args = { "BF:gpt:w", 0, 1, NULL },
|
||||
.usage = "[-Bgpw] [-F format] " CMD_TARGET_PANE_USAGE " [hook]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
||||
|
||||
@@ -85,7 +94,7 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct options *oo;
|
||||
char *argument, *name = NULL, *cause;
|
||||
char *array_key = NULL;
|
||||
int window, ambiguous, parent, scope;
|
||||
int window, ambiguous, parent, print_parent, scope;
|
||||
struct options_entry *o;
|
||||
|
||||
window = (cmd_get_entry(self) == &cmd_show_window_options_entry);
|
||||
@@ -101,8 +110,14 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (cmd_get_entry(self) == &cmd_show_hooks_entry &&
|
||||
args_has(args, 'B'))
|
||||
return (cmd_show_hooks_monitor(self, item, scope, oo));
|
||||
args_has(args, 'B')) {
|
||||
o = options_first(oo);
|
||||
while (o != NULL) {
|
||||
cmd_show_hooks_print_monitor(self, item, o);
|
||||
o = options_next(o);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
return (cmd_show_options_all(self, item, scope, oo));
|
||||
}
|
||||
argument = format_single_from_target(item, args_string(args, 0));
|
||||
@@ -135,11 +150,17 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (o != NULL) {
|
||||
if (cmd_get_entry(self) == &cmd_show_hooks_entry &&
|
||||
args_has(args, 'B'))
|
||||
cmd_show_hooks_print_monitor(item, o);
|
||||
else
|
||||
cmd_show_options_print(self, item, o, array_key, parent);
|
||||
}
|
||||
else if (*name == '@') {
|
||||
cmd_show_hooks_print_monitor(self, item, o);
|
||||
else {
|
||||
print_parent = parent;
|
||||
if (array_key == NULL && options_is_array(o) &&
|
||||
options_array_first(o) == NULL) {
|
||||
print_parent = 0;
|
||||
}
|
||||
cmd_show_options_print(self, item, o, array_key,
|
||||
print_parent);
|
||||
}
|
||||
} else if (*name == '@') {
|
||||
if (args_has(args, 'q'))
|
||||
goto out;
|
||||
cmdq_error(item, "invalid option: %s", argument);
|
||||
@@ -163,22 +184,24 @@ static void
|
||||
cmd_show_options_print(struct cmd *self, struct cmdq_item *item,
|
||||
struct options_entry *o, const char *array_key, int parent)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct options_array_item *a;
|
||||
const char *name = options_name(o);
|
||||
char *value, *tmp = NULL, *escaped;
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct options_array_item *a;
|
||||
struct format_tree *ft;
|
||||
const char *name = options_name(o);
|
||||
const char *template = args_get(args, 'F');
|
||||
char *value, *line;
|
||||
struct timeval tv = { 0 };
|
||||
u_int fire_count;
|
||||
time_t fire_time;
|
||||
int is_hook = 0, is_user = 0;
|
||||
int has_value = 1;
|
||||
const struct options_table_entry *oe = options_table_entry(o);
|
||||
|
||||
if (array_key != NULL) {
|
||||
xasprintf(&tmp, "%s[%s]", name, array_key);
|
||||
name = tmp;
|
||||
} else {
|
||||
if (options_is_array(o)) {
|
||||
a = options_array_first(o);
|
||||
if (a == NULL) {
|
||||
if (!args_has(args, 'v'))
|
||||
cmdq_print(item, "%s", name);
|
||||
return;
|
||||
}
|
||||
if (array_key != NULL)
|
||||
value = options_to_string(o, array_key, 0);
|
||||
else if (options_is_array(o)) {
|
||||
a = options_array_first(o);
|
||||
if (a != NULL) {
|
||||
while (a != NULL) {
|
||||
array_key = options_array_item_key(a);
|
||||
cmd_show_options_print(self, item, o, array_key,
|
||||
@@ -187,54 +210,130 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item,
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (template == NULL && args_has(args, 'v'))
|
||||
return;
|
||||
value = xstrdup("");
|
||||
has_value = 0;
|
||||
} else
|
||||
value = options_to_string(o, NULL, 0);
|
||||
|
||||
value = options_to_string(o, array_key, 0);
|
||||
if (args_has(args, 'v'))
|
||||
cmdq_print(item, "%s", value);
|
||||
else if (options_is_string(o)) {
|
||||
escaped = args_escape(value);
|
||||
if (parent)
|
||||
cmdq_print(item, "%s* %s", name, escaped);
|
||||
else
|
||||
cmdq_print(item, "%s %s", name, escaped);
|
||||
free(escaped);
|
||||
if (template == NULL)
|
||||
template = SHOW_OPTIONS_TEMPLATE;
|
||||
|
||||
if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK))
|
||||
is_hook = 1;
|
||||
else if (oe == NULL)
|
||||
is_user = 1;
|
||||
|
||||
ft = format_create_from_target(item);
|
||||
format_add(ft, "option_name", "%s", name);
|
||||
format_add(ft, "option_value", "%s", value);
|
||||
format_add(ft, "option_value_only", "%d", args_has(args, 'v'));
|
||||
format_add(ft, "option_is_parent", "%d", parent);
|
||||
format_add(ft, "option_is_array", "%d", options_is_array(o));
|
||||
format_add(ft, "option_is_string", "%d", options_is_string(o));
|
||||
format_add(ft, "option_is_hook", "%d", is_hook);
|
||||
format_add(ft, "option_is_user", "%d", is_user);
|
||||
format_add(ft, "option_has_value", "%d", has_value);
|
||||
if (cmd_get_entry(self) == &cmd_show_hooks_entry) {
|
||||
fire_count = options_get_fire_count(o);
|
||||
format_add(ft, "hook_fire_count", "%u", fire_count);
|
||||
|
||||
fire_time = options_get_fire_time(o);
|
||||
if (fire_time != 0) {
|
||||
tv.tv_sec = fire_time;
|
||||
format_add_tv(ft, "hook_fire_time", &tv);
|
||||
}
|
||||
}
|
||||
if (array_key != NULL) {
|
||||
format_add(ft, "option_array_key", "%s", array_key);
|
||||
format_add(ft, "option_has_array_key", "1");
|
||||
} else {
|
||||
if (parent)
|
||||
cmdq_print(item, "%s* %s", name, value);
|
||||
else
|
||||
cmdq_print(item, "%s %s", name, value);
|
||||
format_add(ft, "option_array_key", "%s", "");
|
||||
format_add(ft, "option_has_array_key", "0");
|
||||
}
|
||||
free(value);
|
||||
line = format_expand(ft, template);
|
||||
format_free(ft);
|
||||
|
||||
free(tmp);
|
||||
cmdq_print(item, "%s", line);
|
||||
free(line);
|
||||
free(value);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_show_hooks_print_monitor(struct cmdq_item *item, struct options_entry *o)
|
||||
cmd_show_hooks_print_monitor(struct cmd *self, struct cmdq_item *item,
|
||||
struct options_entry *o)
|
||||
{
|
||||
char *value;
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct format_tree *ft;
|
||||
enum monitor_type type;
|
||||
const char *template = args_get(args, 'F'), *format;
|
||||
char *value, *target, *line;
|
||||
struct timeval tv = { 0 };
|
||||
u_int fire_count;
|
||||
time_t fire_time;
|
||||
int id;
|
||||
|
||||
value = hooks_monitor_to_string(o);
|
||||
if (value == NULL)
|
||||
return;
|
||||
cmdq_print(item, "%s", value);
|
||||
free(value);
|
||||
}
|
||||
|
||||
/* Show all hook monitors. */
|
||||
static enum cmd_retval
|
||||
cmd_show_hooks_monitor(__unused struct cmd *self, struct cmdq_item *item,
|
||||
__unused int scope, struct options *oo)
|
||||
{
|
||||
struct options_entry *o;
|
||||
|
||||
o = options_first(oo);
|
||||
while (o != NULL) {
|
||||
cmd_show_hooks_print_monitor(item, o);
|
||||
o = options_next(o);
|
||||
if (!hooks_monitor_get(o, &type, &id, &format)) {
|
||||
free(value);
|
||||
return;
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (template == NULL)
|
||||
template = SHOW_HOOKS_MONITOR_TEMPLATE;
|
||||
|
||||
switch (type) {
|
||||
case MONITOR_SESSION:
|
||||
target = xstrdup("");
|
||||
break;
|
||||
case MONITOR_PANE:
|
||||
xasprintf(&target, "%%%d", id);
|
||||
break;
|
||||
case MONITOR_ALL_PANES:
|
||||
target = xstrdup("%*");
|
||||
break;
|
||||
case MONITOR_WINDOW:
|
||||
xasprintf(&target, "@%d", id);
|
||||
break;
|
||||
case MONITOR_ALL_WINDOWS:
|
||||
target = xstrdup("@*");
|
||||
break;
|
||||
}
|
||||
|
||||
ft = format_create_from_target(item);
|
||||
format_add(ft, "option_name", "%s", options_name(o));
|
||||
format_add(ft, "option_value", "%s", value);
|
||||
format_add(ft, "option_value_only", "%d", 0);
|
||||
format_add(ft, "option_is_parent", "%d", 0);
|
||||
format_add(ft, "option_is_array", "%d", 0);
|
||||
format_add(ft, "option_is_string", "%d", 1);
|
||||
format_add(ft, "option_is_hook", "%d", 1);
|
||||
format_add(ft, "option_is_user", "%d", 1);
|
||||
format_add(ft, "option_has_value", "%d", 1);
|
||||
format_add(ft, "option_array_key", "%s", "");
|
||||
format_add(ft, "option_has_array_key", "0");
|
||||
|
||||
format_add(ft, "hook_monitor_target", "%s", target);
|
||||
format_add(ft, "hook_monitor_format", "%s", format);
|
||||
|
||||
fire_count = hooks_monitor_get_fire_count(o);
|
||||
format_add(ft, "hook_fire_count", "%u", fire_count);
|
||||
|
||||
fire_time = hooks_monitor_get_fire_time(o);
|
||||
if (fire_time != 0) {
|
||||
tv.tv_sec = fire_time;
|
||||
format_add_tv(ft, "hook_fire_time", &tv);
|
||||
}
|
||||
|
||||
line = format_expand(ft, template);
|
||||
format_free(ft);
|
||||
|
||||
cmdq_print(item, "%s", line);
|
||||
free(line);
|
||||
free(target);
|
||||
free(value);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
@@ -244,17 +343,29 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
|
||||
struct args *args = cmd_get_args(self);
|
||||
const struct options_table_entry *oe;
|
||||
struct options_entry *o;
|
||||
struct options_array_item *a;
|
||||
const char *name, *array_key;
|
||||
int parent;
|
||||
const char *name;
|
||||
int parent, is_user_hook;
|
||||
|
||||
if (cmd_get_entry(self) != &cmd_show_hooks_entry) {
|
||||
o = options_first(oo);
|
||||
while (o != NULL) {
|
||||
if (options_table_entry(o) == NULL)
|
||||
o = options_first(oo);
|
||||
while (o != NULL) {
|
||||
if (options_table_entry(o) == NULL) {
|
||||
name = options_name(o);
|
||||
is_user_hook = 0;
|
||||
if (*name == '@') {
|
||||
if (hooks_is_event(name) ||
|
||||
options_get_monitor_data(o) != NULL) {
|
||||
is_user_hook = 1;
|
||||
}
|
||||
}
|
||||
if (cmd_get_entry(self) != &cmd_show_hooks_entry) {
|
||||
if (!is_user_hook || args_has(args, 'H')) {
|
||||
cmd_show_options_print(self, item, o,
|
||||
NULL, 0);
|
||||
}
|
||||
} else if (is_user_hook)
|
||||
cmd_show_options_print(self, item, o, NULL, 0);
|
||||
o = options_next(o);
|
||||
}
|
||||
o = options_next(o);
|
||||
}
|
||||
for (oe = options_table; oe->name != NULL; oe++) {
|
||||
if (~oe->scope & scope)
|
||||
@@ -278,24 +389,7 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
|
||||
} else
|
||||
parent = 0;
|
||||
|
||||
if (!options_is_array(o))
|
||||
cmd_show_options_print(self, item, o, NULL, parent);
|
||||
else if ((a = options_array_first(o)) == NULL) {
|
||||
if (!args_has(args, 'v')) {
|
||||
name = options_name(o);
|
||||
if (parent)
|
||||
cmdq_print(item, "%s*", name);
|
||||
else
|
||||
cmdq_print(item, "%s", name);
|
||||
}
|
||||
} else {
|
||||
while (a != NULL) {
|
||||
array_key = options_array_item_key(a);
|
||||
cmd_show_options_print(self, item, o, array_key,
|
||||
parent);
|
||||
a = options_array_next(a);
|
||||
}
|
||||
}
|
||||
cmd_show_options_print(self, item, o, NULL, parent);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-source-file.c,v 1.62 2025/11/18 08:42:09 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-source-file.c,v 1.63 2026/08/18 08:05:05 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -50,6 +50,7 @@ const struct cmd_entry cmd_source_file_entry = {
|
||||
|
||||
struct cmd_source_file_data {
|
||||
struct cmdq_item *item;
|
||||
struct client *client;
|
||||
int flags;
|
||||
|
||||
struct cmdq_item *after;
|
||||
@@ -60,10 +61,24 @@ struct cmd_source_file_data {
|
||||
u_int nfiles;
|
||||
};
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
|
||||
static void
|
||||
cmd_source_file_free_data(struct cmd_source_file_data *cdata)
|
||||
{
|
||||
struct client *c = cmdq_get_client(item);
|
||||
u_int i;
|
||||
|
||||
for (i = 0; i < cdata->nfiles; i++)
|
||||
free(cdata->files[i]);
|
||||
free(cdata->files);
|
||||
if (cdata->client != NULL)
|
||||
server_client_unref(cdata->client);
|
||||
free(cdata);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
|
||||
{
|
||||
struct cmd_source_file_data *cdata = data;
|
||||
struct client *c = cdata->client;
|
||||
|
||||
if (c == NULL) {
|
||||
cmd_source_file_depth--;
|
||||
@@ -74,36 +89,36 @@ cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
|
||||
}
|
||||
|
||||
cfg_print_causes(item);
|
||||
cmd_source_file_free_data(cdata);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata)
|
||||
cmd_source_file_complete(struct cmd_source_file_data *cdata)
|
||||
{
|
||||
struct client *c = cdata->client;
|
||||
struct cmdq_item *new_item;
|
||||
u_int i;
|
||||
|
||||
if (cfg_finished) {
|
||||
if (cdata->retval == CMD_RETURN_ERROR &&
|
||||
c != NULL &&
|
||||
c->session == NULL)
|
||||
c->retval = 1;
|
||||
new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL);
|
||||
cmdq_insert_after(cdata->after, new_item);
|
||||
if (!cfg_finished) {
|
||||
cmd_source_file_free_data(cdata);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < cdata->nfiles; i++)
|
||||
free(cdata->files[i]);
|
||||
free(cdata->files);
|
||||
free(cdata);
|
||||
if (cdata->retval == CMD_RETURN_ERROR &&
|
||||
c != NULL &&
|
||||
c->session == NULL)
|
||||
c->retval = 1;
|
||||
new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
|
||||
cmdq_insert_after(cdata->after, new_item);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_source_file_done(struct client *c, const char *path, int error,
|
||||
int closed, struct evbuffer *buffer, void *data)
|
||||
cmd_source_file_done(__unused struct client *oc, const char *path,
|
||||
int error, int closed, struct evbuffer *buffer, void *data)
|
||||
{
|
||||
struct cmd_source_file_data *cdata = data;
|
||||
struct cmdq_item *item = cdata->item;
|
||||
struct client *c = cdata->client;
|
||||
void *bdata = EVBUFFER_DATA(buffer);
|
||||
size_t bsize = EVBUFFER_LENGTH(buffer);
|
||||
u_int n;
|
||||
@@ -127,7 +142,7 @@ cmd_source_file_done(struct client *c, const char *path, int error,
|
||||
if (n < cdata->nfiles)
|
||||
file_read(c, cdata->files[n], cmd_source_file_done, cdata);
|
||||
else {
|
||||
cmd_source_file_complete(c, cdata);
|
||||
cmd_source_file_complete(cdata);
|
||||
cmdq_continue(item);
|
||||
}
|
||||
}
|
||||
@@ -187,6 +202,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
cdata = xcalloc(1, sizeof *cdata);
|
||||
cdata->item = item;
|
||||
cdata->client = c;
|
||||
if (c != NULL)
|
||||
c->references++;
|
||||
|
||||
if (args_has(args, 'q'))
|
||||
cdata->flags |= CMD_PARSE_QUIET;
|
||||
@@ -249,7 +267,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
|
||||
file_read(c, cdata->files[0], cmd_source_file_done, cdata);
|
||||
retval = CMD_RETURN_WAIT;
|
||||
} else
|
||||
cmd_source_file_complete(c, cdata);
|
||||
cmd_source_file_complete(cdata);
|
||||
|
||||
free(cwd);
|
||||
return (retval);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.146 2026/07/21 12:28:43 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
||||
.name = "new-pane",
|
||||
.alias = "newp",
|
||||
|
||||
.args = { "bB:Cc:de:EfF:hIkl:LMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-bCdefhIklMOPvWZ] [-B border-lines] "
|
||||
.args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] "
|
||||
"[-c start-directory] [-e environment] "
|
||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||
"[-s style] [-S active-border-style] "
|
||||
@@ -89,6 +89,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state fs;
|
||||
struct key_event *event = cmdq_get_event(item);
|
||||
int input, empty, is_floating, flags = 0;
|
||||
int restore_zoom = 0;
|
||||
const char *template, *style, *value;
|
||||
char *cause = NULL, *cp, *title;
|
||||
const struct options_table_entry *oe;
|
||||
@@ -96,9 +97,16 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
enum pane_lines lines;
|
||||
u_int count = args_count(args);
|
||||
|
||||
if (window_active_pane_is_over_zoom(w))
|
||||
restore_zoom = 1;
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_new_pane_entry)
|
||||
is_floating = !args_has(args, 'L');
|
||||
else {
|
||||
if (!window_pane_is_visible(wp))
|
||||
restore_zoom = 0;
|
||||
if (!restore_zoom)
|
||||
window_unzoom(w, 1);
|
||||
is_floating = window_pane_is_floating(wp);
|
||||
flags |= SPAWN_SPLIT;
|
||||
}
|
||||
@@ -132,7 +140,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'Z'))
|
||||
flags |= SPAWN_ZOOM;
|
||||
if (args_has(args, 'O'))
|
||||
flags |= SPAWN_MODAL;
|
||||
flags |= SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
|
||||
if (is_floating && args_has(args, 'A'))
|
||||
flags |= SPAWN_FLOATOVERZOOM;
|
||||
if ((w->flags & WINDOW_ZOOMED) && (flags & SPAWN_FLOATOVERZOOM))
|
||||
restore_zoom = 1;
|
||||
|
||||
input = args_has(args, 'I');
|
||||
if (input || (count == 1 && *args_string(args, 0) == '\0'))
|
||||
@@ -168,6 +180,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
if (restore_zoom)
|
||||
window_pop_zoom(w);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
@@ -201,6 +215,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
*/
|
||||
goto fail;
|
||||
}
|
||||
if (args_has(args, 'K') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||
|
||||
@@ -270,7 +286,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (~flags & SPAWN_DETACHED)
|
||||
cmd_find_from_winlink_pane(current, wl, new_wp, 0);
|
||||
|
||||
if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
|
||||
if (restore_zoom) {
|
||||
window_pop_zoom(wp->window);
|
||||
server_redraw_window(wp->window);
|
||||
} else if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
|
||||
window_pop_zoom(wp->window);
|
||||
server_redraw_window(wp->window);
|
||||
}
|
||||
@@ -321,8 +340,9 @@ fail:
|
||||
if (!is_floating)
|
||||
layout_close_pane(new_wp);
|
||||
window_remove_pane(wp->window, new_wp);
|
||||
} else if (args_has(args, 'O'))
|
||||
window_pop_modal_zoom(wp->window);
|
||||
}
|
||||
if (restore_zoom || (~flags & SPAWN_FLOATING))
|
||||
window_pop_zoom(wp->window);
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-swap-window.c,v 1.29 2025/10/30 13:52:08 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-swap-window.c,v 1.30 2026/07/27 08:03:01 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -38,7 +38,7 @@ const struct cmd_entry cmd_swap_window_entry = {
|
||||
.source = { 's', CMD_FIND_WINDOW, CMD_FIND_DEFAULT_MARKED },
|
||||
.target = { 't', CMD_FIND_WINDOW, 0 },
|
||||
|
||||
.flags = 0,
|
||||
.flags = CMD_AFTERHOOK,
|
||||
.exec = cmd_swap_window_exec
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-switch-client.c,v 1.74 2026/05/22 15:22:43 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-switch-client.c,v 1.75 2026/08/20 09:19:24 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -53,7 +53,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state target;
|
||||
const char *tflag = args_get(args, 't');
|
||||
enum cmd_find_type type;
|
||||
int flags;
|
||||
int flags, visible, Zflag = args_has(args, 'Z');
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct session *s;
|
||||
@@ -139,11 +139,15 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (wl != NULL && wp != NULL && wp != wl->window->active) {
|
||||
w = wl->window;
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(wp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, wp);
|
||||
window_set_active_pane(w, wp, 1);
|
||||
if (window_pop_zoom(w))
|
||||
if (!visible && window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
}
|
||||
if (wl != NULL) {
|
||||
|
||||
@@ -31,7 +31,7 @@ getpeereid(int s, uid_t *uid, gid_t *gid)
|
||||
{
|
||||
#ifdef HAVE_SO_PEERCRED
|
||||
struct ucred uc;
|
||||
int len = sizeof uc;
|
||||
socklen_t len = sizeof uc;
|
||||
|
||||
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1)
|
||||
return (-1);
|
||||
|
||||
143
configure.ac
143
configure.ac
@@ -76,9 +76,7 @@ AM_CONDITIONAL(IS_OPTIMIZED, test "x$enable_optimizations" = xyes)
|
||||
# Is this --enable-asan?
|
||||
AC_ARG_ENABLE(
|
||||
asan,
|
||||
AS_HELP_STRING(--enable-asan, enable ASAN build flags),
|
||||
,
|
||||
[case "x$host_os" in *darwin*) enable_asan=yes;; esac]
|
||||
AS_HELP_STRING(--enable-asan, enable ASAN build flags)
|
||||
)
|
||||
AM_CONDITIONAL(IS_ASAN, test "x$enable_asan" = xyes)
|
||||
|
||||
@@ -240,6 +238,7 @@ AC_LIBOBJ(getopt_long)
|
||||
|
||||
# Look for libevent. Try libevent_core or libevent with pkg-config first then
|
||||
# look for the library.
|
||||
libevent_version=off
|
||||
PKG_CHECK_MODULES(
|
||||
LIBEVENT_CORE,
|
||||
[libevent_core >= 2],
|
||||
@@ -248,6 +247,8 @@ PKG_CHECK_MODULES(
|
||||
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$LIBEVENT_CORE_LIBS $LIBS"
|
||||
found_libevent=yes
|
||||
libevent_version=`$PKG_CONFIG --modversion libevent_core 2>/dev/null`
|
||||
test "x$libevent_version" = x && libevent_version=on
|
||||
],
|
||||
found_libevent=no
|
||||
)
|
||||
@@ -260,6 +261,8 @@ if test x$found_libevent = xno; then
|
||||
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$LIBEVENT_LIBS $LIBS"
|
||||
found_libevent=yes
|
||||
libevent_version=`$PKG_CONFIG --modversion libevent 2>/dev/null`
|
||||
test "x$libevent_version" = x && libevent_version=on
|
||||
],
|
||||
found_libevent=no
|
||||
)
|
||||
@@ -268,7 +271,8 @@ if test x$found_libevent = xno; then
|
||||
AC_SEARCH_LIBS(
|
||||
event_init,
|
||||
[event_core event event-1.4],
|
||||
found_libevent=yes,
|
||||
[found_libevent=yes
|
||||
libevent_version=on],
|
||||
found_libevent=no
|
||||
)
|
||||
fi
|
||||
@@ -295,6 +299,7 @@ fi
|
||||
|
||||
# Look for ncurses or curses. Try pkg-config first then directly for the
|
||||
# library.
|
||||
ncurses_version=off
|
||||
PKG_CHECK_MODULES(
|
||||
LIBTINFOW,
|
||||
tinfow,
|
||||
@@ -303,6 +308,8 @@ PKG_CHECK_MODULES(
|
||||
CPPFLAGS="$LIBTINFOW_CFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$LIBTINFOW_LIBS $LIBS"
|
||||
found_ncurses=yes
|
||||
ncurses_version=`$PKG_CONFIG --modversion tinfow 2>/dev/null`
|
||||
test "x$ncurses_version" = x && ncurses_version=on
|
||||
],
|
||||
found_ncurses=no
|
||||
)
|
||||
@@ -315,6 +322,8 @@ if test "x$found_ncurses" = xno; then
|
||||
CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$LIBTINFO_LIBS $LIBS"
|
||||
found_ncurses=yes
|
||||
ncurses_version=`$PKG_CONFIG --modversion tinfo 2>/dev/null`
|
||||
test "x$ncurses_version" = x && ncurses_version=on
|
||||
],
|
||||
found_ncurses=no
|
||||
)
|
||||
@@ -328,6 +337,8 @@ if test "x$found_ncurses" = xno; then
|
||||
CPPFLAGS="$LIBNCURSESW_CFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$LIBNCURSESW_LIBS $LIBS"
|
||||
found_ncurses=yes
|
||||
ncurses_version=`$PKG_CONFIG --modversion ncursesw 2>/dev/null`
|
||||
test "x$ncurses_version" = x && ncurses_version=on
|
||||
],
|
||||
found_ncurses=no
|
||||
)
|
||||
@@ -341,6 +352,8 @@ if test "x$found_ncurses" = xno; then
|
||||
CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$LIBNCURSES_LIBS $LIBS"
|
||||
found_ncurses=yes
|
||||
ncurses_version=`$PKG_CONFIG --modversion ncurses 2>/dev/null`
|
||||
test "x$ncurses_version" = x && ncurses_version=on
|
||||
],
|
||||
found_ncurses=no
|
||||
)
|
||||
@@ -349,7 +362,8 @@ if test "x$found_ncurses" = xno; then
|
||||
AC_SEARCH_LIBS(
|
||||
setupterm,
|
||||
[tinfow tinfo terminfo ncursesw ncurses],
|
||||
found_ncurses=yes,
|
||||
[found_ncurses=yes
|
||||
ncurses_version=on],
|
||||
found_ncurses=no
|
||||
)
|
||||
if test "x$found_ncurses" = xyes; then
|
||||
@@ -379,6 +393,7 @@ else
|
||||
LIBS="$LIBS -lcurses"
|
||||
CPPFLAGS="$CPPFLAGS -DHAVE_CURSES_H"
|
||||
AC_DEFINE(HAVE_CURSES_H)
|
||||
ncurses_version=on
|
||||
else
|
||||
AC_MSG_ERROR("curses not found")
|
||||
fi
|
||||
@@ -389,6 +404,7 @@ AC_CHECK_FUNCS([ \
|
||||
])
|
||||
|
||||
# Look for utempter.
|
||||
utempter_version=off
|
||||
AC_ARG_ENABLE(
|
||||
utempter,
|
||||
AS_HELP_STRING(--enable-utempter, use utempter if it is installed)
|
||||
@@ -405,17 +421,34 @@ if test "x$enable_utempter" = xyes; then
|
||||
fi
|
||||
if test "x$enable_utempter" = xyes; then
|
||||
AC_DEFINE(HAVE_UTEMPTER)
|
||||
utempter_version=on
|
||||
else
|
||||
AC_MSG_ERROR("utempter not found")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Look for utf8proc.
|
||||
utf8proc_version=off
|
||||
AC_ARG_ENABLE(
|
||||
utf8proc,
|
||||
AS_HELP_STRING(--enable-utf8proc, use utf8proc if it is installed)
|
||||
)
|
||||
try_utf8proc=no
|
||||
require_utf8proc=no
|
||||
if test "x$enable_utf8proc" = xyes; then
|
||||
try_utf8proc=yes
|
||||
elif test "x$enable_utf8proc" = x; then
|
||||
case "$host_os" in
|
||||
*darwin*)
|
||||
try_utf8proc=yes
|
||||
require_utf8proc=yes
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
if test "x$try_utf8proc" = xyes; then
|
||||
SAVED_AM_CPPFLAGS="$AM_CPPFLAGS"
|
||||
SAVED_OLD_CPPFLAGS="$CPPFLAGS"
|
||||
SAVED_LIBS="$LIBS"
|
||||
PKG_CHECK_MODULES(
|
||||
LIBUTF8PROC,
|
||||
libutf8proc,
|
||||
@@ -423,7 +456,10 @@ if test "x$enable_utf8proc" = xyes; then
|
||||
AM_CPPFLAGS="$LIBUTF8PROC_CFLAGS $AM_CPPFLAGS"
|
||||
CPPFLAGS="$LIBUTF8PROC_CFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$LIBUTF8PROC_LIBS $LIBS"
|
||||
]
|
||||
utf8proc_version=`$PKG_CONFIG --modversion libutf8proc 2>/dev/null`
|
||||
test "x$utf8proc_version" = x && utf8proc_version=on
|
||||
],
|
||||
[:]
|
||||
)
|
||||
AC_CHECK_HEADER(utf8proc.h, enable_utf8proc=yes, enable_utf8proc=no)
|
||||
if test "x$enable_utf8proc" = xyes; then
|
||||
@@ -436,13 +472,23 @@ if test "x$enable_utf8proc" = xyes; then
|
||||
fi
|
||||
if test "x$enable_utf8proc" = xyes; then
|
||||
AC_DEFINE(HAVE_UTF8PROC)
|
||||
test "x$utf8proc_version" = xoff && utf8proc_version=on
|
||||
else
|
||||
AC_MSG_ERROR("utf8proc not found")
|
||||
AM_CPPFLAGS="$SAVED_AM_CPPFLAGS"
|
||||
CPPFLAGS="$SAVED_OLD_CPPFLAGS"
|
||||
LIBS="$SAVED_LIBS"
|
||||
utf8proc_version=off
|
||||
if test "x$require_utf8proc" = xyes; then
|
||||
enable_utf8proc=
|
||||
else
|
||||
AC_MSG_ERROR("utf8proc not found")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_UTF8PROC, [test "x$enable_utf8proc" = xyes])
|
||||
|
||||
# Check for systemd support.
|
||||
systemd_version=off
|
||||
AC_ARG_ENABLE(
|
||||
systemd,
|
||||
AS_HELP_STRING(--enable-systemd, enable systemd integration)
|
||||
@@ -456,6 +502,8 @@ if test x"$enable_systemd" = xyes; then
|
||||
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$SYSTEMD_LIBS $LIBS"
|
||||
found_systemd=yes
|
||||
systemd_version=`$PKG_CONFIG --modversion libsystemd 2>/dev/null`
|
||||
test "x$systemd_version" = x && systemd_version=on
|
||||
],
|
||||
found_systemd=no
|
||||
)
|
||||
@@ -580,11 +628,34 @@ if test "x$found_malloc_trim" = xyes; then
|
||||
fi
|
||||
|
||||
# Build against jemalloc if requested.
|
||||
jemalloc_version=off
|
||||
AC_ARG_ENABLE(
|
||||
jemalloc,
|
||||
AS_HELP_STRING(--enable-jemalloc, use jemalloc if it is installed)
|
||||
)
|
||||
try_jemalloc=no
|
||||
require_jemalloc=no
|
||||
if test "x$enable_asan" = xyes && test "x$enable_jemalloc" = xyes; then
|
||||
AC_MSG_ERROR([--enable-asan and --enable-jemalloc cannot be used together])
|
||||
fi
|
||||
if test "x$enable_jemalloc" = xyes; then
|
||||
try_jemalloc=yes
|
||||
elif test "x$enable_jemalloc" = x; then
|
||||
case "$host_os" in
|
||||
*darwin*)
|
||||
if test "x$enable_asan" = xyes; then
|
||||
enable_jemalloc=no
|
||||
else
|
||||
try_jemalloc=yes
|
||||
require_jemalloc=yes
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
if test "x$try_jemalloc" = xyes; then
|
||||
SAVED_AM_CPPFLAGS="$AM_CPPFLAGS"
|
||||
SAVED_OLD_CPPFLAGS="$CPPFLAGS"
|
||||
SAVED_LIBS="$LIBS"
|
||||
PKG_CHECK_MODULES(
|
||||
JEMALLOC,
|
||||
jemalloc,
|
||||
@@ -592,9 +663,26 @@ if test "x$enable_jemalloc" = xyes; then
|
||||
AM_CPPFLAGS="$JEMALLOC_CFLAGS $AM_CPPFLAGS"
|
||||
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$LIBS $JEMALLOC_LIBS"
|
||||
jemalloc_version=`$PKG_CONFIG --modversion jemalloc 2>/dev/null`
|
||||
test "x$jemalloc_version" = x && jemalloc_version=on
|
||||
],
|
||||
AC_MSG_ERROR("jemalloc not found")
|
||||
enable_jemalloc=no
|
||||
)
|
||||
if test "x$enable_jemalloc" = xno; then
|
||||
AM_CPPFLAGS="$SAVED_AM_CPPFLAGS"
|
||||
CPPFLAGS="$SAVED_OLD_CPPFLAGS"
|
||||
LIBS="$SAVED_LIBS"
|
||||
jemalloc_version=off
|
||||
if test "x$require_jemalloc" = xyes; then
|
||||
enable_jemalloc=
|
||||
else
|
||||
AC_MSG_ERROR("jemalloc not found")
|
||||
fi
|
||||
else
|
||||
enable_jemalloc=yes
|
||||
AC_DEFINE(HAVE_JEMALLOC)
|
||||
test "x$jemalloc_version" = xoff && jemalloc_version=on
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for CMSG_DATA. On some platforms like HP-UX this requires UNIX 95
|
||||
@@ -941,6 +1029,20 @@ case "$host_os" in
|
||||
AC_MSG_NOTICE([])
|
||||
AC_MSG_ERROR([must give --enable-utf8proc or --disable-utf8proc])
|
||||
fi
|
||||
#
|
||||
# macOS calloc(3) does not appear to always zero memory correctly,
|
||||
# so complain and suggest using jemalloc instead.
|
||||
#
|
||||
if test "x$enable_jemalloc" = x; then
|
||||
AC_MSG_NOTICE([])
|
||||
AC_MSG_NOTICE([ macOS calloc(3) appears not to correctly])
|
||||
AC_MSG_NOTICE([ zero allocations in some circumstances;])
|
||||
AC_MSG_NOTICE([ to avoid this, configuring with])
|
||||
AC_MSG_NOTICE([ --enable-jemalloc is recommended. To build])
|
||||
AC_MSG_NOTICE([ without anyway, use --disable-jemalloc])
|
||||
AC_MSG_NOTICE([])
|
||||
AC_MSG_ERROR([must give --enable-jemalloc or --disable-jemalloc])
|
||||
fi
|
||||
;;
|
||||
*dragonfly*)
|
||||
AC_MSG_RESULT(dragonfly)
|
||||
@@ -1028,6 +1130,31 @@ AC_MSG_CHECKING(lock-command)
|
||||
AC_MSG_RESULT($DEFAULT_LOCK_CMD)
|
||||
AC_SUBST(DEFAULT_LOCK_CMD)
|
||||
|
||||
# Print a summary.
|
||||
AC_MSG_NOTICE([])
|
||||
if test "x$enable_asan" = xyes; then
|
||||
AC_MSG_NOTICE([ASAN: on])
|
||||
else
|
||||
AC_MSG_NOTICE([ASAN: off])
|
||||
fi
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
AC_MSG_NOTICE([SIXEL: on])
|
||||
else
|
||||
AC_MSG_NOTICE([SIXEL: off])
|
||||
fi
|
||||
if test "x$enable_debug" = xyes; then
|
||||
AC_MSG_NOTICE([debug: on])
|
||||
else
|
||||
AC_MSG_NOTICE([debug: off])
|
||||
fi
|
||||
AC_MSG_NOTICE([jemalloc: $jemalloc_version])
|
||||
AC_MSG_NOTICE([libevent: $libevent_version])
|
||||
AC_MSG_NOTICE([ncurses: $ncurses_version])
|
||||
AC_MSG_NOTICE([systemd: $systemd_version])
|
||||
AC_MSG_NOTICE([utempter: $utempter_version])
|
||||
AC_MSG_NOTICE([utf8proc: $utf8proc_version])
|
||||
AC_MSG_NOTICE([])
|
||||
|
||||
# Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user
|
||||
# variables.
|
||||
AC_SUBST(AM_CPPFLAGS)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: control-notify.c,v 1.37 2026/07/10 13:38:45 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>
|
||||
@@ -44,7 +44,8 @@ control_pane_mode_changed_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_write(c, "%%pane-mode-changed %%%u", wp->id);
|
||||
control_notify_write(c, "%%pane-mode-changed %%%u",
|
||||
wp->id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -53,8 +54,9 @@ control_pane_mode_changed_cb(__unused const char *name,
|
||||
if (value == NULL)
|
||||
return;
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
control_write(c, "%%pane-mode-changed %s", value);
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%pane-mode-changed %s", value);
|
||||
}
|
||||
free(value);
|
||||
}
|
||||
@@ -92,7 +94,7 @@ control_window_layout_changed_cb(__unused const char *name,
|
||||
continue;
|
||||
s = c->session;
|
||||
if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
|
||||
control_write(c, "%s", cp);
|
||||
control_notify_write(c, "%s", cp);
|
||||
}
|
||||
free(cp);
|
||||
}
|
||||
@@ -111,7 +113,7 @@ control_window_pane_changed_cb(__unused const char *name,
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_write(c, "%%window-pane-changed @%u %%%u", w->id,
|
||||
control_notify_write(c, "%%window-pane-changed @%u %%%u", w->id,
|
||||
w->active->id);
|
||||
}
|
||||
}
|
||||
@@ -133,9 +135,11 @@ control_window_unlinked_cb(__unused const char *name, struct event_payload *ep,
|
||||
cs = c->session;
|
||||
|
||||
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
|
||||
control_write(c, "%%window-close @%u", w->id);
|
||||
else
|
||||
control_write(c, "%%unlinked-window-close @%u", w->id);
|
||||
control_notify_write(c, "%%window-close @%u", w->id);
|
||||
else {
|
||||
control_notify_write(c, "%%unlinked-window-close @%u",
|
||||
w->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,9 +160,11 @@ control_window_linked_cb(__unused const char *name, struct event_payload *ep,
|
||||
cs = c->session;
|
||||
|
||||
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
|
||||
control_write(c, "%%window-add @%u", w->id);
|
||||
else
|
||||
control_write(c, "%%unlinked-window-add @%u", w->id);
|
||||
control_notify_write(c, "%%window-add @%u", w->id);
|
||||
else {
|
||||
control_notify_write(c, "%%unlinked-window-add @%u",
|
||||
w->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,11 +185,11 @@ control_window_renamed_cb(__unused const char *name, struct event_payload *ep,
|
||||
cs = c->session;
|
||||
|
||||
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) {
|
||||
control_write(c, "%%window-renamed @%u %s", w->id,
|
||||
w->name);
|
||||
} else {
|
||||
control_write(c, "%%unlinked-window-renamed @%u %s",
|
||||
control_notify_write(c, "%%window-renamed @%u %s",
|
||||
w->id, w->name);
|
||||
} else {
|
||||
control_notify_write(c,
|
||||
"%%unlinked-window-renamed @%u %s", w->id, w->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,11 +212,12 @@ control_client_session_changed_cb(__unused const char *name,
|
||||
continue;
|
||||
|
||||
if (cc == c) {
|
||||
control_write(c, "%%session-changed $%u %s", s->id,
|
||||
s->name);
|
||||
control_notify_write(c, "%%session-changed $%u %s",
|
||||
s->id, s->name);
|
||||
} else {
|
||||
control_write(c, "%%client-session-changed %s $%u %s",
|
||||
cc->name, s->id, s->name);
|
||||
control_notify_write(c,
|
||||
"%%client-session-changed %s $%u %s", cc->name,
|
||||
s->id, s->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,8 +233,9 @@ control_client_detached_cb(__unused const char *name, struct event_payload *ep,
|
||||
if (cc == NULL)
|
||||
return;
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
control_write(c, "%%client-detached %s", cc->name);
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%client-detached %s", cc->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,8 +252,8 @@ control_session_renamed_cb(__unused const char *name, struct event_payload *ep,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_write(c, "%%session-renamed $%u %s", s->id, s->name);
|
||||
control_notify_write(c, "%%session-renamed $%u %s", s->id,
|
||||
s->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,8 +267,7 @@ control_session_created_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_write(c, "%%sessions-changed");
|
||||
control_notify_write(c, "%%sessions-changed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,8 +281,7 @@ control_session_closed_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_write(c, "%%sessions-changed");
|
||||
control_notify_write(c, "%%sessions-changed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,9 +304,8 @@ control_session_window_changed_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_write(c, "%%session-window-changed $%u @%u", s->id,
|
||||
s->curw->window->id);
|
||||
control_notify_write(c, "%%session-window-changed $%u @%u",
|
||||
s->id, s->curw->window->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,8 +322,7 @@ control_paste_buffer_changed_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_write(c, "%%paste-buffer-changed %s", pbname);
|
||||
control_notify_write(c, "%%paste-buffer-changed %s", pbname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,8 +339,7 @@ control_paste_buffer_deleted_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_write(c, "%%paste-buffer-deleted %s", pbname);
|
||||
control_notify_write(c, "%%paste-buffer-deleted %s", pbname);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
197
control.c
197
control.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: control.c,v 1.62 2026/07/17 08:37:29 nicm Exp $ */
|
||||
/* $OpenBSD: control.c,v 1.67 2026/09/03 21:35:38 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -53,6 +53,17 @@ struct control_block {
|
||||
TAILQ_ENTRY(control_block) all_entry;
|
||||
};
|
||||
|
||||
/*
|
||||
* A notification line deferred because it was generated while a command's
|
||||
* %begin/%end guard block was open. Notifications must never appear inside a
|
||||
* guard block, so they are held here and flushed once the block closes.
|
||||
*/
|
||||
struct control_line {
|
||||
char *line;
|
||||
|
||||
TAILQ_ENTRY(control_line) entry;
|
||||
};
|
||||
|
||||
/* Control client pane. */
|
||||
struct control_pane {
|
||||
u_int pane;
|
||||
@@ -102,6 +113,13 @@ struct control_state {
|
||||
struct bufferevent *write_event;
|
||||
|
||||
struct monitor_set *subs;
|
||||
|
||||
/*
|
||||
* Depth of open %begin/%end guard blocks and notifications deferred
|
||||
* until the outermost block closes.
|
||||
*/
|
||||
int guard_depth;
|
||||
TAILQ_HEAD(, control_line) deferred;
|
||||
};
|
||||
|
||||
/* Low and high watermarks. */
|
||||
@@ -352,7 +370,7 @@ control_continue_pane(struct client *c, struct window_pane *wp)
|
||||
cp->flags &= ~CONTROL_PANE_PAUSED;
|
||||
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
||||
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
||||
control_write(c, "%%continue %%%u", wp->id);
|
||||
control_notify_write(c, "%%continue %%%u", wp->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,52 +384,149 @@ control_pause_pane(struct client *c, struct window_pane *wp)
|
||||
if (~cp->flags & CONTROL_PANE_PAUSED) {
|
||||
cp->flags |= CONTROL_PANE_PAUSED;
|
||||
control_discard_pane(c, cp);
|
||||
control_write(c, "%%pause %%%u", wp->id);
|
||||
control_notify_write(c, "%%pause %%%u", wp->id);
|
||||
}
|
||||
}
|
||||
|
||||
/* Write a line. */
|
||||
static void printflike(2, 0)
|
||||
control_vwrite(struct client *c, const char *fmt, va_list ap)
|
||||
/*
|
||||
* Reset a pane after its buffer has been replaced: drop any output still
|
||||
* queued from the old buffer and start again from the pane's own offset.
|
||||
*/
|
||||
void
|
||||
control_reset_pane(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
char *s;
|
||||
struct control_pane *cp;
|
||||
|
||||
xvasprintf(&s, fmt, ap);
|
||||
log_debug("%s: %s: writing line: %s", __func__, c->name, s);
|
||||
|
||||
bufferevent_write(cs->write_event, s, strlen(s));
|
||||
bufferevent_write(cs->write_event, "\n", 1);
|
||||
|
||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||
free(s);
|
||||
if (c->control_state == NULL)
|
||||
return;
|
||||
cp = control_get_pane(c, wp);
|
||||
if (cp == NULL)
|
||||
return;
|
||||
control_discard_pane(c, cp);
|
||||
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
||||
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
||||
}
|
||||
|
||||
/* Write a line. */
|
||||
void
|
||||
control_write(struct client *c, const char *fmt, ...)
|
||||
/* Write an already-formatted line, queueing it behind %output if needed. */
|
||||
static void
|
||||
control_write_line(struct client *c, char *line)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_block *cb;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
if (TAILQ_EMPTY(&cs->all_blocks)) {
|
||||
control_vwrite(c, fmt, ap);
|
||||
va_end(ap);
|
||||
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
|
||||
bufferevent_write(cs->write_event, line, strlen(line));
|
||||
bufferevent_write(cs->write_event, "\n", 1);
|
||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||
free(line);
|
||||
return;
|
||||
}
|
||||
|
||||
cb = xcalloc(1, sizeof *cb);
|
||||
xvasprintf(&cb->line, fmt, ap);
|
||||
cb->line = line;
|
||||
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
||||
cb->t = get_timer();
|
||||
|
||||
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||
}
|
||||
|
||||
/* Flush notifications that were deferred while a guard block was open. */
|
||||
static void
|
||||
control_flush_deferred(struct client *c)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_line *cl, *cl1;
|
||||
|
||||
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
|
||||
TAILQ_REMOVE(&cs->deferred, cl, entry);
|
||||
control_write_line(c, cl->line);
|
||||
free(cl);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a line of command output or error text. This is a sink for arbitrary
|
||||
* user-controlled text (command output, capture-pane, error messages), so it
|
||||
* must never try to interpret the content: guard tracking is done only in
|
||||
* control_write_guard.
|
||||
*/
|
||||
void
|
||||
control_write(struct client *c, const char *fmt, ...)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
char *line;
|
||||
va_list ap;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
xvasprintf(&line, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
control_write_line(c, line);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a %begin, %end or %error guard around a command's output. This is the
|
||||
* only place guard lines are produced, so the block depth is maintained here;
|
||||
* when the outermost block closes any deferred notifications are flushed after
|
||||
* it. "guard" is always one of the fixed strings from cmdq_guard, never user
|
||||
* text.
|
||||
*/
|
||||
void
|
||||
control_write_guard(struct client *c, const char *guard, long t, u_int number,
|
||||
int flags)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
char *line;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
if (strcmp(guard, "begin") == 0)
|
||||
cs->guard_depth++;
|
||||
|
||||
xasprintf(&line, "%%%s %ld %u %d", guard, t, number, flags);
|
||||
control_write_line(c, line);
|
||||
|
||||
if (strcmp(guard, "begin") != 0 && cs->guard_depth > 0 &&
|
||||
--cs->guard_depth == 0)
|
||||
control_flush_deferred(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a notification line. Notifications must never appear inside a command's
|
||||
* %begin/%end guard block, so if one is open the line is deferred until it
|
||||
* closes.
|
||||
*/
|
||||
void
|
||||
control_notify_write(struct client *c, const char *fmt, ...)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_line *cl;
|
||||
va_list ap;
|
||||
char *line;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
xvasprintf(&line, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (cs->guard_depth == 0) {
|
||||
control_write_line(c, line);
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("%s: %s: deferring notification: %s", __func__, c->name,
|
||||
line);
|
||||
cl = xcalloc(1, sizeof *cl);
|
||||
cl->line = line;
|
||||
TAILQ_INSERT_TAIL(&cs->deferred, cl, entry);
|
||||
}
|
||||
|
||||
/* Check age for this pane. */
|
||||
@@ -438,7 +553,7 @@ control_check_age(struct client *c, struct window_pane *wp,
|
||||
return (0);
|
||||
cp->flags |= CONTROL_PANE_PAUSED;
|
||||
control_discard_pane(c, cp);
|
||||
control_write(c, "%%pause %%%u", wp->id);
|
||||
control_notify_write(c, "%%pause %%%u", wp->id);
|
||||
} else {
|
||||
if (age < CONTROL_MAXIMUM_AGE)
|
||||
return (0);
|
||||
@@ -805,14 +920,17 @@ control_sub_change(struct monitor_change *change, __unused void *data)
|
||||
|
||||
if (wp != NULL) {
|
||||
w = wp->window;
|
||||
control_write(c, "%%subscription-changed %s $%u @%u %u %%%u : %s",
|
||||
control_notify_write(c,
|
||||
"%%subscription-changed %s $%u @%u %u %%%u : %s",
|
||||
change->name, s->id, w->id, wl->idx, wp->id, change->value);
|
||||
} else if (wl != NULL) {
|
||||
w = wl->window;
|
||||
control_write(c, "%%subscription-changed %s $%u @%u %u - : %s",
|
||||
control_notify_write(c,
|
||||
"%%subscription-changed %s $%u @%u %u - : %s",
|
||||
change->name, s->id, w->id, wl->idx, change->value);
|
||||
} else {
|
||||
control_write(c, "%%subscription-changed %s $%u - - - : %s",
|
||||
control_notify_write(c,
|
||||
"%%subscription-changed %s $%u - - - : %s",
|
||||
change->name, s->id, change->value);
|
||||
}
|
||||
}
|
||||
@@ -835,6 +953,7 @@ control_start(struct client *c)
|
||||
RB_INIT(&cs->windows);
|
||||
TAILQ_INIT(&cs->pending_list);
|
||||
TAILQ_INIT(&cs->all_blocks);
|
||||
TAILQ_INIT(&cs->deferred);
|
||||
cs->subs = monitor_create_client(c, control_sub_change, NULL);
|
||||
|
||||
cs->read_event = bufferevent_new(c->fd, control_read_callback,
|
||||
@@ -878,6 +997,19 @@ control_discard(struct client *c)
|
||||
bufferevent_disable(cs->read_event, EV_READ);
|
||||
}
|
||||
|
||||
/* Discard all tmux-owned queued control blocks and stop writing. */
|
||||
void
|
||||
control_discard_all(struct client *c)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_block *cb, *cb1;
|
||||
|
||||
control_discard(c);
|
||||
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
||||
control_free_block(cs, cb);
|
||||
bufferevent_disable(cs->write_event, EV_WRITE);
|
||||
}
|
||||
|
||||
/* Stop control mode. */
|
||||
void
|
||||
control_stop(struct client *c)
|
||||
@@ -885,12 +1017,19 @@ control_stop(struct client *c)
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_block *cb, *cb1;
|
||||
struct control_window *cw, *cw1;
|
||||
struct control_line *cl, *cl1;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
monitor_destroy(cs->subs);
|
||||
|
||||
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
|
||||
TAILQ_REMOVE(&cs->deferred, cl, entry);
|
||||
free(cl->line);
|
||||
free(cl);
|
||||
}
|
||||
|
||||
if (~c->flags & CLIENT_CONTROLCONTROL)
|
||||
bufferevent_free(cs->write_event);
|
||||
bufferevent_free(cs->read_event);
|
||||
|
||||
87
file.c
87
file.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: file.c,v 1.20 2026/05/17 10:54:01 nicm Exp $ */
|
||||
/* $OpenBSD: file.c,v 1.22 2026/08/17 07:56:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -185,6 +185,7 @@ file_can_print(struct client *c)
|
||||
{
|
||||
if (c == NULL ||
|
||||
(c->flags & CLIENT_ATTACHED) ||
|
||||
(c->flags & CLIENT_DEAD) ||
|
||||
(c->flags & CLIENT_CONTROL))
|
||||
return (0);
|
||||
return (1);
|
||||
@@ -501,7 +502,8 @@ file_push(struct client_file *cf)
|
||||
} else if (cf->stream > 2) {
|
||||
close.stream = cf->stream;
|
||||
proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
|
||||
file_fire_done(cf);
|
||||
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
|
||||
file_fire_done(cf);
|
||||
}
|
||||
free(msg);
|
||||
}
|
||||
@@ -526,14 +528,48 @@ file_write_left(struct client_files *files)
|
||||
return (waiting != 0);
|
||||
}
|
||||
|
||||
/* Finish writing a client file. */
|
||||
static void
|
||||
file_write_finished(struct client_file *cf)
|
||||
{
|
||||
struct msg_write_done msg;
|
||||
|
||||
if (cf->event != NULL) {
|
||||
bufferevent_free(cf->event);
|
||||
cf->event = NULL;
|
||||
}
|
||||
if (cf->fd != -1) {
|
||||
if (close(cf->fd) != 0 && cf->error == 0)
|
||||
cf->error = errno;
|
||||
cf->fd = -1;
|
||||
}
|
||||
|
||||
msg.stream = cf->stream;
|
||||
msg.error = cf->error;
|
||||
proc_send(cf->peer, MSG_WRITE_DONE, -1, &msg, sizeof msg);
|
||||
|
||||
if (cf->cb != NULL)
|
||||
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
|
||||
file_free(cf);
|
||||
}
|
||||
|
||||
/* Client file write error callback. */
|
||||
static void
|
||||
file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
|
||||
file_write_error_callback(__unused struct bufferevent *bev, short what,
|
||||
void *arg)
|
||||
{
|
||||
struct client_file *cf = arg;
|
||||
int error;
|
||||
|
||||
if (what & EVBUFFER_ERROR)
|
||||
error = errno;
|
||||
else
|
||||
error = EIO;
|
||||
if (error == 0)
|
||||
error = EIO;
|
||||
|
||||
log_debug("write error file %d", cf->stream);
|
||||
cf->error = error;
|
||||
|
||||
bufferevent_free(cf->event);
|
||||
cf->event = NULL;
|
||||
@@ -541,7 +577,9 @@ file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
|
||||
close(cf->fd);
|
||||
cf->fd = -1;
|
||||
|
||||
if (cf->cb != NULL)
|
||||
if (cf->closed)
|
||||
file_write_finished(cf);
|
||||
else if (cf->cb != NULL)
|
||||
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
|
||||
}
|
||||
|
||||
@@ -553,15 +591,10 @@ file_write_callback(__unused struct bufferevent *bev, void *arg)
|
||||
|
||||
log_debug("write check file %d", cf->stream);
|
||||
|
||||
if (cf->cb != NULL)
|
||||
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0)
|
||||
file_write_finished(cf);
|
||||
else if (cf->cb != NULL)
|
||||
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
|
||||
|
||||
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) {
|
||||
bufferevent_free(cf->event);
|
||||
close(cf->fd);
|
||||
RB_REMOVE(client_files, cf->tree, cf);
|
||||
file_free(cf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle a file write open message (client). */
|
||||
@@ -662,14 +695,10 @@ file_write_close(struct client_files *files, struct imsg *imsg)
|
||||
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
||||
fatalx("unknown stream number");
|
||||
log_debug("close file %d", cf->stream);
|
||||
cf->closed = 1;
|
||||
|
||||
if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
|
||||
if (cf->event != NULL)
|
||||
bufferevent_free(cf->event);
|
||||
if (cf->fd != -1)
|
||||
close(cf->fd);
|
||||
RB_REMOVE(client_files, files, cf);
|
||||
file_free(cf);
|
||||
file_write_finished(cf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,6 +857,28 @@ file_write_ready(struct client_files *files, struct imsg *imsg)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Handle a write done message (server). */
|
||||
int
|
||||
file_write_done(struct client_files *files, struct imsg *imsg)
|
||||
{
|
||||
struct msg_write_done *msg = imsg->data;
|
||||
size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
|
||||
struct client_file find, *cf;
|
||||
|
||||
if (msglen != sizeof *msg)
|
||||
return (-1);
|
||||
find.stream = msg->stream;
|
||||
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
||||
return (0);
|
||||
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
|
||||
return (0);
|
||||
|
||||
log_debug("file %d write done", cf->stream);
|
||||
cf->error = msg->error;
|
||||
file_fire_done(cf);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Handle read data message (server). */
|
||||
int
|
||||
file_read_data(struct client_files *files, struct imsg *imsg)
|
||||
|
||||
225
format.c
225
format.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: format.c,v 1.407 2026/07/20 07:42:13 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>
|
||||
@@ -128,6 +128,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
|
||||
#define FORMAT_OPTIONS 0x40000000
|
||||
#define FORMAT_ENVIRON 0x80000000ULL
|
||||
#define FORMAT_DIFFERENCE 0x100000000ULL
|
||||
#define FORMAT_CYCLE 0x200000000ULL
|
||||
|
||||
/* Limit on recursion. */
|
||||
#define FORMAT_LOOP_LIMIT 100
|
||||
@@ -138,9 +139,13 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
|
||||
/* How often to check the time in long loops. */
|
||||
#define FORMAT_TIME_LOOP_CHECK 10000
|
||||
|
||||
/* Fixed animation period (ms): redraw interval and shortest frame step. */
|
||||
#define FORMAT_CYCLE_PERIOD 100
|
||||
|
||||
/* Format expand flags. */
|
||||
#define FORMAT_EXPAND_TIME 0x1
|
||||
#define FORMAT_EXPAND_NOJOBS 0x2
|
||||
#define FORMAT_EXPAND_NOCYCLE 0x4
|
||||
|
||||
/* Entry in format tree. */
|
||||
struct format_entry {
|
||||
@@ -413,7 +418,8 @@ format_job_get(struct format_expand_state *es, const char *cmd)
|
||||
RB_INSERT(format_job_tree, jobs, fj);
|
||||
}
|
||||
|
||||
format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS);
|
||||
format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS|
|
||||
FORMAT_EXPAND_NOCYCLE);
|
||||
next.flags &= ~FORMAT_EXPAND_TIME;
|
||||
|
||||
expanded = format_expand1(&next, cmd);
|
||||
@@ -1881,6 +1887,37 @@ format_cb_cursor_blinking(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_added. */
|
||||
static void *
|
||||
format_cb_history_added(struct format_tree *ft)
|
||||
{
|
||||
if (ft->wp != NULL)
|
||||
return (format_printf("%u", ft->wp->base.grid->scroll_added));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_collected. */
|
||||
static void *
|
||||
format_cb_history_collected(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL)
|
||||
return (format_printf("%u", wp->base.grid->scroll_collected));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_generation. */
|
||||
static void *
|
||||
format_cb_history_generation(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL)
|
||||
return (format_printf("%u", wp->base.grid->scroll_generation));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_limit. */
|
||||
static void *
|
||||
format_cb_history_limit(struct format_tree *ft)
|
||||
@@ -2095,6 +2132,60 @@ format_cb_synchronized_output_flag(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_private_modes. */
|
||||
static void *
|
||||
format_cb_pane_private_modes(struct format_tree *ft)
|
||||
{
|
||||
static const struct {
|
||||
int mode;
|
||||
int number;
|
||||
} table[] = {
|
||||
{ MODE_KCURSOR, 1 }, /* DECCKM */
|
||||
{ MODE_ORIGIN, 6 }, /* DECOM */
|
||||
{ MODE_WRAP, 7 }, /* DECAWM */
|
||||
{ MODE_CURSOR_BLINKING, 12 }, /* cursor blinking */
|
||||
{ MODE_CURSOR, 25 }, /* DECTCEM */
|
||||
{ MODE_MOUSE_STANDARD, 1000 }, /* mouse normal tracking */
|
||||
{ MODE_MOUSE_BUTTON, 1002 }, /* mouse button tracking */
|
||||
{ MODE_MOUSE_ALL, 1003 }, /* mouse any tracking */
|
||||
{ MODE_FOCUSON, 1004 }, /* focus reporting */
|
||||
{ MODE_MOUSE_UTF8, 1005 }, /* mouse: UTF-8 */
|
||||
{ MODE_MOUSE_SGR, 1006 }, /* mouse: SGR */
|
||||
{ MODE_BRACKETPASTE, 2004 }, /* bracketed paste */
|
||||
{ MODE_SYNC, 2026 }, /* synchronized output */
|
||||
{ MODE_THEME_UPDATES, 2031 }, /* theme update notifications */
|
||||
};
|
||||
int mode;
|
||||
char *value = NULL, *tmp;
|
||||
u_int i;
|
||||
|
||||
if (ft->wp == NULL)
|
||||
return (NULL);
|
||||
mode = ft->wp->base.mode;
|
||||
|
||||
for (i = 0; i < nitems(table); i++) {
|
||||
if (~mode & table[i].mode)
|
||||
continue;
|
||||
/*
|
||||
* Only report cursor blinking when set by the application, not
|
||||
* when it comes from the cursor-style option.
|
||||
*/
|
||||
if (table[i].mode == MODE_CURSOR_BLINKING &&
|
||||
(~mode & MODE_CURSOR_BLINKING_SET))
|
||||
continue;
|
||||
if (value == NULL)
|
||||
xasprintf(&value, "%d", table[i].number);
|
||||
else {
|
||||
xasprintf(&tmp, "%s,%d", value, table[i].number);
|
||||
free(value);
|
||||
value = tmp;
|
||||
}
|
||||
}
|
||||
if (value == NULL)
|
||||
return (xstrdup(""));
|
||||
return (value);
|
||||
}
|
||||
|
||||
/* Callback for pane_active. */
|
||||
static void *
|
||||
format_cb_pane_active(struct format_tree *ft)
|
||||
@@ -2216,6 +2307,19 @@ format_cb_pane_last_output_time(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_output_generation. */
|
||||
static void *
|
||||
format_cb_pane_output_generation(struct format_tree *ft)
|
||||
{
|
||||
unsigned long long value;
|
||||
|
||||
if (ft->wp != NULL) {
|
||||
value = ft->wp->output_generation;
|
||||
return (format_printf("%llu", value));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_last_prompt_time. */
|
||||
static void *
|
||||
format_cb_pane_last_prompt_time(struct format_tree *ft)
|
||||
@@ -2622,20 +2726,23 @@ format_cb_pane_unzoomed_width(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
struct layout_cell *lc;
|
||||
int sb_w, sb_pad;
|
||||
int saved, sb_w, sb_pad;
|
||||
u_int sx;
|
||||
|
||||
if (wp == NULL)
|
||||
return (NULL);
|
||||
|
||||
lc = wp->saved_layout_cell;
|
||||
saved = (lc != NULL);
|
||||
if (lc == NULL)
|
||||
lc = wp->layout_cell;
|
||||
if (lc == NULL)
|
||||
return (NULL);
|
||||
sx = lc->g.sx;
|
||||
|
||||
if (window_pane_scrollbar_reserve(wp)) {
|
||||
if ((saved && !SCREEN_IS_ALTERNATE(&wp->base) &&
|
||||
wp->window->sb == PANE_SCROLLBARS_ALWAYS) ||
|
||||
(!saved && window_pane_scrollbar_reserve(wp))) {
|
||||
sb_w = wp->scrollbar_style.width;
|
||||
sb_pad = wp->scrollbar_style.pad;
|
||||
if (sb_w < 1)
|
||||
@@ -3600,12 +3707,21 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "cursor_y", FORMAT_TABLE_STRING,
|
||||
format_cb_cursor_y
|
||||
},
|
||||
{ "history_added", FORMAT_TABLE_STRING,
|
||||
format_cb_history_added
|
||||
},
|
||||
{ "history_all_bytes", FORMAT_TABLE_STRING,
|
||||
format_cb_history_all_bytes
|
||||
},
|
||||
{ "history_bytes", FORMAT_TABLE_STRING,
|
||||
format_cb_history_bytes
|
||||
},
|
||||
{ "history_collected", FORMAT_TABLE_STRING,
|
||||
format_cb_history_collected
|
||||
},
|
||||
{ "history_generation", FORMAT_TABLE_STRING,
|
||||
format_cb_history_generation
|
||||
},
|
||||
{ "history_limit", FORMAT_TABLE_STRING,
|
||||
format_cb_history_limit
|
||||
},
|
||||
@@ -3786,6 +3902,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "pane_mode", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_mode
|
||||
},
|
||||
{ "pane_output_generation", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_output_generation
|
||||
},
|
||||
{ "pane_path", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_path
|
||||
},
|
||||
@@ -3804,6 +3923,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "pane_pipe_pid", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_pipe_pid
|
||||
},
|
||||
{ "pane_private_modes", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_private_modes
|
||||
},
|
||||
{ "pane_right", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_right
|
||||
},
|
||||
@@ -4191,6 +4313,8 @@ format_log_debug_cb(const char *key, const char *value, void *arg)
|
||||
void
|
||||
format_log_debug(struct format_tree *ft, const char *prefix)
|
||||
{
|
||||
if (log_get_level() == 0)
|
||||
return;
|
||||
format_each(ft, format_log_debug_cb, (void *)prefix);
|
||||
}
|
||||
|
||||
@@ -4319,7 +4443,7 @@ format_quote_shell(const char *s)
|
||||
|
||||
at = out = xmalloc(strlen(s) * 2 + 1);
|
||||
for (cp = s; *cp != '\0'; cp++) {
|
||||
if (strchr("|&;<>()$`\\\"'*?[# =%", *cp) != NULL)
|
||||
if (strchr("|&;<>(){}$`\\\"'*?[# =%\n\t", *cp) != NULL)
|
||||
*at++ = '\\';
|
||||
*at++ = *cp;
|
||||
}
|
||||
@@ -4797,7 +4921,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
|
||||
/*
|
||||
* Modifiers are a ; separated list of the forms:
|
||||
* l,m,C,a,b,c,d,I,n,t,w,q,E,T,S,W,P,O,V,R,<,>
|
||||
* l,m,C,a,b,c,d,I,n,t,w,q,E,T,S,W,P,O,V,R,A,<,>
|
||||
* =a
|
||||
* =/a
|
||||
* =/a/
|
||||
@@ -4816,7 +4940,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
break;
|
||||
|
||||
/* Check single character modifiers with no arguments. */
|
||||
if (strchr("labdnwETSWPOVL!<>", cp[0]) != NULL &&
|
||||
if (strchr("labdnwETSWPOVL!<>A", cp[0]) != NULL &&
|
||||
format_is_end(cp[1])) {
|
||||
format_add_modifier(&list, count, cp, 1, NULL, 0);
|
||||
cp++;
|
||||
@@ -4838,7 +4962,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
}
|
||||
|
||||
/* Now try single character with arguments. */
|
||||
if (strchr("ImCLNPSOVst=pReqWc", cp[0]) == NULL)
|
||||
if (strchr("ImCLNPSOVst=pReqWcA", cp[0]) == NULL)
|
||||
break;
|
||||
c = cp[0];
|
||||
|
||||
@@ -5797,6 +5921,74 @@ fail:
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for the cycle timer; redraw the status line. */
|
||||
static void
|
||||
format_cycle_callback(__unused int fd, __unused short events, void *arg)
|
||||
{
|
||||
struct client *c = arg;
|
||||
|
||||
if (c->message_string == NULL && c->prompt == NULL)
|
||||
c->flags |= CLIENT_REDRAWSTATUS;
|
||||
}
|
||||
|
||||
/* Arm the cycle timer to redraw the status line if it is not already. */
|
||||
static void
|
||||
format_cycle_start_timer(struct client *c)
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
tv.tv_sec = FORMAT_CYCLE_PERIOD / 1000;
|
||||
tv.tv_usec = (FORMAT_CYCLE_PERIOD % 1000) * 1000L;
|
||||
|
||||
if (!event_initialized(&c->cycle_timer))
|
||||
evtimer_set(&c->cycle_timer, format_cycle_callback, c);
|
||||
if (!evtimer_pending(&c->cycle_timer, NULL))
|
||||
evtimer_add(&c->cycle_timer, &tv);
|
||||
}
|
||||
|
||||
/* Expand the "A" animation modifier; see the manual for the syntax. */
|
||||
static char *
|
||||
format_cycle(struct format_expand_state *es, const char *frames, u_int count)
|
||||
{
|
||||
struct format_tree *ft = es->ft;
|
||||
const char *start, *end, *cp;
|
||||
u_int n, index, i;
|
||||
|
||||
/*
|
||||
* A cycle is only expanded in a status format, and never in the
|
||||
* command or output of #() where it would change on every frame and
|
||||
* make the job run again.
|
||||
*/
|
||||
if (!(ft->flags & FORMAT_STATUS) || (es->flags & FORMAT_EXPAND_NOCYCLE))
|
||||
return (xstrdup(""));
|
||||
if (*frames == '\0')
|
||||
return (xstrdup(""));
|
||||
|
||||
/* Count the comma-separated frames (there is at least one). */
|
||||
n = 1;
|
||||
for (cp = frames; *cp != '\0'; cp++) {
|
||||
if (*cp == ',')
|
||||
n++;
|
||||
}
|
||||
index = (es->start_time / (count * FORMAT_CYCLE_PERIOD)) % n;
|
||||
|
||||
/*
|
||||
* Redraw the status line so the frames advance on their own; a
|
||||
* single frame never changes so there is nothing to redraw for.
|
||||
*/
|
||||
if (n > 1 && ft->client != NULL)
|
||||
format_cycle_start_timer(ft->client);
|
||||
|
||||
/* Walk to the chosen frame and return a copy of it. */
|
||||
start = frames;
|
||||
for (i = 0; i < index; i++)
|
||||
start = strchr(start, ',') + 1;
|
||||
end = strchr(start, ',');
|
||||
if (end == NULL)
|
||||
end = start + strlen(start);
|
||||
return (xstrndup(start, end - start));
|
||||
}
|
||||
|
||||
/* Replace a key. */
|
||||
static int
|
||||
format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
@@ -5817,6 +6009,7 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
struct format_modifier *list, *cmp = NULL, *search = NULL;
|
||||
struct format_modifier **sub = NULL, *mexp = NULL, *fm;
|
||||
struct format_modifier *bool_op_n = NULL;
|
||||
u_int cycle_count = 1;
|
||||
u_int i, count, nsub = 0, nrep, check = 0;
|
||||
const char *loop_flags = "";
|
||||
struct format_expand_state next;
|
||||
@@ -5877,6 +6070,15 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
if (errstr != NULL)
|
||||
width = 0;
|
||||
break;
|
||||
case 'A':
|
||||
modifiers |= FORMAT_CYCLE;
|
||||
if (fm->argc < 1)
|
||||
break;
|
||||
cycle_count = strtonum(fm->argv[0], 1, 100,
|
||||
&errstr);
|
||||
if (errstr != NULL)
|
||||
cycle_count = 1;
|
||||
break;
|
||||
case 'w':
|
||||
modifiers |= FORMAT_WIDTH;
|
||||
break;
|
||||
@@ -6098,6 +6300,13 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Is this an animation cycle? */
|
||||
if (modifiers & FORMAT_CYCLE) {
|
||||
value = format_cycle(es, copy, cycle_count);
|
||||
format_log(es, "cycle '%s' is: %s", copy, value);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Is this a literal string? */
|
||||
if (modifiers & FORMAT_LITERAL) {
|
||||
format_log(es, "literal string is '%s'", copy);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: grid-reader.c,v 1.10 2026/05/17 13:12:21 nicm Exp $ */
|
||||
/* $OpenBSD: grid-reader.c,v 1.11 2026/07/29 17:42:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com>
|
||||
@@ -54,11 +54,8 @@ grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all, int onemore)
|
||||
px = gr->gd->sx;
|
||||
else if (onemore)
|
||||
px = grid_reader_line_length(gr);
|
||||
else {
|
||||
px = grid_reader_line_length(gr);
|
||||
if (px != 0)
|
||||
px--;
|
||||
}
|
||||
else
|
||||
px = grid_line_limit(gr->gd, gr->cy);
|
||||
|
||||
if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) {
|
||||
grid_reader_cursor_start_of_line(gr, 0);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: grid-view.c,v 1.38 2026/01/22 08:55:01 nicm Exp $ */
|
||||
/* $OpenBSD: grid-view.c,v 1.39 2026/08/03 12:58:53 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -47,9 +47,9 @@ grid_view_set_cell(struct grid *gd, u_int px, u_int py,
|
||||
|
||||
/* Set padding. */
|
||||
void
|
||||
grid_view_set_padding(struct grid *gd, u_int px, u_int py)
|
||||
grid_view_set_padding(struct grid *gd, u_int px, u_int py, int bg)
|
||||
{
|
||||
grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py));
|
||||
grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py), bg);
|
||||
}
|
||||
|
||||
/* Set cells. */
|
||||
|
||||
66
grid.c
66
grid.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: grid.c,v 1.154 2026/07/20 11:16:33 nicm Exp $ */
|
||||
/* $OpenBSD: grid.c,v 1.158 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -682,9 +682,13 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
|
||||
|
||||
/* Set padding at position. */
|
||||
void
|
||||
grid_set_padding(struct grid *gd, u_int px, u_int py)
|
||||
grid_set_padding(struct grid *gd, u_int px, u_int py, int bg)
|
||||
{
|
||||
grid_set_cell(gd, px, py, &grid_padding_cell);
|
||||
struct grid_cell gc;
|
||||
|
||||
memcpy(&gc, &grid_padding_cell, sizeof gc);
|
||||
gc.bg = bg;
|
||||
grid_set_cell(gd, px, py, &gc);
|
||||
}
|
||||
|
||||
/* Set cells at position. */
|
||||
@@ -1631,7 +1635,7 @@ grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
|
||||
void
|
||||
grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
|
||||
{
|
||||
u_int yy, ay = 0;
|
||||
u_int yy, ay = 0, ey = gd->hsize + gd->sy - 1;
|
||||
|
||||
for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
|
||||
if (ay == wy)
|
||||
@@ -1645,7 +1649,7 @@ grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
|
||||
* until we find the end or the line now containing wx.
|
||||
*/
|
||||
if (wx == UINT_MAX) {
|
||||
while (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
|
||||
while (yy < ey && gd->linedata[yy].flags & GRID_LINE_WRAPPED)
|
||||
yy++;
|
||||
wx = gd->linedata[yy].cellused;
|
||||
} else {
|
||||
@@ -1681,27 +1685,55 @@ grid_line_length(struct grid *gd, u_int py)
|
||||
return (px);
|
||||
}
|
||||
|
||||
/* Get last position on line, not including padding. */
|
||||
u_int
|
||||
grid_line_limit(struct grid *gd, u_int py)
|
||||
{
|
||||
struct grid_cell gc;
|
||||
u_int px;
|
||||
|
||||
px = grid_line_length(gd, py);
|
||||
if (px == 0)
|
||||
return (0);
|
||||
px--;
|
||||
while (px > 0) {
|
||||
grid_get_cell(gd, px, py, &gc);
|
||||
if (~gc.flags & GRID_FLAG_PADDING)
|
||||
break;
|
||||
px--;
|
||||
}
|
||||
return (px);
|
||||
}
|
||||
|
||||
/* Check if character is in set. */
|
||||
int
|
||||
grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
|
||||
{
|
||||
struct grid_cell gc, tmp_gc;
|
||||
u_int pxx;
|
||||
int has_tab, has_space;
|
||||
|
||||
has_tab = (strchr(set, '\t') != NULL);
|
||||
has_space = (strchr(set, ' ') != NULL);
|
||||
|
||||
grid_get_cell(gd, px, py, &gc);
|
||||
if (strchr(set, '\t')) {
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
pxx = px;
|
||||
do
|
||||
grid_get_cell(gd, --pxx, py, &tmp_gc);
|
||||
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
|
||||
if (tmp_gc.flags & GRID_FLAG_TAB)
|
||||
return (tmp_gc.data.width - (px - pxx));
|
||||
} else if (gc.flags & GRID_FLAG_TAB)
|
||||
return (gc.data.width);
|
||||
}
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
if (!has_tab && !has_space)
|
||||
return (0);
|
||||
pxx = px;
|
||||
do
|
||||
grid_get_cell(gd, --pxx, py, &tmp_gc);
|
||||
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
|
||||
if (((has_tab || has_space) &&
|
||||
(tmp_gc.flags & GRID_FLAG_TAB)) ||
|
||||
(has_space && utf8_has_whitespace(&tmp_gc.data)))
|
||||
return (tmp_gc.data.width - (px - pxx));
|
||||
return (0);
|
||||
}
|
||||
if ((has_tab || has_space) && (gc.flags & GRID_FLAG_TAB))
|
||||
return (gc.data.width);
|
||||
if (has_space && utf8_has_whitespace(&gc.data))
|
||||
return (gc.data.width == 0 ? 1 : gc.data.width);
|
||||
return (utf8_cstrhas(set, &gc.data));
|
||||
}
|
||||
|
||||
|
||||
63
hooks.c
63
hooks.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: hooks.c,v 1.14 2026/07/13 22:03:08 nicm Exp $ */
|
||||
/* $OpenBSD: hooks.c,v 1.16 2026/07/27 19:15:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "tmux.h"
|
||||
|
||||
/* Hook monitor state owned by an option entry. */
|
||||
struct hook_monitor {
|
||||
struct hooks_monitor {
|
||||
struct options *oo;
|
||||
|
||||
struct monitor_set *set;
|
||||
@@ -143,6 +143,7 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd)
|
||||
log_debug("%s: hook %s not found", __func__, hd->name);
|
||||
return;
|
||||
}
|
||||
options_hook_fired(o);
|
||||
|
||||
if (item == NULL)
|
||||
state = cmdq_new_state(&fs, NULL, CMDQ_STATE_NOHOOKS);
|
||||
@@ -233,7 +234,7 @@ hooks_event_cb(const char *name, struct event_payload *ep,
|
||||
{
|
||||
struct cmdq_item *item;
|
||||
|
||||
if (event_payload_get_pointer(ep, "_hook_monitor") != NULL)
|
||||
if (event_payload_get_pointer(ep, "_hooks_monitor") != NULL)
|
||||
return;
|
||||
|
||||
item = event_payload_get_pointer(ep, "_cmdq_item");
|
||||
@@ -324,7 +325,7 @@ hooks_run(struct cmdq_item *item, const char *name)
|
||||
void
|
||||
hooks_monitor_free(void *data)
|
||||
{
|
||||
struct hook_monitor *hm = data;
|
||||
struct hooks_monitor *hm = data;
|
||||
|
||||
events_remove_sink(hm->sink);
|
||||
monitor_destroy(hm->set);
|
||||
@@ -337,7 +338,7 @@ void
|
||||
hooks_monitor_remove(struct options *oo, const char *name)
|
||||
{
|
||||
struct options_entry *o;
|
||||
struct hook_monitor *hm;
|
||||
struct hooks_monitor *hm;
|
||||
|
||||
o = options_get_only(oo, name);
|
||||
if (o == NULL)
|
||||
@@ -355,9 +356,9 @@ static void
|
||||
hooks_monitor_hook_cb(const char *name, struct event_payload *ep,
|
||||
void *sink_data)
|
||||
{
|
||||
struct hook_monitor *hm = sink_data;
|
||||
struct hooks_monitor *hm = sink_data;
|
||||
|
||||
if (event_payload_get_pointer(ep, "_hook_monitor") == hm)
|
||||
if (event_payload_get_pointer(ep, "_hooks_monitor") == hm)
|
||||
hooks_insert_event(cmdq_running(NULL), name, ep, hm->oo, 1);
|
||||
}
|
||||
|
||||
@@ -365,14 +366,14 @@ hooks_monitor_hook_cb(const char *name, struct event_payload *ep,
|
||||
static void
|
||||
hooks_monitor_cb(struct monitor_change *change, void *data)
|
||||
{
|
||||
struct hook_monitor *hm = data;
|
||||
struct hooks_monitor *hm = data;
|
||||
struct event_payload *ep;
|
||||
struct winlink *wl = change->wl;
|
||||
struct window_pane *wp = change->wp;
|
||||
struct cmd_find_state fs;
|
||||
|
||||
ep = event_payload_create();
|
||||
event_payload_set_pointer(ep, "_hook_monitor", data, NULL, NULL);
|
||||
event_payload_set_pointer(ep, "_hooks_monitor", data, NULL, NULL);
|
||||
|
||||
cmd_find_clear_state(&fs, 0);
|
||||
if (wl != NULL && wp != NULL && wp->window == wl->window)
|
||||
@@ -402,8 +403,7 @@ hooks_monitor_cb(struct monitor_change *change, void *data)
|
||||
event_payload_set_session(ep, "session", change->s);
|
||||
if (wl != NULL) {
|
||||
if (change->s == NULL)
|
||||
event_payload_set_session(ep, "session",
|
||||
wl->session);
|
||||
event_payload_set_session(ep, "session", wl->session);
|
||||
event_payload_set_window(ep, "window", wl->window);
|
||||
event_payload_set_int(ep, "window_index", wl->idx);
|
||||
}
|
||||
@@ -423,7 +423,7 @@ hooks_monitor_add(__unused struct cmdq_item *item, struct options *oo,
|
||||
int flags, struct cmd_find_state *fs, struct session *s)
|
||||
{
|
||||
struct options_entry *o;
|
||||
struct hook_monitor *hm;
|
||||
struct hooks_monitor *hm;
|
||||
|
||||
hooks_monitor_remove(oo, name);
|
||||
o = options_get_only(oo, name);
|
||||
@@ -446,7 +446,7 @@ hooks_monitor_add(__unused struct cmdq_item *item, struct options *oo,
|
||||
char *
|
||||
hooks_monitor_to_string(struct options_entry *o)
|
||||
{
|
||||
struct hook_monitor *hm = options_get_monitor_data(o);
|
||||
struct hooks_monitor *hm = options_get_monitor_data(o);
|
||||
const char *name = options_name(o);
|
||||
char *s;
|
||||
|
||||
@@ -472,3 +472,40 @@ hooks_monitor_to_string(struct options_entry *o)
|
||||
}
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* Get the parts of a hook monitor. */
|
||||
int
|
||||
hooks_monitor_get(struct options_entry *o, enum monitor_type *type, int *id,
|
||||
const char **format)
|
||||
{
|
||||
struct hooks_monitor *hm = options_get_monitor_data(o);
|
||||
|
||||
if (hm == NULL)
|
||||
return (0);
|
||||
*type = hm->type;
|
||||
*id = hm->id;
|
||||
*format = hm->format;
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Get hook monitor firing count. */
|
||||
u_int
|
||||
hooks_monitor_get_fire_count(struct options_entry *o)
|
||||
{
|
||||
struct hooks_monitor *hm = options_get_monitor_data(o);
|
||||
|
||||
if (hm == NULL)
|
||||
return (0);
|
||||
return (monitor_get_fire_count(hm->set, options_name(o)));
|
||||
}
|
||||
|
||||
/* Get hook monitor firing time. */
|
||||
time_t
|
||||
hooks_monitor_get_fire_time(struct options_entry *o)
|
||||
{
|
||||
struct hooks_monitor *hm = options_get_monitor_data(o);
|
||||
|
||||
if (hm == NULL)
|
||||
return (0);
|
||||
return (monitor_get_fire_time(hm->set, options_name(o)));
|
||||
}
|
||||
|
||||
5
image.c
5
image.c
@@ -84,6 +84,11 @@ image_fallback(char **ret, u_int sx, u_int sy)
|
||||
char *buf, *label;
|
||||
u_int py, size, lsize;
|
||||
|
||||
if (sy == 0) {
|
||||
*ret = xstrdup("");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allocate first line. */
|
||||
lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1;
|
||||
if (sx < lsize - 3)
|
||||
|
||||
5
input.c
5
input.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: input.c,v 1.269 2026/07/20 11:16:33 nicm Exp $ */
|
||||
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -1049,6 +1049,7 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
wp->output_generation++;
|
||||
window_update_activity(wp->window);
|
||||
if (~wp->flags & PANE_ACTIVITY) {
|
||||
wp->flags |= PANE_ACTIVITY;
|
||||
@@ -1978,7 +1979,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
|
||||
screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
|
||||
break;
|
||||
case 2026:
|
||||
screen_write_stop_sync(ictx->wp);
|
||||
screen_write_end_sync(sctx);
|
||||
break;
|
||||
case 2031:
|
||||
screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: key-bindings.c,v 1.189 2026/07/15 10:38:31 nicm Exp $ */
|
||||
/* $OpenBSD: key-bindings.c,v 1.192 2026/08/06 09:05:04 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -45,6 +45,9 @@
|
||||
" ''" \
|
||||
" 'New After' 'w' {new-window -a}" \
|
||||
" 'New At End' 'W' {new-window}"
|
||||
#define DEFAULT_EMPTY_MENU \
|
||||
" 'New Pane' 'p' {new-pane; join-pane}" \
|
||||
" 'New Window' 'w' {new-window}"
|
||||
#define DEFAULT_PANE_MENU \
|
||||
" '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}' '<' {send -X history-top}" \
|
||||
" '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \
|
||||
@@ -419,6 +422,7 @@ key_bindings_init(void)
|
||||
"bind -N 'Spread panes out evenly' E { select-layout -E }",
|
||||
"bind -N 'Switch to the last client' L { switch-client -l }",
|
||||
"bind -N 'Clear the marked pane' M { select-pane -M }",
|
||||
"bind -N 'Change the pane title' T { command-prompt -I'#T' { select-pane -T '%%' } }",
|
||||
"bind -N 'Enter copy mode' [ { copy-mode }",
|
||||
"bind -N 'Paste the most recent paste buffer' ] { paste-buffer -p }",
|
||||
"bind -N 'Create a new window' c { new-window }",
|
||||
@@ -552,6 +556,8 @@ key_bindings_init(void)
|
||||
/* Mouse button 3 down on pane. */
|
||||
"bind -n MouseDown3Pane { if -Ft= '#{||:#{mouse_any_flag},#{&&:#{pane_in_mode},#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}}}' { select-pane -t=; send -M } { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " } }",
|
||||
"bind -n M-MouseDown3Pane { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }",
|
||||
"bind -n MouseDown3Empty { display-menu -t= -xM -yM -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_EMPTY_MENU " }",
|
||||
"bind -n M-MouseDown3Empty { display-menu -t= -xM -yM -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_EMPTY_MENU " }",
|
||||
|
||||
/* Mouse on scrollbar. */
|
||||
"bind -n MouseDown1ScrollbarUp { if -Ft= '#{pane_in_mode}' { send -X page-up } {copy-mode -u } }",
|
||||
@@ -594,7 +600,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
|
||||
"bind -Tcopy-mode n { send -X search-again }",
|
||||
"bind -Tcopy-mode q { send -X cancel }",
|
||||
"bind -Tcopy-mode r { send -X refresh-toggle }",
|
||||
"bind -Tcopy-mode r { send -X refresh-now }",
|
||||
"bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode Home { send -X start-of-line }",
|
||||
"bind -Tcopy-mode End { send -X end-of-line }",
|
||||
@@ -707,7 +713,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi n { send -X search-again }",
|
||||
"bind -Tcopy-mode-vi o { send -X other-end }",
|
||||
"bind -Tcopy-mode-vi q { send -X cancel }",
|
||||
"bind -Tcopy-mode-vi r { send -X refresh-toggle }",
|
||||
"bind -Tcopy-mode-vi r { send -X refresh-now }",
|
||||
"bind -Tcopy-mode-vi t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi v { send -X rectangle-toggle }",
|
||||
"bind -Tcopy-mode-vi w { send -X next-word }",
|
||||
|
||||
50
layout.c
50
layout.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: layout.c,v 1.96 2026/07/15 13:02:33 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>
|
||||
@@ -539,6 +539,10 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
|
||||
|
||||
status = window_get_pane_status(w);
|
||||
|
||||
/* Floating cells do not take space from the tiled layout. */
|
||||
if (!layout_cell_is_tiled(lc) && !layout_cell_has_tiled_child(lc))
|
||||
return (0);
|
||||
|
||||
if (lc->type == LAYOUT_WINDOWPANE) {
|
||||
/* Space available in this cell only. */
|
||||
if (type == LAYOUT_LEFTRIGHT) {
|
||||
@@ -568,6 +572,9 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
|
||||
/* Different type: minimum of available space in child cells. */
|
||||
minimum = UINT_MAX;
|
||||
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
|
||||
if (!layout_cell_is_tiled(lcchild) &&
|
||||
!layout_cell_has_tiled_child(lcchild))
|
||||
continue;
|
||||
available = layout_resize_check(w, lcchild, type);
|
||||
if (available < minimum)
|
||||
minimum = available;
|
||||
@@ -661,7 +668,7 @@ layout_resize_set_size(struct window *w, struct layout_cell *lc,
|
||||
|
||||
/* Find and return the nearest neighbour to a cell in a specific direction. */
|
||||
static struct layout_cell *
|
||||
layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction)
|
||||
layout_cell_get_neighbour_dir(struct layout_cell *lc, int direction)
|
||||
{
|
||||
struct layout_cell *lcn = lc;
|
||||
|
||||
@@ -695,9 +702,9 @@ layout_cell_get_neighbour(struct layout_cell *lc)
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
|
||||
direction = !direction;
|
||||
|
||||
lcother = layout_cell_get_neighbour_direction(lc, direction);
|
||||
lcother = layout_cell_get_neighbour_dir(lc, direction);
|
||||
if (lcother == NULL)
|
||||
lcother = layout_cell_get_neighbour_direction(lc, !direction);
|
||||
lcother = layout_cell_get_neighbour_dir(lc, !direction);
|
||||
|
||||
return (lcother);
|
||||
}
|
||||
@@ -860,7 +867,7 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
|
||||
size = lc->g.sx;
|
||||
else
|
||||
size = lc->g.sy;
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
|
||||
if (layout_cell_is_last_tiled(lc))
|
||||
change = size - new_size;
|
||||
else
|
||||
change = new_size - size;
|
||||
@@ -985,11 +992,11 @@ layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
|
||||
if (lcparent == NULL)
|
||||
return;
|
||||
|
||||
/* If this is the last cell, move back one. */
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) {
|
||||
do
|
||||
lc = TAILQ_PREV(lc, layout_cells, entry);
|
||||
while (lc->flags & LAYOUT_CELL_FLOATING);
|
||||
/* If this is the last tiled cell, move back one. */
|
||||
if (layout_cell_is_last_tiled(lc)) {
|
||||
lc = layout_cell_get_neighbour_dir(lc, 0);
|
||||
if (lc == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
layout_resize_layout(wp->window, lc, type, change, opposite);
|
||||
@@ -1007,22 +1014,22 @@ layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
|
||||
lcadd = lc;
|
||||
|
||||
/* Look towards the tail for a suitable cell for reduction. */
|
||||
lcremove = TAILQ_NEXT(lc, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lc, 1);
|
||||
while (lcremove != NULL) {
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size > 0)
|
||||
break;
|
||||
lcremove = TAILQ_NEXT(lcremove, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 1);
|
||||
}
|
||||
|
||||
/* If none found, look towards the head. */
|
||||
if (opposite && lcremove == NULL) {
|
||||
lcremove = TAILQ_PREV(lc, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lc, 0);
|
||||
while (lcremove != NULL) {
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size > 0)
|
||||
break;
|
||||
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
|
||||
}
|
||||
}
|
||||
if (lcremove == NULL)
|
||||
@@ -1050,13 +1057,13 @@ layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size != 0)
|
||||
break;
|
||||
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
|
||||
} while (lcremove != NULL);
|
||||
if (lcremove == NULL)
|
||||
return (0);
|
||||
|
||||
/* And add onto the next cell (from the original cell). */
|
||||
lcadd = TAILQ_NEXT(lc, entry);
|
||||
lcadd = layout_cell_get_neighbour_dir(lc, 1);
|
||||
if (lcadd == NULL)
|
||||
return (0);
|
||||
|
||||
@@ -1650,7 +1657,10 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
|
||||
if (window_active_pane_is_over_zoom(w))
|
||||
window_push_zoom(w, 0, 1);
|
||||
else
|
||||
window_push_zoom(w, 1, (flags & SPAWN_ZOOM));
|
||||
lc = layout_split_pane(wp, type, size, flags);
|
||||
if (lc == NULL)
|
||||
*cause = xstrdup("no space for a new pane");
|
||||
@@ -1677,8 +1687,10 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (flags & SPAWN_MODAL)
|
||||
window_push_modal_zoom(w);
|
||||
if (flags & SPAWN_FLOATOVERZOOM)
|
||||
window_push_zoom(wp->window, 0, 1);
|
||||
else if (window_active_pane_is_over_zoom(w))
|
||||
window_push_zoom(wp->window, 0, 1);
|
||||
else
|
||||
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
|
||||
lcnew = layout_floating_pane(w, wp, &fg);
|
||||
|
||||
6
menu.c
6
menu.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: menu.c,v 1.69 2026/07/14 19:07:03 nicm Exp $ */
|
||||
/* $OpenBSD: menu.c,v 1.70 2026/08/17 07:33:55 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -337,6 +337,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
||||
struct cmdq_state *state;
|
||||
enum cmd_parse_status status;
|
||||
char *error;
|
||||
key_code key;
|
||||
|
||||
if (KEYC_IS_MOUSE(event->key)) {
|
||||
/*
|
||||
@@ -387,7 +388,8 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
||||
name = menu->items[i].name;
|
||||
if (name == NULL || *name == '-')
|
||||
continue;
|
||||
if ((event->key & ~KEYC_MASK_FLAGS) == menu->items[i].key) {
|
||||
key = (event->key & ~KEYC_MASK_FLAGS);
|
||||
if (key == (menu->items[i].key & ~KEYC_MASK_FLAGS)) {
|
||||
md->choice = i;
|
||||
goto chosen;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: mode-tree.c,v 1.100 2026/07/14 19:07:03 nicm Exp $ */
|
||||
/* $OpenBSD: mode-tree.c,v 1.101 2026/08/05 07:50:21 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -47,7 +47,7 @@ enum mode_tree_preview {
|
||||
"#[acs]x" MODE_TREE_PREFIX_STYLE " }," \
|
||||
"#{mode_tree_repeat}}" \
|
||||
"#{?mode_tree_branch," \
|
||||
"#[acs]#{?mode_tree_last,mq,tq}" MODE_TREE_PREFIX_STYLE "> ,}" \
|
||||
"#[acs]#{?mode_tree_last,mq,tq}+" MODE_TREE_PREFIX_STYLE " ,}" \
|
||||
"#{?mode_tree_has_children," \
|
||||
"#{?mode_tree_expanded,#[fg=themered]-" MODE_TREE_PREFIX_STYLE " ," \
|
||||
"#[fg=themegreen]+" MODE_TREE_PREFIX_STYLE " }," \
|
||||
|
||||
30
monitor.c
30
monitor.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: monitor.c,v 1.6 2026/07/10 15:20:06 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>
|
||||
@@ -59,6 +59,9 @@ struct monitor_item {
|
||||
struct monitor_panes panes;
|
||||
struct monitor_windows windows;
|
||||
|
||||
u_int fire_count;
|
||||
time_t fire_time;
|
||||
|
||||
RB_ENTRY(monitor_item) entry;
|
||||
};
|
||||
RB_HEAD(monitor_items, monitor_item);
|
||||
@@ -180,6 +183,9 @@ monitor_report(struct monitor_set *ms, struct monitor_item *me,
|
||||
|
||||
log_debug("%s: %s changed to %s", __func__, me->name, value);
|
||||
|
||||
me->fire_count++;
|
||||
me->fire_time = current_time;
|
||||
|
||||
change.name = me->name;
|
||||
change.value = value;
|
||||
change.last = last;
|
||||
@@ -659,3 +665,25 @@ monitor_remove(struct monitor_set *ms, const char *name)
|
||||
if (RB_EMPTY(&ms->items) && evtimer_initialized(&ms->timer))
|
||||
evtimer_del(&ms->timer);
|
||||
}
|
||||
|
||||
/* Get subscription firing count. */
|
||||
u_int
|
||||
monitor_get_fire_count(struct monitor_set *ms, const char *name)
|
||||
{
|
||||
struct monitor_item *me, find = { .name = (char *)name };
|
||||
|
||||
if ((me = RB_FIND(monitor_items, &ms->items, &find)) == NULL)
|
||||
return (0);
|
||||
return (me->fire_count);
|
||||
}
|
||||
|
||||
/* Get subscription firing time. */
|
||||
time_t
|
||||
monitor_get_fire_time(struct monitor_set *ms, const char *name)
|
||||
{
|
||||
struct monitor_item *me, find = { .name = (char *)name };
|
||||
|
||||
if ((me = RB_FIND(monitor_items, &ms->items, &find)) == NULL)
|
||||
return (0);
|
||||
return (me->fire_time);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: options-table.c,v 1.240 2026/07/21 11:52:13 nicm Exp $ */
|
||||
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -304,6 +304,16 @@ const struct options_table_entry options_table[] = {
|
||||
"When this is reached, the oldest buffer is deleted."
|
||||
},
|
||||
|
||||
{ .name = "clear-on-attach",
|
||||
.type = OPTIONS_TABLE_FLAG,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.default_num = 1,
|
||||
.text = "Whether to use the alternate screen and clear it when "
|
||||
"a client is attached. When disabled, tmux does not "
|
||||
"enter the alternate screen on attach so terminal "
|
||||
"content before tmux remains in scrollback."
|
||||
},
|
||||
|
||||
{ .name = "command-alias",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
@@ -1239,7 +1249,8 @@ const struct options_table_entry options_table[] = {
|
||||
* underscore.
|
||||
*/
|
||||
.default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
|
||||
.text = "Characters considered to separate words."
|
||||
.text = "Characters considered to separate words; a space matches "
|
||||
"any character with the Unicode White_Space property."
|
||||
},
|
||||
|
||||
/* Window options. */
|
||||
@@ -1465,8 +1476,8 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "fill-character",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "",
|
||||
.text = "Character used to fill unused parts of window."
|
||||
.default_str = "#{?is_inside,#[bg=themedarkgrey] ,#[fg=themelightgrey]#[acs]~}",
|
||||
.text = "Format used to fill unused parts of window."
|
||||
},
|
||||
|
||||
{ .name = "main-pane-height",
|
||||
@@ -1950,6 +1961,7 @@ const struct options_table_entry options_table[] = {
|
||||
OPTIONS_TABLE_AFTER_HOOK("show-messages"),
|
||||
OPTIONS_TABLE_AFTER_HOOK("show-options"),
|
||||
OPTIONS_TABLE_AFTER_HOOK("split-window"),
|
||||
OPTIONS_TABLE_AFTER_HOOK("swap-window"),
|
||||
OPTIONS_TABLE_AFTER_HOOK("unbind-key"),
|
||||
OPTIONS_TABLE_HOOK("alert-activity", "",
|
||||
"Run when a window has activity."),
|
||||
|
||||
26
options.c
26
options.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: options.c,v 1.90 2026/07/10 13:38:45 nicm Exp $ */
|
||||
/* $OpenBSD: options.c,v 1.92 2026/07/27 19:15:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -107,7 +107,10 @@ struct options_entry {
|
||||
|
||||
int cached;
|
||||
struct style style;
|
||||
|
||||
void *monitor_data;
|
||||
u_int fire_count;
|
||||
time_t fire_time;
|
||||
|
||||
RB_ENTRY(options_entry) entry;
|
||||
};
|
||||
@@ -440,6 +443,25 @@ options_set_monitor_data(struct options_entry *o, void *data)
|
||||
o->monitor_data = data;
|
||||
}
|
||||
|
||||
void
|
||||
options_hook_fired(struct options_entry *o)
|
||||
{
|
||||
o->fire_count++;
|
||||
o->fire_time = current_time;
|
||||
}
|
||||
|
||||
u_int
|
||||
options_get_fire_count(struct options_entry *o)
|
||||
{
|
||||
return (o->fire_count);
|
||||
}
|
||||
|
||||
time_t
|
||||
options_get_fire_time(struct options_entry *o)
|
||||
{
|
||||
return (o->fire_time);
|
||||
}
|
||||
|
||||
const struct options_table_entry *
|
||||
options_table_entry(struct options_entry *o)
|
||||
{
|
||||
@@ -1372,7 +1394,7 @@ options_push_changes(const char *name)
|
||||
}
|
||||
if (strcmp(name, "fill-character") == 0) {
|
||||
RB_FOREACH(w, windows, &windows)
|
||||
window_set_fill_character(w);
|
||||
window_set_fill_cells(w);
|
||||
}
|
||||
if (strcmp(name, "key-table") == 0) {
|
||||
TAILQ_FOREACH(loop, &clients, entry)
|
||||
|
||||
5
popup.c
5
popup.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: popup.c,v 1.76 2026/07/14 19:07:03 nicm Exp $ */
|
||||
/* $OpenBSD: popup.c,v 1.77 2026/08/28 08:02:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -79,7 +79,8 @@ popup_free(struct popup_data *pd)
|
||||
|
||||
if (pd->job != NULL)
|
||||
job_free(pd->job);
|
||||
input_free(pd->ictx);
|
||||
if (pd->ictx != NULL)
|
||||
input_free(pd->ictx);
|
||||
|
||||
free(pd->r.ranges);
|
||||
screen_free(&pd->s);
|
||||
|
||||
19
proc.c
19
proc.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: proc.c,v 1.31 2026/06/08 21:38:19 nicm Exp $ */
|
||||
/* $OpenBSD: proc.c,v 1.32 2026/08/04 13:16:03 claudio Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -31,6 +31,10 @@
|
||||
#include <ncurses.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_JEMALLOC
|
||||
#include <jemalloc/jemalloc.h>
|
||||
#endif
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
struct tmuxproc {
|
||||
@@ -75,7 +79,7 @@ static void
|
||||
proc_event_cb(__unused int fd, short events, void *arg)
|
||||
{
|
||||
struct tmuxpeer *peer = arg;
|
||||
ssize_t n;
|
||||
int n;
|
||||
struct imsg imsg;
|
||||
|
||||
if (!(peer->flags & PEER_BAD) && (events & EV_READ)) {
|
||||
@@ -84,7 +88,7 @@ proc_event_cb(__unused int fd, short events, void *arg)
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
if ((n = imsg_get(&peer->ibuf, &imsg)) == -1) {
|
||||
if ((n = imsgbuf_get(&peer->ibuf, &imsg)) == -1) {
|
||||
peer->dispatchcb(NULL, peer->arg);
|
||||
return;
|
||||
}
|
||||
@@ -181,6 +185,10 @@ proc_start(const char *name)
|
||||
{
|
||||
struct tmuxproc *tp;
|
||||
struct utsname u;
|
||||
#ifdef HAVE_JEMALLOC
|
||||
const char *version;
|
||||
size_t size = sizeof version;
|
||||
#endif
|
||||
|
||||
log_open(name);
|
||||
setproctitle("%s (%s)", name, socket_path);
|
||||
@@ -195,6 +203,11 @@ proc_start(const char *name)
|
||||
#ifdef HAVE_UTF8PROC
|
||||
log_debug("using utf8proc %s", utf8proc_version());
|
||||
#endif
|
||||
#ifdef HAVE_JEMALLOC
|
||||
if (mallctl("version", &version, &size, NULL, 0) != 0)
|
||||
version = "(unknown version)";
|
||||
log_debug("using jemalloc %s", version);
|
||||
#endif
|
||||
#ifdef NCURSES_VERSION
|
||||
log_debug("using ncurses %s %06u", NCURSES_VERSION, NCURSES_VERSION_PATCH);
|
||||
#endif
|
||||
|
||||
4
prompt.c
4
prompt.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: prompt.c,v 1.5 2026/07/13 10:29:17 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>
|
||||
@@ -1334,7 +1334,7 @@ process_key:
|
||||
if (pr->index != size) {
|
||||
memmove(pr->buffer + pr->index,
|
||||
pr->buffer + pr->index + 1,
|
||||
(size + 1 - pr->index) *
|
||||
(size - pr->index) *
|
||||
sizeof *pr->buffer);
|
||||
goto changed;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ $TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
TMP2=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$TMP2"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
trap 'rm -f "$TMP" "$TMP2" "$TMP".*; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
@@ -270,6 +270,27 @@ if [ $? -eq 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A write error after a successful open must be reported to the server.
|
||||
dd if=/dev/zero of="$TMP.big" bs=100000 count=1 2>/dev/null
|
||||
check_ok load-buffer -b write-error "$TMP.big"
|
||||
mkfifo "$TMP.fifo" || exit 1
|
||||
dd if="$TMP.fifo" of="$TMP.got" bs=1 count=8 2>/dev/null &
|
||||
reader=$!
|
||||
exec 3>"$TMP.fifo"
|
||||
out=$($TMUX save-buffer -b write-error "$TMP.fifo" 3>&- 2>&1)
|
||||
status=$?
|
||||
exec 3>&-
|
||||
wait "$reader"
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "save-buffer after write error succeeded."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(wc -c <"$TMP.got")" -ne 8 ]; then
|
||||
echo "save-buffer write error test wrote wrong amount."
|
||||
exit 1
|
||||
fi
|
||||
check_ok delete-buffer -b write-error
|
||||
|
||||
# save-buffer - writes to stdout and load-buffer - reads from stdin.
|
||||
out=$($TMUX save-buffer -b sb -)
|
||||
if [ "$out" != "data" ]; then
|
||||
|
||||
0
regress/cfg-client-lost.sh
Executable file → Normal file
0
regress/cfg-client-lost.sh
Executable file → Normal file
@@ -290,4 +290,92 @@ wait_count ': G4' 7
|
||||
fail "window 1 of zzz not killed"
|
||||
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
|
||||
|
||||
96
regress/clear-on-attach.sh
Normal file
96
regress/clear-on-attach.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/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
|
||||
82
regress/client-key-order-focus.sh
Normal file
82
regress/client-key-order-focus.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/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
|
||||
101
regress/clock-mode.sh
Normal file
101
regress/clock-mode.sh
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/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
|
||||
150
regress/confirm-before-lock.sh
Normal file
150
regress/confirm-before-lock.sh
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/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
|
||||
77
regress/control-client-exit-stalled.sh
Normal file
77
regress/control-client-exit-stalled.sh
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A control client whose terminal has stopped accepting output (for example
|
||||
# its ssh connection or terminal emulator died) never drains the output that
|
||||
# server_client_check_exit waits on before sending MSG_EXIT, and may then
|
||||
# never close its socket either. This must not prevent the server from
|
||||
# exiting: after kill-server the server previously stayed alive forever with
|
||||
# server_exit set, closing every new connection immediately, so every new
|
||||
# client failed with "server exited unexpectedly" until the server was killed
|
||||
# by hand. The server now bounds the exit handshake: it discards output it
|
||||
# can never deliver and drops the client if it still does not close.
|
||||
#
|
||||
# The stalled terminal is simulated by a fifo whose read end is held open but
|
||||
# never read.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
FIFO=$DIR/fifo
|
||||
SERVER=
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
|
||||
cleanup() {
|
||||
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exec 8<&- 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
# A detached session whose pane floods printable output forever.
|
||||
$TMUX -f/dev/null new -d -x 80 -y 24 -s rt 'cat /dev/zero | tr "\000" x' || exit 1
|
||||
SERVER=$($TMUX display -pt rt '#{pid}')
|
||||
|
||||
# Attach a control client with output down the fifo; stdin held open by sleep
|
||||
# so it stays attached. The fifo fills and is never read, so a backlog forms
|
||||
# in the server that can never be delivered.
|
||||
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >"$FIFO" 2>&1 &
|
||||
exec 8<"$FIFO"
|
||||
|
||||
n=0
|
||||
while [ $n -lt 50 ]; do
|
||||
$TMUX lsc -F '#{client_name}' 2>/dev/null | grep -q . && break
|
||||
sleep 0.1
|
||||
n=$((n + 1))
|
||||
done
|
||||
$TMUX lsc -F '#{client_name}' 2>/dev/null | grep -q . ||
|
||||
{ echo "control client did not attach"; exit 1; }
|
||||
|
||||
# Let the pane flood fill the fifo so the client's output is stuck.
|
||||
sleep 2
|
||||
|
||||
# Ask the server to exit. This sets CLIENT_EXIT on the stalled client, whose
|
||||
# output can never drain.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# The server must exit within the exit timeout (10 seconds) plus slack,
|
||||
# rather than staying in limbo forever rejecting new clients.
|
||||
n=0
|
||||
while [ $n -lt 100 ]; do
|
||||
if ! kill -0 "$SERVER" 2>/dev/null; then
|
||||
SERVER=
|
||||
exit 0
|
||||
fi
|
||||
sleep 0.2
|
||||
n=$((n + 1))
|
||||
done
|
||||
|
||||
echo "server did not exit after kill-server; new clients see:"
|
||||
$TMUX ls 2>&1
|
||||
exit 1
|
||||
63
regress/control-client-popup.sh
Normal file
63
regress/control-client-popup.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/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
|
||||
38
regress/control-client-run-shell-exit.sh
Normal file
38
regress/control-client-run-shell-exit.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/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
|
||||
158
regress/control-notify-guard.sh
Normal file
158
regress/control-notify-guard.sh
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
LANG=C.UTF-8
|
||||
export TERM LC_ALL LANG
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
IN="$TMPDIR/in"
|
||||
OUT="$TMPDIR/out"
|
||||
BADCFG="$TMPDIR/bad.conf"
|
||||
PID=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&- 2>/dev/null
|
||||
[ -n "$PID" ] && kill "$PID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" >&2
|
||||
[ -s "$OUT" ] && cat "$OUT" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
wait_for()
|
||||
{
|
||||
pattern=$1
|
||||
timeout=${2:-60}
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt "$timeout" ]; do
|
||||
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "missing: $pattern"
|
||||
}
|
||||
|
||||
wait_for_count()
|
||||
{
|
||||
pattern=$1
|
||||
expected=$2
|
||||
timeout=${3:-60}
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt "$timeout" ]; do
|
||||
count=$(grep -F -c -- "$pattern" "$OUT" 2>/dev/null)
|
||||
[ "$count" -ge "$expected" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "missing count $expected for: $pattern"
|
||||
}
|
||||
|
||||
send()
|
||||
{
|
||||
printf '%s\n' "$1" >&3 || fail "failed to send: $1"
|
||||
}
|
||||
|
||||
# A notification is a standalone protocol message. It must never be part of
|
||||
# the response between the real %begin and matching %end or %error for a
|
||||
# command. Ignore guard-looking command output while a real guard is open.
|
||||
check_guards()
|
||||
{
|
||||
awk '
|
||||
function notification(line) {
|
||||
return line ~ /^%(sessions-changed|session-changed |unlinked-window-add )/ ||
|
||||
line ~ /^%(pause |continue |config-error )/
|
||||
}
|
||||
!open && $0 ~ /^%begin [0-9]+ [0-9]+ [0-9]+$/ {
|
||||
open = 1
|
||||
time = $2
|
||||
number = $3
|
||||
flags = $4
|
||||
next
|
||||
}
|
||||
open && ($0 == "%end " time " " number " " flags ||
|
||||
$0 == "%error " time " " number " " flags) {
|
||||
open = 0
|
||||
next
|
||||
}
|
||||
notification($0) {
|
||||
seen[$1]++
|
||||
if (open) {
|
||||
print "notification inside guard: " $0 > "/dev/stderr"
|
||||
bad = 1
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (open) {
|
||||
print "unterminated command guard" > "/dev/stderr"
|
||||
bad = 1
|
||||
}
|
||||
if (seen["%sessions-changed"] < 2 ||
|
||||
seen["%session-changed"] < 1 ||
|
||||
seen["%unlinked-window-add"] < 2 ||
|
||||
seen["%pause"] < 1 || seen["%continue"] < 1 ||
|
||||
seen["%config-error"] < 1) {
|
||||
print "missing expected notification" > "/dev/stderr"
|
||||
bad = 1
|
||||
}
|
||||
exit bad
|
||||
}' "$OUT" || fail "bad control protocol notification ordering"
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s guard -x 80 -y 24 || exit 1
|
||||
pane=$($TMUX display-message -p -t guard:0.0 '#{pane_id}') || exit 1
|
||||
|
||||
mkfifo "$IN" || exit 1
|
||||
: >"$OUT"
|
||||
$TMUX -C attach-session -t guard <"$IN" >"$OUT" 2>&1 &
|
||||
PID=$!
|
||||
exec 3>"$IN"
|
||||
|
||||
# Attaching generates a session notification synchronously from inside the
|
||||
# attach command. It must follow the closing guard.
|
||||
wait_for '%session-changed '
|
||||
|
||||
# Guard-looking output is arbitrary command output, not protocol state. Make
|
||||
# new-session both generate notifications and print an unmatched fake %begin;
|
||||
# it must not leave those notifications permanently deferred.
|
||||
send "set-option -g @fake-begin '%begin 1 2 3'"
|
||||
send "new-session -d -P -F '#{@fake-begin}' -s fake-begin"
|
||||
wait_for_count '%sessions-changed' 1
|
||||
|
||||
# Likewise, fake %end output from a command that generates multiple
|
||||
# notifications must not flush them before the command actually ends.
|
||||
send "set-option -g @fake-end '%end 1 2 3'"
|
||||
send "new-session -d -P -F '#{@fake-end}' -s fake-end"
|
||||
wait_for_count '%sessions-changed' 2
|
||||
|
||||
# Exercise notification paths outside control-notify.c.
|
||||
send "refresh-client -A '$pane:pause'"
|
||||
wait_for "%pause $pane"
|
||||
send "refresh-client -A '$pane:continue'"
|
||||
wait_for "%continue $pane"
|
||||
|
||||
printf '%s\n' 'not-a-command' >"$BADCFG"
|
||||
send "source-file '$BADCFG'"
|
||||
wait_for '%config-error '
|
||||
|
||||
# A final command guarantees all earlier command guards have completed before
|
||||
# the transcript is checked.
|
||||
send "display-message -p 'guard-check-done'"
|
||||
wait_for 'guard-check-done'
|
||||
check_guards
|
||||
|
||||
exit 0
|
||||
253
regress/copy-mode-commands.sh
Normal file
253
regress/copy-mode-commands.sh
Normal file
@@ -0,0 +1,253 @@
|
||||
#!/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
|
||||
81
regress/copy-mode-selection-mode.sh
Normal file
81
regress/copy-mode-selection-mode.sh
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/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
|
||||
@@ -114,5 +114,30 @@ $TMUX send-keys -X next-space-end
|
||||
$TMUX send-keys -X copy-selection
|
||||
[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1
|
||||
|
||||
# Test that vi cursor movement does not stop on the padding cell of a wide
|
||||
# character at the end of a line.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 1
|
||||
$TMUX new -d -x20 -y5 \
|
||||
"printf 'abc中\nxyz\n'; exec cat" || exit 1
|
||||
$TMUX set-window-option -g mode-keys vi
|
||||
$TMUX copy-mode
|
||||
$TMUX send-keys -X history-top
|
||||
$TMUX send-keys -X start-of-line
|
||||
$TMUX send-keys -X cursor-right
|
||||
$TMUX send-keys -X cursor-right
|
||||
$TMUX send-keys -X cursor-right
|
||||
[ "$($TMUX display -p '#{copy_cursor_x},#{copy_cursor_y}')" = "3,0" ] ||
|
||||
exit 1
|
||||
$TMUX send-keys -X cursor-right
|
||||
[ "$($TMUX display -p '#{copy_cursor_x},#{copy_cursor_y}')" = "0,1" ] ||
|
||||
exit 1
|
||||
$TMUX send-keys -X cursor-left
|
||||
[ "$($TMUX display -p '#{copy_cursor_x},#{copy_cursor_y}')" = "3,0" ] ||
|
||||
exit 1
|
||||
$TMUX send-keys -X cursor-left
|
||||
[ "$($TMUX display -p '#{copy_cursor_x},#{copy_cursor_y}')" = "2,0" ] ||
|
||||
exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
|
||||
163
regress/copy-mode-unicode-whitespace.sh
Normal file
163
regress/copy-mode-unicode-whitespace.sh
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
[ -z "$TMUX" ] || $TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
goto_cell()
|
||||
{
|
||||
row=$1
|
||||
column=$2
|
||||
|
||||
$TMUX send-keys -X history-top || fail "$mode: history-top failed"
|
||||
$TMUX send-keys -X start-of-line || fail "$mode: start-of-line failed"
|
||||
[ "$row" -eq 0 ] ||
|
||||
$TMUX send-keys -X -N "$row" cursor-down ||
|
||||
fail "$mode: cursor-down failed"
|
||||
[ "$column" -eq 0 ] ||
|
||||
$TMUX send-keys -X -N "$column" cursor-right ||
|
||||
fail "$mode: cursor-right failed"
|
||||
}
|
||||
|
||||
check_cursor()
|
||||
{
|
||||
expected=$1
|
||||
actual=$($TMUX display-message -p '#{copy_cursor_x},#{copy_cursor_y}')
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected cursor $expected, got $actual"
|
||||
}
|
||||
|
||||
check_word()
|
||||
{
|
||||
row=$1
|
||||
column=$2
|
||||
expected=$3
|
||||
|
||||
goto_cell "$row" "$column"
|
||||
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
|
||||
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
|
||||
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
|
||||
actual=$($TMUX show-buffer 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected word '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
check_next_word()
|
||||
{
|
||||
row=$1
|
||||
expected=$2
|
||||
|
||||
goto_cell "$row" 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: next-word failed"
|
||||
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
|
||||
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
|
||||
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
|
||||
actual=$($TMUX show-buffer 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected next word '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
for mode in emacs vi; do
|
||||
TMUX="$TEST_TMUX -Lunicode-whitespace-$mode-$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x16 -y20 \
|
||||
"printf 'aa bb\naa\tbb\naa\302\240bb\naa\343\200\200bb\naa:bb\naaaaaaaaaaaaaaa\302\240b\nabcdefghijklmnopq\nhardone\nhardtwo\naa\341\232\200bb\naa\342\200\200bb\naa\342\200\250bb\naa\342\200\251bb\naa\342\200\257bb\naa\342\201\237bb\nxa\302\205b c\n'; exec cat" ||
|
||||
fail "$mode: new-session failed"
|
||||
$TMUX set-option -g window-size manual || fail "$mode: set size failed"
|
||||
$TMUX set-window-option -g mode-keys "$mode" ||
|
||||
fail "$mode: set mode-keys failed"
|
||||
$TMUX set-window-option -g word-separators "" ||
|
||||
fail "$mode: clear word-separators failed"
|
||||
$TMUX copy-mode || fail "$mode: copy-mode failed"
|
||||
|
||||
# ASCII space and TAB retain their existing behaviour.
|
||||
goto_cell 0 0
|
||||
$TMUX send-keys -X next-space || fail "$mode: ASCII next-space failed"
|
||||
check_cursor 3,0
|
||||
goto_cell 1 0
|
||||
$TMUX send-keys -X next-space || fail "$mode: TAB next-space failed"
|
||||
check_cursor 8,1
|
||||
$TMUX send-keys -X previous-space ||
|
||||
fail "$mode: TAB previous-space failed"
|
||||
check_cursor 0,1
|
||||
|
||||
# NBSP is whitespace for selection and word movement.
|
||||
goto_cell 2 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: NBSP next-word failed"
|
||||
check_cursor 3,2
|
||||
$TMUX send-keys -X previous-word ||
|
||||
fail "$mode: NBSP previous-word failed"
|
||||
check_cursor 0,2
|
||||
check_word 2 0 aa
|
||||
goto_cell 2 0
|
||||
$TMUX send-keys -X next-word-end ||
|
||||
fail "$mode: NBSP next-word-end failed"
|
||||
if [ "$mode" = emacs ]; then
|
||||
check_cursor 2,2
|
||||
else
|
||||
check_cursor 1,2
|
||||
fi
|
||||
|
||||
# U+3000 is two columns wide; both its leading and padding cells must be
|
||||
# skipped as one whitespace character in either direction.
|
||||
goto_cell 3 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: U+3000 next-word failed"
|
||||
check_cursor 4,3
|
||||
$TMUX send-keys -X previous-word ||
|
||||
fail "$mode: U+3000 previous-word failed"
|
||||
check_cursor 0,3
|
||||
goto_cell 3 0
|
||||
$TMUX send-keys -X next-space-end ||
|
||||
fail "$mode: U+3000 next-space-end failed"
|
||||
if [ "$mode" = emacs ]; then
|
||||
check_cursor 2,3
|
||||
else
|
||||
check_cursor 1,3
|
||||
fi
|
||||
|
||||
# Other characters in word-separators remain literal when space enables
|
||||
# Unicode whitespace matching.
|
||||
$TMUX set-window-option -g word-separators ': ' ||
|
||||
fail "$mode: set custom word-separators failed"
|
||||
goto_cell 4 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: literal next-word failed"
|
||||
check_cursor 2,4
|
||||
$TMUX set-window-option -g word-separators "" ||
|
||||
fail "$mode: restore word-separators failed"
|
||||
|
||||
# Whitespace in the last column remains a boundary across a soft wrap.
|
||||
goto_cell 5 0
|
||||
$TMUX send-keys -X next-space ||
|
||||
fail "$mode: wrapped NBSP next-space failed"
|
||||
check_cursor 0,6
|
||||
|
||||
# A word split only by a soft wrap remains one word, while a hard line
|
||||
# boundary still terminates selection.
|
||||
check_word 7 0 abcdefghijklmnopq
|
||||
check_word 9 0 hardone
|
||||
|
||||
# Check the remaining representative White_Space ranges and U+0085
|
||||
# combined into a cell with another character.
|
||||
check_next_word 11 bb
|
||||
check_next_word 12 bb
|
||||
check_next_word 13 bb
|
||||
check_next_word 14 bb
|
||||
check_next_word 15 bb
|
||||
check_next_word 16 bb
|
||||
goto_cell 17 0
|
||||
$TMUX send-keys -X next-word ||
|
||||
fail "$mode: combined U+0085 next-word failed"
|
||||
check_cursor 2,17
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -280,6 +280,7 @@ test_user_hook()
|
||||
settle
|
||||
assert_equals "$($TMUX show-option -qv @cm_hook)" \
|
||||
"display-message hook" "add user hook"
|
||||
$TMUX set-hook -E @cm_hook || fail "fire user hook failed"
|
||||
|
||||
send q
|
||||
wait_mode 0
|
||||
@@ -287,7 +288,10 @@ test_user_hook()
|
||||
repeat_key j 3
|
||||
send Right j
|
||||
settle
|
||||
assert_contains "$(capture)" "@cm_hook" "user hook shown as hook"
|
||||
screen=$(capture)
|
||||
assert_contains "$screen" "@cm_hook" "user hook shown as hook"
|
||||
assert_contains "$screen" "This hook has been fired 1 times, last " \
|
||||
"user hook fire count and time"
|
||||
|
||||
send u y
|
||||
settle
|
||||
@@ -302,10 +306,17 @@ test_hook_array()
|
||||
|
||||
$TMUX set-hook -g 'after-new-session[0]' 'display-message old' ||
|
||||
fail "set hook array failed"
|
||||
$TMUX set-hook -g -R after-new-session || fail "fire hook array failed"
|
||||
|
||||
open_customize '#{==:#{option_name},after-new-session}'
|
||||
repeat_key j 3
|
||||
send Right j Right j
|
||||
send Right j
|
||||
settle
|
||||
screen=$(capture)
|
||||
assert_contains "$screen" "This hook has been fired " \
|
||||
"array hook fire count"
|
||||
assert_contains "$screen" ", last " "array hook fire time"
|
||||
send Right j
|
||||
settle
|
||||
|
||||
send a
|
||||
|
||||
118
regress/find-window.sh
Normal file
118
regress/find-window.sh
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/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
|
||||
@@ -199,5 +199,74 @@ must_fail $TMUX resize-pane -t "$id" -x 0
|
||||
must_fail $TMUX resize-pane -t "$id" -y 0
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# --- Tiled pane resize with floating cells in the layout ---
|
||||
|
||||
$TMUX set-option -w -u pane-border-lines || exit 1
|
||||
base=$($TMUX display-message -p '#{pane_id}')
|
||||
|
||||
# A floating cell after the last tiled cell must not become the recipient of a
|
||||
# relative or absolute resize. This is the sequence from GitHub issue 5135.
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 50% -y 50% -X 50% -Y 50% 'sleep 100') ||
|
||||
fail "new-pane for tiled resize test failed"
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for tiled resize test failed"
|
||||
|
||||
$TMUX resize-pane -t "$lower" -U 5 || fail "relative tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 13
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
|
||||
|
||||
$TMUX resize-pane -t "$lower" -y 11 || fail "absolute tiled reset failed"
|
||||
$TMUX resize-pane -t "$lower" -y 16 || fail "absolute tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# A floating cell between tiled siblings must be skipped when finding the cell
|
||||
# which donates space to a resize.
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for middle floating cell test failed"
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 8 -X 30 -Y 8 'sleep 100') ||
|
||||
fail "new-pane for middle floating cell test failed"
|
||||
|
||||
$TMUX resize-pane -t "$base" -D 3 || fail "resize past floating cell failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 6
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# A small floating child must not limit the available tiled space in a nested
|
||||
# layout with a different split direction.
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for nested floating cell test failed"
|
||||
right=$($TMUX split-window -dhPF '#{pane_id}' -t "$lower" 'sleep 100') ||
|
||||
fail "horizontal split for nested floating cell test failed"
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$lower" \
|
||||
-x 20 -y 3 -X 30 -Y 10 'sleep 100') ||
|
||||
fail "new-pane for nested floating cell test failed"
|
||||
|
||||
$TMUX resize-pane -t "$base" -D 3 || fail "nested tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$right" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$right" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 1
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$right" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
|
||||
109
regress/format-animation.sh
Normal file
109
regress/format-animation.sh
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise the A format modifier (format_cycle in format.c). It is expanded
|
||||
# only in a status format and arms the client cycle timer to redraw the status
|
||||
# line as the frames advance, so the only place its behaviour shows is a
|
||||
# rendered status line.
|
||||
#
|
||||
# The status line is rendered by an inner tmux attached inside an outer tmux
|
||||
# pane; capturing the outer pane shows what the inner tmux drew. The inner
|
||||
# window runs sleep and every other status line is blanked, so the capture
|
||||
# contains the frame and nothing else.
|
||||
#
|
||||
# Each frame lasts 700 milliseconds and the capture is repeated once a second,
|
||||
# so consecutive samples always land on a different frame: no exact period is
|
||||
# assumed and nothing in the test asks for a redraw, so seeing the frame change
|
||||
# means the status line animated on its own.
|
||||
|
||||
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"
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
|
||||
0 1 15
|
||||
|
||||
fail() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Capture the outer pane and return the text the inner tmux drew.
|
||||
capture() {
|
||||
$TMUX capturep -p >$TMP || fail "capture failed"
|
||||
tr -d '[:space:]' <$TMP
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
# Inner: a window which draws nothing and a status line which is only the
|
||||
# animation, so the outer capture is the current frame on its own.
|
||||
$TMUX2 new -d -x30 -y8 "sh -c 'exec sleep 100'" || exit 1
|
||||
$TMUX2 set -g window-size latest || exit 1
|
||||
i=1
|
||||
while [ $i -le 4 ]; do
|
||||
$TMUX2 set -g status-format[$i] "" || exit 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
$TMUX2 set -g status-format[0] "#{A/7:AAAA,BBBB,CCCC}" || exit 1
|
||||
|
||||
$TMUX new -d -x30 -y8 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX set -g window-size manual || exit 1
|
||||
$TMUX set -g default-terminal "tmux-256color" || exit 1
|
||||
$TMUX send -l "$TMUX2 attach" || exit 1
|
||||
$TMUX send Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
# Sample the frames. Every sample must be one of the frames and at least two
|
||||
# different ones must be seen; status-interval is 15 seconds so only the cycle
|
||||
# timer can have redrawn the status line.
|
||||
seen=""
|
||||
i=0
|
||||
while [ $i -lt 4 ]; do
|
||||
[ $i -eq 0 ] || sleep 1
|
||||
frame=$(capture)
|
||||
case "$frame" in
|
||||
AAAA|BBBB|CCCC) ;;
|
||||
*) fail "status line is '$frame', not a frame" ;;
|
||||
esac
|
||||
case " $seen " in
|
||||
*" $frame "*) ;;
|
||||
*) seen="$seen $frame" ;;
|
||||
esac
|
||||
i=$((i + 1))
|
||||
done
|
||||
set -- $seen
|
||||
[ $# -ge 2 ] || fail "status line did not animate, only saw$seen"
|
||||
|
||||
# Outside a status format there is no animation at all.
|
||||
out=$($TMUX2 display-message -p "#{A:ZZZZ,YYYY}") || exit 1
|
||||
[ -z "$out" ] || fail "display-message gave '$out', want empty"
|
||||
out=$($TMUX2 list-panes -F "#{A:ZZZZ,YYYY}") || exit 1
|
||||
[ -z "$out" ] || fail "list-panes gave '$out', want empty"
|
||||
|
||||
# Nor inside #(), where the frames would change the command on every frame.
|
||||
$TMUX2 set -g status-format[0] '#(echo "[#{A:ZZZZ,YYYY}]")' || exit 1
|
||||
sleep 1
|
||||
out=$(capture)
|
||||
case "$out" in
|
||||
*ZZZZ*|*YYYY*) fail "job command saw a frame: '$out'" ;;
|
||||
*"[]"*) ;;
|
||||
*) fail "job output is '$out', want []" ;;
|
||||
esac
|
||||
|
||||
# Empty frames and a missing or bad count must not upset the server.
|
||||
for f in '#{A:}' '#{A/0:a,b}' '#{A/x:a,b}' '#{A:,,,}'; do
|
||||
$TMUX2 set -g status-format[0] "$f" || fail "setting $f failed"
|
||||
$TMUX2 has-session >/dev/null 2>&1 || fail "server lost with $f"
|
||||
done
|
||||
sleep 1
|
||||
$TMUX2 has-session >/dev/null 2>&1 || fail "server lost after empty frames"
|
||||
|
||||
exit 0
|
||||
@@ -71,7 +71,10 @@ cursor_very_visible
|
||||
cursor_x
|
||||
cursor_y
|
||||
history_all_bytes
|
||||
history_added
|
||||
history_bytes
|
||||
history_collected
|
||||
history_generation
|
||||
history_limit
|
||||
history_size
|
||||
host
|
||||
@@ -103,6 +106,11 @@ pane_at_right
|
||||
pane_at_top
|
||||
pane_bg
|
||||
pane_bottom
|
||||
pane_command_duration
|
||||
pane_command_end_time
|
||||
pane_command_running
|
||||
pane_command_start_time
|
||||
pane_command_status
|
||||
pane_current_command
|
||||
pane_current_path
|
||||
pane_dead
|
||||
@@ -120,16 +128,21 @@ pane_index
|
||||
pane_input_off
|
||||
pane_key_mode
|
||||
pane_last
|
||||
pane_last_output_time
|
||||
pane_last_prompt_time
|
||||
pane_left
|
||||
pane_marked
|
||||
pane_marked_set
|
||||
pane_modal_flag
|
||||
pane_mode
|
||||
pane_output_generation
|
||||
pane_path
|
||||
pane_pb_progress
|
||||
pane_pb_state
|
||||
pane_pid
|
||||
pane_pipe
|
||||
pane_pipe_pid
|
||||
pane_private_modes
|
||||
pane_right
|
||||
pane_search_string
|
||||
pane_start_command
|
||||
@@ -141,6 +154,8 @@ pane_title
|
||||
pane_top
|
||||
pane_tty
|
||||
pane_unseen_changes
|
||||
pane_unzoomed_height
|
||||
pane_unzoomed_width
|
||||
pane_width
|
||||
pane_x
|
||||
pane_y
|
||||
@@ -206,7 +221,10 @@ window_layout
|
||||
window_linked
|
||||
window_linked_sessions
|
||||
window_linked_sessions_list
|
||||
window_manual_height
|
||||
window_manual_width
|
||||
window_marked_flag
|
||||
window_modal_pane
|
||||
window_name
|
||||
window_offset_x
|
||||
window_offset_y
|
||||
|
||||
@@ -218,11 +218,13 @@ $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.
|
||||
$TMUX set -g @x 0 || fail "set @x failed"
|
||||
$TMUX set-hook -g pane-exited 'set -gF @x "#{hook}:#{hook_pane}"' ||
|
||||
$TMUX set-hook -g pane-exited \
|
||||
'set -gF @x "#{hook}:#{hook_pane}:#{hook_exit_status}:#{hook_exit_success}"' ||
|
||||
fail "set-hook pane-exited failed"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') ||
|
||||
fail "split-window true failed"
|
||||
wait_for @x "pane-exited:$pane"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
|
||||
'trap "" HUP; exec </dev/null >/dev/null 2>&1; sleep 1; exit 42') ||
|
||||
fail "split-window exit 42 failed"
|
||||
wait_for @x "pane-exited:$pane:42:0"
|
||||
$TMUX set-hook -gu pane-exited || fail "unset pane-exited failed"
|
||||
|
||||
# pane-died when a pane's command exits with remain-on-exit on.
|
||||
|
||||
@@ -85,6 +85,24 @@ echo "$shown" | grep -q '^session-created\[0\]' ||
|
||||
fail "missing first array item: $shown"
|
||||
echo "$shown" | grep -q '^session-created\[1\]' ||
|
||||
fail "missing second array item: $shown"
|
||||
shown=$($TMUX show-hooks -gF '#{option_name}:#{hook_fire_count}:#{t/p:hook_fire_time}' \
|
||||
session-created) ||
|
||||
fail "show-hooks -gF failed"
|
||||
echo "$shown" | grep -q '^session-created:[1-9][0-9]*:[^-][^ ]*$' ||
|
||||
fail "missing hook fire formats: $shown"
|
||||
|
||||
# User hooks are options, but show-hooks should list only registered @ hooks
|
||||
# and not ordinary user options.
|
||||
$TMUX set -g @not-a-hook value || fail "set @not-a-hook failed"
|
||||
$TMUX set-hook -g @user-hook 'lsk' ||
|
||||
fail "set-hook @user-hook failed"
|
||||
shown=$($TMUX show-hooks -g) ||
|
||||
fail "show-hooks -g all failed"
|
||||
echo "$shown" | grep -q '^@user-hook lsk$' ||
|
||||
fail "missing user hook: $shown"
|
||||
if echo "$shown" | grep -q '^@not-a-hook '; then
|
||||
fail "show-hooks listed user option: $shown"
|
||||
fi
|
||||
|
||||
# Unsetting removes the whole hook.
|
||||
$TMUX set-hook -gu session-created || fail "set-hook -gu failed"
|
||||
|
||||
@@ -174,3 +174,17 @@ check_raw_matches()
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_raw_no_matches()
|
||||
{
|
||||
name=$1
|
||||
shift
|
||||
|
||||
capture_raw "$name" >"$TMP"
|
||||
for unexpected in "$@"; do
|
||||
if grep -Eq "$unexpected" "$TMP"; then
|
||||
printf "%s\n" "$unexpected" >"$EXP"
|
||||
fail "$name raw unexpected"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
@@ -22,6 +22,16 @@ check_capture rename 'X'
|
||||
start_pane apc-title 20 3 '\033_test-title\033\\X\n'
|
||||
check_capture apc-title 'X'
|
||||
|
||||
cmd='i=0; while [ "$i" -lt 12 ]; do '
|
||||
cmd="${cmd}printf '\\033[22;0t'; i=\$((i + 1)); done; "
|
||||
cmd="${cmd}printf X; sleep 2"
|
||||
start_cmd title-stack 20 3 "$cmd"
|
||||
check_capture title-stack 'X'
|
||||
$TMUX respawn-pane -k -t title-stack: \
|
||||
"printf '\\033[22;0tY'; sleep 2" || exit 1
|
||||
sleep 0.3
|
||||
check_capture title-stack 'Y'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 20 -y 3 -s osc52 "sleep 2" || exit 1
|
||||
|
||||
@@ -41,5 +41,9 @@ start_pane combining-left 10 3 '\314\201A\n'
|
||||
check_capture combining-left 'A'
|
||||
check_cursor combining-left '0,1'
|
||||
|
||||
start_pane combining-overflow 10 3 'u\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\n'
|
||||
check_raw_matches combining-overflow '^ C 0,0 data=\(1,31,u'
|
||||
check_raw_no_matches combining-overflow 'data=\(0,'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
|
||||
126
regress/layout-set.sh
Normal file
126
regress/layout-set.sh
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/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
|
||||
46
regress/list-commands.sh
Normal file
46
regress/list-commands.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/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
|
||||
66
regress/menu-mouse.sh
Normal file
66
regress/menu-mouse.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that mouse selection in an active menu uses the correct coordinates
|
||||
# when the status line is at the top.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -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
|
||||
}
|
||||
|
||||
# click COL ROW
|
||||
#
|
||||
# Write an SGR mouse press then release at a 1-based position to the outer pane
|
||||
# holding the inner client.
|
||||
click()
|
||||
{
|
||||
col="$1"
|
||||
row="$2"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM\033[<0;%s;%sm' \
|
||||
"$col" "$row" "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
$TMUX new-session -d -s inner -x 80 -y 24 'sleep 100' || exit 1
|
||||
$TMUX set -g mouse on || exit 1
|
||||
$TMUX set -g status-position top || exit 1
|
||||
$TMUX set -g @menu-choice '' || exit 1
|
||||
|
||||
$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
$TMUX display-menu -M -x 5 -y 7 \
|
||||
"First item" f "set -g @menu-choice first" \
|
||||
"Second item" s "set -g @menu-choice second" || exit 1
|
||||
sleep 1
|
||||
|
||||
# -y is the bottom of the menu, so with four menu lines this puts the menu at
|
||||
# window y=3. The first item is then at window y=4. With one status line at the
|
||||
# top, this is terminal row 6 in SGR's 1-based coordinates.
|
||||
click 8 6
|
||||
|
||||
choice=$($TMUX show -gv @menu-choice 2>/dev/null)
|
||||
[ "$choice" = "first" ] || fail "got '$choice', expected 'first'"
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
@@ -105,6 +105,24 @@ drag()
|
||||
sleep 1
|
||||
}
|
||||
|
||||
meta_drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<8;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<40;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<8;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
check_ok new-session -d -s modal -x 80 -y 24 'cat'
|
||||
@@ -127,12 +145,16 @@ case "$(fmt "$modal" '#{pane_flags}')" in
|
||||
*O*) ;;
|
||||
*) fail "modal pane flags do not include O" ;;
|
||||
esac
|
||||
case "$(fmt "$modal" '#{pane_flags}')" in
|
||||
*A*) ;;
|
||||
*) fail "modal pane flags do not include A" ;;
|
||||
esac
|
||||
case "$(fmt modal:0 '#{window_flags}')" in
|
||||
*O*) ;;
|
||||
*) fail "modal window flags do not include O" ;;
|
||||
esac
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 0
|
||||
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
|
||||
|
||||
check_fail "window already has a modal pane" \
|
||||
new-pane -O -x 10 -y 4 'cat'
|
||||
@@ -159,12 +181,14 @@ under=$($TMUX split-window -PF '#{pane_id}' -t "$p0" 'cat') ||
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{pane_id}')" "$modal"
|
||||
must_equal "$(fmt "$under" '#{pane_active}')" 0
|
||||
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
|
||||
|
||||
float=$($TMUX new-pane -PF '#{pane_id}' -x 10 -y 4 -X 5 -Y 3 'cat') ||
|
||||
fail "new floating pane under modal failed"
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{pane_id}')" "$modal"
|
||||
must_equal "$(fmt "$float" '#{pane_active}')" 0
|
||||
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
|
||||
|
||||
check_ok new-window -d -t modal: -n other 'cat'
|
||||
check_ok select-window -t modal:other
|
||||
@@ -303,6 +327,164 @@ check_ok kill-pane -t "$modal"
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
must_equal "$(fmt modal:0 '#{pane_id}')" "$p0"
|
||||
must_equal "$(fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 0:0
|
||||
|
||||
ignored=$($TMUX new-pane -KdPF '#{pane_id}' -t "$p0" 'cat') ||
|
||||
fail "new-pane -K without -O failed"
|
||||
check_ok kill-pane -t "$ignored"
|
||||
|
||||
$TMUX set -g @modal-prefix no
|
||||
$TMUX set -g @modal-root no
|
||||
$TMUX bind -n z set -g @modal-root yes
|
||||
|
||||
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "new-pane -OK failed"
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||
sleep 1
|
||||
must_equal "$($TMUX show -gv @modal-prefix)" no
|
||||
must_equal "$($TMUX show -gv @modal-root)" no
|
||||
case "$($TMUX capture-pane -pt "$modal")" in
|
||||
*xz*) ;;
|
||||
*) fail "keys did not reach key-capturing modal pane" ;;
|
||||
esac
|
||||
left=$(fmt "$modal" '#{pane_left}')
|
||||
top=$(fmt "$modal" '#{pane_top}')
|
||||
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
|
||||
new_left=$(fmt "$modal" '#{pane_left}')
|
||||
new_top=$(fmt "$modal" '#{pane_top}')
|
||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||
fail "key-capturing modal pane did not move"
|
||||
check_ok kill-pane -t "$modal"
|
||||
sleep 1
|
||||
|
||||
# A nonmodal floating pane may remain above zoom, and switching between it and
|
||||
# the zoomed tiled pane must not unzoom the window.
|
||||
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
||||
base=$(fmt modal:float-over-zoom '#{pane_id}')
|
||||
check_ok split-window -dh -t "$base" 'cat'
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
over=$($TMUX new-pane -APF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "new-pane -A failed"
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_active}')" 1:1
|
||||
case "$(fmt "$over" '#{pane_flags}')" in
|
||||
*A*) ;;
|
||||
*) fail "float-over-zoom pane flags do not include A" ;;
|
||||
esac
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
check_ok select-pane -t "$base"
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
check_ok select-pane -t "$over"
|
||||
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
client=$($TMUX list-clients -F '#{client_name}' | head -1)
|
||||
[ -n "$client" ] || fail "no client for switch-client test"
|
||||
check_ok switch-client -c "$client" -t "$base"
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
check_ok switch-client -c "$client" -t "$over"
|
||||
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
check_ok kill-pane -t "$over"
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
|
||||
ignored=$($TMUX new-pane -ALdPF '#{pane_id}' -t "$base" 'cat') ||
|
||||
fail "new-pane -A -L failed"
|
||||
case "$(fmt "$ignored" '#{pane_flags}')" in
|
||||
*A*) fail "tiled pane flags include A" ;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# Existing floating panes are filtered when zoom begins: -A panes remain in the
|
||||
# visible layout and ordinary floating panes do not.
|
||||
check_ok new-window -d -t modal: -n existing-over-zoom 'cat'
|
||||
base=$(fmt modal:existing-over-zoom '#{pane_id}')
|
||||
check_ok split-window -dh -t "$base" 'cat'
|
||||
over=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "pre-existing new-pane -A failed"
|
||||
under=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 15 -y 4 -X 2 -Y 2 'cat') ||
|
||||
fail "pre-existing ordinary new-pane failed"
|
||||
check_ok select-window -t modal:existing-over-zoom
|
||||
check_ok select-pane -t "$base"
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}')" 1
|
||||
must_equal "$(fmt "$under" '#{pane_floating_flag}')" 0
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
|
||||
# Geometry changed in the visible zoom layout is copied back when unzooming.
|
||||
check_ok select-pane -t "$over"
|
||||
left=$(fmt "$over" '#{pane_left}')
|
||||
top=$(fmt "$over" '#{pane_top}')
|
||||
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
|
||||
new_left=$(fmt "$over" '#{pane_left}')
|
||||
new_top=$(fmt "$over" '#{pane_top}')
|
||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||
fail "float-over-zoom pane did not move"
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
must_equal "$(fmt "$over" '#{pane_left}:#{pane_top}')" \
|
||||
"$new_left:$new_top"
|
||||
must_equal "$(fmt "$under" '#{pane_floating_flag}')" 1
|
||||
|
||||
# Resizing with the over-zoom pane active restores the original zoom target.
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
check_ok select-pane -t "$over"
|
||||
check_ok resize-window -t modal:existing-over-zoom -x 90 -y 30
|
||||
must_equal "$(fmt "$base" \
|
||||
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}')" \
|
||||
90x30:1:1
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_active}')" 1:1
|
||||
|
||||
# Natural pane exit uses a different removal path from kill-pane and must also
|
||||
# preserve zoom.
|
||||
dying=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
|
||||
-x 12 -y 4 -X 4 -Y 3 'true') ||
|
||||
fail "short-lived new-pane -A failed"
|
||||
i=0
|
||||
while $TMUX list-panes -a -F '#{pane_id}' | grep -qx "$dying"; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && fail "short-lived float-over-zoom pane did not exit"
|
||||
sleep 0.1
|
||||
done
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
|
||||
# A pane with -A is also above a zoom target which was itself floating. The
|
||||
# temporary tiled target must sit behind retained floating panes, then return
|
||||
# to the normal floating z order when unzoomed.
|
||||
check_ok new-window -d -t modal: -n floating-zoom-target 'cat'
|
||||
base=$(fmt modal:floating-zoom-target '#{pane_id}')
|
||||
check_ok split-window -dh -t "$base" 'cat'
|
||||
target=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 30 -y 10 -X 10 -Y 5 'cat') ||
|
||||
fail "floating zoom target creation failed"
|
||||
over=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
|
||||
-x 15 -y 5 -X 15 -Y 8 'cat') ||
|
||||
fail "float-over-zoom pane creation failed"
|
||||
check_ok select-window -t modal:floating-zoom-target
|
||||
check_ok select-pane -t "$target"
|
||||
must_equal "$(fmt "$target" '#{pane_z}')" 0
|
||||
must_equal "$(fmt "$over" '#{pane_z}')" 1
|
||||
|
||||
check_ok resize-pane -Z -t "$target"
|
||||
must_equal "$(fmt "$target" \
|
||||
'#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_floating_flag}:#{pane_z}')" \
|
||||
1:1:0:2
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_z}')" 1:0
|
||||
check_ok select-pane -t "$over"
|
||||
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
check_ok resize-pane -Z -t "$target"
|
||||
must_equal "$(fmt "$over" '#{pane_active}:#{pane_z}')" 1:0
|
||||
must_equal "$(fmt "$target" '#{pane_floating_flag}:#{pane_z}')" 1:1
|
||||
|
||||
# If the target remains active, it returns to the front on unzoom.
|
||||
check_ok select-pane -t "$target"
|
||||
check_ok resize-pane -Z -t "$target"
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_z}')" 1:0
|
||||
check_ok resize-pane -Z -t "$target"
|
||||
must_equal "$(fmt "$target" '#{pane_active}:#{pane_z}')" 1:0
|
||||
must_equal "$(fmt "$over" '#{pane_z}')" 1
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
|
||||
0
regress/mode-kill.sh
Executable file → Normal file
0
regress/mode-kill.sh
Executable file → Normal file
@@ -269,6 +269,22 @@ test_customize_mode()
|
||||
assert_alive "customize-mode exit"
|
||||
}
|
||||
|
||||
test_customize_break_pane()
|
||||
{
|
||||
start_client option-break
|
||||
side=$($TMUX split-window -d -P -F '#{pane_id}' \
|
||||
-t option-break:0 'cat') || fail "customize split failed"
|
||||
|
||||
$TMUX customize-mode -t "$side" || fail "customize-mode failed"
|
||||
wait_mode "$side" 1
|
||||
$TMUX break-pane -d -s "$side" || fail "customize break-pane failed"
|
||||
|
||||
assert_alive "customize-mode break-pane"
|
||||
wait_mode "$side" 1
|
||||
$TMUX send-keys -t "$side" q || fail "customize-mode quit failed"
|
||||
wait_mode "$side" 0
|
||||
}
|
||||
|
||||
test_copy_mode()
|
||||
{
|
||||
start_client copy-a 'i=0; while [ $i -lt 200 ]; do echo "copy mutation line $i"; i=$((i + 1)); done; cat'
|
||||
@@ -302,6 +318,7 @@ test_choose_tree
|
||||
test_choose_buffer
|
||||
test_choose_client
|
||||
test_customize_mode
|
||||
test_customize_break_pane
|
||||
test_copy_mode
|
||||
cleanup
|
||||
exit 0
|
||||
|
||||
@@ -49,11 +49,33 @@ drag()
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# right_click COL ROW KEY
|
||||
#
|
||||
# Open the right-click menu at a 1-based position and choose KEY.
|
||||
right_click()
|
||||
{
|
||||
col="$1"
|
||||
row="$2"
|
||||
key="$3"
|
||||
|
||||
seq=$(printf '\033[<2;%s;%sM' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
$TMUX2 send-keys -t "$OUTER" "$key" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
$TMUX new-session -d -s inner -x 80 -y 24 || exit 1
|
||||
$TMUX set -g mouse on
|
||||
$TMUX set -g default-command 'sleep 100'
|
||||
keys=$($TMUX list-keys -T root -F '#{key_command}' MouseDown3Empty) ||
|
||||
fail "list MouseDown3Empty failed"
|
||||
case "$keys" in
|
||||
*"New Pane"*"New Window"*) ;;
|
||||
*) fail "missing empty-area menu binding: $keys" ;;
|
||||
esac
|
||||
|
||||
$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
@@ -80,6 +102,17 @@ $TMUX break-pane -W -s "$BASE" || fail "break base pane failed"
|
||||
$TMUX resize-pane -t "$BASE" -x10 -y4 || fail "resize floating base failed"
|
||||
$TMUX move-pane -t "$BASE" -P top-left || fail "move floating base failed"
|
||||
|
||||
# Right-click in empty window space and choose New Pane. This creates a new
|
||||
# floating pane and tiles it.
|
||||
right_click 20 8 p
|
||||
|
||||
id=$($TMUX list-panes -F '#{?pane_active,#{pane_id},}' | tail -n 1)
|
||||
[ -n "$id" ] || fail "no pane created from empty-area menu"
|
||||
[ "$id" != "$BASE" ] || fail "empty-area menu did not create a new pane"
|
||||
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_floating_flag}')" 0
|
||||
$TMUX kill-pane -t "$id" || fail "kill empty-area menu pane failed"
|
||||
|
||||
# Drag in empty window space with no tiled pane underneath.
|
||||
drag 40 10 50 15
|
||||
|
||||
@@ -92,6 +125,12 @@ must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 10
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 4
|
||||
|
||||
TILED=$($TMUX new-pane -PF '#{pane_id}' 'sleep 100') ||
|
||||
fail "new pane from only floating panes failed"
|
||||
$TMUX join-pane -t "$TILED" ||
|
||||
fail "tile new pane from only floating panes failed"
|
||||
must_equal "$($TMUX display-message -p -t "$TILED" '#{pane_floating_flag}')" 0
|
||||
|
||||
TILED=$($TMUX new-window -dPF '#{pane_id}' 'sleep 100') ||
|
||||
fail "new tiled window failed"
|
||||
$TMUX new-pane -d -M -L -t "$TILED" 'sleep 100' || fail "new-pane -M -L failed"
|
||||
|
||||
@@ -140,6 +140,24 @@ update-environment[notify] EEE"
|
||||
# sorts by ascending numeric key and keeps the gap at [1].
|
||||
check_ok set -g status-format ""
|
||||
check_array "-g status-format" "status-format"
|
||||
out=$($TMUX show -gF \
|
||||
'#{option_name}:#{option_has_value}:#{option_value}:#{option_is_array}:#{option_has_array_key}:#{option_array_key}' \
|
||||
status-format 2>&1)
|
||||
[ "$out" = "status-format:0::1:0:" ] || {
|
||||
echo "show -F empty status-format failed."
|
||||
echo "Expected: 'status-format:0::1:0:'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
}
|
||||
out=$($TMUX show -gvF \
|
||||
'#{option_name}:#{option_value_only}:#{option_has_value}:#{option_value}' \
|
||||
status-format 2>&1)
|
||||
[ "$out" = "status-format:1:0:" ] || {
|
||||
echo "show -vF empty status-format failed."
|
||||
echo "Expected: 'status-format:1:0:'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
}
|
||||
check_ok set -g status-format[5] "five"
|
||||
check_ok set -g status-format[0] "zero"
|
||||
check_ok set -g status-format[2] "two"
|
||||
@@ -154,6 +172,21 @@ status-format[5] five
|
||||
status-format[foo-bar] foo-bar
|
||||
status-format[xterm-256color] xterm
|
||||
status-format[zoom] zoom"
|
||||
out=$($TMUX show -gF \
|
||||
'#{option_name}:#{option_array_key}:#{option_is_array}:#{option_has_value}:#{option_has_array_key}:#{option_value}' \
|
||||
status-format 2>&1)
|
||||
[ "$out" = "$(printf '%s' 'status-format:0:1:1:1:zero
|
||||
status-format:1:1:1:1:one
|
||||
status-format:2:1:1:1:two
|
||||
status-format:5:1:1:1:five
|
||||
status-format:foo-bar:1:1:1:foo-bar
|
||||
status-format:xterm-256color:1:1:1:xterm
|
||||
status-format:zoom:1:1:1:zoom')" ] || {
|
||||
echo "show -F status-format failed."
|
||||
echo "Expected formatted array output"
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
}
|
||||
check_value "-gv status-format[01]" "one"
|
||||
check_ok set -gu status-format[zoom]
|
||||
check_value "-gv status-format[zoom]" ""
|
||||
|
||||
@@ -168,6 +168,30 @@ check_ok set -g @str "foo"
|
||||
check_ok set -ga @str "bar"
|
||||
check_value "-gv @str" "foobar"
|
||||
|
||||
# --- show -F custom format ------------------------------------------------
|
||||
#
|
||||
# show-options and show-hooks use a format for their output, with the default
|
||||
# format preserving the traditional output.
|
||||
check_ok set -g @str "two words"
|
||||
check_value "-g @str" '@str "two words"'
|
||||
out=$($TMUX show -gF \
|
||||
'#{option_name}=#{option_value}=#{option_is_user}=#{option_is_string}=#{option_value_only}' \
|
||||
@str 2>&1)
|
||||
[ "$out" = "@str=two words=1=1=0" ] || {
|
||||
echo "show -F @str failed."
|
||||
echo "Expected: '@str=two words=1=1=0'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
}
|
||||
out=$($TMUX show -gvF '#{option_name}=#{option_value}=#{option_value_only}' \
|
||||
@str 2>&1)
|
||||
[ "$out" = "@str=two words=1" ] || {
|
||||
echo "show -vF @str failed."
|
||||
echo "Expected: '@str=two words=1'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- -F expands at set time -----------------------------------------------
|
||||
#
|
||||
# With -F the value is expanded as a format once, at set time; without -F it is
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
# - respawn-pane/respawn-window refusing a live pane without -k, working on a
|
||||
# dead pane (remain-on-exit) and killing with -k;
|
||||
# - resize-pane -x/-y in cells and percent, -L/-R/-U/-D adjustments and -Z
|
||||
# zoom/unzoom (including implicit unzoom on split).
|
||||
# zoom/unzoom, layout round trips, zoom target selection, pane removal,
|
||||
# window resizing and implicit unzoom on layout changes;
|
||||
# - swap-pane preserving one or two zoomed windows, including cross-window
|
||||
# swaps of hidden panes and zoom targets.
|
||||
#
|
||||
# window-ops.sh covers window-level commands and buffers.sh paste buffers.
|
||||
|
||||
@@ -144,7 +147,8 @@ check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
|
||||
# break-pane and join-pane.
|
||||
|
||||
# break-pane moves a pane to a new window; -P -F prints where it went and -n
|
||||
# names the new window.
|
||||
# names the new window. Moving a pane out of a zoomed window unzooms it first.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
out=$($TMUX break-pane -d -P -F '#{window_index}:#{pane_id}' -n broken \
|
||||
-s "$p1" -t P:)
|
||||
if [ "$out" != "1:$p1" ]; then
|
||||
@@ -153,10 +157,14 @@ if [ "$out" != "1:$p1" ]; then
|
||||
fi
|
||||
check_fmt 'P:1' '#{window_name}:#{window_panes}' 'broken:1'
|
||||
check_fmt 'P:0' '#{window_panes}' '3'
|
||||
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
|
||||
|
||||
# join-pane -v moves it back (the source window, left empty, is destroyed).
|
||||
# join-pane -v moves it back (the source window, left empty, is destroyed) and
|
||||
# also unzooms the destination before changing its layout.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok join-pane -d -v -s P:broken.0 -t "$p2"
|
||||
check_fmt 'P:0' '#{window_panes}' '4'
|
||||
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
|
||||
if $TMUX has-session -t P:broken 2>/dev/null; then
|
||||
echo "Window 'broken' still exists after join-pane."
|
||||
exit 1
|
||||
@@ -192,13 +200,13 @@ check_fail "invalid window name: $(printf 'a\377b')" \
|
||||
break-pane -d -n "$(printf 'a\377b')" -s "$p1" -t P:
|
||||
|
||||
# join-pane can move a pane from one window to another without destroying
|
||||
# the source window if other panes remain. (On this branch move-pane is
|
||||
# reserved for floating panes, covered by floating-pane-geometry.sh.)
|
||||
# the source window if other panes remain. move-pane does the same when no
|
||||
# floating-pane movement flags are given.
|
||||
check_ok new-window -d -t P:5 -n other
|
||||
check_ok join-pane -d -s "$p1" -t P:5.0
|
||||
check_fmt 'P:5' '#{window_panes}' '2'
|
||||
check_fmt 'P:0' '#{window_panes}' '3'
|
||||
check_ok join-pane -d -v -s "$p1" -t "$p2"
|
||||
check_ok move-pane -d -v -s "$p1" -t "$p2"
|
||||
check_fmt 'P:0' '#{window_panes}' '4'
|
||||
check_fmt 'P:5' '#{window_panes}' '1'
|
||||
|
||||
@@ -299,6 +307,122 @@ check_fmt 'P:0' '#{window_panes}' '5'
|
||||
p6=$($TMUX display-message -p -t P:0.2 '#{pane_id}')
|
||||
check_ok kill-pane -t "$p6"
|
||||
|
||||
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
||||
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
||||
layout=$($TMUX display-message -p -t P:0 '#{window_layout}')
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -Z -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
|
||||
# Directional selection temporarily restores the full layout to find its
|
||||
# neighbour, then follows the same unzoom or -Z transfer rules.
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -t "$p0"
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok select-pane -D -t "$p0"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -D -Z -t "$p0"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
|
||||
# The last-pane path has separate zoom handling, both with and without -Z.
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_ok select-pane -l -t P:0
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'0:0:1'
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_ok select-pane -l -Z -t P:0
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
|
||||
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
||||
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
||||
zk0=$($TMUX display-message -p -t P:12.0 '#{pane_id}')
|
||||
zk1=$($TMUX split-window -d -P -F '#{pane_id}' -t P:12.0 'cat')
|
||||
zk2=$($TMUX split-window -d -P -F '#{pane_id}' -t P:12.0 'cat')
|
||||
check_ok resize-pane -Z -t "$zk0"
|
||||
check_ok kill-pane -t "$zk1"
|
||||
check_fmt "$zk0" '#{window_panes}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
|
||||
'2:0:0'
|
||||
check_ok resize-pane -Z -t "$zk0"
|
||||
check_ok kill-pane -t "$zk0"
|
||||
check_fmt "$zk2" '#{window_panes}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
|
||||
'1:0:0'
|
||||
check_ok kill-window -t P:12
|
||||
|
||||
# The same cases through natural process exit exercise server_destroy_pane.
|
||||
check_ok new-window -d -t P:13 -n zoom-exit 'cat'
|
||||
ze0=$($TMUX display-message -p -t P:13.0 '#{pane_id}')
|
||||
ze1=$($TMUX split-window -d -P -F '#{pane_id}' -t P:13.0 'cat')
|
||||
ze2=$($TMUX split-window -d -P -F '#{pane_id}' -t P:13.0 'cat')
|
||||
check_ok resize-pane -Z -t "$ze0"
|
||||
check_ok send-keys -t "$ze1" C-d
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t P:13 '#{window_panes}')" != 2 ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "Hidden pane did not exit."; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
check_fmt "$ze0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
|
||||
check_ok resize-pane -Z -t "$ze2"
|
||||
check_ok send-keys -t "$ze2" C-d
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t P:13 '#{window_panes}')" != 1 ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "Zoomed pane did not exit."; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
check_fmt "$ze0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
|
||||
check_ok kill-window -t P:13
|
||||
|
||||
# resize-window restores both tiled and floating zoom targets after rebuilding
|
||||
# the layout at the new size.
|
||||
check_ok new-window -d -t P:14 -n zoom-resize 'cat'
|
||||
zr0=$($TMUX display-message -p -t P:14.0 '#{pane_id}')
|
||||
check_ok split-window -d -t P:14.0 'cat'
|
||||
check_ok resize-pane -Z -t "$zr0"
|
||||
check_ok resize-window -t P:14 -x 90 -y 30
|
||||
check_fmt "$zr0" \
|
||||
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
|
||||
'90x30:1:1'
|
||||
check_ok resize-pane -Z -t "$zr0"
|
||||
zrf=$($TMUX new-pane -dP -F '#{pane_id}' -t P:14 -x 20 -y 6 'cat')
|
||||
zrf_size=$($TMUX display-message -p -t "$zrf" \
|
||||
'#{pane_width}x#{pane_height}')
|
||||
check_ok resize-pane -Z -t "$zrf"
|
||||
check_ok resize-window -t P:14 -x 100 -y 32
|
||||
check_fmt "$zrf" \
|
||||
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
|
||||
'100x32:1:1'
|
||||
check_ok resize-pane -Z -t "$zrf"
|
||||
check_fmt "$zrf" '#{pane_floating_flag}:#{pane_width}x#{pane_height}' \
|
||||
"1:$zrf_size"
|
||||
check_ok kill-window -t P:14
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# kill-pane.
|
||||
|
||||
@@ -307,8 +431,10 @@ check_ok kill-pane -t "$p3"
|
||||
check_panes P:0 "0:$p0 1:$p2 2:$p1"
|
||||
|
||||
# -a kills every pane except the target.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok kill-pane -a -t "$p0"
|
||||
check_panes P:0 "0:$p0"
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
|
||||
|
||||
# Killing the last pane in a window kills the window.
|
||||
check_ok new-window -d -t P:7 -n goner
|
||||
@@ -516,6 +642,10 @@ check_panes P:3 "0:$r0 1:$r1 2:$r2"
|
||||
# Swapping a pane with itself quietly does nothing.
|
||||
check_ok swap-pane -d -s "$r1" -t "$r1"
|
||||
check_panes P:3 "0:$r0 1:$r1 2:$r2"
|
||||
check_ok resize-pane -Z -t "$r1"
|
||||
check_ok swap-pane -d -Z -s "$r1" -t "$r1"
|
||||
check_fmt "$r1" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
|
||||
check_ok resize-pane -Z -t "$r1"
|
||||
|
||||
# Panes can be swapped between different windows.
|
||||
check_ok swap-pane -d -s "$o0" -t "$r1"
|
||||
@@ -525,10 +655,38 @@ check_ok swap-pane -d -s "$r1" -t "$o0"
|
||||
check_panes P:3 "0:$r0 1:$r1 2:$r2"
|
||||
check_panes P:5 "0:$o0"
|
||||
|
||||
# With both windows zoomed, -Z preserves each window's zoom when hidden panes
|
||||
# are exchanged and when the zoom targets themselves are exchanged.
|
||||
o1=$($TMUX split-window -dP -F '#{pane_id}' -t P:5.0)
|
||||
check_ok resize-pane -Z -t "$r0"
|
||||
check_ok resize-pane -Z -t "$o0"
|
||||
check_ok swap-pane -d -Z -s "$r1" -t "$o1"
|
||||
check_fmt "$r0" \
|
||||
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'swaps:1:1'
|
||||
check_fmt "$o0" \
|
||||
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'other:1:1'
|
||||
check_fmt "$r1" '#{window_name}' 'other'
|
||||
check_fmt "$o1" '#{window_name}' 'swaps'
|
||||
|
||||
check_ok swap-pane -d -Z -s "$r0" -t "$o0"
|
||||
check_fmt "$o0" \
|
||||
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'swaps:1:1'
|
||||
check_fmt "$r0" \
|
||||
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'other:1:1'
|
||||
check_ok resize-pane -Z -t "$o0"
|
||||
check_ok resize-pane -Z -t "$r0"
|
||||
|
||||
# Restore the original panes before the remaining swap tests.
|
||||
check_ok swap-pane -d -s "$r0" -t "$o0"
|
||||
check_ok swap-pane -d -s "$r1" -t "$o1"
|
||||
check_ok kill-pane -t "$o1"
|
||||
check_panes P:3 "0:$r0 1:$r1 2:$r2"
|
||||
check_panes P:5 "0:$o0"
|
||||
|
||||
# -Z keeps the window zoomed across the swap.
|
||||
check_ok resize-pane -Z -t "$r0"
|
||||
check_ok swap-pane -d -Z -s "$r0" -t "$r1"
|
||||
check_fmt 'P:3' '#{window_zoomed_flag}' '1'
|
||||
check_fmt "$r0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
|
||||
check_ok resize-pane -Z -t P:3
|
||||
check_panes P:3 "0:$r1 1:$r0 2:$r2"
|
||||
|
||||
|
||||
52
regress/pipe-pane.sh
Normal file
52
regress/pipe-pane.sh
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of pipe-pane behaviour.
|
||||
|
||||
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 "$1"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
fail "Command failed: $*"
|
||||
fi
|
||||
}
|
||||
|
||||
check_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
|
||||
fail "Server died"
|
||||
fi
|
||||
}
|
||||
|
||||
# A pipe-pane -I child may write after the pane process has exited. With
|
||||
# remain-on-exit, the pane stays around but its bufferevent has been freed.
|
||||
check_ok new-session -d -s pipe -x 80 -y 24 'sleep 1'
|
||||
check_ok set-option -t pipe:0 remain-on-exit on
|
||||
check_ok pipe-pane -t pipe:0.0 -I 'sleep 2; printf x'
|
||||
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t pipe:0.0 '#{pane_dead}')" != "1" ]; do
|
||||
i=$((i + 1))
|
||||
[ "$i" -gt 50 ] && fail "Pane did not die"
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
sleep 2
|
||||
check_alive
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
165
regress/prompt-words-history.sh
Normal file
165
regress/prompt-words-history.sh
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/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
|
||||
87
regress/respawn-pane-control-lag.sh
Normal file
87
regress/respawn-pane-control-lag.sh
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/bin/sh
|
||||
|
||||
# respawn-pane frees the pane's buffer and creates an empty one. The pane
|
||||
# offsets and each control client's offsets used to keep pointing into the old
|
||||
# buffer, so if an attached control client had not caught up on the pane's
|
||||
# output, the next read from the new process ran off the end of the new buffer
|
||||
# and the server crashed in input_parse.
|
||||
#
|
||||
# Two ways for a client to be behind are tested: a control client whose output
|
||||
# goes down a fifo that is never read (a second, healthy control client keeps
|
||||
# the pane being read), and a healthy control client viewing a session that
|
||||
# the pane's window has been moved out of.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
FIFO=$DIR/fifo
|
||||
SERVER=
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
|
||||
cleanup() {
|
||||
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exec 8<&- 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
alive() {
|
||||
if ! kill -0 "$SERVER" 2>/dev/null; then
|
||||
SERVER=
|
||||
echo "server died after $1"
|
||||
exit 1
|
||||
fi
|
||||
$TMUX has -t rt || exit 1
|
||||
}
|
||||
|
||||
wait_clients() {
|
||||
n=0
|
||||
while [ $n -lt 50 ]; do
|
||||
[ "$($TMUX lsc 2>/dev/null | wc -l)" -ge $1 ] && return
|
||||
sleep 0.1
|
||||
n=$((n + 1))
|
||||
done
|
||||
echo "control clients did not attach"; exit 1
|
||||
}
|
||||
|
||||
# A detached session with a pane that writes a line every 10 milliseconds.
|
||||
$TMUX -f/dev/null new -d -x 80 -y 24 -s rt \
|
||||
'while :; do echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; sleep 0.01; done' ||
|
||||
exit 1
|
||||
SERVER=$($TMUX display -pt rt '#{pid}')
|
||||
|
||||
# One control client with output down the unread fifo and one reading
|
||||
# normally; stdin of both held open by sleep so they stay attached.
|
||||
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >"$FIFO" 2>&1 &
|
||||
exec 8<"$FIFO"
|
||||
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >/dev/null 2>&1 &
|
||||
wait_clients 2
|
||||
|
||||
# Let the first client fall behind, then respawn the pane. The new process
|
||||
# writes at once and the server must survive reading it.
|
||||
sleep 3
|
||||
$TMUX respawn-pane -k -t rt:0 'echo respawned; sleep 60' || exit 1
|
||||
sleep 1
|
||||
alive "respawn-pane with a lagging control client"
|
||||
|
||||
# Now a pane whose window is moved out of the session both clients view: their
|
||||
# offsets for it stop advancing, so they fall behind until it is respawned.
|
||||
$TMUX neww -d -t rt \
|
||||
'while :; do echo yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy; sleep 0.01; done' ||
|
||||
exit 1
|
||||
sleep 1
|
||||
$TMUX new -d -s other || exit 1
|
||||
$TMUX movew -d -s rt:1 -t other: || exit 1
|
||||
sleep 2
|
||||
$TMUX respawn-pane -k -t other:1 'echo respawned; sleep 60' || exit 1
|
||||
sleep 1
|
||||
alive "respawn-pane on a window moved out of the clients' session"
|
||||
|
||||
exit 0
|
||||
@@ -11,7 +11,7 @@ TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "rm -f $TMP" 0 1 15
|
||||
trap "$TMUX kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX -f/dev/null new -d "$TMUX run 'echo foo' >$TMP; sleep 10" || exit 1
|
||||
sleep 1 && [ "$(cat $TMP)" = "foo" ] || exit 1
|
||||
@@ -20,6 +20,15 @@ $TMUX -f/dev/null new -d "$TMUX run -t: 'echo foo' >$TMP; sleep 10" || exit 1
|
||||
sleep 1 && [ "$(cat $TMP)" = "" ] || exit 1
|
||||
[ "$($TMUX display -p '#{pane_mode}')" = "view-mode" ] || exit 1
|
||||
|
||||
$TMUX -f/dev/null new -d -s t1 'sleep 10' || exit 1
|
||||
$TMUX -f/dev/null run -d 1 'echo delayed' >$TMP 2>&1 &
|
||||
pid=$!
|
||||
sleep 0.2
|
||||
kill -9 "$pid" 2>/dev/null
|
||||
wait "$pid" 2>/dev/null
|
||||
sleep 2
|
||||
$TMUX has-session -t t1 || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
exit 0
|
||||
|
||||
63
regress/screen-redraw-fill-character.sh
Normal file
63
regress/screen-redraw-fill-character.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise fill-character as a format. The window is smaller than the attached
|
||||
# client so OUTSIDE spans exist, then the only tiled pane is removed so EMPTY
|
||||
# spans exist inside the window around a floating pane.
|
||||
|
||||
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"
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
|
||||
0 1 15
|
||||
|
||||
fail() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
must_equal() {
|
||||
if [ "$1" != "$2" ]; then
|
||||
fail "expected '$2', got '$1'"
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
$TMUX2 new -d -x40 -y12 "sh -c 'printf base; exec sleep 100'" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -g window-size manual || exit 1
|
||||
$TMUX2 setw fill-character '#{?is_inside,I,#{?is_outside,O,X}}' || exit 1
|
||||
$TMUX2 resizew -x28 -y8 || exit 1
|
||||
$TMUX2 new-pane -x12 -y4 -X8 -Y2 "sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
tiled=$($TMUX2 list-panes -F '#{pane_floating_flag} #{pane_id}' | \
|
||||
awk '$1==0{print $2; exit}') || exit 1
|
||||
$TMUX2 kill-pane -t "$tiled" || exit 1
|
||||
|
||||
$TMUX new -d -x40 -y12 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX set -g window-size manual || exit 1
|
||||
$TMUX set -g default-terminal "tmux-256color" || exit 1
|
||||
$TMUX send -l "$TMUX2 attach" || exit 1
|
||||
$TMUX send Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
|
||||
must_equal "$(sed -n '1p' $TMP | cut -c1-28)" \
|
||||
"IIIIIIIIIIIIIIIIIIIIIIIIIIII"
|
||||
must_equal "$(sed -n '1p' $TMP | cut -c29-40)" "OOOOOOOOOOOO"
|
||||
must_equal "$(sed -n '9p' $TMP)" "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"
|
||||
|
||||
if grep -q X "$TMP"; then
|
||||
fail "fill-character used neither inside nor outside"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -215,9 +215,9 @@ $TMUX2 new-pane -x20 -y6 -X8 -Y7 "sh -c 'printf OVERST; exec sleep 100'" || exit
|
||||
compare floating-over-status
|
||||
|
||||
# A window left with only a floating pane: killing the single tiled pane removes
|
||||
# it from the layout, so the area it occupied is no longer owned by any pane and
|
||||
# is drawn as EMPTY cells (middle dots) around the float. This is the only way to
|
||||
# produce a REDRAW_SPAN_EMPTY span.
|
||||
# it from the layout, so the area it occupied is no longer owned by any pane.
|
||||
# Use a visible fill character for the EMPTY cells around the float.
|
||||
$TMUX2 set -g fill-character '#[acs]~' || exit 1
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y3 "sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
tiled=$($TMUX2 list-panes -F '#{pane_floating_flag} #{pane_id}' | \
|
||||
|
||||
96
regress/screen-redraw-scrollbars-auto-hide.sh
Normal file
96
regress/screen-redraw-scrollbars-auto-hide.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/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
|
||||
112
regress/server-access.sh
Normal file
112
regress/server-access.sh
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/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
|
||||
49
regress/server-socket-error.sh
Normal file
49
regress/server-socket-error.sh
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Server socket creation failures must produce a nonzero client exit status.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
# Root bypasses directory mode bits, so it cannot trigger this failure.
|
||||
[ "$(id -u)" -eq 0 ] && exit 0
|
||||
|
||||
TMP=$(mktemp -d) || exit 1
|
||||
SOCKET="$TMP/ro/socket"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
chmod 700 "$TMP/ro" 2>/dev/null
|
||||
rm -rf "$TMP"
|
||||
}
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
mkdir "$TMP/ro" || exit 1
|
||||
chmod 500 "$TMP/ro" || exit 1
|
||||
trap cleanup 0 1 15
|
||||
|
||||
check_failure()
|
||||
{
|
||||
output=$($TEST_TMUX -S "$SOCKET" -f/dev/null "$@" 2>&1)
|
||||
retval=$?
|
||||
|
||||
[ "$retval" -ne 0 ] || fail "tmux $* exited zero: $output"
|
||||
case "$output" in
|
||||
"error creating $SOCKET ("*) ;;
|
||||
*) fail "tmux $* produced unexpected error: $output" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_failure start-server
|
||||
check_failure new-session -d
|
||||
|
||||
exit 0
|
||||
@@ -67,10 +67,25 @@ shown=$($TMUX show-hooks -g -B @session-name) ||
|
||||
fail "show-hooks -B failed"
|
||||
[ "$shown" = '@session-name::#{session_name}' ] ||
|
||||
fail "unexpected show-hooks -B output: $shown"
|
||||
shown=$($TMUX show-hooks -g -BF \
|
||||
'#{option_name}:#{hook_monitor_target}:#{hook_monitor_format}:#{option_value}:#{option_is_hook}:#{option_is_user}' \
|
||||
@session-name) ||
|
||||
fail "show-hooks -BF failed"
|
||||
[ "$shown" = '@session-name::#{session_name}:@session-name::#{session_name}:1:1' ] ||
|
||||
fail "unexpected show-hooks -BF output: $shown"
|
||||
shown=$($TMUX show-hooks -g) ||
|
||||
fail "show-hooks -g failed"
|
||||
echo "$shown" | grep -q '^@session-name ' ||
|
||||
fail "show-hooks -g did not show monitor hook: $shown"
|
||||
assert_unchanged @seen 0
|
||||
|
||||
$TMUX rename-session two || fail "rename-session two failed"
|
||||
wait_for @seen '@session-name:two'
|
||||
shown=$($TMUX show-hooks -g -BF '#{hook_fire_count}:#{t/p:hook_fire_time}' \
|
||||
@session-name) ||
|
||||
fail "show-hooks -BF fire failed"
|
||||
echo "$shown" | grep -q '^1:[^-][^ ]*$' ||
|
||||
fail "unexpected show-hooks -BF fire output: $shown"
|
||||
|
||||
$TMUX set -g @seen-last 0 || fail "set @seen-last failed"
|
||||
$TMUX set-hook -g -B '@session-name::#{session_name}' \
|
||||
@@ -112,6 +127,11 @@ $TMUX set -g @pane-seen 0 || fail "set @pane-seen failed"
|
||||
$TMUX set-hook -g -B "@pane:%$pane_number:#{pane_width}" \
|
||||
'set -g @pane-seen "#{hook_session}:#{hook_window}:#{hook_window_index}:#{hook_pane}:#{hook_value}"' ||
|
||||
fail "set-hook -B pane selector failed"
|
||||
shown=$($TMUX show-hooks -g -BF \
|
||||
'#{option_name}:#{hook_monitor_target}:#{hook_monitor_format}' @pane) ||
|
||||
fail "show-hooks -BF pane failed"
|
||||
[ "$shown" = "@pane:%$pane_number:#{pane_width}" ] ||
|
||||
fail "unexpected show-hooks -BF pane output: $shown"
|
||||
assert_unchanged @pane-seen 0
|
||||
$TMUX set-hook -g -B "@pane:%$pane_number:#{@pane-value}" \
|
||||
'set -g @pane-seen "#{hook_session}:#{hook_window}:#{hook_window_index}:#{hook_pane}:#{hook_value}"' ||
|
||||
|
||||
0
regress/set-hook-E.sh
Executable file → Normal file
0
regress/set-hook-E.sh
Executable file → Normal file
136
regress/show-options-output.sh
Normal file
136
regress/show-options-output.sh
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the default output from show-options and show-hooks. This is
|
||||
# intended to guard the output compatibility when the implementation is changed
|
||||
# to use formats internally, so it deliberately does not use show -F.
|
||||
|
||||
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
|
||||
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$(printf '%s' "$2")" ]; then
|
||||
echo "show $1 failed."
|
||||
echo "Expected:"; printf '%s\n' "$2"
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_hooks()
|
||||
{
|
||||
out=$($TMUX show-hooks $1 2>&1)
|
||||
if [ "$out" != "$(printf '%s' "$2")" ]; then
|
||||
echo "show-hooks $1 failed."
|
||||
echo "Expected:"; printf '%s\n' "$2"
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# Scalar options: strings are escaped, non-strings are not, and -v suppresses
|
||||
# the option name.
|
||||
check_ok set -g @words "two words"
|
||||
check_value "-g @words" '@words "two words"'
|
||||
check_value "-gv @words" 'two words'
|
||||
check_ok set -g display-time 1234
|
||||
check_value "-g display-time" 'display-time 1234'
|
||||
check_value "-gv display-time" '1234'
|
||||
|
||||
# Inherited options shown with -A are marked with an asterisk.
|
||||
check_ok set -g status-left "GLOBAL"
|
||||
check_ok set -u status-left
|
||||
out=$($TMUX show -A 2>/dev/null | grep '^status-left\*')
|
||||
if [ "$out" != "status-left* GLOBAL" ]; then
|
||||
echo "show -A did not mark inherited status-left."
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Arrays: an empty array prints just the name unless -v is used; populated
|
||||
# arrays include every key, including key 0.
|
||||
check_ok set -g status-format ""
|
||||
check_value "-g status-format" 'status-format'
|
||||
check_value "-gv status-format" ''
|
||||
out=$($TMUX show -A 2>/dev/null | grep '^status-format\*')
|
||||
if [ "$out" != "status-format*" ]; then
|
||||
echo "show -A did not mark inherited empty status-format."
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
fi
|
||||
check_value "-A status-format" 'status-format'
|
||||
check_ok set -g update-environment "AAA BBB,CCC"
|
||||
check_value "-g update-environment" 'update-environment[0] AAA
|
||||
update-environment[1] BBB
|
||||
update-environment[2] CCC'
|
||||
check_value "-gv update-environment[0]" 'AAA'
|
||||
|
||||
# Hooks use the same array output style, and monitor hooks have their own
|
||||
# default output form.
|
||||
check_ok set-hook -g window-renamed[first] "display-message renamed"
|
||||
check_hooks "-g window-renamed" 'window-renamed[first] display-message renamed'
|
||||
check_ok set-hook -g -B '@monitor:%*:#{pane_width}'
|
||||
check_hooks "-g -B @monitor" '@monitor:%*:#{pane_width}'
|
||||
check_ok set-hook -g @user-hook "display-message user"
|
||||
check_hooks "-g @user-hook" '@user-hook "display-message user"'
|
||||
out=$($TMUX show -g 2>&1)
|
||||
echo "$out" | grep -q "^@monitor ''$" && {
|
||||
echo "show -g showed monitor hook without -H."
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
}
|
||||
echo "$out" | grep -q '^@user-hook "display-message user"$' && {
|
||||
echo "show -g showed user hook without -H."
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
}
|
||||
out=$($TMUX show -gH 2>&1)
|
||||
echo "$out" | grep -q "^@monitor ''$" || {
|
||||
echo "show -gH did not show monitor hook."
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
}
|
||||
echo "$out" | grep -q '^@user-hook "display-message user"$' || {
|
||||
echo "show -gH did not show user hook."
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
}
|
||||
out=$($TMUX show-hooks -g 2>&1)
|
||||
echo "$out" | grep -q "^@monitor ''$" || {
|
||||
echo "show-hooks -g did not show monitor hook."
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
}
|
||||
echo "$out" | grep -q '^@user-hook "display-message user"$' || {
|
||||
echo "show-hooks -g did not show user hook."
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_alive "after show-options output tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
203
regress/switch-mode.sh
Normal file
203
regress/switch-mode.sh
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise switch-mode's session and window lists with a real attached client:
|
||||
# fuzzy filtering, movement and selection, the default and custom commands,
|
||||
# no-match cancellation, resize, zoom restoration, and -k mode teardown.
|
||||
|
||||
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_count()
|
||||
{
|
||||
marker=$1
|
||||
want=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
count=$(printf '%s\n' "$captured" | grep -Fc "$marker")
|
||||
[ "$count" -eq "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "found $count '$marker' rows, expected $want"
|
||||
}
|
||||
|
||||
wait_format()
|
||||
{
|
||||
target=$1
|
||||
format=$2
|
||||
want=$3
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t "$target" "$format" \
|
||||
2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "$target format $format is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_client_session()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER list-clients -F '#{client_session}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client session is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
# Three alphabetically ordered sessions. alpha:main has two panes so -Z can be
|
||||
# observed; the two pick-* windows make a deterministic two-row window filter.
|
||||
$INNER new-session -d -s alpha -n main -x60 -y15 'exec sleep 100' || exit 1
|
||||
$INNER split-window -d -h -t alpha:main 'exec sleep 100' || exit 1
|
||||
$INNER new-window -d -t alpha: -n pick-one 'exec sleep 100' || exit 1
|
||||
$INNER new-session -d -s bravo -n pick-two -x60 -y15 \
|
||||
'exec sleep 100' || exit 1
|
||||
$INNER new-session -d -s charlie -n last -x60 -y15 \
|
||||
'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER select-window -t alpha:main || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x60 -y15 "$INNER attach -t alpha" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_client_session alpha
|
||||
|
||||
# Session rows are sorted alpha, bravo, charlie. Up from the first row wraps to
|
||||
# charlie; Enter runs a custom command with the selected session target.
|
||||
$INNER set-option -g @picked '' || exit 1
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'SESSION #{session_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_count 'SESSION ' 3
|
||||
$INNER send-keys -t alpha:main.0 Up Enter || exit 1
|
||||
wait_format alpha:main.0 '#{@picked}' '=charlie:'
|
||||
wait_format alpha:main.0 '#{pane_in_mode}' 0
|
||||
|
||||
# Fuzzy filtering keeps only bravo for "brv". The default command switches the
|
||||
# real client to that session and closes the mode.
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'SESSION #{session_name}' || exit 1
|
||||
wait_count 'SESSION ' 3
|
||||
$INNER send-keys -t alpha:main.0 -l brv || exit 1
|
||||
wait_count 'SESSION ' 1
|
||||
printf '%s\n' "$captured" | grep -Fq 'SESSION bravo' ||
|
||||
fail "fuzzy session filter did not select bravo"
|
||||
$INNER send-keys -t alpha:main.0 Enter || exit 1
|
||||
wait_client_session bravo
|
||||
client=$($INNER list-clients -F '#{client_name}')
|
||||
$INNER switch-client -c "$client" -t alpha || exit 1
|
||||
wait_client_session alpha
|
||||
|
||||
# Window mode sees pick-one then pick-two for the "pick" filter. Down selects
|
||||
# the second row, and the custom command receives its window target.
|
||||
$INNER set-option -g @picked '' || exit 1
|
||||
$INNER switch-mode -w -t alpha:main.0 -F \
|
||||
'WINDOW #{session_name}:#{window_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_capture 'WINDOW '
|
||||
$INNER send-keys -t alpha:main.0 -l pick || exit 1
|
||||
wait_count 'WINDOW ' 2
|
||||
$INNER send-keys -t alpha:main.0 Down Enter || exit 1
|
||||
picked=$($INNER show-option -gv @picked)
|
||||
case "$picked" in
|
||||
=bravo:0.) ;;
|
||||
*) fail "window selection produced '$picked', expected '=bravo:0.'" ;;
|
||||
esac
|
||||
|
||||
# Navigation with an empty result set must be harmless, and Escape cancels
|
||||
# without running the custom command.
|
||||
$INNER set-option -g @picked unchanged || exit 1
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'NONE #{session_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_capture 'NONE '
|
||||
$INNER send-keys -t alpha:main.0 -l no-such-session || exit 1
|
||||
wait_count 'NONE ' 0
|
||||
$INNER send-keys -t alpha:main.0 Up Down PPage NPage Home End || exit 1
|
||||
wait_format alpha:main.0 '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{pane_in_mode}' 0
|
||||
[ "$($INNER show-option -gv @picked)" = unchanged ] ||
|
||||
fail "cancelled switch-mode ran its command"
|
||||
|
||||
# -Z temporarily zooms an unzoomed window. Resizing while the mode is open
|
||||
# rebuilds and redraws it; exiting restores the original unzoomed state.
|
||||
$INNER switch-mode -Zs -t alpha:main.0 -F 'RESIZE #{session_name}' || exit 1
|
||||
wait_capture 'RESIZE alpha'
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 1
|
||||
$INNER resize-window -t alpha:main -x50 -y12 || exit 1
|
||||
wait_format alpha:main.0 '#{window_width}x#{window_height}' 50x12
|
||||
wait_format alpha:main.0 '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 0
|
||||
|
||||
# If the window was already zoomed, leaving -Z mode must keep it zoomed.
|
||||
$INNER resize-pane -Z -t alpha:main.0 || exit 1
|
||||
$INNER switch-mode -Zs -t alpha:main.0 -F 'ZOOMED #{session_name}' || exit 1
|
||||
wait_capture 'ZOOMED alpha'
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 1
|
||||
$INNER resize-pane -Z -t alpha:main.0 || exit 1
|
||||
|
||||
# -k uses the shared mode teardown flag. Put it on a disposable pane and make
|
||||
# sure exiting the mode removes that pane.
|
||||
kill_pane=$($INNER split-window -d -P -F '#{pane_id}' -t alpha:main \
|
||||
'exec sleep 100') || exit 1
|
||||
$INNER switch-mode -ks -t "$kill_pane" -F 'KILL #{session_name}' || exit 1
|
||||
wait_format "$kill_pane" '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t "$kill_pane" Escape || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER list-panes -s -t alpha -F '#{pane_id}' 2>/dev/null | \
|
||||
grep -q -x "$kill_pane" || break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "switch-mode -k did not kill its pane"
|
||||
|
||||
exit 0
|
||||
173
regress/sync-output-atomic.sh
Normal file
173
regress/sync-output-atomic.sh
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Application synchronized updates must remain one physical client transaction,
|
||||
# including when BSU, printable cells and ESU arrive in one pane read.
|
||||
|
||||
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 -Li$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
|
||||
CLIENT_BYTES=$DIR/client-bytes
|
||||
CONTROL=$DIR/control
|
||||
EMITTER=$DIR/emitter.pl
|
||||
ASSERT=$DIR/assert-sync-output.pl
|
||||
|
||||
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
|
||||
$INNER list-clients -F '#{client_termfeatures}' 2>/dev/null |
|
||||
grep -q 'sync' && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "sync-capable client did not attach"
|
||||
}
|
||||
|
||||
wait_for_marker()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
grep -q 'FRAME_000001_ROW_23' "$CLIENT_BYTES" 2>/dev/null && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client did not receive both synchronized frames"
|
||||
}
|
||||
|
||||
wait_for_stable_bytes()
|
||||
{
|
||||
previous=-1
|
||||
stable=0
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
current=$(wc -c <"$CLIENT_BYTES" 2>/dev/null) || current=0
|
||||
if [ "$current" -gt 0 ] && [ "$current" -eq "$previous" ]; then
|
||||
stable=$((stable + 1))
|
||||
[ "$stable" -eq 5 ] && return 0
|
||||
else
|
||||
stable=0
|
||||
fi
|
||||
previous=$current
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client byte stream did not become stable"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $control = $ENV{CONTROL};
|
||||
(my $dir = $control) =~ s{/[^/]+$}{};
|
||||
open my $ready, '>', "$dir/ready" or die "$dir/ready: $!\n";
|
||||
close $ready;
|
||||
while (!-e $control) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
for my $frame (0 .. 1) {
|
||||
my @rows;
|
||||
for my $row (0 .. 23) {
|
||||
my $marker = sprintf "FRAME_%06d_ROW_%02d_", $frame, $row;
|
||||
push @rows, substr($marker . ('X' x 79), 0, 79);
|
||||
}
|
||||
my $frame = "\e[?2026h\e[H" . join("\r\n", @rows) .
|
||||
"\e[?2026l";
|
||||
my $written = syswrite STDOUT, $frame;
|
||||
die "short synchronized frame write\n"
|
||||
unless defined $written && $written == length $frame;
|
||||
select undef, undef, undef, 0.2;
|
||||
}
|
||||
select undef, undef, undef, 2;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 80 -y 24 \
|
||||
"CONTROL='$CONTROL' perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -as terminal-features '*:sync' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 80 -y 24 \
|
||||
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_for_client
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ] && [ ! -e "$DIR/ready" ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -e "$DIR/ready" ] || fail "application emitter did not become ready"
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$CLIENT_BYTES'" || exit 1
|
||||
: >"$CONTROL"
|
||||
wait_for_marker
|
||||
wait_for_stable_bytes
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
|
||||
cat >"$ASSERT" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $path = shift;
|
||||
open my $fh, '<:raw', $path or die "$path: $!\n";
|
||||
local $/;
|
||||
my $bytes = <$fh>;
|
||||
my ($on, $off) = ("\e[?2026h", "\e[?2026l");
|
||||
my ($active, %seen);
|
||||
|
||||
for (my $i = 0; $i < length($bytes);) {
|
||||
if (substr($bytes, $i, length($on)) eq $on) {
|
||||
die "duplicate synchronized-output start at byte $i\n" if $active;
|
||||
$active = 1;
|
||||
$i += length($on);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($off)) eq $off) {
|
||||
die "unmatched synchronized-output end at byte $i\n" unless $active;
|
||||
$active = 0;
|
||||
$i += length($off);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i) =~ /\AFRAME_(\d{6})_ROW_(\d{2})_/) {
|
||||
my $marker = "$1:$2";
|
||||
die "marker $marker outside synchronized output\n" unless $active;
|
||||
$seen{$marker}++;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
die "unterminated synchronized-output transaction\n" if $active;
|
||||
for my $frame (0 .. 1) {
|
||||
for my $row (0 .. 23) {
|
||||
my $marker = sprintf "%06d:%02d", $frame, $row;
|
||||
die "missing marker $marker\n" unless $seen{$marker};
|
||||
}
|
||||
}
|
||||
PERL
|
||||
|
||||
perl "$ASSERT" "$CLIENT_BYTES" || exit 1
|
||||
160
regress/sync-output-cursor.sh
Normal file
160
regress/sync-output-cursor.sh
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A synchronized update elsewhere in the pane must not hide a stationary
|
||||
# cursor while tmux waits for the end of the update.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
TEST_TMUX="$TEST_TMUX" sh "$0" pane || exit 1
|
||||
TEST_TMUX="$TEST_TMUX" sh "$0" prompt || exit 1
|
||||
exit 0
|
||||
fi
|
||||
case $1 in
|
||||
pane|prompt) MODE=$1 ;;
|
||||
*) echo "usage: $0 [pane|prompt]" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
|
||||
INNER="$TEST_TMUX -Li$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
|
||||
CLIENT_BYTES=$DIR/client-bytes
|
||||
CONTROL=$DIR/control
|
||||
EMITTER=$DIR/emitter.pl
|
||||
ASSERT=$DIR/assert-cursor.pl
|
||||
|
||||
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
|
||||
$INNER list-clients -F '#{client_termfeatures}' 2>/dev/null |
|
||||
grep -q 'sync' && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "sync-capable client did not attach"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $control = $ENV{CONTROL};
|
||||
(my $dir = $control) =~ s{/[^/]+$}{};
|
||||
|
||||
# Draw a prompt and leave its cursor stationary on the last line.
|
||||
syswrite STDOUT, "\e[2J\e[HWorking .\e[24;1H> ";
|
||||
open my $ready, '>', "$dir/ready" or die "$dir/ready: $!\n";
|
||||
close $ready;
|
||||
while (!-e $control) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
# Deliberately split BSU, content, and ESU across pane reads. This mirrors an
|
||||
# animation producer which begins a frame before it has finished rendering it.
|
||||
for my $spinner (qw(| / - \\)) {
|
||||
syswrite STDOUT, "\e[?2026h";
|
||||
select undef, undef, undef, 0.05;
|
||||
syswrite STDOUT, "\e[1;9H$spinner\e[24;3H";
|
||||
select undef, undef, undef, 0.01;
|
||||
syswrite STDOUT, "\e[?2026l";
|
||||
select undef, undef, undef, 0.15;
|
||||
}
|
||||
select undef, undef, undef, 5;
|
||||
PERL
|
||||
|
||||
cat >"$ASSERT" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $path = shift;
|
||||
open my $fh, '<:raw', $path or die "$path: $!\n";
|
||||
local $/;
|
||||
my $bytes = <$fh>;
|
||||
my ($bsu, $esu, $hide, $show) =
|
||||
("\e[?2026h", "\e[?2026l", "\e[?25l", "\e[?25h");
|
||||
my ($sync, $outside, $hides, $shows) = (0, 0, 0, 0);
|
||||
|
||||
for (my $i = 0; $i < length($bytes);) {
|
||||
if (substr($bytes, $i, length($bsu)) eq $bsu) {
|
||||
$sync++;
|
||||
$i += length($bsu);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($esu)) eq $esu) {
|
||||
$sync-- if $sync;
|
||||
$i += length($esu);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($hide)) eq $hide) {
|
||||
$hides++;
|
||||
$outside++ unless $sync;
|
||||
$i += length($hide);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($show)) eq $show) {
|
||||
$shows++;
|
||||
$outside++ unless $sync;
|
||||
$i += length($show);
|
||||
next;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
printf "%s cursor hides: %d; cursor shows: %d; changes outside sync: %d\n",
|
||||
$ENV{MODE}, $hides, $shows, $outside;
|
||||
die "stationary cursor changed outside synchronized output\n" if $outside;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 80 -y 24 \
|
||||
"CONTROL='$CONTROL' perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -as terminal-features '*:sync' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 80 -y 24 \
|
||||
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_for_client
|
||||
|
||||
if [ "$MODE" = prompt ]; then
|
||||
$OUTER send-keys -t outer:0.0 C-b : || exit 1
|
||||
sleep 0.2
|
||||
fi
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ] && [ ! -e "$DIR/ready" ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -e "$DIR/ready" ] || fail "application emitter did not become ready"
|
||||
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$CLIENT_BYTES'" || exit 1
|
||||
: >"$CONTROL"
|
||||
sleep 2
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
|
||||
MODE=$MODE perl "$ASSERT" "$CLIENT_BYTES"
|
||||
43
regress/tab-cell-background.sh
Normal file
43
regress/tab-cell-background.sh
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
|
||||
# When a write breaks up a wide character or a tab, the columns it covered must
|
||||
# keep the background colour, both in the grid and on the terminal. The second
|
||||
# server is attached to the first so that what is actually sent is checked, not
|
||||
# just what tmux has stored.
|
||||
|
||||
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
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "rm -f $TMP" 0 1 15
|
||||
|
||||
# 1. Write inside a tab. 2. Overwrite the right half of a wide character.
|
||||
# 3. Two tabs of different colours, the first partly deleted so that padding
|
||||
# from both ends up next to each other, then written over.
|
||||
# The sleep is so that the client is attached before anything is written and
|
||||
# the changes are sent as they happen rather than as one redraw.
|
||||
$TMUX2 -f/dev/null new -d -x24 -y3 "
|
||||
sleep 2
|
||||
printf '\033[44m\033[K\t\033[4GX'
|
||||
printf '\033[2;1H\033[44m\033[K\344\270\226\033[2GX'
|
||||
printf '\033[3;1H\033[42m\033[K\t\033[43m\033[K\t\033[0m\033[4G\033[8P\rx'
|
||||
cat" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
|
||||
$TMUX -f/dev/null new -x24 -y4 -d "$TMUX2 attach" || exit 1
|
||||
sleep 4
|
||||
|
||||
$TMUX capturep -pe -S0 -E2 >$TMP || exit 1
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
printf '\033[44m X\n X\n\033[49mx\033[42m \033[43m \033[49m\n' |
|
||||
cmp - $TMP || exit 1
|
||||
|
||||
exit 0
|
||||
58
regress/terminal-feature-utf8.sh
Normal file
58
regress/terminal-feature-utf8.sh
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
|
||||
# The utf8 terminal feature must mark an attached client as UTF-8 even when
|
||||
# neither its environment nor -u does so. It must also be reported by the
|
||||
# client terminal-feature format lookup.
|
||||
|
||||
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
|
||||
|
||||
cleanup
|
||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
|
||||
# A clean C-locale environment avoids the ordinary UTF-8 detection paths.
|
||||
# -T utf8 is therefore solely responsible for setting client_utf8.
|
||||
client_command="env -i PATH=/bin:/usr/bin TERM=screen LC_ALL=C $TEST_TMUX -T utf8 -LtestI$$ -f/dev/null attach-session -t inner"
|
||||
$OUTER new-session -d -s outer -x40 -y10 "$client_command" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
client=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
utf8=$($INNER list-clients -F '#{client_utf8}' 2>/dev/null)
|
||||
[ -n "$client" ] && [ "$utf8" = 1 ] && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "utf8 terminal feature did not set client_utf8"
|
||||
|
||||
features=$($INNER list-clients -F '#{client_termfeatures}')
|
||||
case ",$features," in
|
||||
*,utf8,*) ;;
|
||||
*) fail "utf8 is missing from client_termfeatures: $features" ;;
|
||||
esac
|
||||
|
||||
present=$($INNER display-message -c "$client" -p '#{I/f:utf8}')
|
||||
[ "$present" = 1 ] || fail "utf8 terminal feature lookup returned '$present'"
|
||||
|
||||
exit 0
|
||||
0
regress/theme-report.sh
Executable file → Normal file
0
regress/theme-report.sh
Executable file → Normal file
@@ -34,6 +34,18 @@ captureen() {
|
||||
$TMUX capturep -peNS0 -E- >$TMP || exit 1
|
||||
}
|
||||
|
||||
timed() {
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout 5 "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
capture_timed() {
|
||||
timed $TMUX capturep -pS0 -E- >$TMP || exit 1
|
||||
}
|
||||
|
||||
check_line() {
|
||||
line=$1
|
||||
want=$2
|
||||
@@ -81,6 +93,8 @@ $TMUX2 neww -d \
|
||||
"printf '\033(0x\033(B'; exec sleep 100" || exit 1
|
||||
$TMUX2 neww -d \
|
||||
"awk 'BEGIN { for (i = 0; i < 1100; i++) printf \"a\" }'; exec sleep 100" || exit 1
|
||||
$TMUX2 neww -d \
|
||||
"printf 'u\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245\314\245'; exec sleep 100" || exit 1
|
||||
$TMUX2 selectw -t:0 || exit 1
|
||||
|
||||
$TMUX -f/dev/null new -d -x20 -y6 || exit 1
|
||||
@@ -91,6 +105,21 @@ sleep 1
|
||||
CLIENT=$($TMUX2 list-clients -F '#{client_name}' | head -1)
|
||||
[ -n "$CLIENT" ] || fail "no inner client"
|
||||
|
||||
# A variation selector which widens the previous character must use window
|
||||
# coordinates when checking visibility in a pane with a nonzero x offset.
|
||||
$TMUX2 set -s variation-selector-always-wide on || exit 1
|
||||
WINDOW=$($TMUX2 neww -dPF '#{window_id}' "exec sleep 100") || exit 1
|
||||
PANE=$($TMUX2 splitw -dhPF '#{pane_id}' -t "$WINDOW" \
|
||||
"exec sleep 100") || exit 1
|
||||
$TMUX2 selectw -t "$WINDOW" || exit 1
|
||||
$TMUX2 respawnp -k -t "$PANE" \
|
||||
"printf '\nA\342\234\217\357\270\217B'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX2 capturep -p -t "$PANE" >$TMP || exit 1
|
||||
EXPECTED=$(printf 'A\342\234\217\357\270\217B')
|
||||
check_line 2 "$EXPECTED"
|
||||
$TMUX2 killw -t "$WINDOW" || exit 1
|
||||
|
||||
# Long line, then short line: default cells after cellsize must clear stale text.
|
||||
capture
|
||||
check_line 1 "abcdefghijklmnopqrst"
|
||||
@@ -245,4 +274,12 @@ capture
|
||||
len=$(sed -n 1p $TMP | wc -c)
|
||||
[ "$len" -ge 1100 ] || fail "long same-style line was truncated"
|
||||
|
||||
# Too many combining marks on one base character must not leave a standalone
|
||||
# width-zero cell that can make tty_draw_line loop forever on redraw.
|
||||
$TMUX resizew -x20 -y6 || exit 1
|
||||
timed $TMUX2 selectw -t:13 || fail "zero-width overflow select hung"
|
||||
sleep 1
|
||||
capture_timed
|
||||
check_grep '^u'
|
||||
|
||||
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