mirror of
https://github.com/tmux/tmux.git
synced 2026-08-03 14:08:59 +00:00
Merge remote-tracking branch 'origin/master' into 5211-fix-select-layout-list-windows
# Conflicts: # layout-custom.c
This commit is contained in:
79
.github/workflows/regress.yml
vendored
Normal file
79
.github/workflows/regress.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: 'Run Tests'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '33 3 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: tmux-tests
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
regress:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 45
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: ubuntu-24.04-x64
|
||||
runner: ubuntu-24.04
|
||||
make: make
|
||||
configure: --enable-utf8proc --enable-asan
|
||||
#- name: ubuntu-24.04-arm64
|
||||
# runner: ubuntu-24.04-arm
|
||||
# make: make
|
||||
# configure: --enable-utf8proc --enable-asan
|
||||
- name: macos-26-arm64
|
||||
runner: macos-26
|
||||
make: gmake
|
||||
configure: --enable-utf8proc
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
autoconf \
|
||||
automake \
|
||||
bison \
|
||||
build-essential \
|
||||
libevent-dev \
|
||||
libncurses-dev \
|
||||
libutf8proc-dev \
|
||||
pkg-config
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install \
|
||||
autoconf \
|
||||
automake \
|
||||
bison \
|
||||
libevent \
|
||||
make \
|
||||
ncurses \
|
||||
utf8proc \
|
||||
pkg-config
|
||||
|
||||
- name: build
|
||||
run: |
|
||||
sh autogen.sh
|
||||
./configure ${{ matrix.configure }}
|
||||
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
|
||||
- name: test
|
||||
run: |
|
||||
cd regress
|
||||
export ASAN_OPTIONS="abort_on_error=1:detect_leaks=0"
|
||||
${{ matrix.make }}
|
||||
6
CHANGES
6
CHANGES
@@ -1,4 +1,8 @@
|
||||
CHANGES FROM 3.7 to 3.7a
|
||||
CHANGES FROM 3.7a TO 3.7b
|
||||
|
||||
* Fix so that the end of a synchronized update again triggers a redraw.
|
||||
|
||||
CHANGES FROM 3.7 TO 3.7a
|
||||
|
||||
* Fix crash in break-pane when no name is provided.
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ AM_CPPFLAGS += @XOPEN_DEFINES@ \
|
||||
-DTMUX_VERSION='"@VERSION@"' \
|
||||
-DTMUX_CONF='"$(sysconfdir)/tmux.conf:~/.tmux.conf:$$XDG_CONFIG_HOME/tmux/tmux.conf:~/.config/tmux/tmux.conf"' \
|
||||
-DTMUX_LOCK_CMD='"@DEFAULT_LOCK_CMD@"' \
|
||||
-DTMUX_TERM='"@DEFAULT_TERM@"'
|
||||
-DTMUX_TERM='"@DEFAULT_TERM@"' \
|
||||
-DTMUX_MOUSE=1
|
||||
|
||||
# Additional object files.
|
||||
LDADD = $(LIBOBJS)
|
||||
@@ -178,6 +179,7 @@ dist_tmux_SOURCES = \
|
||||
log.c \
|
||||
menu.c \
|
||||
mode-tree.c \
|
||||
monitor.c \
|
||||
names.c \
|
||||
notify.c \
|
||||
options-table.c \
|
||||
|
||||
@@ -45,7 +45,7 @@ attributes_tostring(int attr)
|
||||
(attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "",
|
||||
(attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-underscore," : "",
|
||||
(attr & GRID_ATTR_UNDERSCORE_5) ? "dashed-underscore," : "",
|
||||
(attr & GRID_ATTR_OVERLINE) ? "overline," : "",
|
||||
(attr & GRID_ATTR_OVERLINE) ? "overline," : "",
|
||||
(attr & GRID_ATTR_NOATTR) ? "noattr," : "");
|
||||
if (len > 0)
|
||||
buf[len - 1] = '\0';
|
||||
|
||||
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
|
||||
.name = "capture-pane",
|
||||
.alias = "capturep",
|
||||
|
||||
.args = { "ab:CeE:FHJLMNpPqS:Tt:", 0, 0, NULL },
|
||||
.usage = "[-aCeFHJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||
.args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
|
||||
.usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||
"[-S start-line] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -75,6 +75,96 @@ cmd_capture_pane_append(char *buf, size_t *len, const char *line,
|
||||
return (buf);
|
||||
}
|
||||
|
||||
static char *
|
||||
cmd_capture_pane_cell(struct screen *s, u_int xx, u_int yy)
|
||||
{
|
||||
struct grid *gd = s->grid;
|
||||
struct hyperlinks *hl = s->hyperlinks;
|
||||
struct grid_cell gc;
|
||||
char *line, *data, *link, *linkid, *f, *b, *u;
|
||||
char c[UTF8_SIZE + 1];
|
||||
const char *uri, *iid;
|
||||
u_int flags;
|
||||
|
||||
grid_get_cell(gd, xx, yy, &gc);
|
||||
|
||||
memcpy(c, gc.data.data, gc.data.size);
|
||||
c[gc.data.size] = '\0';
|
||||
utf8_stravis(&data, c, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
|
||||
|
||||
if (gc.link != 0 && hyperlinks_get(hl, gc.link, &uri, &iid, NULL)) {
|
||||
xasprintf(&link, "%s", uri);
|
||||
if (iid != NULL && *iid != '\0')
|
||||
xasprintf(&linkid, "%s", iid);
|
||||
else
|
||||
xasprintf(&linkid, "NONE");
|
||||
} else {
|
||||
xasprintf(&link, "NONE");
|
||||
xasprintf(&linkid, "NONE");
|
||||
}
|
||||
|
||||
flags = gc.flags;
|
||||
if (gc.fg & COLOUR_FLAG_256)
|
||||
flags |= GRID_FLAG_FG256;
|
||||
if (gc.bg & COLOUR_FLAG_256)
|
||||
flags |= GRID_FLAG_BG256;
|
||||
|
||||
xasprintf(&f, "%s[%x]", colour_tostring(gc.fg), gc.fg);
|
||||
xasprintf(&b, "%s[%x]", colour_tostring(gc.bg), gc.bg);
|
||||
xasprintf(&u, "%s[%x]", colour_tostring(gc.us), gc.us);
|
||||
|
||||
xasprintf(&line, "\t\tC %u,%u data=(%u,%u,%s) flags=%s[%x] "
|
||||
"attr=%s[%x] fg=%s bg=%s us=%s link=%s linkid=%s\n",
|
||||
yy, xx, gc.data.width, gc.data.size, data,
|
||||
grid_cell_flags_string(flags), flags,
|
||||
grid_cell_attr_string(gc.attr), gc.attr, f, b, u, link, linkid);
|
||||
|
||||
free(f);
|
||||
free(b);
|
||||
free(u);
|
||||
free(link);
|
||||
free(linkid);
|
||||
free(data);
|
||||
return (line);
|
||||
}
|
||||
|
||||
static char *
|
||||
cmd_capture_pane_grid(struct window_pane *wp, size_t *len)
|
||||
{
|
||||
struct screen *s = &wp->base;
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_line *gl;
|
||||
char *buf = xstrdup(""), *line;
|
||||
char p[11];
|
||||
u_int yy, xx, total = gd->hsize + gd->sy;
|
||||
|
||||
xasprintf(&line, "G %ux%u (%u/%u)\n", gd->sx, gd->sy, gd->hsize,
|
||||
gd->hlimit);
|
||||
buf = cmd_capture_pane_append(buf, len, line, strlen(line));
|
||||
free(line);
|
||||
|
||||
for (yy = 0; yy < total; yy++) {
|
||||
gl = grid_get_line(gd, yy);
|
||||
if (yy < gd->hsize)
|
||||
snprintf(p, sizeof p, "-");
|
||||
else
|
||||
snprintf(p, sizeof p, "%u", yy - gd->hsize);
|
||||
xasprintf(&line, "\tL %u (%s) flags=%s[%x] %u/%u\n", yy,
|
||||
p, grid_line_flags_string(gl->flags), gl->flags,
|
||||
gl->cellused, gl->cellsize);
|
||||
buf = cmd_capture_pane_append(buf, len, line, strlen(line));
|
||||
free(line);
|
||||
|
||||
for (xx = 0; xx < gd->sx; xx++) {
|
||||
line = cmd_capture_pane_cell(s, xx, yy);
|
||||
buf = cmd_capture_pane_append(buf, len, line,
|
||||
strlen(line));
|
||||
free(line);
|
||||
}
|
||||
}
|
||||
return (buf);
|
||||
}
|
||||
|
||||
static char *
|
||||
cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
|
||||
size_t *len)
|
||||
@@ -323,7 +413,9 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
len = 0;
|
||||
if (args_has(args, 'P') && !args_has(args, 'H'))
|
||||
if (args_has(args, 'R'))
|
||||
buf = cmd_capture_pane_grid(wp, &len);
|
||||
else if (args_has(args, 'P') && !args_has(args, 'H'))
|
||||
buf = cmd_capture_pane_pending(args, wp, &len);
|
||||
else
|
||||
buf = cmd_capture_pane_history(args, item, wp, &len);
|
||||
|
||||
@@ -1011,7 +1011,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
|
||||
strcmp(target, "{active}") == 0 ||
|
||||
strcmp(target, "{current}") == 0) {
|
||||
c = cmdq_get_client(item);
|
||||
if (c == NULL) {
|
||||
if (c == NULL || c->session == NULL) {
|
||||
cmdq_error(item, "no current client");
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ const struct cmd_entry cmd_move_pane_entry = {
|
||||
.name = "move-pane",
|
||||
.alias = "movep",
|
||||
|
||||
.args = { "bdfhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL },
|
||||
.args = { "bdD::fhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL },
|
||||
.usage = "[-bdfhMv] [-D lines] [-l size] [-L columns] [-P position] "
|
||||
"[-R columns] " CMD_SRCDST_PANE_USAGE " [-U lines] "
|
||||
"[-X x-position] [-Y y-position] [-z z-index]",
|
||||
@@ -363,6 +363,44 @@ cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_join_pane_tile(struct cmdq_item *item, struct args *args, struct window *w,
|
||||
struct window_pane *wp)
|
||||
{
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
|
||||
if (!window_pane_is_floating(wp)) {
|
||||
cmdq_error(item, "pane is not floating");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (w->flags & WINDOW_ZOOMED) {
|
||||
cmdq_error(item, "can't tile a pane while window is zoomed");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
lc->saved_sx = lc->sx;
|
||||
lc->saved_sy = lc->sy;
|
||||
lc->saved_xoff = lc->xoff;
|
||||
lc->saved_yoff = lc->yoff;
|
||||
if (layout_insert_tile(w, lc) != 0) {
|
||||
cmdq_error(item, "no space for a new pane");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
lc->flags &= ~LAYOUT_CELL_FLOATING;
|
||||
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
||||
|
||||
if (!args_has(args, 'd'))
|
||||
window_set_active_pane(w, wp, 1);
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
@@ -412,6 +450,8 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_unzoom_window(src_w);
|
||||
|
||||
if (src_wp == dst_wp) {
|
||||
if (window_pane_is_floating(src_wp))
|
||||
return (cmd_join_pane_tile(item, args, src_w, src_wp));
|
||||
cmdq_error(item, "source and target panes must be different");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
||||
n = 1;
|
||||
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_defaults(ft, NULL, NULL, NULL, NULL);
|
||||
format_defaults(ft, tc, NULL, NULL, NULL);
|
||||
format_add(ft, "notes_only", "%d", notes_only);
|
||||
format_add(ft, "key_has_repeat", "%d", key_bindings_has_repeat(l, n));
|
||||
format_add(ft, "key_string_width", "%u", cmd_list_keys_get_width(l, n));
|
||||
|
||||
@@ -38,8 +38,8 @@ const struct cmd_entry cmd_new_window_entry = {
|
||||
.name = "new-window",
|
||||
.alias = "neww",
|
||||
|
||||
.args = { "abc:de:F:kn:PSt:", 0, -1, NULL },
|
||||
.usage = "[-abdkPS] [-c start-directory] [-e environment] [-F format] "
|
||||
.args = { "abc:de:EF:kn:PSt:", 0, -1, NULL },
|
||||
.usage = "[-abdEkPS] [-c start-directory] [-e environment] [-F format] "
|
||||
"[-n window-name] " CMD_TARGET_WINDOW_USAGE
|
||||
" [shell-command [argument ...]]",
|
||||
|
||||
@@ -60,12 +60,19 @@ 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;
|
||||
int idx = target->idx, before, count = args_count(args);
|
||||
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
||||
const char *template, *name;
|
||||
struct cmd_find_state fs;
|
||||
struct args_value *av;
|
||||
|
||||
if (args_has(args, 'E') &&
|
||||
count != 0 &&
|
||||
(count != 1 || *args_string(args, 0) != '\0')) {
|
||||
cmdq_error(item, "command cannot be given for empty pane");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
/*
|
||||
* If -S and -n are given and -t is not and a single window with this
|
||||
* name already exists, select it.
|
||||
@@ -134,6 +141,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
sc.cwd = args_get(args, 'c');
|
||||
|
||||
sc.flags = 0;
|
||||
if (args_has(args, 'E'))
|
||||
sc.flags |= SPAWN_EMPTY;
|
||||
if (args_has(args, 'd'))
|
||||
sc.flags |= SPAWN_DETACHED;
|
||||
if (args_has(args, 'k'))
|
||||
|
||||
@@ -46,36 +46,17 @@ const struct cmd_entry cmd_refresh_client_entry = {
|
||||
static void
|
||||
cmd_refresh_client_update_subscription(struct client *tc, const char *value)
|
||||
{
|
||||
char *copy, *split, *name, *what;
|
||||
enum control_sub_type subtype;
|
||||
int subid = -1;
|
||||
char *name, *format;
|
||||
enum monitor_type type;
|
||||
int id;
|
||||
|
||||
copy = name = xstrdup(value);
|
||||
if ((split = strchr(copy, ':')) == NULL) {
|
||||
control_remove_sub(tc, copy);
|
||||
goto out;
|
||||
if (monitor_parse(value, &name, &type, &id, &format) != 0) {
|
||||
control_remove_sub(tc, value);
|
||||
return;
|
||||
}
|
||||
*split++ = '\0';
|
||||
|
||||
what = split;
|
||||
if ((split = strchr(what, ':')) == NULL)
|
||||
goto out;
|
||||
*split++ = '\0';
|
||||
|
||||
if (strcmp(what, "%*") == 0)
|
||||
subtype = CONTROL_SUB_ALL_PANES;
|
||||
else if (sscanf(what, "%%%d", &subid) == 1 && subid >= 0)
|
||||
subtype = CONTROL_SUB_PANE;
|
||||
else if (strcmp(what, "@*") == 0)
|
||||
subtype = CONTROL_SUB_ALL_WINDOWS;
|
||||
else if (sscanf(what, "@%d", &subid) == 1 && subid >= 0)
|
||||
subtype = CONTROL_SUB_WINDOW;
|
||||
else
|
||||
subtype = CONTROL_SUB_SESSION;
|
||||
control_add_sub(tc, name, subtype, subid, split);
|
||||
|
||||
out:
|
||||
free(copy);
|
||||
control_add_sub(tc, name, type, id, format);
|
||||
free(name);
|
||||
free(format);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
|
||||
@@ -34,8 +34,8 @@ const struct cmd_entry cmd_respawn_pane_entry = {
|
||||
.name = "respawn-pane",
|
||||
.alias = "respawnp",
|
||||
|
||||
.args = { "c:e:kt:", 0, -1, NULL },
|
||||
.usage = "[-k] [-c start-directory] [-e environment] "
|
||||
.args = { "c:e:Ekt:", 0, -1, NULL },
|
||||
.usage = "[-Ek] [-c start-directory] [-e environment] "
|
||||
CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -75,6 +75,8 @@ cmd_respawn_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
sc.cwd = args_get(args, 'c');
|
||||
|
||||
sc.flags = SPAWN_RESPAWN;
|
||||
if (args_has(args, 'E'))
|
||||
sc.flags |= SPAWN_EMPTY;
|
||||
if (args_has(args, 'k'))
|
||||
sc.flags |= SPAWN_KILL;
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ const struct cmd_entry cmd_respawn_window_entry = {
|
||||
.name = "respawn-window",
|
||||
.alias = "respawnw",
|
||||
|
||||
.args = { "c:e:kt:", 0, -1, NULL },
|
||||
.usage = "[-k] [-c start-directory] [-e environment] "
|
||||
.args = { "c:e:Ekt:", 0, -1, NULL },
|
||||
.usage = "[-Ek] [-c start-directory] [-e environment] "
|
||||
CMD_TARGET_WINDOW_USAGE " [shell-command [argument ...]]",
|
||||
|
||||
.target = { 't', CMD_FIND_WINDOW, 0 },
|
||||
@@ -74,6 +74,8 @@ cmd_respawn_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
sc.cwd = args_get(args, 'c');
|
||||
|
||||
sc.flags = SPAWN_RESPAWN;
|
||||
if (args_has(args, 'E'))
|
||||
sc.flags |= SPAWN_EMPTY;
|
||||
if (args_has(args, 'k'))
|
||||
sc.flags |= SPAWN_KILL;
|
||||
|
||||
|
||||
101
cmd-set-option.c
101
cmd-set-option.c
@@ -31,6 +31,8 @@ static enum args_parse_type cmd_set_option_args_parse(struct args *,
|
||||
u_int, char **);
|
||||
static enum cmd_retval cmd_set_option_exec(struct cmd *,
|
||||
struct cmdq_item *);
|
||||
static enum cmd_retval cmd_set_hook_monitor_exec(struct cmdq_item *,
|
||||
struct args *, int);
|
||||
|
||||
const struct cmd_entry cmd_set_option_entry = {
|
||||
.name = "set-option",
|
||||
@@ -62,8 +64,9 @@ const struct cmd_entry cmd_set_hook_entry = {
|
||||
.name = "set-hook",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "agpRt:uw", 1, 2, cmd_set_option_args_parse },
|
||||
.usage = "[-agpRuw] " CMD_TARGET_PANE_USAGE " hook [command]",
|
||||
.args = { "agpRt:uB:w", 0, 2, cmd_set_option_args_parse },
|
||||
.usage = "[-agpRuw] [-B name:what:format] " CMD_TARGET_PANE_USAGE " "
|
||||
"[hook] [command]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
||||
|
||||
@@ -72,14 +75,100 @@ const struct cmd_entry cmd_set_hook_entry = {
|
||||
};
|
||||
|
||||
static enum args_parse_type
|
||||
cmd_set_option_args_parse(__unused struct args *args, u_int idx,
|
||||
cmd_set_option_args_parse(struct args *args, u_int idx,
|
||||
__unused char **cause)
|
||||
{
|
||||
if (args_has(args, 'B'))
|
||||
return (ARGS_PARSE_COMMANDS_OR_STRING);
|
||||
if (idx == 1)
|
||||
return (ARGS_PARSE_COMMANDS_OR_STRING);
|
||||
return (ARGS_PARSE_STRING);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_set_hook_monitor_exec(struct cmdq_item *item, struct args *args, int window)
|
||||
{
|
||||
struct cmd_find_state *target = cmdq_get_target(item), fs;
|
||||
struct options *oo;
|
||||
struct options_entry *o;
|
||||
char *cause = NULL, *name = NULL, *format = NULL;
|
||||
char *expanded = NULL, *newvalue = NULL;
|
||||
const char *value, *old;
|
||||
enum monitor_type type;
|
||||
int id, scope;
|
||||
|
||||
if (args_count(args) > 1) {
|
||||
cmdq_error(item, "too many arguments");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
value = args_get(args, 'B');
|
||||
if (args_has(args, 'u')) {
|
||||
if (monitor_parse(value, &name, &type, &id, &format) != 0)
|
||||
name = xstrdup(value);
|
||||
free(format);
|
||||
format = NULL;
|
||||
} else {
|
||||
if (monitor_parse(value, &name, &type, &id, &format) != 0) {
|
||||
cmdq_error(item, "invalid subscription: %s", value);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (*name != '@') {
|
||||
cmdq_error(item, "monitor hook name must start with @");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
scope = options_scope_from_name(args, window, name, target, &oo,
|
||||
&cause);
|
||||
if (scope == OPTIONS_TABLE_NONE) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
goto fail;
|
||||
}
|
||||
cmd_find_copy_state(&fs, target);
|
||||
|
||||
if (args_has(args, 'u')) {
|
||||
notify_monitor_remove(oo, name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (args_count(args) != 0) {
|
||||
value = args_string(args, 0);
|
||||
if (args_has(args, 'F')) {
|
||||
expanded = format_single_from_target(item, value);
|
||||
value = expanded;
|
||||
}
|
||||
o = options_get_only(oo, name);
|
||||
if (!args_has(args, 'o') || o == NULL) {
|
||||
if (args_has(args, 'a') && o != NULL) {
|
||||
old = options_get_string(oo, name);
|
||||
xasprintf(&newvalue, "%s%s", old, value);
|
||||
value = newvalue;
|
||||
}
|
||||
options_set_string(oo, name, 0, "%s", value);
|
||||
options_push_changes(name);
|
||||
}
|
||||
}
|
||||
|
||||
notify_monitor_add(item, oo, name, type, id, format, &fs, target->s);
|
||||
|
||||
out:
|
||||
free(newvalue);
|
||||
free(expanded);
|
||||
free(name);
|
||||
free(format);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
fail:
|
||||
free(newvalue);
|
||||
free(expanded);
|
||||
free(name);
|
||||
free(format);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
@@ -96,6 +185,12 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
|
||||
int scope;
|
||||
|
||||
window = (cmd_get_entry(self) == &cmd_set_window_option_entry);
|
||||
if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'B'))
|
||||
return (cmd_set_hook_monitor_exec(item, args, window));
|
||||
if (args_count(args) == 0) {
|
||||
cmdq_error(item, "missing argument");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
/* Expand argument. */
|
||||
argument = format_single_from_target(item, args_string(args, 0));
|
||||
|
||||
@@ -31,6 +31,10 @@ 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 *, int, 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 enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *,
|
||||
int, struct options *);
|
||||
|
||||
@@ -64,8 +68,8 @@ const struct cmd_entry cmd_show_hooks_entry = {
|
||||
.name = "show-hooks",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "gpt:w", 0, 1, NULL },
|
||||
.usage = "[-gpw] " CMD_TARGET_PANE_USAGE " [hook]",
|
||||
.args = { "Bgpt:w", 0, 1, NULL },
|
||||
.usage = "[-Bgpw] " CMD_TARGET_PANE_USAGE " [hook]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
||||
|
||||
@@ -95,6 +99,9 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
|
||||
free(cause);
|
||||
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));
|
||||
return (cmd_show_options_all(self, item, scope, oo));
|
||||
}
|
||||
argument = format_single_from_target(item, args_string(args, 0));
|
||||
@@ -124,8 +131,13 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
|
||||
parent = 1;
|
||||
} else
|
||||
parent = 0;
|
||||
if (o != NULL)
|
||||
cmd_show_options_print(self, item, o, idx, parent);
|
||||
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, idx, parent);
|
||||
}
|
||||
else if (*name == '@') {
|
||||
if (args_has(args, 'q'))
|
||||
goto out;
|
||||
@@ -195,6 +207,33 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item,
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_show_hooks_print_monitor(struct cmdq_item *item, struct options_entry *o)
|
||||
{
|
||||
char *value;
|
||||
|
||||
value = notify_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);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
|
||||
struct options *oo)
|
||||
|
||||
@@ -81,7 +81,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl = target->wl;
|
||||
struct window *w = wl->window;
|
||||
struct window_pane *wp = target->wp, *new_wp;
|
||||
struct window_pane *wp = target->wp, *new_wp = NULL;
|
||||
struct layout_cell *lc = NULL;
|
||||
struct cmd_find_state fs;
|
||||
int input, empty, is_floating, flags = 0;
|
||||
@@ -167,6 +167,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if ((new_wp = spawn_pane(&sc, &cause)) == NULL) {
|
||||
cmdq_error(item, "create pane failed: %s", cause);
|
||||
free(cause);
|
||||
/*
|
||||
* spawn_pane has already torn the half-built pane down (its
|
||||
* fork-failure path removes the pane and destroys the layout
|
||||
* cell), so new_wp is NULL and there is nothing for fail to do.
|
||||
*/
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@@ -219,10 +224,6 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (input) {
|
||||
switch (window_pane_start_input(new_wp, item, &cause)) {
|
||||
case -1:
|
||||
server_client_remove_pane(new_wp);
|
||||
if (!is_floating)
|
||||
layout_close_pane(new_wp);
|
||||
window_remove_pane(wp->window, new_wp);
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
goto fail;
|
||||
@@ -269,10 +270,20 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
fail:
|
||||
/*
|
||||
* If the pane was spawned before we failed, tear it down here; this
|
||||
* also destroys its layout cell. spawn_pane's own failure path has
|
||||
* already done this, so new_wp is NULL in that case.
|
||||
*/
|
||||
if (new_wp != NULL) {
|
||||
server_client_remove_pane(new_wp);
|
||||
if (!is_floating)
|
||||
layout_close_pane(new_wp);
|
||||
window_remove_pane(wp->window, new_wp);
|
||||
}
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
layout_destroy_cell(w, lc, &w->layout_root);
|
||||
|
||||
return (CMD_RETURN_ERROR);
|
||||
|
||||
|
||||
@@ -42,6 +42,22 @@ const struct cmd_entry cmd_swap_pane_entry = {
|
||||
.exec = cmd_swap_pane_exec
|
||||
};
|
||||
|
||||
static struct window_pane *
|
||||
cmd_swap_pane_next_tiled_pane(struct window_pane *wp)
|
||||
{
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
return (wp);
|
||||
}
|
||||
|
||||
static struct window_pane *
|
||||
cmd_swap_pane_prev_tiled_pane(struct window_pane *wp)
|
||||
{
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_PREV(wp, window_panes, entry);
|
||||
return (wp);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
@@ -62,15 +78,29 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_redraw_window(dst_w);
|
||||
|
||||
if (args_has(args, 'D')) {
|
||||
if (window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "cannot swap down on floating pane");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
src_w = dst_w;
|
||||
src_wp = TAILQ_NEXT(dst_wp, entry);
|
||||
if (src_wp == NULL)
|
||||
src_wp = cmd_swap_pane_next_tiled_pane(src_wp);
|
||||
if (src_wp == NULL) {
|
||||
src_wp = TAILQ_FIRST(&dst_w->panes);
|
||||
src_wp = cmd_swap_pane_next_tiled_pane(src_wp);
|
||||
}
|
||||
} else if (args_has(args, 'U')) {
|
||||
if (window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "cannot swap up on floating pane");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
src_w = dst_w;
|
||||
src_wp = TAILQ_PREV(dst_wp, window_panes, entry);
|
||||
if (src_wp == NULL)
|
||||
src_wp = cmd_swap_pane_prev_tiled_pane(src_wp);
|
||||
if (src_wp == NULL) {
|
||||
src_wp = TAILQ_LAST(&dst_w->panes, window_panes);
|
||||
src_wp = cmd_swap_pane_prev_tiled_pane(src_wp);
|
||||
}
|
||||
}
|
||||
|
||||
if (src_w != dst_w && window_push_zoom(src_w, 0, args_has(args, 'Z')))
|
||||
@@ -79,12 +109,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (src_wp == dst_wp)
|
||||
goto out;
|
||||
|
||||
if (window_pane_is_floating(src_wp) ||
|
||||
window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "cannot swap floating panes");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
server_client_remove_pane(src_wp);
|
||||
server_client_remove_pane(dst_wp);
|
||||
|
||||
|
||||
52
colour.c
52
colour.c
@@ -290,6 +290,58 @@ colour_tostring(int c)
|
||||
return ("invalid");
|
||||
}
|
||||
|
||||
/* Convert colour to an SGR escape sequence. */
|
||||
const char *
|
||||
colour_toescape(struct client *c, int colour, int bg)
|
||||
{
|
||||
static char s[32];
|
||||
u_char r, g, b;
|
||||
int n, flags = (TERM_256COLOURS|TERM_RGBCOLOURS);
|
||||
u_int o = (bg ? 40 : 30);
|
||||
|
||||
if (c != NULL && (c->tty.flags & TTY_OPENED) && c->tty.term != NULL)
|
||||
flags = c->tty.term->flags;
|
||||
|
||||
if (colour & COLOUR_FLAG_THEME) {
|
||||
n = colour & 0xff;
|
||||
if (c != NULL && (u_int)n < COLOUR_THEME_COUNT)
|
||||
colour = c->theme_colours[n];
|
||||
else
|
||||
colour = colour_theme_terminal_colour(n);
|
||||
}
|
||||
|
||||
if (colour == 8 || colour == 9) {
|
||||
xsnprintf(s, sizeof s, "\033[%dm", o + 9);
|
||||
return (s);
|
||||
}
|
||||
|
||||
if ((~flags & TERM_RGBCOLOURS) & (colour & COLOUR_FLAG_RGB)) {
|
||||
colour_split_rgb(colour, &r, &g, &b);
|
||||
colour = colour_find_rgb(r, g, b);
|
||||
}
|
||||
if ((~flags & TERM_256COLOURS) & (colour & COLOUR_FLAG_256))
|
||||
colour = colour_256to16(colour);
|
||||
|
||||
if (colour & COLOUR_FLAG_RGB) {
|
||||
colour_split_rgb(colour, &r, &g, &b);
|
||||
xsnprintf(s, sizeof s, "\033[%d;2;%u;%u;%um", o + 8, r, g, b);
|
||||
return (s);
|
||||
}
|
||||
if (colour & COLOUR_FLAG_256) {
|
||||
xsnprintf(s, sizeof s, "\033[%d;5;%um", o + 8, colour & 0xff);
|
||||
return (s);
|
||||
}
|
||||
if (colour >= 0 && colour <= 7) {
|
||||
xsnprintf(s, sizeof s, "\033[%dm", colour + o);
|
||||
return (s);
|
||||
}
|
||||
if (colour >= 90 && colour <= 97) {
|
||||
xsnprintf(s, sizeof s, "\033[%dm", colour + o - 30);
|
||||
return (s);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Convert background colour to theme. */
|
||||
enum client_theme
|
||||
colour_totheme(int c)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# configure.ac
|
||||
|
||||
AC_INIT([tmux], next-3.7)
|
||||
AC_INIT([tmux], next-3.8)
|
||||
AC_PREREQ([2.60])
|
||||
|
||||
AC_CONFIG_AUX_DIR(etc)
|
||||
|
||||
452
control.c
452
control.c
@@ -75,42 +75,6 @@ struct control_pane {
|
||||
};
|
||||
RB_HEAD(control_panes, control_pane);
|
||||
|
||||
/* Subscription pane. */
|
||||
struct control_sub_pane {
|
||||
u_int pane;
|
||||
u_int idx;
|
||||
char *last;
|
||||
|
||||
RB_ENTRY(control_sub_pane) entry;
|
||||
};
|
||||
RB_HEAD(control_sub_panes, control_sub_pane);
|
||||
|
||||
/* Subscription window. */
|
||||
struct control_sub_window {
|
||||
u_int window;
|
||||
u_int idx;
|
||||
char *last;
|
||||
|
||||
RB_ENTRY(control_sub_window) entry;
|
||||
};
|
||||
RB_HEAD(control_sub_windows, control_sub_window);
|
||||
|
||||
/* Control client subscription. */
|
||||
struct control_sub {
|
||||
char *name;
|
||||
char *format;
|
||||
|
||||
enum control_sub_type type;
|
||||
u_int id;
|
||||
|
||||
char *last;
|
||||
struct control_sub_panes panes;
|
||||
struct control_sub_windows windows;
|
||||
|
||||
RB_ENTRY(control_sub) entry;
|
||||
};
|
||||
RB_HEAD(control_subs, control_sub);
|
||||
|
||||
/* Control client state. */
|
||||
struct control_state {
|
||||
struct control_panes panes;
|
||||
@@ -123,8 +87,7 @@ struct control_state {
|
||||
struct bufferevent *read_event;
|
||||
struct bufferevent *write_event;
|
||||
|
||||
struct control_subs subs;
|
||||
struct event subs_timer;
|
||||
struct monitor_set *subs;
|
||||
};
|
||||
|
||||
/* Low and high watermarks. */
|
||||
@@ -154,75 +117,6 @@ control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
|
||||
}
|
||||
RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp);
|
||||
|
||||
/* Compare client subs. */
|
||||
static int
|
||||
control_sub_cmp(struct control_sub *csub1, struct control_sub *csub2)
|
||||
{
|
||||
return (strcmp(csub1->name, csub2->name));
|
||||
}
|
||||
RB_GENERATE_STATIC(control_subs, control_sub, entry, control_sub_cmp);
|
||||
|
||||
/* Compare client subscription panes. */
|
||||
static int
|
||||
control_sub_pane_cmp(struct control_sub_pane *csp1,
|
||||
struct control_sub_pane *csp2)
|
||||
{
|
||||
if (csp1->pane < csp2->pane)
|
||||
return (-1);
|
||||
if (csp1->pane > csp2->pane)
|
||||
return (1);
|
||||
if (csp1->idx < csp2->idx)
|
||||
return (-1);
|
||||
if (csp1->idx > csp2->idx)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry,
|
||||
control_sub_pane_cmp);
|
||||
|
||||
/* Compare client subscription windows. */
|
||||
static int
|
||||
control_sub_window_cmp(struct control_sub_window *csw1,
|
||||
struct control_sub_window *csw2)
|
||||
{
|
||||
if (csw1->window < csw2->window)
|
||||
return (-1);
|
||||
if (csw1->window > csw2->window)
|
||||
return (1);
|
||||
if (csw1->idx < csw2->idx)
|
||||
return (-1);
|
||||
if (csw1->idx > csw2->idx)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
RB_GENERATE_STATIC(control_sub_windows, control_sub_window, entry,
|
||||
control_sub_window_cmp);
|
||||
|
||||
/* Free a subscription. */
|
||||
static void
|
||||
control_free_sub(struct control_state *cs, struct control_sub *csub)
|
||||
{
|
||||
struct control_sub_pane *csp, *csp1;
|
||||
struct control_sub_window *csw, *csw1;
|
||||
|
||||
RB_FOREACH_SAFE(csp, control_sub_panes, &csub->panes, csp1) {
|
||||
RB_REMOVE(control_sub_panes, &csub->panes, csp);
|
||||
free(csp->last);
|
||||
free(csp);
|
||||
}
|
||||
RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) {
|
||||
RB_REMOVE(control_sub_windows, &csub->windows, csw);
|
||||
free(csw->last);
|
||||
free(csw);
|
||||
}
|
||||
free(csub->last);
|
||||
|
||||
RB_REMOVE(control_subs, &cs->subs, csub);
|
||||
free(csub->name);
|
||||
free(csub->format);
|
||||
free(csub);
|
||||
}
|
||||
|
||||
/* Free a block. */
|
||||
static void
|
||||
control_free_block(struct control_state *cs, struct control_block *cb)
|
||||
@@ -766,6 +660,30 @@ control_write_callback(__unused struct bufferevent *bufev, void *data)
|
||||
bufferevent_disable(cs->write_event, EV_WRITE);
|
||||
}
|
||||
|
||||
/* Write a subscription change. */
|
||||
static void
|
||||
control_sub_change(struct monitor_change *change, __unused void *data)
|
||||
{
|
||||
struct client *c = change->c;
|
||||
struct session *s = change->s;
|
||||
struct winlink *wl = change->wl;
|
||||
struct window_pane *wp = change->wp;
|
||||
struct window *w;
|
||||
|
||||
if (wp != NULL) {
|
||||
w = wp->window;
|
||||
control_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",
|
||||
change->name, s->id, w->id, wl->idx, change->value);
|
||||
} else {
|
||||
control_write(c, "%%subscription-changed %s $%u - - - : %s",
|
||||
change->name, s->id, change->value);
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize for control mode. */
|
||||
void
|
||||
control_start(struct client *c)
|
||||
@@ -783,7 +701,7 @@ control_start(struct client *c)
|
||||
RB_INIT(&cs->panes);
|
||||
TAILQ_INIT(&cs->pending_list);
|
||||
TAILQ_INIT(&cs->all_blocks);
|
||||
RB_INIT(&cs->subs);
|
||||
cs->subs = monitor_create_client(c, control_sub_change, NULL);
|
||||
|
||||
cs->read_event = bufferevent_new(c->fd, control_read_callback,
|
||||
control_write_callback, control_error_callback, c);
|
||||
@@ -832,20 +750,16 @@ control_stop(struct client *c)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_block *cb, *cb1;
|
||||
struct control_sub *csub, *csub1;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
monitor_destroy(cs->subs);
|
||||
|
||||
if (~c->flags & CLIENT_CONTROLCONTROL)
|
||||
bufferevent_free(cs->write_event);
|
||||
bufferevent_free(cs->read_event);
|
||||
|
||||
RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1)
|
||||
control_free_sub(cs, csub);
|
||||
if (evtimer_initialized(&cs->subs_timer))
|
||||
evtimer_del(&cs->subs_timer);
|
||||
|
||||
control_reset_offsets(c);
|
||||
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
||||
control_free_block(cs, cb);
|
||||
@@ -854,313 +768,14 @@ control_stop(struct client *c)
|
||||
free(cs);
|
||||
}
|
||||
|
||||
/* Check session subscription. */
|
||||
static void
|
||||
control_check_subs_session(struct client *c, struct control_sub *csub,
|
||||
struct format_tree *ft)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
char *value;
|
||||
|
||||
value = format_expand(ft, csub->format);
|
||||
|
||||
if (csub->last != NULL && strcmp(value, csub->last) == 0) {
|
||||
free(value);
|
||||
return;
|
||||
}
|
||||
control_write(c,
|
||||
"%%subscription-changed %s $%u - - - : %s",
|
||||
csub->name, s->id, value);
|
||||
free(csub->last);
|
||||
csub->last = value;
|
||||
}
|
||||
|
||||
/* Check pane subscription. */
|
||||
static void
|
||||
control_check_subs_pane(struct client *c, struct control_sub *csub)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window_pane *wp;
|
||||
struct window *w;
|
||||
struct winlink *wl;
|
||||
struct format_tree *ft;
|
||||
char *value;
|
||||
struct control_sub_pane *csp, find;
|
||||
|
||||
wp = window_pane_find_by_id(csub->id);
|
||||
if (wp == NULL || wp->fd == -1)
|
||||
return;
|
||||
w = wp->window;
|
||||
|
||||
TAILQ_FOREACH(wl, &w->winlinks, wentry) {
|
||||
if (wl->session != s)
|
||||
continue;
|
||||
|
||||
ft = format_create_defaults(NULL, c, s, wl, wp);
|
||||
value = format_expand(ft, csub->format);
|
||||
format_free(ft);
|
||||
|
||||
find.pane = wp->id;
|
||||
find.idx = wl->idx;
|
||||
|
||||
csp = RB_FIND(control_sub_panes, &csub->panes, &find);
|
||||
if (csp == NULL) {
|
||||
csp = xcalloc(1, sizeof *csp);
|
||||
csp->pane = wp->id;
|
||||
csp->idx = wl->idx;
|
||||
RB_INSERT(control_sub_panes, &csub->panes, csp);
|
||||
}
|
||||
|
||||
if (csp->last != NULL && strcmp(value, csp->last) == 0) {
|
||||
free(value);
|
||||
continue;
|
||||
}
|
||||
control_write(c,
|
||||
"%%subscription-changed %s $%u @%u %u %%%u : %s",
|
||||
csub->name, s->id, w->id, wl->idx, wp->id, value);
|
||||
free(csp->last);
|
||||
csp->last = value;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all-panes subscription for a pane. */
|
||||
static void
|
||||
control_check_subs_all_panes_one(struct client *c, struct control_sub *csub,
|
||||
struct format_tree *ft, struct winlink *wl, struct window_pane *wp)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window *w = wl->window;
|
||||
char *value;
|
||||
struct control_sub_pane *csp, find;
|
||||
|
||||
value = format_expand(ft, csub->format);
|
||||
|
||||
find.pane = wp->id;
|
||||
find.idx = wl->idx;
|
||||
|
||||
csp = RB_FIND(control_sub_panes, &csub->panes, &find);
|
||||
if (csp == NULL) {
|
||||
csp = xcalloc(1, sizeof *csp);
|
||||
csp->pane = wp->id;
|
||||
csp->idx = wl->idx;
|
||||
RB_INSERT(control_sub_panes, &csub->panes, csp);
|
||||
}
|
||||
|
||||
if (csp->last != NULL && strcmp(value, csp->last) == 0) {
|
||||
free(value);
|
||||
return;
|
||||
}
|
||||
control_write(c,
|
||||
"%%subscription-changed %s $%u @%u %u %%%u : %s",
|
||||
csub->name, s->id, w->id, wl->idx, wp->id, value);
|
||||
free(csp->last);
|
||||
csp->last = value;
|
||||
}
|
||||
|
||||
/* Check window subscription. */
|
||||
static void
|
||||
control_check_subs_window(struct client *c, struct control_sub *csub)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window *w;
|
||||
struct winlink *wl;
|
||||
struct format_tree *ft;
|
||||
char *value;
|
||||
struct control_sub_window *csw, find;
|
||||
|
||||
w = window_find_by_id(csub->id);
|
||||
if (w == NULL)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(wl, &w->winlinks, wentry) {
|
||||
if (wl->session != s)
|
||||
continue;
|
||||
|
||||
ft = format_create_defaults(NULL, c, s, wl, NULL);
|
||||
value = format_expand(ft, csub->format);
|
||||
format_free(ft);
|
||||
|
||||
find.window = w->id;
|
||||
find.idx = wl->idx;
|
||||
|
||||
csw = RB_FIND(control_sub_windows, &csub->windows, &find);
|
||||
if (csw == NULL) {
|
||||
csw = xcalloc(1, sizeof *csw);
|
||||
csw->window = w->id;
|
||||
csw->idx = wl->idx;
|
||||
RB_INSERT(control_sub_windows, &csub->windows, csw);
|
||||
}
|
||||
|
||||
if (csw->last != NULL && strcmp(value, csw->last) == 0) {
|
||||
free(value);
|
||||
continue;
|
||||
}
|
||||
control_write(c,
|
||||
"%%subscription-changed %s $%u @%u %u - : %s",
|
||||
csub->name, s->id, w->id, wl->idx, value);
|
||||
free(csw->last);
|
||||
csw->last = value;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all-windows subscription for a window. */
|
||||
static void
|
||||
control_check_subs_all_windows_one(struct client *c, struct control_sub *csub,
|
||||
struct format_tree *ft, struct winlink *wl)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window *w = wl->window;
|
||||
char *value;
|
||||
struct control_sub_window *csw, find;
|
||||
|
||||
value = format_expand(ft, csub->format);
|
||||
|
||||
find.window = w->id;
|
||||
find.idx = wl->idx;
|
||||
|
||||
csw = RB_FIND(control_sub_windows, &csub->windows, &find);
|
||||
if (csw == NULL) {
|
||||
csw = xcalloc(1, sizeof *csw);
|
||||
csw->window = w->id;
|
||||
csw->idx = wl->idx;
|
||||
RB_INSERT(control_sub_windows, &csub->windows, csw);
|
||||
}
|
||||
|
||||
if (csw->last != NULL && strcmp(value, csw->last) == 0) {
|
||||
free(value);
|
||||
return;
|
||||
}
|
||||
control_write(c,
|
||||
"%%subscription-changed %s $%u @%u %u - : %s",
|
||||
csub->name, s->id, w->id, wl->idx, value);
|
||||
free(csw->last);
|
||||
csw->last = value;
|
||||
}
|
||||
|
||||
/* Check subscriptions timer. */
|
||||
static void
|
||||
control_check_subs_timer(__unused int fd, __unused short events, void *data)
|
||||
{
|
||||
struct client *c = data;
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_sub *csub, *csub1;
|
||||
struct session *s = c->session;
|
||||
struct format_tree *ft;
|
||||
struct winlink *wl;
|
||||
struct window_pane *wp;
|
||||
struct timeval tv = { .tv_sec = 1 };
|
||||
int have_session = 0, have_all_panes = 0;
|
||||
int have_all_windows = 0;
|
||||
|
||||
log_debug("%s: timer fired", __func__);
|
||||
evtimer_add(&cs->subs_timer, &tv);
|
||||
|
||||
if (s == NULL)
|
||||
return;
|
||||
|
||||
/* Find which subscription types are present. */
|
||||
RB_FOREACH(csub, control_subs, &cs->subs) {
|
||||
switch (csub->type) {
|
||||
case CONTROL_SUB_SESSION:
|
||||
have_session = 1;
|
||||
break;
|
||||
case CONTROL_SUB_ALL_PANES:
|
||||
have_all_panes = 1;
|
||||
break;
|
||||
case CONTROL_SUB_ALL_WINDOWS:
|
||||
have_all_windows = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check session subscriptions. */
|
||||
if (have_session) {
|
||||
ft = format_create_defaults(NULL, c, s, NULL, NULL);
|
||||
RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
|
||||
if (csub->type == CONTROL_SUB_SESSION)
|
||||
control_check_subs_session(c, csub, ft);
|
||||
}
|
||||
format_free(ft);
|
||||
}
|
||||
|
||||
/* Check pane and window subscriptions. */
|
||||
RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
|
||||
switch (csub->type) {
|
||||
case CONTROL_SUB_PANE:
|
||||
control_check_subs_pane(c, csub);
|
||||
break;
|
||||
case CONTROL_SUB_WINDOW:
|
||||
control_check_subs_window(c, csub);
|
||||
break;
|
||||
case CONTROL_SUB_SESSION:
|
||||
case CONTROL_SUB_ALL_PANES:
|
||||
case CONTROL_SUB_ALL_WINDOWS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all-panes subscriptions. */
|
||||
if (have_all_panes) {
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
TAILQ_FOREACH(wp, &wl->window->panes, entry) {
|
||||
ft = format_create_defaults(NULL, c, s, wl, wp);
|
||||
RB_FOREACH_SAFE(csub, control_subs, &cs->subs,
|
||||
csub1) {
|
||||
if (csub->type != CONTROL_SUB_ALL_PANES)
|
||||
continue;
|
||||
control_check_subs_all_panes_one(c,
|
||||
csub, ft, wl, wp);
|
||||
}
|
||||
format_free(ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all-windows subscriptions. */
|
||||
if (have_all_windows) {
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
ft = format_create_defaults(NULL, c, s, wl, NULL);
|
||||
RB_FOREACH_SAFE(csub, control_subs, &cs->subs,
|
||||
csub1) {
|
||||
if (csub->type != CONTROL_SUB_ALL_WINDOWS)
|
||||
continue;
|
||||
control_check_subs_all_windows_one(c, csub, ft,
|
||||
wl);
|
||||
}
|
||||
format_free(ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a subscription. */
|
||||
void
|
||||
control_add_sub(struct client *c, const char *name, enum control_sub_type type,
|
||||
control_add_sub(struct client *c, const char *name, enum monitor_type type,
|
||||
int id, const char *format)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_sub *csub, find;
|
||||
struct timeval tv = { .tv_sec = 1 };
|
||||
|
||||
find.name = (char *)name;
|
||||
if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
|
||||
control_free_sub(cs, csub);
|
||||
|
||||
csub = xcalloc(1, sizeof *csub);
|
||||
csub->name = xstrdup(name);
|
||||
csub->type = type;
|
||||
csub->id = id;
|
||||
csub->format = xstrdup(format);
|
||||
RB_INSERT(control_subs, &cs->subs, csub);
|
||||
|
||||
RB_INIT(&csub->panes);
|
||||
RB_INIT(&csub->windows);
|
||||
|
||||
if (!evtimer_initialized(&cs->subs_timer))
|
||||
evtimer_set(&cs->subs_timer, control_check_subs_timer, c);
|
||||
if (!evtimer_pending(&cs->subs_timer, NULL))
|
||||
evtimer_add(&cs->subs_timer, &tv);
|
||||
monitor_add(cs->subs, name, type, id, format, MONITOR_NOTIFY_INITIAL);
|
||||
}
|
||||
|
||||
/* Remove a subscription. */
|
||||
@@ -1168,11 +783,6 @@ void
|
||||
control_remove_sub(struct client *c, const char *name)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_sub *csub, find;
|
||||
|
||||
find.name = (char *)name;
|
||||
if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
|
||||
control_free_sub(cs, csub);
|
||||
if (RB_EMPTY(&cs->subs))
|
||||
evtimer_del(&cs->subs_timer);
|
||||
monitor_remove(cs->subs, name);
|
||||
}
|
||||
|
||||
509
format.c
509
format.c
@@ -42,6 +42,7 @@
|
||||
struct format_expand_state;
|
||||
|
||||
static char *format_job_get(struct format_expand_state *, const char *);
|
||||
static char *format_quote_shell_single(const char *);
|
||||
static char *format_expand1(struct format_expand_state *, const char *);
|
||||
static int format_replace(struct format_expand_state *, const char *,
|
||||
size_t, char **, size_t *, size_t *);
|
||||
@@ -121,6 +122,11 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
|
||||
#define FORMAT_CLIENT_TERMCAP 0x1000000
|
||||
#define FORMAT_CLIENT_TERMFEAT 0x2000000
|
||||
#define FORMAT_CLIENT_ENVIRON 0x4000000
|
||||
#define FORMAT_COLOUR_ESC_FG 0x8000000
|
||||
#define FORMAT_COLOUR_ESC_BG 0x10000000
|
||||
#define FORMAT_QUOTE_SHELL_SQ 0x20000000
|
||||
#define FORMAT_OPTIONS 0x40000000
|
||||
#define FORMAT_ENVIRON 0x80000000ULL
|
||||
|
||||
/* Limit on recursion. */
|
||||
#define FORMAT_LOOP_LIMIT 100
|
||||
@@ -885,6 +891,37 @@ format_cb_start_command(struct format_tree *ft)
|
||||
return (cmd_stringify_argv(wp->argc, wp->argv));
|
||||
}
|
||||
|
||||
/* Callback for pane_start_command_list. */
|
||||
static void *
|
||||
format_cb_start_command_list(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
char *buf = NULL, *s;
|
||||
size_t len = 0;
|
||||
int i;
|
||||
|
||||
if (wp == NULL)
|
||||
return (NULL);
|
||||
if (wp->argc == 0)
|
||||
return (xstrdup(""));
|
||||
|
||||
for (i = 0; i < wp->argc; i++) {
|
||||
s = format_quote_shell_single(wp->argv[i]);
|
||||
|
||||
len += strlen(s) + 1;
|
||||
buf = xrealloc(buf, len);
|
||||
|
||||
if (i == 0)
|
||||
*buf = '\0';
|
||||
else
|
||||
strlcat(buf, " ", len);
|
||||
strlcat(buf, s, len);
|
||||
|
||||
free(s);
|
||||
}
|
||||
return (buf);
|
||||
}
|
||||
|
||||
/* Callback for pane_start_path. */
|
||||
static void *
|
||||
format_cb_start_path(struct format_tree *ft)
|
||||
@@ -2313,7 +2350,7 @@ format_cb_pane_path(struct format_tree *ft)
|
||||
static void *
|
||||
format_cb_pane_pid(struct format_tree *ft)
|
||||
{
|
||||
if (ft->wp != NULL)
|
||||
if (ft->wp != NULL && ft->wp->fd != -1)
|
||||
return (format_printf("%ld", (long)ft->wp->pid));
|
||||
return (NULL);
|
||||
}
|
||||
@@ -2868,6 +2905,19 @@ format_cb_window_height(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for window_manual_height. */
|
||||
static void *
|
||||
format_cb_window_manual_height(struct format_tree *ft)
|
||||
{
|
||||
struct window *w = ft->w;
|
||||
|
||||
if (w == NULL)
|
||||
return (NULL);
|
||||
if (options_get_number(w->options, "window-size") != WINDOW_SIZE_MANUAL)
|
||||
return (xstrdup(""));
|
||||
return (format_printf("%u", w->manual_sy));
|
||||
}
|
||||
|
||||
/* Callback for window_id. */
|
||||
static void *
|
||||
format_cb_window_id(struct format_tree *ft)
|
||||
@@ -3048,6 +3098,19 @@ format_cb_window_width(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for window_manual_width. */
|
||||
static void *
|
||||
format_cb_window_manual_width(struct format_tree *ft)
|
||||
{
|
||||
struct window *w = ft->w;
|
||||
|
||||
if (w == NULL)
|
||||
return (NULL);
|
||||
if (options_get_number(w->options, "window-size") != WINDOW_SIZE_MANUAL)
|
||||
return (xstrdup(""));
|
||||
return (format_printf("%u", w->manual_sx));
|
||||
}
|
||||
|
||||
/* Callback for window_zoomed_flag. */
|
||||
static void *
|
||||
format_cb_window_zoomed_flag(struct format_tree *ft)
|
||||
@@ -3538,6 +3601,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "pane_start_command", FORMAT_TABLE_STRING,
|
||||
format_cb_start_command
|
||||
},
|
||||
{ "pane_start_command_list", FORMAT_TABLE_STRING,
|
||||
format_cb_start_command_list
|
||||
},
|
||||
{ "pane_start_path", FORMAT_TABLE_STRING,
|
||||
format_cb_start_path
|
||||
},
|
||||
@@ -3754,6 +3820,12 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "window_linked_sessions_list", FORMAT_TABLE_STRING,
|
||||
format_cb_window_linked_sessions_list
|
||||
},
|
||||
{ "window_manual_height", FORMAT_TABLE_STRING,
|
||||
format_cb_window_manual_height
|
||||
},
|
||||
{ "window_manual_width", FORMAT_TABLE_STRING,
|
||||
format_cb_window_manual_width
|
||||
},
|
||||
{ "window_marked_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_window_marked_flag
|
||||
},
|
||||
@@ -4034,6 +4106,29 @@ format_quote_shell(const char *s)
|
||||
return (out);
|
||||
}
|
||||
|
||||
/* Quote string with POSIX shell single quotes. */
|
||||
static char *
|
||||
format_quote_shell_single(const char *s)
|
||||
{
|
||||
const char *cp;
|
||||
char *out, *at;
|
||||
|
||||
at = out = xmalloc(strlen(s) * 4 + 3);
|
||||
*at++ = '\'';
|
||||
for (cp = s; *cp != '\0'; cp++) {
|
||||
if (*cp == '\'') {
|
||||
*at++ = '\'';
|
||||
*at++ = '\\';
|
||||
*at++ = '\'';
|
||||
*at++ = '\'';
|
||||
} else
|
||||
*at++ = *cp;
|
||||
}
|
||||
*at++ = '\'';
|
||||
*at = '\0';
|
||||
return (out);
|
||||
}
|
||||
|
||||
/* Quote #s in string. */
|
||||
static char *
|
||||
format_quote_style(const char *s)
|
||||
@@ -4137,7 +4232,7 @@ format_relative_time(time_t t)
|
||||
|
||||
/* Find a format entry. */
|
||||
static char *
|
||||
format_find(struct format_tree *ft, const char *key, int modifiers,
|
||||
format_find(struct format_tree *ft, const char *key, uint64_t modifiers,
|
||||
const char *time_format)
|
||||
{
|
||||
const struct format_table_entry *fte;
|
||||
@@ -4252,6 +4347,11 @@ found:
|
||||
found = format_quote_shell(saved);
|
||||
free(saved);
|
||||
}
|
||||
if (modifiers & FORMAT_QUOTE_SHELL_SQ) {
|
||||
saved = found;
|
||||
found = format_quote_shell_single(saved);
|
||||
free(saved);
|
||||
}
|
||||
if (modifiers & FORMAT_QUOTE_STYLE) {
|
||||
saved = found;
|
||||
found = format_quote_style(saved);
|
||||
@@ -4462,7 +4562,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,R,<,>
|
||||
* l,m,C,a,b,c,d,I,n,t,w,q,E,T,S,W,P,O,V,R,<,>
|
||||
* =a
|
||||
* =/a
|
||||
* =/a/
|
||||
@@ -4481,7 +4581,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
break;
|
||||
|
||||
/* Check single character modifiers with no arguments. */
|
||||
if (strchr("labcdnwETSWPL!<>", cp[0]) != NULL &&
|
||||
if (strchr("labdnwETSWPOVL!<>", cp[0]) != NULL &&
|
||||
format_is_end(cp[1])) {
|
||||
format_add_modifier(&list, count, cp, 1, NULL, 0);
|
||||
cp++;
|
||||
@@ -4503,7 +4603,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
}
|
||||
|
||||
/* Now try single character with arguments. */
|
||||
if (strchr("ImCLNPSst=pReqW", cp[0]) == NULL)
|
||||
if (strchr("ImCLNPSOVst=pReqWc", cp[0]) == NULL)
|
||||
break;
|
||||
c = cp[0];
|
||||
|
||||
@@ -4780,11 +4880,14 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
|
||||
else
|
||||
use = all;
|
||||
nft = format_create(c, item, FORMAT_NONE, ft->flags);
|
||||
|
||||
format_add(nft, "loop_index", "%d", i);
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
|
||||
format_defaults(nft, ft->c, s, NULL, NULL);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
|
||||
expanded = format_expand1(&next, use);
|
||||
format_free(next.ft);
|
||||
|
||||
@@ -4827,9 +4930,9 @@ format_window_name(struct format_expand_state *es, const char *fmt)
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
|
||||
/* Add neighbor window variables to the format tree. */
|
||||
/* Add neighbour window variables to the format tree. */
|
||||
static void
|
||||
format_add_window_neighbor(struct format_tree *nft, struct winlink *wl,
|
||||
format_add_window_neighbour(struct format_tree *nft, struct winlink *wl,
|
||||
struct session *s, const char *prefix)
|
||||
{
|
||||
struct options_entry *o;
|
||||
@@ -4862,20 +4965,21 @@ format_add_window_neighbor(struct format_tree *nft, struct winlink *wl,
|
||||
static char *
|
||||
format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
{
|
||||
struct sort_criteria *sc = &sort_crit;
|
||||
struct format_tree *ft = es->ft;
|
||||
struct client *c = ft->client;
|
||||
struct cmdq_item *item = ft->item;
|
||||
struct format_tree *nft;
|
||||
struct format_expand_state next;
|
||||
char *all, *active, *use, *expanded, *value;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
struct winlink *wl, **l;
|
||||
struct window *w;
|
||||
int i, n;
|
||||
struct sort_criteria *sc = &sort_crit;
|
||||
struct format_tree *ft = es->ft;
|
||||
struct client *c = ft->client;
|
||||
struct session *s = ft->s;
|
||||
struct cmdq_item *item = ft->item;
|
||||
struct format_tree *nft;
|
||||
struct format_expand_state next;
|
||||
char *all, *active, *use, *expanded, *value;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
struct winlink *wl, **l;
|
||||
struct window *w;
|
||||
int i, n;
|
||||
|
||||
if (ft->s == NULL) {
|
||||
if (s == NULL) {
|
||||
format_log(es, "window loop but no session");
|
||||
return (NULL);
|
||||
}
|
||||
@@ -4889,33 +4993,38 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
if (buffer == NULL)
|
||||
fatalx("out of memory");
|
||||
|
||||
l = sort_get_winlinks_session(ft->s, &n, sc);
|
||||
l = sort_get_winlinks_session(s, &n, sc);
|
||||
for (i = 0; i < n; i++) {
|
||||
wl = l[i];
|
||||
w = wl->window;
|
||||
format_log(es, "window loop: %u @%u", wl->idx, w->id);
|
||||
if (active != NULL && wl == ft->s->curw)
|
||||
if (active != NULL && wl == s->curw)
|
||||
use = active;
|
||||
else
|
||||
use = all;
|
||||
nft = format_create(c, item, FORMAT_WINDOW|w->id,
|
||||
ft->flags);
|
||||
nft = format_create(c, item, FORMAT_WINDOW|w->id, ft->flags);
|
||||
|
||||
format_add(nft, "loop_index", "%d", i);
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
format_defaults(nft, ft->c, ft->s, wl, NULL);
|
||||
|
||||
/* Add neighbor window data to the format tree. */
|
||||
format_add(nft, "window_after_active", "%d",
|
||||
i > 0 && l[i - 1] == ft->s->curw);
|
||||
format_add(nft, "window_before_active", "%d",
|
||||
i + 1 < n && l[i + 1] == ft->s->curw);
|
||||
/* Add neighbour window data to the format tree. */
|
||||
if (i > 0 && l[i - 1] == s->curw)
|
||||
format_add(nft, "window_after_active", "1");
|
||||
else
|
||||
format_add(nft, "window_after_active", "0");
|
||||
if (i + 1 < n && l[i + 1] == s->curw)
|
||||
format_add(nft, "window_before_active", "1");
|
||||
else
|
||||
format_add(nft, "window_before_active", "0");
|
||||
if (i + 1 < n)
|
||||
format_add_window_neighbor(nft, l[i + 1], ft->s, "next");
|
||||
format_add_window_neighbour(nft, l[i + 1], s, "next");
|
||||
if (i > 0)
|
||||
format_add_window_neighbor(nft, l[i - 1], ft->s, "prev");
|
||||
format_add_window_neighbour(nft, l[i - 1], s, "prev");
|
||||
|
||||
format_defaults(nft, ft->c, s, wl, NULL);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
|
||||
expanded = format_expand1(&next, use);
|
||||
format_free(nft);
|
||||
|
||||
@@ -4972,13 +5081,15 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
|
||||
use = active;
|
||||
else
|
||||
use = all;
|
||||
nft = format_create(c, item, FORMAT_PANE|wp->id,
|
||||
ft->flags);
|
||||
nft = format_create(c, item, FORMAT_PANE|wp->id, ft->flags);
|
||||
|
||||
format_add(nft, "loop_index", "%d", i);
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
|
||||
format_defaults(nft, ft->c, ft->s, ft->wl, wp);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
|
||||
expanded = format_expand1(&next, use);
|
||||
format_free(nft);
|
||||
|
||||
@@ -4997,6 +5108,269 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
|
||||
return (value);
|
||||
}
|
||||
|
||||
/* Add an option to an options loop. */
|
||||
static void
|
||||
format_loop_add_option(struct format_expand_state *es, const char *fmt,
|
||||
struct evbuffer *buffer, struct options_entry *o, u_int n, u_int i)
|
||||
{
|
||||
struct format_tree *ft = es->ft, *nft;
|
||||
struct format_expand_state next;
|
||||
const struct options_table_entry *oe = options_table_entry(o);
|
||||
const char *name = options_name(o);
|
||||
char *expanded, *s;
|
||||
int is_array = options_is_array(o);
|
||||
|
||||
format_log(es, "option loop: %s", name);
|
||||
nft = format_create(ft->client, ft->item, FORMAT_NONE, ft->flags);
|
||||
|
||||
format_add(nft, "option_name", "%s", name);
|
||||
s = options_to_string(o, -1, 0);
|
||||
format_add(nft, "option_value", "%s", s);
|
||||
free(s);
|
||||
|
||||
format_add(nft, "option_is_array", "%d", is_array);
|
||||
format_add(nft, "option_array_index", "%s", "");
|
||||
format_add(nft, "option_array_first", "%d", is_array);
|
||||
format_add(nft, "option_array_last", "%d", is_array);
|
||||
format_add(nft, "option_array_count", "%u", n);
|
||||
|
||||
if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK))
|
||||
format_add(nft, "option_is_hook", "1");
|
||||
else
|
||||
format_add(nft, "option_is_hook", "0");
|
||||
format_add(nft, "option_is_user", "%d", oe == NULL);
|
||||
|
||||
if (options_next(o) == NULL)
|
||||
format_add(nft, "loop_last_flag", "1");
|
||||
else
|
||||
format_add(nft, "loop_last_flag", "0");
|
||||
format_add(nft, "loop_index", "%u", i);
|
||||
|
||||
format_defaults(nft, ft->c, ft->s, ft->wl, ft->wp);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
|
||||
expanded = format_expand1(&next, fmt);
|
||||
format_free(nft);
|
||||
evbuffer_add(buffer, expanded, strlen(expanded));
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
/* Add an array option item to an options loop. */
|
||||
static void
|
||||
format_loop_add_array_item(struct format_expand_state *es, const char *fmt,
|
||||
struct evbuffer *buffer, struct options_entry *o,
|
||||
struct options_array_item *a, int n, u_int i)
|
||||
{
|
||||
struct format_tree *ft = es->ft, *nft;
|
||||
struct format_expand_state next;
|
||||
const struct options_table_entry *oe = options_table_entry(o);
|
||||
const char *name = options_name(o);
|
||||
char *expanded, *s;
|
||||
u_int idx;
|
||||
|
||||
idx = options_array_item_index(a);
|
||||
format_log(es, "option loop: %s[%u]", name, idx);
|
||||
nft = format_create(ft->client, ft->item, FORMAT_NONE, ft->flags);
|
||||
|
||||
format_add(nft, "option_name", "%s", name);
|
||||
s = options_to_string(o, idx, 0);
|
||||
format_add(nft, "option_value", "%s", s);
|
||||
free(s);
|
||||
|
||||
format_add(nft, "option_is_array", "1");
|
||||
format_add(nft, "option_array_index", "%u", idx);
|
||||
if (a == options_array_first(o))
|
||||
format_add(nft, "option_array_first", "1");
|
||||
else
|
||||
format_add(nft, "option_array_first", "0");
|
||||
if (options_array_next(a) == NULL)
|
||||
format_add(nft, "option_array_last", "1");
|
||||
else
|
||||
format_add(nft, "option_array_last", "0");
|
||||
format_add(nft, "option_array_count", "%u", n);
|
||||
|
||||
if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK))
|
||||
format_add(nft, "option_is_hook", "1");
|
||||
else
|
||||
format_add(nft, "option_is_hook", "0");
|
||||
format_add(nft, "option_is_user", "%d", oe == NULL);
|
||||
|
||||
if (options_array_next(a) == NULL && options_next(o) == NULL)
|
||||
format_add(nft, "loop_last_flag", "1");
|
||||
else
|
||||
format_add(nft, "loop_last_flag", "0");
|
||||
format_add(nft, "loop_index", "%u", i);
|
||||
|
||||
format_defaults(nft, ft->c, ft->s, ft->wl, ft->wp);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
|
||||
expanded = format_expand1(&next, fmt);
|
||||
format_free(nft);
|
||||
evbuffer_add(buffer, expanded, strlen(expanded));
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
/* Loop over options. */
|
||||
static char *
|
||||
format_loop_options(struct format_expand_state *es, const char *fmt,
|
||||
const char *flags)
|
||||
{
|
||||
struct format_tree *ft = es->ft;
|
||||
struct options *oo = NULL;
|
||||
struct options_entry *o;
|
||||
struct options_array_item *a;
|
||||
char *value;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
u_int i = 0, n;
|
||||
int global = 0;
|
||||
|
||||
if (flags == NULL || *flags == '\0')
|
||||
flags = "s";
|
||||
if (strchr(flags, 'v') != NULL)
|
||||
oo = global_options;
|
||||
else {
|
||||
if (strchr(flags, 'g') != NULL)
|
||||
global = 1;
|
||||
if (strchr(flags, 'w') != NULL) {
|
||||
if (global)
|
||||
oo = global_w_options;
|
||||
else if (ft->w != NULL)
|
||||
oo = ft->w->options;
|
||||
} else if (strchr(flags, 's') != NULL) {
|
||||
if (global)
|
||||
oo = global_s_options;
|
||||
else if (ft->s != NULL)
|
||||
oo = ft->s->options;
|
||||
} else if (strchr(flags, 'p') != NULL) {
|
||||
if (global)
|
||||
/* invalid */;
|
||||
else if (ft->wp != NULL)
|
||||
oo = ft->wp->options;
|
||||
} else if (global)
|
||||
oo = global_s_options;
|
||||
}
|
||||
if (oo == NULL)
|
||||
return (xstrdup(""));
|
||||
|
||||
buffer = evbuffer_new();
|
||||
if (buffer == NULL)
|
||||
fatalx("out of memory");
|
||||
|
||||
o = options_first(oo);
|
||||
while (o != NULL) {
|
||||
|
||||
n = 0;
|
||||
if (options_is_array(o)) {
|
||||
a = options_array_first(o);
|
||||
while (a != NULL) {
|
||||
n++;
|
||||
a = options_array_next(a);
|
||||
}
|
||||
}
|
||||
|
||||
if (!options_is_array(o) || n == 0) {
|
||||
format_loop_add_option(es, fmt, buffer, o, n, i);
|
||||
i++;
|
||||
o = options_next(o);
|
||||
continue;
|
||||
}
|
||||
|
||||
a = options_array_first(o);
|
||||
while (a != NULL) {
|
||||
format_loop_add_array_item(es, fmt, buffer, o, a, n, i);
|
||||
i++;
|
||||
a = options_array_next(a);
|
||||
}
|
||||
o = options_next(o);
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
else
|
||||
value = xstrdup("");
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
|
||||
/* Loop over an environment. */
|
||||
static char *
|
||||
format_loop_environ(struct format_expand_state *es, const char *fmt,
|
||||
const char *flags)
|
||||
{
|
||||
struct format_tree *ft = es->ft, *nft;
|
||||
struct client *c = ft->client;
|
||||
struct cmdq_item *item = ft->item;
|
||||
struct format_expand_state next;
|
||||
struct environ *env = NULL;
|
||||
struct environ_entry *envent;
|
||||
char *expanded, *value;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
u_int i = 0;
|
||||
|
||||
if (flags == NULL || *flags == '\0' || strcmp(flags, "s") == 0) {
|
||||
if (ft->s != NULL)
|
||||
env = ft->s->environ;
|
||||
} else if (strcmp(flags, "g") == 0)
|
||||
env = global_environ;
|
||||
else if (strcmp(flags, "c") == 0) {
|
||||
if (ft->client != NULL)
|
||||
env = ft->client->environ;
|
||||
}
|
||||
if (env == NULL)
|
||||
return (xstrdup(""));
|
||||
|
||||
buffer = evbuffer_new();
|
||||
if (buffer == NULL)
|
||||
fatalx("out of memory");
|
||||
|
||||
envent = environ_first(env);
|
||||
while (envent != NULL) {
|
||||
format_log(es, "environment loop: %s", envent->name);
|
||||
nft = format_create(c, item, FORMAT_NONE, ft->flags);
|
||||
|
||||
format_add(nft, "environ_name", "%s", envent->name);
|
||||
if (envent->value == NULL)
|
||||
format_add(nft, "environ_value", "%s", "");
|
||||
else
|
||||
format_add(nft, "environ_value", "%s", envent->value);
|
||||
|
||||
if (envent->flags & ENVIRON_HIDDEN)
|
||||
format_add(nft, "environ_hidden", "1");
|
||||
else
|
||||
format_add(nft, "environ_hidden", "0");
|
||||
format_add(nft, "environ_removed", "%d", envent->value == NULL);
|
||||
|
||||
if (environ_next(envent) == NULL)
|
||||
format_add(nft, "loop_last_flag", "1");
|
||||
else
|
||||
format_add(nft, "loop_last_flag", "0");
|
||||
format_add(nft, "loop_index", "%u", i);
|
||||
|
||||
format_defaults(nft, ft->c, ft->s, ft->wl, ft->wp);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
|
||||
expanded = format_expand1(&next, fmt);
|
||||
format_free(nft);
|
||||
evbuffer_add(buffer, expanded, strlen(expanded));
|
||||
free(expanded);
|
||||
|
||||
i++;
|
||||
envent = environ_next(envent);
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
else
|
||||
value = xstrdup("");
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
|
||||
/* Loop over clients. */
|
||||
static char *
|
||||
format_loop_clients(struct format_expand_state *es, const char *fmt)
|
||||
@@ -5021,14 +5395,16 @@ format_loop_clients(struct format_expand_state *es, const char *fmt)
|
||||
c = l[i];
|
||||
format_log(es, "client loop: %s", c->name);
|
||||
nft = format_create(c, item, 0, ft->flags);
|
||||
|
||||
format_add(nft, "loop_index", "%d", i);
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
|
||||
format_defaults(nft, c, ft->s, ft->wl, ft->wp);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
|
||||
expanded = format_expand1(&next, fmt);
|
||||
format_free(nft);
|
||||
|
||||
evbuffer_add(buffer, expanded, strlen(expanded));
|
||||
free(expanded);
|
||||
}
|
||||
@@ -5198,12 +5574,14 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
char *copy0, *condition, *found, *new;
|
||||
char *value, *left, *right;
|
||||
size_t valuelen;
|
||||
int modifiers = 0, limit = 0, width = 0;
|
||||
uint64_t modifiers = 0;
|
||||
int limit = 0, width = 0;
|
||||
int j, c;
|
||||
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 i, count, nsub = 0, nrep, check = 0;
|
||||
const char *loop_flags = "";
|
||||
struct format_expand_state next;
|
||||
struct environ_entry *envent;
|
||||
|
||||
@@ -5281,6 +5659,12 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
break;
|
||||
case 'c':
|
||||
modifiers |= FORMAT_COLOUR;
|
||||
if (fm->argc < 1)
|
||||
break;
|
||||
if (strchr(fm->argv[0], 'f') != NULL)
|
||||
modifiers |= FORMAT_COLOUR_ESC_FG;
|
||||
if (strchr(fm->argv[0], 'b') != NULL)
|
||||
modifiers |= FORMAT_COLOUR_ESC_BG;
|
||||
break;
|
||||
case 'd':
|
||||
modifiers |= FORMAT_DIRNAME;
|
||||
@@ -5316,6 +5700,8 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
case 'q':
|
||||
if (fm->argc < 1)
|
||||
modifiers |= FORMAT_QUOTE_SHELL;
|
||||
else if (strchr(fm->argv[0], 's') != NULL)
|
||||
modifiers |= FORMAT_QUOTE_SHELL_SQ;
|
||||
else if (strchr(fm->argv[0], 'e') != NULL ||
|
||||
strchr(fm->argv[0], 'h') != NULL)
|
||||
modifiers |= FORMAT_QUOTE_STYLE;
|
||||
@@ -5382,11 +5768,27 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
sc->reversed = 0;
|
||||
break;
|
||||
}
|
||||
if (strchr(fm->argv[0], 'i') != NULL)
|
||||
sc->order = SORT_INDEX;
|
||||
else if (strchr(fm->argv[0], 'z') != NULL)
|
||||
sc->order = SORT_Z;
|
||||
else
|
||||
sc->order = SORT_CREATION;
|
||||
if (strchr(fm->argv[0], 'r') != NULL)
|
||||
sc->reversed = 1;
|
||||
else
|
||||
sc->reversed = 0;
|
||||
break;
|
||||
case 'O':
|
||||
modifiers |= FORMAT_OPTIONS;
|
||||
if (fm->argc == 1)
|
||||
loop_flags = fm->argv[0];
|
||||
break;
|
||||
case 'V':
|
||||
modifiers |= FORMAT_ENVIRON;
|
||||
if (fm->argc == 1)
|
||||
loop_flags = fm->argv[0];
|
||||
break;
|
||||
case 'L':
|
||||
modifiers |= FORMAT_CLIENTS;
|
||||
if (fm->argc < 1) {
|
||||
@@ -5479,11 +5881,28 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
/* Is this a colour? */
|
||||
if (modifiers & FORMAT_COLOUR) {
|
||||
new = format_expand1(es, copy);
|
||||
c = colour_fromstring(new);
|
||||
if (c == -1 || (c = colour_force_rgb(c)) == -1)
|
||||
value = xstrdup("");
|
||||
else
|
||||
xasprintf(&value, "%06x", c & 0xffffff);
|
||||
if (modifiers & (FORMAT_COLOUR_ESC_FG|FORMAT_COLOUR_ESC_BG)) {
|
||||
if (strcasecmp(new, "none") == 0)
|
||||
value = xstrdup("\033[0m");
|
||||
else if ((c = colour_fromstring(new)) == -1)
|
||||
value = xstrdup("");
|
||||
else {
|
||||
if (modifiers & FORMAT_COLOUR_ESC_BG)
|
||||
cp = colour_toescape(ft->c, c, 1);
|
||||
else
|
||||
cp = colour_toescape(ft->c, c, 0);
|
||||
if (cp == NULL)
|
||||
value = xstrdup("");
|
||||
else
|
||||
value = xstrdup(cp);
|
||||
}
|
||||
} else {
|
||||
c = colour_fromstring(new);
|
||||
if (c == -1 || (c = colour_force_rgb(c)) == -1)
|
||||
value = xstrdup("");
|
||||
else
|
||||
xasprintf(&value, "%06x", c & 0xffffff);
|
||||
}
|
||||
free(new);
|
||||
goto done;
|
||||
}
|
||||
@@ -5505,6 +5924,14 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
value = format_loop_clients(es, copy);
|
||||
if (value == NULL)
|
||||
goto fail;
|
||||
} else if (modifiers & FORMAT_OPTIONS) {
|
||||
value = format_loop_options(es, copy, loop_flags);
|
||||
if (value == NULL)
|
||||
goto fail;
|
||||
} else if (modifiers & FORMAT_ENVIRON) {
|
||||
value = format_loop_environ(es, copy, loop_flags);
|
||||
if (value == NULL)
|
||||
goto fail;
|
||||
} else if (modifiers & FORMAT_WINDOW_NAME) {
|
||||
value = format_window_name(es, copy);
|
||||
if (value == NULL)
|
||||
|
||||
157
grid.c
157
grid.c
@@ -60,23 +60,37 @@ static const struct grid_cell_entry grid_cleared_entry = {
|
||||
};
|
||||
|
||||
#ifdef __APPLE__
|
||||
static void
|
||||
grid_check_lines(struct grid *gd)
|
||||
void
|
||||
grid_check_is_clear(struct grid *gd)
|
||||
{
|
||||
u_int i, j;
|
||||
struct grid_line *gl;
|
||||
u_int yy, ny;
|
||||
|
||||
for (i = 0; i < gd->hsize + gd->sy; i++) {
|
||||
for (j = i + 1; j < gd->hsize + gd->sy; j++) {
|
||||
if (gd->linedata[i].celldata != NULL)
|
||||
assert(gd->linedata[i].celldata != gd->linedata[j].celldata);
|
||||
if (gd->linedata[i].extddata != NULL)
|
||||
assert(gd->linedata[i].extddata != gd->linedata[j].extddata);
|
||||
}
|
||||
assert(gd != NULL);
|
||||
|
||||
if (gd->sy == 0) {
|
||||
assert(gd->linedata == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(gd->linedata != NULL);
|
||||
|
||||
ny = gd->hsize + gd->sy;
|
||||
for (yy = 0; yy < ny; yy++) {
|
||||
gl = &gd->linedata[yy];
|
||||
|
||||
assert(gl->celldata == NULL);
|
||||
assert(gl->cellused == 0);
|
||||
assert(gl->cellsize == 0);
|
||||
assert(gl->extddata == NULL);
|
||||
assert(gl->extdsize == 0);
|
||||
assert(gl->flags == 0);
|
||||
assert(gl->time == 0);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void
|
||||
grid_check_lines(__unused struct grid *gd)
|
||||
void
|
||||
grid_check_is_clear(__unused struct grid *gd)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
@@ -341,28 +355,18 @@ grid_create(u_int sx, u_int sy, u_int hlimit)
|
||||
{
|
||||
struct grid *gd;
|
||||
|
||||
gd = xmalloc(sizeof *gd);
|
||||
gd = xcalloc(1, sizeof *gd);
|
||||
gd->sx = sx;
|
||||
gd->sy = sy;
|
||||
|
||||
if (hlimit != 0)
|
||||
gd->flags = GRID_HISTORY;
|
||||
else
|
||||
gd->flags = 0;
|
||||
|
||||
gd->hscrolled = 0;
|
||||
gd->hsize = 0;
|
||||
gd->hlimit = hlimit;
|
||||
|
||||
gd->scroll_added = 0;
|
||||
gd->scroll_collected = 0;
|
||||
gd->scroll_generation = 0;
|
||||
|
||||
if (gd->sy != 0)
|
||||
gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata);
|
||||
else
|
||||
gd->linedata = NULL;
|
||||
|
||||
grid_check_is_clear(gd);
|
||||
return (gd);
|
||||
}
|
||||
|
||||
@@ -482,8 +486,6 @@ grid_scroll_history(struct grid *gd, u_int bg)
|
||||
gd->linedata[gd->hsize].time = current_time;
|
||||
gd->hsize++;
|
||||
gd->scroll_added++;
|
||||
|
||||
grid_check_lines(gd);
|
||||
}
|
||||
|
||||
/* Clear the history. */
|
||||
@@ -533,8 +535,6 @@ grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg)
|
||||
gd->hscrolled++;
|
||||
gd->hsize++;
|
||||
gd->scroll_added++;
|
||||
|
||||
grid_check_lines(gd);
|
||||
}
|
||||
|
||||
/* Expand line to fit to cell. */
|
||||
@@ -796,8 +796,6 @@ grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg)
|
||||
}
|
||||
if (py != 0 && (py < dy || py >= dy + ny))
|
||||
gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
|
||||
|
||||
grid_check_lines(gd);
|
||||
}
|
||||
|
||||
/* Move a group of cells. */
|
||||
@@ -1287,8 +1285,6 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
sy++;
|
||||
dy++;
|
||||
}
|
||||
|
||||
grid_check_lines(dst);
|
||||
}
|
||||
|
||||
/* Mark line as dead. */
|
||||
@@ -1585,8 +1581,6 @@ grid_reflow(struct grid *gd, u_int sx)
|
||||
gd->linedata = target->linedata;
|
||||
free(target);
|
||||
gd->scroll_generation++;
|
||||
|
||||
grid_check_lines(gd);
|
||||
}
|
||||
|
||||
/* Convert to position based on wrapped lines. */
|
||||
@@ -1688,3 +1682,98 @@ grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
|
||||
return (0);
|
||||
return (utf8_cstrhas(set, &gc.data));
|
||||
}
|
||||
|
||||
/* Line flags to string. */
|
||||
const char *
|
||||
grid_line_flags_string(int flags)
|
||||
{
|
||||
static char s[128];
|
||||
|
||||
*s = '\0';
|
||||
if (flags & GRID_LINE_WRAPPED)
|
||||
strlcat(s, "WRAPPED,", sizeof s);
|
||||
if (flags & GRID_LINE_EXTENDED)
|
||||
strlcat(s, "EXTENDED,", sizeof s);
|
||||
if (flags & GRID_LINE_DEAD)
|
||||
strlcat(s, "DEAD,", sizeof s);
|
||||
if (flags & GRID_LINE_START_PROMPT)
|
||||
strlcat(s, "START_PROMPT,", sizeof s);
|
||||
if (flags & GRID_LINE_START_OUTPUT)
|
||||
strlcat(s, "START_OUTPUT,", sizeof s);
|
||||
if (flags & GRID_LINE_HYPERLINK)
|
||||
strlcat(s, "HYPERLINK,", sizeof s);
|
||||
if (*s == '\0')
|
||||
return ("NONE");
|
||||
s[strlen(s) - 1] = '\0';
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* Cell flags to string. */
|
||||
const char *
|
||||
grid_cell_flags_string(int flags)
|
||||
{
|
||||
static char s[128];
|
||||
|
||||
*s = '\0';
|
||||
if (flags & GRID_FLAG_FG256)
|
||||
strlcat(s, "FG256,", sizeof s);
|
||||
if (flags & GRID_FLAG_BG256)
|
||||
strlcat(s, "BG256,", sizeof s);
|
||||
if (flags & GRID_FLAG_PADDING)
|
||||
strlcat(s, "PADDING,", sizeof s);
|
||||
if (flags & GRID_FLAG_EXTENDED)
|
||||
strlcat(s, "EXTENDED,", sizeof s);
|
||||
if (flags & GRID_FLAG_SELECTED)
|
||||
strlcat(s, "SELECTED,", sizeof s);
|
||||
if (flags & GRID_FLAG_CLEARED)
|
||||
strlcat(s, "CLEARED,", sizeof s);
|
||||
if (flags & GRID_FLAG_TAB)
|
||||
strlcat(s, "TAB,", sizeof s);
|
||||
if (flags & GRID_FLAG_NOPALETTE)
|
||||
strlcat(s, "NOPALETTE,", sizeof s);
|
||||
if (*s == '\0')
|
||||
return ("NONE");
|
||||
s[strlen(s) - 1] = '\0';
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* Cell attributes to string. */
|
||||
const char *
|
||||
grid_cell_attr_string(int attr)
|
||||
{
|
||||
static char s[256];
|
||||
|
||||
*s = '\0';
|
||||
if (attr & GRID_ATTR_CHARSET)
|
||||
strlcat(s, "CHARSET,", sizeof s);
|
||||
if (attr & GRID_ATTR_BRIGHT)
|
||||
strlcat(s, "BRIGHT,", sizeof s);
|
||||
if (attr & GRID_ATTR_DIM)
|
||||
strlcat(s, "DIM,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE)
|
||||
strlcat(s, "UNDERSCORE,", sizeof s);
|
||||
if (attr & GRID_ATTR_BLINK)
|
||||
strlcat(s, "BLINK,", sizeof s);
|
||||
if (attr & GRID_ATTR_REVERSE)
|
||||
strlcat(s, "REVERSE,", sizeof s);
|
||||
if (attr & GRID_ATTR_HIDDEN)
|
||||
strlcat(s, "HIDDEN,", sizeof s);
|
||||
if (attr & GRID_ATTR_ITALICS)
|
||||
strlcat(s, "ITALICS,", sizeof s);
|
||||
if (attr & GRID_ATTR_STRIKETHROUGH)
|
||||
strlcat(s, "STRIKETHROUGH,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE_2)
|
||||
strlcat(s, "UNDERSCORE_2,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE_3)
|
||||
strlcat(s, "UNDERSCORE_3,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE_4)
|
||||
strlcat(s, "UNDERSCORE_4,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE_5)
|
||||
strlcat(s, "UNDERSCORE_5,", sizeof s);
|
||||
if (attr & GRID_ATTR_OVERLINE)
|
||||
strlcat(s, "OVERLINE,", sizeof s);
|
||||
if (*s == '\0')
|
||||
return ("NONE");
|
||||
s[strlen(s) - 1] = '\0';
|
||||
return (s);
|
||||
}
|
||||
|
||||
@@ -59,12 +59,14 @@
|
||||
" '#{?mouse_hyperlink,Type #[underscore]#{=/9/...:mouse_hyperlink},}' 'C-h' {copy-mode -q; send-keys -l -- \"#{q:mouse_hyperlink}\"}" \
|
||||
" '#{?mouse_hyperlink,Copy #[underscore]#{=/9/...:mouse_hyperlink},}' 'h' {copy-mode -q; set-buffer -- \"#{q:mouse_hyperlink}\"}" \
|
||||
" ''" \
|
||||
" '#{?#{#{pane_floating_flag}},Tile,}' 't' { join-pane }" \
|
||||
" '#{?#{!:#{pane_floating_flag}},Float,}' 'f' { break-pane -W }" \
|
||||
" '#{?#{!:#{pane_floating_flag}},Horizontal Split,}' 'h' {split-window -h}" \
|
||||
" '#{?#{!:#{pane_floating_flag}},Vertical Split,}' 'v' {split-window -v}" \
|
||||
" ''" \
|
||||
" '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Up,}' 'u' {swap-pane -U}" \
|
||||
" '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Down,}' 'd' {swap-pane -D}" \
|
||||
" '#{?#{!:#{pane_floating_flag}},#{?pane_marked_set,,-}Swap Marked,}' 's' {swap-pane}" \
|
||||
" '#{?pane_marked_set,,-}Swap Marked' 's' {swap-pane}" \
|
||||
" ''" \
|
||||
" 'Kill' 'X' {kill-pane}" \
|
||||
" 'Respawn' 'R' {respawn-pane -k}" \
|
||||
@@ -362,6 +364,7 @@ key_bindings_init(void)
|
||||
"bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }",
|
||||
"bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }",
|
||||
"bind -N 'New floating pane' * { new-pane }",
|
||||
"bind -N 'Toggle pane between floating and tiled' @ { if -F '#{pane_floating_flag}' { join-pane } { break-pane -W } }",
|
||||
"bind -N 'Switch to previous client' ( { switch-client -p }",
|
||||
"bind -N 'Switch to next client' ) { switch-client -n }",
|
||||
"bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }",
|
||||
@@ -479,6 +482,7 @@ key_bindings_init(void)
|
||||
/* Mouse button 1 down on default pane-border-format */
|
||||
"bind -n MouseDown1Control9 { display-menu -t= -xM -yM -O -T 'Kill pane #{pane_index}?' 'Yes' 'y' { kill-pane -t= } 'No' 'n' {}}",
|
||||
"bind -n MouseDown1Control8 { resize-pane -Z }",
|
||||
"bind -n MouseDown1Control7 { if -Ft= '#{pane_floating_flag}' { join-pane } { break-pane -W } }",
|
||||
|
||||
/* Mouse wheel down on status line. */
|
||||
"bind -n WheelDownStatus { next-window }",
|
||||
|
||||
@@ -727,7 +727,7 @@ layout_construct(struct layout_parse_ctx *ctx, struct layout_cell *parent,
|
||||
|
||||
fail:
|
||||
ctx->depth--;
|
||||
layout_free_cell(lc);
|
||||
layout_free_cell(lc, 0);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
@@ -919,20 +919,20 @@ layout_prepare(struct window *w, const char *layout, char **cause)
|
||||
}
|
||||
if (layout_construct(&ctx, NULL, &root) != 0) {
|
||||
*cause = xstrdup("invalid layout");
|
||||
layout_free_cell(root);
|
||||
layout_free_cell(root, 0);
|
||||
return (NULL);
|
||||
}
|
||||
layout_skip_space(&ctx);
|
||||
if (ctx.ptr != ctx.end || root == NULL) {
|
||||
*cause = xstrdup("invalid layout");
|
||||
layout_free_cell(root);
|
||||
layout_free_cell(root, 0);
|
||||
return (NULL);
|
||||
}
|
||||
if (layout_resolve_relative(root,
|
||||
root->type == LAYOUT_WINDOWPANE ? w->sx : root->sx,
|
||||
root->type == LAYOUT_WINDOWPANE ? w->sy : root->sy) != 0) {
|
||||
*cause = xstrdup("invalid layout");
|
||||
layout_free_cell(root);
|
||||
layout_free_cell(root, 0);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
@@ -1004,7 +1004,7 @@ layout_prepare(struct window *w, const char *layout, char **cause)
|
||||
return (prepared);
|
||||
|
||||
fail:
|
||||
layout_free_cell(root);
|
||||
layout_free_cell(root, 0);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
@@ -1014,7 +1014,7 @@ layout_free_prepared(struct layout_prepared *prepared)
|
||||
{
|
||||
if (prepared == NULL)
|
||||
return;
|
||||
layout_free_cell(prepared->root);
|
||||
layout_free_cell(prepared->root, 0);
|
||||
free(prepared);
|
||||
}
|
||||
|
||||
@@ -1029,7 +1029,7 @@ layout_apply_prepared(struct window *w, struct layout_prepared *prepared)
|
||||
prepared->root = NULL;
|
||||
|
||||
/* The layout was fully validated before the existing layout is changed. */
|
||||
layout_free_cell(w->layout_root);
|
||||
layout_free_cell(w->layout_root, 0);
|
||||
w->layout_root = root;
|
||||
wp = TAILQ_FIRST(&w->panes);
|
||||
layout_assign(w, &wp, root, prepared->pane_ids);
|
||||
|
||||
318
layout-set.c
318
layout-set.c
@@ -124,12 +124,12 @@ layout_set_previous(struct window *w)
|
||||
}
|
||||
|
||||
static struct window_pane *
|
||||
layout_first_tiled(struct window *w)
|
||||
layout_set_first_tiled(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (!window_pane_is_floating(wp))
|
||||
if (wp->layout_cell && layout_cell_is_tiled(wp->layout_cell))
|
||||
return (wp);
|
||||
}
|
||||
return (NULL);
|
||||
@@ -139,19 +139,15 @@ static void
|
||||
layout_set_even(struct window *w, enum layout_type type)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcnew;
|
||||
struct layout_cell *lcroot, *lcchild;
|
||||
u_int n, sx, sy;
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
/* Get number of panes. */
|
||||
n = window_count_panes(w, 0);
|
||||
if (n <= 1)
|
||||
return;
|
||||
|
||||
/* Free the old root and construct a new. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
if (type == LAYOUT_LEFTRIGHT) {
|
||||
sx = (n * (PANE_MINIMUM + 1)) - 1;
|
||||
if (sx < w->sx)
|
||||
@@ -163,30 +159,30 @@ layout_set_even(struct window *w, enum layout_type type)
|
||||
sy = w->sy;
|
||||
sx = w->sx;
|
||||
}
|
||||
layout_set_size(lc, sx, sy, 0, 0);
|
||||
layout_make_node(lc, type);
|
||||
|
||||
/* Build new leaf cells. */
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, sx, sy, 0, 0);
|
||||
layout_make_node(lcroot, type);
|
||||
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
continue;
|
||||
lcnew = layout_create_cell(lc);
|
||||
layout_make_leaf(lcnew, wp);
|
||||
lcnew->sx = w->sx;
|
||||
lcnew->sy = w->sy;
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry);
|
||||
lcchild->parent = lcroot;
|
||||
if (layout_cell_is_tiled(lcchild)) {
|
||||
lcchild->sx = w->sx;
|
||||
lcchild->sy = w->sy;
|
||||
}
|
||||
}
|
||||
|
||||
/* Spread out cells. */
|
||||
layout_spread_cell(w, lc);
|
||||
layout_spread_cell(w, lcroot);
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
@@ -206,15 +202,14 @@ layout_set_even_v(struct window *w)
|
||||
static void
|
||||
layout_set_main_h(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcmain, *lcother, *lcchild;
|
||||
struct window_pane *wp, *wpmain;
|
||||
struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
|
||||
u_int n, mainh, otherh, sx, sy;
|
||||
char *cause;
|
||||
const char *s;
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
/* Get number of panes. */
|
||||
n = window_count_panes(w, 0);
|
||||
if (n <= 1)
|
||||
return;
|
||||
@@ -255,52 +250,49 @@ layout_set_main_h(struct window *w)
|
||||
if (sx < w->sx)
|
||||
sx = w->sx;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lc, sx, mainh + otherh + 1, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_TOPBOTTOM);
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, sx, mainh + otherh + 1, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
|
||||
|
||||
/* Create the main pane. */
|
||||
lcmain = layout_create_cell(lc);
|
||||
wpmain = layout_set_first_tiled(w);
|
||||
lcmain = wpmain->layout_cell;
|
||||
lcmain->parent = lcroot;
|
||||
layout_set_size(lcmain, sx, mainh, 0, 0);
|
||||
layout_make_leaf(lcmain, layout_first_tiled(w));
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
|
||||
|
||||
/* Create the other pane. */
|
||||
lcother = layout_create_cell(lc);
|
||||
layout_set_size(lcother, sx, otherh, 0, 0);
|
||||
if (n == 1) {
|
||||
wp = TAILQ_NEXT(layout_first_tiled(w), entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wpmain, entry);
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
layout_make_leaf(lcother, wp);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
layout_set_size(wp->layout_cell, sx, otherh, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry);
|
||||
wp->layout_cell->parent = lcroot;
|
||||
} else {
|
||||
lcother = layout_create_cell(lcroot);
|
||||
layout_set_size(lcother, sx, otherh, 0, 0);
|
||||
layout_make_node(lcother, LAYOUT_LEFTRIGHT);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcother, entry);
|
||||
|
||||
/* Add the remaining panes as children. */
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
if (wp == wpmain)
|
||||
continue;
|
||||
if (wp == layout_first_tiled(w))
|
||||
continue;
|
||||
lcchild = layout_create_cell(lcother);
|
||||
layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
|
||||
lcchild->parent = lcother;
|
||||
if (layout_cell_is_tiled(lcchild))
|
||||
layout_set_size(lcchild, PANE_MINIMUM, otherh,
|
||||
0, 0);
|
||||
}
|
||||
layout_spread_cell(w, lcother);
|
||||
}
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
@@ -308,15 +300,14 @@ layout_set_main_h(struct window *w)
|
||||
static void
|
||||
layout_set_main_h_mirrored(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcmain, *lcother, *lcchild;
|
||||
struct window_pane *wp, *wpmain;
|
||||
struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
|
||||
u_int n, mainh, otherh, sx, sy;
|
||||
char *cause;
|
||||
const char *s;
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
/* Get number of panes. */
|
||||
n = window_count_panes(w, 0);
|
||||
if (n <= 1)
|
||||
return;
|
||||
@@ -357,52 +348,49 @@ layout_set_main_h_mirrored(struct window *w)
|
||||
if (sx < w->sx)
|
||||
sx = w->sx;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lc, sx, mainh + otherh + 1, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_TOPBOTTOM);
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, sx, mainh + otherh + 1, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
|
||||
|
||||
wpmain = layout_set_first_tiled(w);
|
||||
lcmain = wpmain->layout_cell;
|
||||
lcmain->parent = lcroot;
|
||||
layout_set_size(lcmain, sx, mainh, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
|
||||
|
||||
/* Create the other pane. */
|
||||
lcother = layout_create_cell(lc);
|
||||
layout_set_size(lcother, sx, otherh, 0, 0);
|
||||
if (n == 1) {
|
||||
wp = TAILQ_NEXT(layout_first_tiled(w), entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wpmain, entry);
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
layout_make_leaf(lcother, wp);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
layout_set_size(wp->layout_cell, sx, otherh, 0, 0);
|
||||
TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry);
|
||||
wp->layout_cell->parent = lcroot;
|
||||
} else {
|
||||
lcother = layout_create_cell(lcroot);
|
||||
layout_set_size(lcother, sx, otherh, 0, 0);
|
||||
layout_make_node(lcother, LAYOUT_LEFTRIGHT);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
TAILQ_INSERT_HEAD(&lcroot->cells, lcother, entry);
|
||||
|
||||
/* Add the remaining panes as children. */
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
if (wp == wpmain)
|
||||
continue;
|
||||
if (wp == layout_first_tiled(w))
|
||||
continue;
|
||||
lcchild = layout_create_cell(lcother);
|
||||
layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
|
||||
lcchild->parent = lcother;
|
||||
if (layout_cell_is_tiled(lcchild))
|
||||
layout_set_size(lcchild, PANE_MINIMUM, otherh,
|
||||
0, 0);
|
||||
}
|
||||
layout_spread_cell(w, lcother);
|
||||
}
|
||||
|
||||
/* Create the main pane. */
|
||||
lcmain = layout_create_cell(lc);
|
||||
layout_set_size(lcmain, sx, mainh, 0, 0);
|
||||
layout_make_leaf(lcmain, layout_first_tiled(w));
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
@@ -410,21 +398,20 @@ layout_set_main_h_mirrored(struct window *w)
|
||||
static void
|
||||
layout_set_main_v(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcmain, *lcother, *lcchild;
|
||||
struct window_pane *wp, *wpmain;
|
||||
struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
|
||||
u_int n, mainw, otherw, sx, sy;
|
||||
char *cause;
|
||||
const char *s;
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
/* Get number of panes. */
|
||||
n = window_count_panes(w, 0);
|
||||
if (n <= 1)
|
||||
return;
|
||||
n--; /* take off main pane */
|
||||
|
||||
/* Find available width - take off one line for the border. */
|
||||
/* Find available width - take off one column for the border. */
|
||||
sx = w->sx - 1;
|
||||
|
||||
/* Get the main pane width. */
|
||||
@@ -459,52 +446,49 @@ layout_set_main_v(struct window *w)
|
||||
if (sy < w->sy)
|
||||
sy = w->sy;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lc, mainw + otherw + 1, sy, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_LEFTRIGHT);
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, mainw + otherw + 1, sy, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_LEFTRIGHT);
|
||||
|
||||
/* Create the main pane. */
|
||||
lcmain = layout_create_cell(lc);
|
||||
wpmain = layout_set_first_tiled(w);
|
||||
lcmain = wpmain->layout_cell;
|
||||
lcmain->parent = lcroot;
|
||||
layout_set_size(lcmain, mainw, sy, 0, 0);
|
||||
layout_make_leaf(lcmain, layout_first_tiled(w));
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
|
||||
|
||||
/* Create the other pane. */
|
||||
lcother = layout_create_cell(lc);
|
||||
layout_set_size(lcother, otherw, sy, 0, 0);
|
||||
if (n == 1) {
|
||||
wp = TAILQ_NEXT(layout_first_tiled(w), entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wpmain, entry);
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
layout_make_leaf(lcother, wp);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
layout_set_size(wp->layout_cell, otherw, sy, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry);
|
||||
wp->layout_cell->parent = lcroot;
|
||||
} else {
|
||||
lcother = layout_create_cell(lcroot);
|
||||
layout_make_node(lcother, LAYOUT_TOPBOTTOM);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
layout_set_size(lcother, otherw, sy, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcother, entry);
|
||||
|
||||
/* Add the remaining panes as children. */
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
if (wp == wpmain)
|
||||
continue;
|
||||
if (wp == layout_first_tiled(w))
|
||||
continue;
|
||||
lcchild = layout_create_cell(lcother);
|
||||
layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
|
||||
lcchild->parent = lcother;
|
||||
if (layout_cell_is_tiled(lcchild))
|
||||
layout_set_size(lcchild, otherw, PANE_MINIMUM,
|
||||
0, 0);
|
||||
}
|
||||
layout_spread_cell(w, lcother);
|
||||
}
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
@@ -512,8 +496,8 @@ layout_set_main_v(struct window *w)
|
||||
static void
|
||||
layout_set_main_v_mirrored(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcmain, *lcother, *lcchild;
|
||||
struct window_pane *wp, *wpmain;
|
||||
struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
|
||||
u_int n, mainw, otherw, sx, sy;
|
||||
char *cause;
|
||||
const char *s;
|
||||
@@ -526,7 +510,7 @@ layout_set_main_v_mirrored(struct window *w)
|
||||
return;
|
||||
n--; /* take off main pane */
|
||||
|
||||
/* Find available width - take off one line for the border. */
|
||||
/* Find available width - take off one column for the border. */
|
||||
sx = w->sx - 1;
|
||||
|
||||
/* Get the main pane width. */
|
||||
@@ -561,62 +545,59 @@ layout_set_main_v_mirrored(struct window *w)
|
||||
if (sy < w->sy)
|
||||
sy = w->sy;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lc, mainw + otherw + 1, sy, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_LEFTRIGHT);
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, mainw + otherw + 1, sy, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_LEFTRIGHT);
|
||||
|
||||
wpmain = layout_set_first_tiled(w);
|
||||
lcmain = wpmain->layout_cell;
|
||||
lcmain->parent = lcroot;
|
||||
layout_set_size(lcmain, mainw, sy, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
|
||||
|
||||
/* Create the other pane. */
|
||||
lcother = layout_create_cell(lc);
|
||||
layout_set_size(lcother, otherw, sy, 0, 0);
|
||||
if (n == 1) {
|
||||
wp = TAILQ_NEXT(layout_first_tiled(w), entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wpmain, entry);
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
layout_make_leaf(lcother, wp);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
layout_set_size(wp->layout_cell, otherw, sy, 0, 0);
|
||||
TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry);
|
||||
wp->layout_cell->parent = lcroot;
|
||||
} else {
|
||||
lcother = layout_create_cell(lcroot);
|
||||
layout_make_node(lcother, LAYOUT_TOPBOTTOM);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
layout_set_size(lcother, otherw, sy, 0, 0);
|
||||
TAILQ_INSERT_HEAD(&lcroot->cells, lcother, entry);
|
||||
|
||||
/* Add the remaining panes as children. */
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
if (wp == wpmain)
|
||||
continue;
|
||||
if (wp == layout_first_tiled(w))
|
||||
continue;
|
||||
lcchild = layout_create_cell(lcother);
|
||||
layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
|
||||
lcchild->parent = lcother;
|
||||
if (layout_cell_is_tiled(lcchild))
|
||||
layout_set_size(lcchild, otherw, PANE_MINIMUM,
|
||||
0, 0);
|
||||
}
|
||||
layout_spread_cell(w, lcother);
|
||||
}
|
||||
|
||||
/* Create the main pane. */
|
||||
lcmain = layout_create_cell(lc);
|
||||
layout_set_size(lcmain, mainw, sy, 0, 0);
|
||||
layout_make_leaf(lcmain, layout_first_tiled(w));
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
|
||||
void
|
||||
static void
|
||||
layout_set_tiled(struct window *w)
|
||||
{
|
||||
struct options *oo = w->options;
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcrow, *lcchild;
|
||||
struct layout_cell *lcroot, *lcrow, *lcchild;
|
||||
u_int n, width, height, used, sx, sy;
|
||||
u_int i, j, columns, rows, max_columns;
|
||||
|
||||
@@ -647,56 +628,59 @@ layout_set_tiled(struct window *w)
|
||||
if (height < PANE_MINIMUM)
|
||||
height = PANE_MINIMUM;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
sx = ((width + 1) * columns) - 1;
|
||||
if (sx < w->sx)
|
||||
sx = w->sx;
|
||||
sy = ((height + 1) * rows) - 1;
|
||||
if (sy < w->sy)
|
||||
sy = w->sy;
|
||||
layout_set_size(lc, sx, sy, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_TOPBOTTOM);
|
||||
|
||||
/* Create a grid of the cells, skipping any floating panes. */
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, sx, sy, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
|
||||
|
||||
/* Create a grid of the tiled cells. */
|
||||
wp = TAILQ_FIRST(&w->panes);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
for (j = 0; j < rows; j++) {
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
/* If this is the last cell, all done. */
|
||||
if (wp == NULL)
|
||||
break;
|
||||
|
||||
/* Create the new row. */
|
||||
lcrow = layout_create_cell(lc);
|
||||
layout_set_size(lcrow, w->sx, height, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
|
||||
lcchild = wp->layout_cell;
|
||||
|
||||
/* If only one column, just use the row directly. */
|
||||
if (n - (j * columns) == 1 || columns == 1) {
|
||||
layout_make_leaf(lcrow, wp);
|
||||
lcchild->parent = lcroot;
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry);
|
||||
layout_set_size(lcchild, w->sx, height, 0, 0);
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Add in the columns. */
|
||||
/* Create the new row. */
|
||||
lcrow = layout_create_cell(lcroot);
|
||||
layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
|
||||
layout_set_size(lcrow, w->sx, height, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcrow, entry);
|
||||
|
||||
/* Add in the columns. */
|
||||
for (i = 0; i < columns; i++) {
|
||||
/* Create and add a pane cell. */
|
||||
lcchild = layout_create_cell(lcrow);
|
||||
layout_set_size(lcchild, width, height, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild->parent = lcrow;
|
||||
TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
|
||||
layout_set_size(lcchild, width, height, 0, 0);
|
||||
|
||||
/* Move to the next non-floating cell. */
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
while (wp != NULL &&
|
||||
!layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
if (wp == NULL)
|
||||
break;
|
||||
lcchild = wp->layout_cell;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -713,21 +697,19 @@ layout_set_tiled(struct window *w)
|
||||
w->sx - used);
|
||||
}
|
||||
|
||||
/* Adjust the last row height to fit if necessary. */
|
||||
used = (rows * height) + rows - 1;
|
||||
if (w->sy > used) {
|
||||
lcrow = TAILQ_LAST(&lc->cells, layout_cells);
|
||||
lcrow = TAILQ_LAST(&lcroot->cells, layout_cells);
|
||||
layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
|
||||
w->sy - used);
|
||||
}
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
|
||||
57
layout.c
57
layout.c
@@ -90,20 +90,24 @@ layout_create_cell(struct layout_cell *lcparent)
|
||||
|
||||
/* Free a layout cell. */
|
||||
void
|
||||
layout_free_cell(struct layout_cell *lc)
|
||||
layout_free_cell(struct layout_cell *lc, int only_nodes)
|
||||
{
|
||||
struct layout_cell *lcchild;
|
||||
struct layout_cell *lcchild, *lcnext;
|
||||
|
||||
if (lc == NULL)
|
||||
if (lc == NULL || (only_nodes && lc->type == LAYOUT_WINDOWPANE))
|
||||
return;
|
||||
|
||||
switch (lc->type) {
|
||||
case LAYOUT_LEFTRIGHT:
|
||||
case LAYOUT_TOPBOTTOM:
|
||||
while (!TAILQ_EMPTY(&lc->cells)) {
|
||||
lcchild = TAILQ_FIRST(&lc->cells);
|
||||
TAILQ_REMOVE(&lc->cells, lcchild, entry);
|
||||
layout_free_cell(lcchild);
|
||||
lcchild = TAILQ_FIRST(&lc->cells);
|
||||
while (lcchild != NULL) {
|
||||
lcnext = TAILQ_NEXT(lcchild, entry);
|
||||
if (!only_nodes || lcchild->type != LAYOUT_WINDOWPANE) {
|
||||
TAILQ_REMOVE(&lc->cells, lcchild, entry);
|
||||
layout_free_cell(lcchild, only_nodes);
|
||||
}
|
||||
lcchild = lcnext;
|
||||
}
|
||||
break;
|
||||
case LAYOUT_WINDOWPANE:
|
||||
@@ -257,7 +261,7 @@ layout_fix_zindexes(struct window *w, struct layout_cell *lc)
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
int
|
||||
layout_cell_is_tiled(struct layout_cell *lc)
|
||||
{
|
||||
int is_leaf = lc->type == LAYOUT_WINDOWPANE;
|
||||
@@ -694,13 +698,13 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
|
||||
if (lcparent == NULL) {
|
||||
if (lc->wp != NULL)
|
||||
*lcroot = NULL;
|
||||
layout_free_cell(lc);
|
||||
layout_free_cell(lc, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!layout_cell_is_tiled(lc)) {
|
||||
TAILQ_REMOVE(&lcparent->cells, lc, entry);
|
||||
layout_free_cell(lc);
|
||||
layout_free_cell(lc, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -716,7 +720,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
|
||||
|
||||
/* Remove this from the parent's list. */
|
||||
TAILQ_REMOVE(&lcparent->cells, lc, entry);
|
||||
layout_free_cell(lc);
|
||||
layout_free_cell(lc, 0);
|
||||
|
||||
out:
|
||||
/*
|
||||
@@ -737,7 +741,7 @@ out:
|
||||
} else
|
||||
TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry);
|
||||
|
||||
layout_free_cell(lcparent);
|
||||
layout_free_cell(lcparent, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,9 +759,9 @@ layout_init(struct window *w, struct window_pane *wp)
|
||||
|
||||
/* Free layout for pane. */
|
||||
void
|
||||
layout_free(struct window *w)
|
||||
layout_free(struct window *w, int only_nodes)
|
||||
{
|
||||
layout_free_cell(w->layout_root);
|
||||
layout_free_cell(w->layout_root, only_nodes);
|
||||
}
|
||||
|
||||
/* Resize the entire layout after window resize. */
|
||||
@@ -1500,7 +1504,8 @@ layout_spread_cell(struct window *w, struct layout_cell *parent)
|
||||
|
||||
number = 0;
|
||||
TAILQ_FOREACH (lc, &parent->cells, entry)
|
||||
number++;
|
||||
if (layout_cell_is_tiled(lc))
|
||||
number++;
|
||||
if (number <= 1)
|
||||
return (0);
|
||||
status = window_get_pane_status(w);
|
||||
@@ -1528,6 +1533,8 @@ layout_spread_cell(struct window *w, struct layout_cell *parent)
|
||||
|
||||
changed = 0;
|
||||
TAILQ_FOREACH (lc, &parent->cells, entry) {
|
||||
if (!layout_cell_is_tiled(lc))
|
||||
continue;
|
||||
change = 0;
|
||||
if (parent->type == LAYOUT_LEFTRIGHT) {
|
||||
change = each - (int)lc->sx;
|
||||
@@ -1716,7 +1723,7 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
||||
ox = 4;
|
||||
}
|
||||
w->last_new_pane_x = ox;
|
||||
} else
|
||||
} else if (args_has(args, 'X'))
|
||||
if (lines != PANE_LINES_NONE)
|
||||
ox += 1;
|
||||
if (oy == INT_MAX) {
|
||||
@@ -1728,7 +1735,7 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
||||
oy = 2;
|
||||
}
|
||||
w->last_new_pane_y = oy;
|
||||
} else
|
||||
} else if (args_has(args, 'Y'))
|
||||
if (lines != PANE_LINES_NONE)
|
||||
oy += 1;
|
||||
|
||||
@@ -1760,7 +1767,7 @@ layout_remove_tile(struct window *w, struct layout_cell *lc)
|
||||
int change;
|
||||
|
||||
if (lc->flags & LAYOUT_CELL_FLOATING)
|
||||
return (0);
|
||||
return (-1);
|
||||
|
||||
lcneighbour = layout_cell_get_neighbour(lc);
|
||||
if (lcneighbour == NULL) {
|
||||
@@ -1785,7 +1792,7 @@ layout_remove_tile(struct window *w, struct layout_cell *lc)
|
||||
*/
|
||||
if (lc->parent != NULL)
|
||||
layout_set_size(lc, 0, 0, 0, 0);
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1802,14 +1809,14 @@ layout_insert_tile(struct window *w, struct layout_cell *lc)
|
||||
if (lc == NULL)
|
||||
fatalx("layout cell cannot be null when tiling");
|
||||
|
||||
lcparent = lc->parent;
|
||||
if (lc->flags & LAYOUT_CELL_FLOATING)
|
||||
return (1);
|
||||
if (layout_cell_is_tiled(lc))
|
||||
return (-1);
|
||||
|
||||
lcparent = lc->parent;
|
||||
if (lcparent == NULL) {
|
||||
/* Only pane in the layout. */
|
||||
layout_set_size(lc, w->sx, w->sy, 0, 0);
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
type = lcparent->type;
|
||||
@@ -1832,7 +1839,7 @@ layout_insert_tile(struct window *w, struct layout_cell *lc)
|
||||
*/
|
||||
lctiled = layout_cell_get_first_tiled(lcneighbour);
|
||||
if (!layout_split_check_space(lctiled->wp, lcneighbour, type))
|
||||
return (0);
|
||||
return (-1);
|
||||
layout_split_sizes(lcneighbour, -1, 0, type, &size1, &size2,
|
||||
&saved_size);
|
||||
layout_resize_set_size(w, lc, type, size1);
|
||||
@@ -1849,5 +1856,5 @@ layout_insert_tile(struct window *w, struct layout_cell *lc)
|
||||
}
|
||||
layout_resize_set_size(w, lc, type, size1);
|
||||
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@ enum mode_tree_preview {
|
||||
};
|
||||
|
||||
#define MODE_TREE_PREFIX_STYLE \
|
||||
"#{?mode_tree_selected,#[default]#[noacs]," \
|
||||
"#[fg=themelightgrey]#[bg=default]#[noacs]}"
|
||||
"#[fg=themelightgrey]#[bg=default]#[noacs]"
|
||||
|
||||
#define MODE_TREE_PREFIX_FORMAT \
|
||||
MODE_TREE_PREFIX_STYLE \
|
||||
@@ -950,16 +949,16 @@ mode_tree_draw(struct mode_tree_data *mtd)
|
||||
}
|
||||
} else {
|
||||
screen_write_clearendofline(&ctx, gc.bg);
|
||||
format_draw(&ctx, &gc, prefix_width, prefix, NULL, 0);
|
||||
format_draw(&ctx, &gc, prefix_width, prefix, NULL, 1);
|
||||
if (left != 0) {
|
||||
screen_write_cursormove(&ctx, prefix_width,
|
||||
i - mtd->offset, 0);
|
||||
format_draw(&ctx, &gc, left, text, NULL, 0);
|
||||
format_draw(&ctx, &gc, left, text, NULL, 1);
|
||||
if (mti->text != NULL && width < w) {
|
||||
screen_write_cursormove(&ctx, width,
|
||||
i - mtd->offset, 0);
|
||||
format_draw(&ctx, &gc, w - width,
|
||||
mti->text, NULL, 0);
|
||||
mti->text, NULL, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
646
monitor.c
Normal file
646
monitor.c
Normal file
@@ -0,0 +1,646 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicm@users.sourceforge.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Subscription pane. */
|
||||
struct monitor_pane {
|
||||
u_int pane;
|
||||
u_int idx;
|
||||
char *last;
|
||||
u_int generation;
|
||||
|
||||
RB_ENTRY(monitor_pane) entry;
|
||||
};
|
||||
RB_HEAD(monitor_panes, monitor_pane);
|
||||
|
||||
/* Subscription window. */
|
||||
struct monitor_window {
|
||||
u_int window;
|
||||
u_int idx;
|
||||
char *last;
|
||||
u_int generation;
|
||||
|
||||
RB_ENTRY(monitor_window) entry;
|
||||
};
|
||||
RB_HEAD(monitor_windows, monitor_window);
|
||||
|
||||
/* Subscription. */
|
||||
struct monitor_item {
|
||||
char *name;
|
||||
char *format;
|
||||
|
||||
enum monitor_type type;
|
||||
u_int id;
|
||||
u_int flags;
|
||||
|
||||
char *last;
|
||||
struct monitor_panes panes;
|
||||
struct monitor_windows windows;
|
||||
|
||||
RB_ENTRY(monitor_item) entry;
|
||||
};
|
||||
RB_HEAD(monitor_items, monitor_item);
|
||||
|
||||
/* Monitored subscription set. */
|
||||
struct monitor_set {
|
||||
struct client *client;
|
||||
struct session *session;
|
||||
monitor_cb cb;
|
||||
void *data;
|
||||
|
||||
struct monitor_items items;
|
||||
struct event timer;
|
||||
u_int generation;
|
||||
};
|
||||
|
||||
static void monitor_timer(__unused int, __unused short, void *);
|
||||
|
||||
/* Get the session for this monitor set. */
|
||||
static struct session *
|
||||
monitor_get_session(struct monitor_set *ms)
|
||||
{
|
||||
struct session *s;
|
||||
|
||||
if (ms->client != NULL)
|
||||
return (ms->client->session);
|
||||
s = ms->session;
|
||||
if (s == NULL)
|
||||
return (RB_MIN(sessions, &sessions));
|
||||
if (session_find_by_id(s->id) != s)
|
||||
return (NULL);
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* Compare subscriptions. */
|
||||
static int
|
||||
monitor_item_cmp(struct monitor_item *m1, struct monitor_item *m2)
|
||||
{
|
||||
return (strcmp(m1->name, m2->name));
|
||||
}
|
||||
RB_GENERATE_STATIC(monitor_items, monitor_item, entry, monitor_item_cmp);
|
||||
|
||||
/* Compare subscription panes. */
|
||||
static int
|
||||
monitor_pane_cmp(struct monitor_pane *mp1, struct monitor_pane *mp2)
|
||||
{
|
||||
if (mp1->pane < mp2->pane)
|
||||
return (-1);
|
||||
if (mp1->pane > mp2->pane)
|
||||
return (1);
|
||||
if (mp1->idx < mp2->idx)
|
||||
return (-1);
|
||||
if (mp1->idx > mp2->idx)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
RB_GENERATE_STATIC(monitor_panes, monitor_pane, entry, monitor_pane_cmp);
|
||||
|
||||
/* Compare subscription windows. */
|
||||
static int
|
||||
monitor_window_cmp(struct monitor_window *mw1, struct monitor_window *mw2)
|
||||
{
|
||||
if (mw1->window < mw2->window)
|
||||
return (-1);
|
||||
if (mw1->window > mw2->window)
|
||||
return (1);
|
||||
if (mw1->idx < mw2->idx)
|
||||
return (-1);
|
||||
if (mw1->idx > mw2->idx)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
RB_GENERATE_STATIC(monitor_windows, monitor_window, entry, monitor_window_cmp);
|
||||
|
||||
/* Free a subscription. */
|
||||
static void
|
||||
monitor_free_item(struct monitor_set *ms, struct monitor_item *me)
|
||||
{
|
||||
struct monitor_pane *mp, *mp1;
|
||||
struct monitor_window *mw, *mw1;
|
||||
|
||||
RB_FOREACH_SAFE(mp, monitor_panes, &me->panes, mp1) {
|
||||
RB_REMOVE(monitor_panes, &me->panes, mp);
|
||||
free(mp->last);
|
||||
free(mp);
|
||||
}
|
||||
RB_FOREACH_SAFE(mw, monitor_windows, &me->windows, mw1) {
|
||||
RB_REMOVE(monitor_windows, &me->windows, mw);
|
||||
free(mw->last);
|
||||
free(mw);
|
||||
}
|
||||
free(me->last);
|
||||
|
||||
RB_REMOVE(monitor_items, &ms->items, me);
|
||||
free(me->name);
|
||||
free(me->format);
|
||||
free(me);
|
||||
}
|
||||
|
||||
/* Report a changed value. */
|
||||
static void
|
||||
monitor_report(struct monitor_set *ms, struct monitor_item *me,
|
||||
struct session *s, struct winlink *wl, struct window_pane *wp,
|
||||
const char *value, const char *last)
|
||||
{
|
||||
struct monitor_change change = { 0 };
|
||||
|
||||
log_debug("%s: %s changed to %s", __func__, me->name, value);
|
||||
|
||||
change.name = me->name;
|
||||
change.value = value;
|
||||
change.last = last;
|
||||
change.c = ms->client;
|
||||
change.s = s;
|
||||
change.wl = wl;
|
||||
change.wp = wp;
|
||||
ms->cb(&change, ms->data);
|
||||
}
|
||||
|
||||
/* Check a value against its last value and report if changed. */
|
||||
static void
|
||||
monitor_check_value(struct monitor_set *ms, struct monitor_item *me,
|
||||
struct session *s, struct winlink *wl, struct window_pane *wp,
|
||||
char *value, char **last)
|
||||
{
|
||||
if (*last == NULL) {
|
||||
*last = value;
|
||||
if (me->flags & MONITOR_NOTIFY_INITIAL)
|
||||
monitor_report(ms, me, s, wl, wp, value, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(value, *last) == 0) {
|
||||
free(value);
|
||||
return;
|
||||
}
|
||||
|
||||
monitor_report(ms, me, s, wl, wp, value, *last);
|
||||
free(*last);
|
||||
*last = value;
|
||||
}
|
||||
|
||||
/* Check session subscription. */
|
||||
static void
|
||||
monitor_check_session(struct monitor_set *ms, struct monitor_item *me,
|
||||
struct format_tree *ft)
|
||||
{
|
||||
struct session *s = monitor_get_session(ms);
|
||||
char *value;
|
||||
|
||||
value = format_expand(ft, me->format);
|
||||
|
||||
monitor_check_value(ms, me, s, NULL, NULL, value, &me->last);
|
||||
}
|
||||
|
||||
/* Check pane subscription. */
|
||||
static void
|
||||
monitor_check_pane(struct monitor_set *ms, struct monitor_item *me)
|
||||
{
|
||||
struct client *c = ms->client;
|
||||
struct session *s = monitor_get_session(ms);
|
||||
struct window_pane *wp;
|
||||
struct window *w;
|
||||
struct winlink *wl;
|
||||
struct format_tree *ft;
|
||||
char *value;
|
||||
struct monitor_pane *mp, find;
|
||||
|
||||
wp = window_pane_find_by_id(me->id);
|
||||
if (wp == NULL || wp->fd == -1)
|
||||
return;
|
||||
w = wp->window;
|
||||
|
||||
TAILQ_FOREACH(wl, &w->winlinks, wentry) {
|
||||
if (wl->session != s)
|
||||
continue;
|
||||
|
||||
ft = format_create_defaults(NULL, c, s, wl, wp);
|
||||
value = format_expand(ft, me->format);
|
||||
format_free(ft);
|
||||
|
||||
find.pane = wp->id;
|
||||
find.idx = wl->idx;
|
||||
mp = RB_FIND(monitor_panes, &me->panes, &find);
|
||||
if (mp == NULL) {
|
||||
mp = xcalloc(1, sizeof *mp);
|
||||
mp->pane = wp->id;
|
||||
mp->idx = wl->idx;
|
||||
RB_INSERT(monitor_panes, &me->panes, mp);
|
||||
}
|
||||
|
||||
monitor_check_value(ms, me, s, wl, wp, value, &mp->last);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check one all-panes subscription. */
|
||||
static void
|
||||
monitor_check_all_panes_one(struct monitor_set *ms, struct monitor_item *me,
|
||||
struct format_tree *ft, struct winlink *wl, struct window_pane *wp)
|
||||
{
|
||||
struct session *s = monitor_get_session(ms);
|
||||
char *value;
|
||||
struct monitor_pane *mp, find;
|
||||
|
||||
value = format_expand(ft, me->format);
|
||||
|
||||
find.pane = wp->id;
|
||||
find.idx = wl->idx;
|
||||
mp = RB_FIND(monitor_panes, &me->panes, &find);
|
||||
if (mp == NULL) {
|
||||
mp = xcalloc(1, sizeof *mp);
|
||||
mp->pane = wp->id;
|
||||
mp->idx = wl->idx;
|
||||
RB_INSERT(monitor_panes, &me->panes, mp);
|
||||
}
|
||||
mp->generation = ms->generation;
|
||||
|
||||
monitor_check_value(ms, me, s, wl, wp, value, &mp->last);
|
||||
}
|
||||
|
||||
/* Remove all-panes entries not seen during the current scan. */
|
||||
static void
|
||||
monitor_sweep_all_panes(struct monitor_item *me, u_int generation)
|
||||
{
|
||||
struct monitor_pane *mp, *mp1;
|
||||
|
||||
RB_FOREACH_SAFE(mp, monitor_panes, &me->panes, mp1) {
|
||||
if (mp->generation == generation)
|
||||
continue;
|
||||
RB_REMOVE(monitor_panes, &me->panes, mp);
|
||||
free(mp->last);
|
||||
free(mp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check window subscription. */
|
||||
static void
|
||||
monitor_check_window(struct monitor_set *ms, struct monitor_item *me)
|
||||
{
|
||||
struct client *c = ms->client;
|
||||
struct session *s = monitor_get_session(ms);
|
||||
struct window *w;
|
||||
struct winlink *wl;
|
||||
struct format_tree *ft;
|
||||
char *value;
|
||||
struct monitor_window *mw, find;
|
||||
|
||||
w = window_find_by_id(me->id);
|
||||
if (w == NULL)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(wl, &w->winlinks, wentry) {
|
||||
if (wl->session != s)
|
||||
continue;
|
||||
|
||||
ft = format_create_defaults(NULL, c, s, wl, NULL);
|
||||
value = format_expand(ft, me->format);
|
||||
format_free(ft);
|
||||
|
||||
find.window = w->id;
|
||||
find.idx = wl->idx;
|
||||
mw = RB_FIND(monitor_windows, &me->windows, &find);
|
||||
if (mw == NULL) {
|
||||
mw = xcalloc(1, sizeof *mw);
|
||||
mw->window = w->id;
|
||||
mw->idx = wl->idx;
|
||||
RB_INSERT(monitor_windows, &me->windows, mw);
|
||||
}
|
||||
|
||||
monitor_check_value(ms, me, s, wl, NULL, value, &mw->last);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check one all-windows subscription. */
|
||||
static void
|
||||
monitor_check_all_windows_one(struct monitor_set *ms, struct monitor_item *me,
|
||||
struct format_tree *ft, struct winlink *wl)
|
||||
{
|
||||
struct session *s = monitor_get_session(ms);
|
||||
struct window *w = wl->window;
|
||||
char *value;
|
||||
struct monitor_window *mw, find;
|
||||
|
||||
value = format_expand(ft, me->format);
|
||||
|
||||
find.window = w->id;
|
||||
find.idx = wl->idx;
|
||||
mw = RB_FIND(monitor_windows, &me->windows, &find);
|
||||
if (mw == NULL) {
|
||||
mw = xcalloc(1, sizeof *mw);
|
||||
mw->window = w->id;
|
||||
mw->idx = wl->idx;
|
||||
RB_INSERT(monitor_windows, &me->windows, mw);
|
||||
}
|
||||
mw->generation = ms->generation;
|
||||
|
||||
monitor_check_value(ms, me, s, wl, NULL, value, &mw->last);
|
||||
}
|
||||
|
||||
/* Remove all-windows entries not seen during the current scan. */
|
||||
static void
|
||||
monitor_sweep_all_windows(struct monitor_item *me, u_int generation)
|
||||
{
|
||||
struct monitor_window *mw, *mw1;
|
||||
|
||||
RB_FOREACH_SAFE(mw, monitor_windows, &me->windows, mw1) {
|
||||
if (mw->generation == generation)
|
||||
continue;
|
||||
RB_REMOVE(monitor_windows, &me->windows, mw);
|
||||
free(mw->last);
|
||||
free(mw);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check session subscriptions. */
|
||||
static void
|
||||
monitor_check_sessions(struct monitor_set *ms)
|
||||
{
|
||||
struct client *c = ms->client;
|
||||
struct session *s = monitor_get_session(ms);
|
||||
struct monitor_item *me, *me1;
|
||||
struct format_tree *ft;
|
||||
|
||||
ft = format_create_defaults(NULL, c, s, NULL, NULL);
|
||||
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
||||
if (me->type == MONITOR_SESSION)
|
||||
monitor_check_session(ms, me, ft);
|
||||
}
|
||||
format_free(ft);
|
||||
}
|
||||
|
||||
/* Check pane and window subscriptions. */
|
||||
static void
|
||||
monitor_check_panes_windows(struct monitor_set *ms)
|
||||
{
|
||||
struct monitor_item *me, *me1;
|
||||
|
||||
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
||||
switch (me->type) {
|
||||
case MONITOR_PANE:
|
||||
monitor_check_pane(ms, me);
|
||||
break;
|
||||
case MONITOR_WINDOW:
|
||||
monitor_check_window(ms, me);
|
||||
break;
|
||||
case MONITOR_SESSION:
|
||||
case MONITOR_ALL_PANES:
|
||||
case MONITOR_ALL_WINDOWS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all-panes subscriptions. */
|
||||
static void
|
||||
monitor_check_all_panes(struct monitor_set *ms)
|
||||
{
|
||||
struct client *c = ms->client;
|
||||
struct session *s = monitor_get_session(ms);
|
||||
struct monitor_item *me, *me1;
|
||||
struct window_pane *wp;
|
||||
struct format_tree *ft;
|
||||
struct winlink *wl;
|
||||
|
||||
if (++ms->generation == 0)
|
||||
ms->generation = 1;
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
TAILQ_FOREACH(wp, &wl->window->panes, entry) {
|
||||
ft = format_create_defaults(NULL, c, s, wl, wp);
|
||||
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
||||
if (me->type != MONITOR_ALL_PANES)
|
||||
continue;
|
||||
monitor_check_all_panes_one(ms, me, ft, wl, wp);
|
||||
}
|
||||
format_free(ft);
|
||||
}
|
||||
}
|
||||
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
||||
if (me->type == MONITOR_ALL_PANES)
|
||||
monitor_sweep_all_panes(me, ms->generation);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all-windows subscriptions. */
|
||||
static void
|
||||
monitor_check_all_windows(struct monitor_set *ms)
|
||||
{
|
||||
struct client *c = ms->client;
|
||||
struct session *s = monitor_get_session(ms);
|
||||
struct monitor_item *me, *me1;
|
||||
struct format_tree *ft;
|
||||
struct winlink *wl;
|
||||
|
||||
if (++ms->generation == 0)
|
||||
ms->generation = 1;
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
ft = format_create_defaults(NULL, c, s, wl, NULL);
|
||||
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
||||
if (me->type != MONITOR_ALL_WINDOWS)
|
||||
continue;
|
||||
monitor_check_all_windows_one(ms, me, ft, wl);
|
||||
}
|
||||
format_free(ft);
|
||||
}
|
||||
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
||||
if (me->type == MONITOR_ALL_WINDOWS)
|
||||
monitor_sweep_all_windows(me, ms->generation);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check subscriptions. */
|
||||
static void
|
||||
monitor_timer(__unused int fd, __unused short events, void *data)
|
||||
{
|
||||
struct monitor_set *ms = data;
|
||||
struct monitor_item *me;
|
||||
struct timeval tv = { .tv_sec = 1 };
|
||||
int have_session = 0, have_all_panes = 0;
|
||||
int have_all_windows = 0;
|
||||
|
||||
log_debug("%s: timer fired", __func__);
|
||||
evtimer_add(&ms->timer, &tv);
|
||||
|
||||
if (monitor_get_session(ms) == NULL)
|
||||
return;
|
||||
|
||||
RB_FOREACH(me, monitor_items, &ms->items) {
|
||||
switch (me->type) {
|
||||
case MONITOR_SESSION:
|
||||
have_session = 1;
|
||||
break;
|
||||
case MONITOR_ALL_PANES:
|
||||
have_all_panes = 1;
|
||||
break;
|
||||
case MONITOR_ALL_WINDOWS:
|
||||
have_all_windows = 1;
|
||||
break;
|
||||
case MONITOR_PANE:
|
||||
case MONITOR_WINDOW:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (have_session)
|
||||
monitor_check_sessions(ms);
|
||||
monitor_check_panes_windows(ms);
|
||||
if (have_all_panes)
|
||||
monitor_check_all_panes(ms);
|
||||
if (have_all_windows)
|
||||
monitor_check_all_windows(ms);
|
||||
}
|
||||
|
||||
/* Create a monitor set. */
|
||||
static struct monitor_set *
|
||||
monitor_create(monitor_cb cb, void *data)
|
||||
{
|
||||
struct monitor_set *ms;
|
||||
|
||||
ms = xcalloc(1, sizeof *ms);
|
||||
ms->cb = cb;
|
||||
ms->data = data;
|
||||
RB_INIT(&ms->items);
|
||||
return (ms);
|
||||
}
|
||||
|
||||
/* Create a client monitor set. */
|
||||
struct monitor_set *
|
||||
monitor_create_client(struct client *c, monitor_cb cb, void *data)
|
||||
{
|
||||
struct monitor_set *ms;
|
||||
|
||||
ms = monitor_create(cb, data);
|
||||
ms->client = c;
|
||||
return (ms);
|
||||
}
|
||||
|
||||
/* Create a monitor set for a session. */
|
||||
struct monitor_set *
|
||||
monitor_create_session(struct session *s, monitor_cb cb, void *data)
|
||||
{
|
||||
struct monitor_set *ms;
|
||||
|
||||
ms = monitor_create(cb, data);
|
||||
ms->session = s;
|
||||
if (s != NULL)
|
||||
session_add_ref(s, __func__);
|
||||
return (ms);
|
||||
}
|
||||
|
||||
/* Destroy a monitor set. */
|
||||
void
|
||||
monitor_destroy(struct monitor_set *ms)
|
||||
{
|
||||
struct monitor_item *me, *me1;
|
||||
|
||||
if (ms != NULL) {
|
||||
if (evtimer_initialized(&ms->timer))
|
||||
evtimer_del(&ms->timer);
|
||||
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1)
|
||||
monitor_free_item(ms, me);
|
||||
if (ms->session != NULL)
|
||||
session_remove_ref(ms->session, __func__);
|
||||
free(ms);
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse a subscription. */
|
||||
int
|
||||
monitor_parse(const char *value, char **name, enum monitor_type *type, int *id,
|
||||
char **format)
|
||||
{
|
||||
char *copy, *what, *split;
|
||||
|
||||
copy = xstrdup(value);
|
||||
*id = -1;
|
||||
|
||||
what = strchr(copy, ':');
|
||||
if (what == NULL)
|
||||
goto fail;
|
||||
*what++ = '\0';
|
||||
|
||||
split = strchr(what, ':');
|
||||
if (split == NULL)
|
||||
goto fail;
|
||||
*split++ = '\0';
|
||||
|
||||
if (strcmp(what, "%*") == 0)
|
||||
*type = MONITOR_ALL_PANES;
|
||||
else if (sscanf(what, "%%%d", id) == 1 && *id >= 0)
|
||||
*type = MONITOR_PANE;
|
||||
else if (strcmp(what, "@*") == 0)
|
||||
*type = MONITOR_ALL_WINDOWS;
|
||||
else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
|
||||
*type = MONITOR_WINDOW;
|
||||
else
|
||||
*type = MONITOR_SESSION;
|
||||
*name = xstrdup(copy);
|
||||
*format = xstrdup(split);
|
||||
|
||||
free(copy);
|
||||
return (0);
|
||||
|
||||
fail:
|
||||
free(copy);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* Add a subscription. */
|
||||
void
|
||||
monitor_add(struct monitor_set *ms, const char *name, enum monitor_type type,
|
||||
int id, const char *format, u_int flags)
|
||||
{
|
||||
struct monitor_item *me, find = { .name = (char *)name };
|
||||
struct timeval tv = { .tv_sec = 1 };
|
||||
|
||||
if ((me = RB_FIND(monitor_items, &ms->items, &find)) != NULL)
|
||||
monitor_free_item(ms, me);
|
||||
|
||||
me = xcalloc(1, sizeof *me);
|
||||
me->name = xstrdup(name);
|
||||
me->format = xstrdup(format);
|
||||
me->type = type;
|
||||
me->id = id;
|
||||
me->flags = flags;
|
||||
RB_INIT(&me->panes);
|
||||
RB_INIT(&me->windows);
|
||||
RB_INSERT(monitor_items, &ms->items, me);
|
||||
|
||||
if (!evtimer_initialized(&ms->timer))
|
||||
evtimer_set(&ms->timer, monitor_timer, ms);
|
||||
if (!evtimer_pending(&ms->timer, NULL))
|
||||
evtimer_add(&ms->timer, &tv);
|
||||
}
|
||||
|
||||
/* Remove a subscription. */
|
||||
void
|
||||
monitor_remove(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)
|
||||
monitor_free_item(ms, me);
|
||||
if (RB_EMPTY(&ms->items) && evtimer_initialized(&ms->timer))
|
||||
evtimer_del(&ms->timer);
|
||||
}
|
||||
218
notify.c
218
notify.c
@@ -27,12 +27,25 @@ struct notify_entry {
|
||||
const char *name;
|
||||
struct cmd_find_state fs;
|
||||
struct format_tree *formats;
|
||||
struct options *oo;
|
||||
|
||||
struct client *client;
|
||||
struct session *session;
|
||||
struct window *window;
|
||||
int pane;
|
||||
const char *pbname;
|
||||
int expand;
|
||||
};
|
||||
|
||||
struct notify_monitor {
|
||||
struct options *oo;
|
||||
|
||||
struct monitor_set *set;
|
||||
struct cmd_find_state fs;
|
||||
|
||||
enum monitor_type type;
|
||||
int id;
|
||||
char *format;
|
||||
};
|
||||
|
||||
static struct cmdq_item *
|
||||
@@ -50,7 +63,31 @@ notify_insert_one_hook(struct cmdq_item *item, struct notify_entry *ne,
|
||||
free(s);
|
||||
}
|
||||
new_item = cmdq_get_command(cmdlist, state);
|
||||
return (cmdq_insert_after(item, new_item));
|
||||
if (item != NULL)
|
||||
return (cmdq_insert_after(item, new_item));
|
||||
return (cmdq_append(NULL, new_item));
|
||||
}
|
||||
|
||||
static struct cmd_parse_result *
|
||||
notify_parse_hook(struct notify_entry *ne, struct cmd_find_state *fs,
|
||||
const char *value)
|
||||
{
|
||||
struct cmd_parse_result *pr;
|
||||
struct format_tree *ft;
|
||||
char *expanded;
|
||||
|
||||
if (!ne->expand)
|
||||
return (cmd_parse_from_string(value, NULL));
|
||||
|
||||
ft = format_create_defaults(NULL, ne->client, fs->s, fs->wl, fs->wp);
|
||||
if (ne->formats != NULL)
|
||||
format_merge(ft, ne->formats);
|
||||
expanded = format_expand(ft, value);
|
||||
format_free(ft);
|
||||
|
||||
pr = cmd_parse_from_string(expanded, NULL);
|
||||
free(expanded);
|
||||
return (pr);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -73,18 +110,23 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne)
|
||||
else
|
||||
cmd_find_copy_state(&fs, &ne->fs);
|
||||
|
||||
if (fs.s == NULL)
|
||||
oo = global_s_options;
|
||||
else
|
||||
oo = fs.s->options;
|
||||
o = options_get(oo, ne->name);
|
||||
if (o == NULL && fs.wp != NULL) {
|
||||
oo = fs.wp->options;
|
||||
o = options_get(oo, ne->name);
|
||||
}
|
||||
if (o == NULL && fs.wl != NULL) {
|
||||
oo = fs.wl->window->options;
|
||||
if (ne->oo != NULL) {
|
||||
oo = ne->oo;
|
||||
o = options_get_only(oo, ne->name);
|
||||
} else {
|
||||
if (fs.s == NULL)
|
||||
oo = global_s_options;
|
||||
else
|
||||
oo = fs.s->options;
|
||||
o = options_get(oo, ne->name);
|
||||
if (o == NULL && fs.wp != NULL) {
|
||||
oo = fs.wp->options;
|
||||
o = options_get(oo, ne->name);
|
||||
}
|
||||
if (o == NULL && fs.wl != NULL) {
|
||||
oo = fs.wl->window->options;
|
||||
o = options_get(oo, ne->name);
|
||||
}
|
||||
}
|
||||
if (o == NULL) {
|
||||
log_debug("%s: hook %s not found", __func__, ne->name);
|
||||
@@ -96,7 +138,7 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne)
|
||||
|
||||
if (*ne->name == '@') {
|
||||
value = options_get_string(oo, ne->name);
|
||||
pr = cmd_parse_from_string(value, NULL);
|
||||
pr = notify_parse_hook(ne, &fs, value);
|
||||
switch (pr->status) {
|
||||
case CMD_PARSE_ERROR:
|
||||
log_debug("%s: can't parse hook %s: %s", __func__,
|
||||
@@ -110,8 +152,24 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne)
|
||||
} else {
|
||||
a = options_array_first(o);
|
||||
while (a != NULL) {
|
||||
cmdlist = options_array_item_value(a)->cmdlist;
|
||||
item = notify_insert_one_hook(item, ne, cmdlist, state);
|
||||
if (ne->expand) {
|
||||
value = options_array_item_value(a)->string;
|
||||
pr = notify_parse_hook(ne, &fs, value);
|
||||
switch (pr->status) {
|
||||
case CMD_PARSE_ERROR:
|
||||
if (pr->error != NULL)
|
||||
cmdq_error(item, "%s", pr->error);
|
||||
break;
|
||||
case CMD_PARSE_SUCCESS:
|
||||
item = notify_insert_one_hook(item, ne,
|
||||
pr->cmdlist, state);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
cmdlist = options_array_item_value(a)->cmdlist;
|
||||
item = notify_insert_one_hook(item, ne, cmdlist,
|
||||
state);
|
||||
}
|
||||
a = options_array_next(a);
|
||||
}
|
||||
}
|
||||
@@ -175,6 +233,136 @@ notify_callback(struct cmdq_item *item, void *data)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
void
|
||||
notify_monitor_free(void *data)
|
||||
{
|
||||
struct notify_monitor *nhm = data;
|
||||
|
||||
monitor_destroy(nhm->set);
|
||||
free(nhm->format);
|
||||
free(nhm);
|
||||
}
|
||||
|
||||
void
|
||||
notify_monitor_remove(struct options *oo, const char *name)
|
||||
{
|
||||
struct options_entry *o;
|
||||
struct notify_monitor *nhm;
|
||||
|
||||
o = options_get_only(oo, name);
|
||||
if (o == NULL)
|
||||
return;
|
||||
|
||||
nhm = options_get_monitor_data(o);
|
||||
if (nhm != NULL) {
|
||||
options_set_monitor_data(o, NULL);
|
||||
notify_monitor_free(nhm);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
notify_monitor_cb(struct monitor_change *change, void *data)
|
||||
{
|
||||
struct notify_monitor *nhm = data;
|
||||
struct notify_entry ne;
|
||||
struct cmdq_item *item;
|
||||
struct window *w;
|
||||
|
||||
item = cmdq_running(NULL);
|
||||
if (item != NULL && (cmdq_get_flags(item) & CMDQ_STATE_NOHOOKS))
|
||||
return;
|
||||
|
||||
memset(&ne, 0, sizeof ne);
|
||||
ne.name = change->name;
|
||||
ne.oo = nhm->oo;
|
||||
ne.client = change->c;
|
||||
ne.expand = 1;
|
||||
if (change->wp != NULL && change->wl != NULL)
|
||||
cmd_find_from_winlink_pane(&ne.fs, change->wl, change->wp, 0);
|
||||
else if (change->wl != NULL)
|
||||
cmd_find_from_winlink(&ne.fs, change->wl, 0);
|
||||
else if (change->s != NULL)
|
||||
cmd_find_from_session(&ne.fs, change->s, 0);
|
||||
else
|
||||
cmd_find_copy_state(&ne.fs, &nhm->fs);
|
||||
ne.formats = format_create(change->c, item, FORMAT_NONE, FORMAT_NOJOBS);
|
||||
format_add(ne.formats, "hook", "%s", change->name);
|
||||
format_add(ne.formats, "hook_value", "%s", change->value);
|
||||
format_add(ne.formats, "hook_last", "%s",
|
||||
change->last == NULL ? "" : change->last);
|
||||
if (change->s != NULL) {
|
||||
format_add(ne.formats, "hook_session", "$%u", change->s->id);
|
||||
format_add(ne.formats, "hook_session_name", "%s", change->s->name);
|
||||
}
|
||||
if (change->wl != NULL) {
|
||||
w = change->wl->window;
|
||||
format_add(ne.formats, "hook_window", "@%u", w->id);
|
||||
format_add(ne.formats, "hook_window_name", "%s", w->name);
|
||||
format_add(ne.formats, "hook_window_index", "%d", change->wl->idx);
|
||||
}
|
||||
if (change->wp != NULL) {
|
||||
format_add(ne.formats, "hook_pane", "%%%u", change->wp->id);
|
||||
}
|
||||
|
||||
notify_insert_hook(item, &ne);
|
||||
format_free(ne.formats);
|
||||
}
|
||||
|
||||
void
|
||||
notify_monitor_add(__unused struct cmdq_item *item, struct options *oo,
|
||||
const char *name, enum monitor_type type, int id, const char *format,
|
||||
struct cmd_find_state *fs, struct session *s)
|
||||
{
|
||||
struct options_entry *o;
|
||||
struct notify_monitor *nhm;
|
||||
|
||||
notify_monitor_remove(oo, name);
|
||||
o = options_get_only(oo, name);
|
||||
if (o == NULL)
|
||||
o = options_set_string(oo, name, 0, "%s", "");
|
||||
|
||||
nhm = xcalloc(1, sizeof *nhm);
|
||||
nhm->oo = oo;
|
||||
cmd_find_copy_state(&nhm->fs, fs);
|
||||
nhm->type = type;
|
||||
nhm->id = id;
|
||||
nhm->format = xstrdup(format);
|
||||
nhm->set = monitor_create_session(s, notify_monitor_cb, nhm);
|
||||
options_set_monitor_data(o, nhm);
|
||||
monitor_add(nhm->set, name, type, id, format, 0);
|
||||
}
|
||||
|
||||
/* Convert a hook monitor to its value. */
|
||||
char *
|
||||
notify_monitor_to_string(struct options_entry *o)
|
||||
{
|
||||
struct notify_monitor *nhm = options_get_monitor_data(o);
|
||||
const char *name = options_name(o);
|
||||
char *s;
|
||||
|
||||
if (nhm == NULL)
|
||||
return (NULL);
|
||||
|
||||
switch (nhm->type) {
|
||||
case MONITOR_SESSION:
|
||||
xasprintf(&s, "%s::%s", name, nhm->format);
|
||||
break;
|
||||
case MONITOR_PANE:
|
||||
xasprintf(&s, "%s:%%%d:%s", name, nhm->id, nhm->format);
|
||||
break;
|
||||
case MONITOR_ALL_PANES:
|
||||
xasprintf(&s, "%s:%%*:%s", name, nhm->format);
|
||||
break;
|
||||
case MONITOR_WINDOW:
|
||||
xasprintf(&s, "%s:@%d:%s", name, nhm->id, nhm->format);
|
||||
break;
|
||||
case MONITOR_ALL_WINDOWS:
|
||||
xasprintf(&s, "%s:@*:%s", name, nhm->format);
|
||||
break;
|
||||
}
|
||||
return (s);
|
||||
}
|
||||
|
||||
static void
|
||||
notify_add(const char *name, struct cmd_find_state *fs, struct client *c,
|
||||
struct session *s, struct window *w, struct window_pane *wp,
|
||||
|
||||
@@ -955,7 +955,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "mouse",
|
||||
.type = OPTIONS_TABLE_FLAG,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_num = 0,
|
||||
.default_num = TMUX_MOUSE,
|
||||
.text = "Whether the mouse is recognised and mouse key bindings are "
|
||||
"executed. "
|
||||
"Applications inside panes can use the mouse even when 'off'."
|
||||
@@ -1538,6 +1538,9 @@ const struct options_table_entry options_table[] = {
|
||||
"\"#{pane_title}\""
|
||||
"#{?#{mouse},"
|
||||
"#[align=right]"
|
||||
"#[range=control|7]["
|
||||
"#{?#{pane_floating_flag},t,f}"
|
||||
"]#[norange]"
|
||||
"#[range=control|8]["
|
||||
"#{?#{window_zoomed_flag},u,z}"
|
||||
"]#[norange]"
|
||||
|
||||
15
options.c
15
options.c
@@ -56,6 +56,7 @@ struct options_entry {
|
||||
|
||||
int cached;
|
||||
struct style style;
|
||||
void *monitor_data;
|
||||
|
||||
RB_ENTRY(options_entry) entry;
|
||||
};
|
||||
@@ -354,6 +355,8 @@ options_remove(struct options_entry *o)
|
||||
options_array_clear(o);
|
||||
else
|
||||
options_value_free(o, &o->value);
|
||||
if (o->monitor_data != NULL)
|
||||
notify_monitor_free(o->monitor_data);
|
||||
RB_REMOVE(options_tree, &oo->tree, o);
|
||||
free((void *)o->name);
|
||||
free(o);
|
||||
@@ -371,6 +374,18 @@ options_owner(struct options_entry *o)
|
||||
return (o->owner);
|
||||
}
|
||||
|
||||
void *
|
||||
options_get_monitor_data(struct options_entry *o)
|
||||
{
|
||||
return (o->monitor_data);
|
||||
}
|
||||
|
||||
void
|
||||
options_set_monitor_data(struct options_entry *o, void *data)
|
||||
{
|
||||
o->monitor_data = data;
|
||||
}
|
||||
|
||||
const struct options_table_entry *
|
||||
options_table_entry(struct options_entry *o)
|
||||
{
|
||||
|
||||
@@ -3,8 +3,34 @@ TESTS!= echo *.sh
|
||||
.PHONY: all $(TESTS)
|
||||
.NOTPARALLEL: all $(TESTS)
|
||||
|
||||
all: $(TESTS)
|
||||
all:
|
||||
@failed=0; failures=; \
|
||||
for test in $(TESTS); do \
|
||||
printf '%-40s ' "$$test"; \
|
||||
start=$$(date +%s); \
|
||||
ASAN_OPTIONS="abort_on_error=1:detect_leaks=0:$$ASAN_OPTIONS"; \
|
||||
env -i LC_CTYPE=C.UTF-8 ASAN_OPTIONS="$$ASAN_OPTIONS" \
|
||||
sh "$$test" >/dev/null 2>&1; \
|
||||
if [ $$? -eq 0 ]; then \
|
||||
end=$$(date +%s); \
|
||||
echo "PASS ($$((end - start))s)"; \
|
||||
else \
|
||||
end=$$(date +%s); \
|
||||
echo "FAIL ($$((end - start))s)"; \
|
||||
failed=1; \
|
||||
failures="$$failures $$test"; \
|
||||
fi; \
|
||||
sleep 1; \
|
||||
done; \
|
||||
if [ "$$failed" -ne 0 ]; then \
|
||||
echo; \
|
||||
echo "failures:"; \
|
||||
for test in $$failures; do \
|
||||
echo " $$test"; \
|
||||
done; \
|
||||
fi; \
|
||||
exit $$failed
|
||||
|
||||
$(TESTS):
|
||||
sh $*.sh
|
||||
sh $@
|
||||
sleep 1
|
||||
|
||||
Binary file not shown.
287
regress/buffers.sh
Normal file
287
regress/buffers.sh
Normal file
@@ -0,0 +1,287 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of paste buffer command semantics, as implemented in cmd-set-buffer.c
|
||||
# (set-buffer and delete-buffer), cmd-paste-buffer.c, cmd-load-buffer.c,
|
||||
# cmd-save-buffer.c (save-buffer and show-buffer), cmd-list-buffers.c and
|
||||
# paste.c.
|
||||
#
|
||||
# This exercises:
|
||||
# - set-buffer creating automatic buffers (buffer0, buffer1, ... with the
|
||||
# most recent first), -b creating/replacing a named buffer, -a appending,
|
||||
# -n renaming and the error paths (no data, unknown buffer);
|
||||
# - show-buffer for the top and for named buffers;
|
||||
# - delete-buffer for the top and named buffers, and when nothing exists;
|
||||
# - list-buffers -F custom formats and -f filters;
|
||||
# - paste-buffer into a pane: newline-to-CR translation by default, -r raw,
|
||||
# -s custom separator, -d delete-after-paste, unknown buffer error;
|
||||
# - the buffer-limit option evicting the oldest automatic buffers but not
|
||||
# named buffers;
|
||||
# - load-buffer/save-buffer round trips including control characters and
|
||||
# UTF-8, save-buffer -a appending and errors for missing files/buffers.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
TMP2=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$TMP2"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
# Run a command and require that it succeeds.
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
#
|
||||
# Run a command and require that it fails with the given error message.
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_buffers $expected
|
||||
#
|
||||
# Compare the buffer list (as "name=content ...", most recent first) with
|
||||
# $expected.
|
||||
check_buffers()
|
||||
{
|
||||
out=$(echo $($TMUX list-buffers -F \
|
||||
'#{buffer_name}=#{buffer_sample}'))
|
||||
if [ "$out" != "$1" ]; then
|
||||
echo "Buffer list wrong."
|
||||
echo "Expected: '$1'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_show $args $expected
|
||||
#
|
||||
# Compare show-buffer output with $expected.
|
||||
check_show()
|
||||
{
|
||||
out=$($TMUX show-buffer $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show-buffer $1 wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok new-session -d -s B -x 80 -y 24
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# set-buffer, show-buffer, delete-buffer, list-buffers.
|
||||
|
||||
# Automatic buffers stack with the most recent first.
|
||||
check_ok set-buffer one
|
||||
check_ok set-buffer two
|
||||
check_buffers 'buffer1=two buffer0=one'
|
||||
check_show '' 'two'
|
||||
check_show '-b buffer0' 'one'
|
||||
|
||||
# -a only appends to a buffer named with -b: without -b it creates a new
|
||||
# automatic buffer. Empty data is silently ignored.
|
||||
check_ok set-buffer -a '!'
|
||||
check_buffers 'buffer2=! buffer1=two buffer0=one'
|
||||
check_ok delete-buffer
|
||||
check_ok set-buffer ''
|
||||
check_buffers 'buffer1=two buffer0=one'
|
||||
|
||||
# -b names a buffer explicitly; setting it again replaces the content;
|
||||
# -a appends to it.
|
||||
check_ok set-buffer -b named abc
|
||||
check_buffers 'named=abc buffer1=two buffer0=one'
|
||||
check_ok set-buffer -b named xyz
|
||||
check_ok set-buffer -a -b named 123
|
||||
check_buffers 'named=xyz123 buffer1=two buffer0=one'
|
||||
check_show '-b named' 'xyz123'
|
||||
|
||||
# -n renames; renaming to a bad source is an error, as is no data at all.
|
||||
check_ok set-buffer -b named -n other
|
||||
check_buffers 'other=xyz123 buffer1=two buffer0=one'
|
||||
check_fail 'unknown buffer: nosuch' set-buffer -b nosuch -n foo
|
||||
check_fail 'no data specified' set-buffer -b other
|
||||
check_fail 'no buffer nosuch' show-buffer -b nosuch
|
||||
|
||||
# list-buffers -f filters.
|
||||
out=$($TMUX list-buffers -f '#{==:#{buffer_name},other}' -F '#{buffer_name}')
|
||||
if [ "$out" != "other" ]; then
|
||||
echo "list-buffers -f wrong: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# delete-buffer -b removes one buffer; without -b the most recent automatic
|
||||
# buffer goes - named buffers are not candidates for the top, for
|
||||
# show-buffer and delete-buffer alike.
|
||||
check_ok delete-buffer -b buffer1
|
||||
check_buffers 'other=xyz123 buffer0=one'
|
||||
check_fail 'unknown buffer: buffer1' delete-buffer -b buffer1
|
||||
check_ok delete-buffer
|
||||
check_buffers 'other=xyz123'
|
||||
check_fail 'no buffers' show-buffer
|
||||
check_fail 'no buffer' delete-buffer
|
||||
check_ok delete-buffer -b other
|
||||
check_fail 'no buffers' show-buffer
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# buffer-limit.
|
||||
|
||||
# Only automatic buffers count against buffer-limit and the oldest are
|
||||
# evicted; named buffers survive. (Automatic buffer numbers keep counting
|
||||
# up over the life of the server, so compare content only.)
|
||||
check_ok set-option -g buffer-limit 3
|
||||
check_ok set-buffer -b keepme precious
|
||||
check_ok set-buffer a1
|
||||
check_ok set-buffer a2
|
||||
check_ok set-buffer a3
|
||||
check_ok set-buffer a4
|
||||
out=$(echo $($TMUX list-buffers -F '#{buffer_sample}'))
|
||||
if [ "$out" != 'a4 a3 a2 precious' ]; then
|
||||
echo "buffer-limit eviction wrong: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
check_ok set-option -g buffer-limit 50
|
||||
check_ok delete-buffer -b keepme
|
||||
check_ok delete-buffer; check_ok delete-buffer; check_ok delete-buffer
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# paste-buffer.
|
||||
|
||||
# Paste into a raw, echo-free pane running cat -v so control characters are
|
||||
# visible; a fresh window per paste keeps assertions simple.
|
||||
|
||||
# paste_line $bufdata $pasteargs $expected
|
||||
#
|
||||
# Set a buffer, paste it into a fresh cat -v pane and compare the first
|
||||
# screen line with $expected.
|
||||
paste_line()
|
||||
{
|
||||
$TMUX kill-window -t B:9 2>/dev/null
|
||||
check_ok new-window -d -t B:9 'stty raw -echo && exec cat -v'
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t B:9.0 \
|
||||
'#{pane_current_command}')" != "cat" ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "cat did not start."; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
check_ok set-buffer -b paste "$1"
|
||||
check_ok paste-buffer $2 -b paste -t B:9.0
|
||||
i=0
|
||||
while out=$($TMUX capture-pane -p -t B:9.0 | sed -n 1p) && \
|
||||
[ "$out" != "$3" ]; do
|
||||
i=$((i + 1))
|
||||
if [ $i -gt 50 ]; then
|
||||
echo "Paste of '$1' ($2) wrong."
|
||||
echo "Expected: '$3'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
|
||||
# By default linefeeds are replaced with carriage returns (shown as ^M by
|
||||
# cat -v); -r pastes raw and -s sets an explicit separator.
|
||||
paste_line 'one
|
||||
two' '' 'one^Mtwo'
|
||||
paste_line 'one
|
||||
two' '-r' 'one'
|
||||
paste_line 'one
|
||||
two' '-s |' 'one|two'
|
||||
paste_line 'one
|
||||
two' '-s XX' 'oneXXtwo'
|
||||
|
||||
# -d deletes the buffer after pasting.
|
||||
paste_line 'gone' '-d' 'gone'
|
||||
check_fail 'no buffer paste' show-buffer -b paste
|
||||
|
||||
# Unknown buffer is an error.
|
||||
check_fail 'no buffer nosuch' paste-buffer -b nosuch -t B:9.0
|
||||
check_ok kill-window -t B:9
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# load-buffer and save-buffer.
|
||||
|
||||
# Round trip a file with control characters and UTF-8 through load-buffer
|
||||
# and save-buffer.
|
||||
printf 'line1\tx\033[31m\001\002\303\251\n' >"$TMP"
|
||||
check_ok load-buffer -b file "$TMP"
|
||||
check_ok save-buffer -b file "$TMP2"
|
||||
if ! cmp -s "$TMP" "$TMP2"; then
|
||||
echo "load-buffer/save-buffer round trip differs."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# save-buffer -a appends.
|
||||
check_ok save-buffer -a -b file "$TMP2"
|
||||
cat "$TMP" "$TMP" >"$TMP".x
|
||||
if ! cmp -s "$TMP".x "$TMP2"; then
|
||||
rm -f "$TMP".x
|
||||
echo "save-buffer -a did not append."
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$TMP".x
|
||||
|
||||
# show-buffer prints the loaded content (text form).
|
||||
check_ok delete-buffer -b file
|
||||
|
||||
# load-buffer of a missing file and save-buffer of a missing buffer or to a
|
||||
# bad path are errors.
|
||||
check_fail "No such file or directory: $TMP.nosuch" \
|
||||
load-buffer -b x "$TMP.nosuch"
|
||||
check_fail 'no buffer nosuch' save-buffer -b nosuch "$TMP2"
|
||||
check_ok set-buffer -b sb data
|
||||
out=$($TMUX save-buffer -b sb /nonexistent/dir/file 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "save-buffer to bad path succeeded."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# save-buffer - writes to stdout and load-buffer - reads from stdin.
|
||||
out=$($TMUX save-buffer -b sb -)
|
||||
if [ "$out" != "data" ]; then
|
||||
echo "save-buffer - wrong: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
check_ok delete-buffer -b sb
|
||||
printf 'from stdin' | $TMUX load-buffer -b stdinbuf -
|
||||
check_show '-b stdinbuf' 'from stdin'
|
||||
check_ok delete-buffer -b stdinbuf
|
||||
|
||||
assert_alive
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
@@ -35,68 +35,59 @@ $TMUX set-option -qg allow-set-title on || exit 1
|
||||
$TMUX set-option -qg allow-rename on || exit 1
|
||||
$TMUX set-option -qg automatic-rename off || exit 1
|
||||
|
||||
# Commands reject ':' and '.' for sessions and windows, but allow '#'.
|
||||
$TMUX rename-session 'session#ok' || fail "session name with # rejected"
|
||||
must_equal "$($TMUX display-message -p '#{session_name}')" 'session#ok'
|
||||
must_fail $TMUX rename-session 'session:bad'
|
||||
must_fail $TMUX rename-session 'session.bad'
|
||||
# Commands allow empty names, ':', '.', '#' and '#('.
|
||||
$TMUX rename-session '' || fail "empty session name rejected"
|
||||
must_equal "$($TMUX display-message -p '#{session_name}')" ''
|
||||
$TMUX rename-session 'session:.##(ok)' || \
|
||||
fail "session name with : . or #( rejected"
|
||||
must_equal "$($TMUX display-message -p '#{session_name}')" 'session:.#(ok)'
|
||||
|
||||
$TMUX rename-window 'window#ok' || fail "window name with # rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'window#ok'
|
||||
must_fail $TMUX rename-window 'window:bad'
|
||||
must_fail $TMUX rename-window 'window.bad'
|
||||
$TMUX rename-window '' || fail "empty window name rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" ''
|
||||
$TMUX rename-window 'window:.##(ok)' || \
|
||||
fail "window name with : . or #( rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'window:.#(ok)'
|
||||
|
||||
$TMUX set-option -q @name 'format#ok' || exit 1
|
||||
$TMUX set-option -q @name 'format:.#(ok)' || exit 1
|
||||
$TMUX rename-session '#{@name}' || fail "format in session name not expanded"
|
||||
must_equal "$($TMUX display-message -p '#{session_name}')" 'format#ok'
|
||||
must_equal "$($TMUX display-message -p '#{session_name}')" 'format:.#(ok)'
|
||||
$TMUX rename-window '#{@name}' || fail "format in window name not expanded"
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'format#ok'
|
||||
must_fail $TMUX rename-session '#{session_name}:bad'
|
||||
must_fail $TMUX rename-window '#{window_name}.bad'
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'format:.#(ok)'
|
||||
$TMUX set-option -q @name 'format:.#(ok)' || exit 1
|
||||
pid=$($TMUX display-message -p '#{pid}')
|
||||
|
||||
created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \
|
||||
-s 'new-session#ok' -n 'new-window#ok') || \
|
||||
fail "new-session name with # rejected"
|
||||
-s 'new-session:.##(ok)' -n 'new-window:.##(ok)') || \
|
||||
fail "new-session name with : . or #( rejected"
|
||||
created_session=${created%:*}
|
||||
created_window=${created#*:}
|
||||
must_equal "$($TMUX display-message -pt "$created_session" '#{session_name}')" \
|
||||
'new-session#ok'
|
||||
'new-session:.#(ok)'
|
||||
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
|
||||
'new-window#ok'
|
||||
'new-window:.#(ok)'
|
||||
$TMUX kill-session -t "$created_session"
|
||||
|
||||
must_fail $TMUX new-session -d -s 'new-session:bad'
|
||||
must_fail $TMUX new-session -d -s 'new-session.bad'
|
||||
must_fail $TMUX new-session -d -n 'new-window:bad'
|
||||
must_fail $TMUX new-session -d -n 'new-window.bad'
|
||||
|
||||
created_window=$($TMUX new-window -dP -F '#{window_id}' \
|
||||
-n 'created-window#ok') || \
|
||||
fail "new-window name with # rejected"
|
||||
-n 'created-window:.##(ok)') || \
|
||||
fail "new-window name with : . or #( rejected"
|
||||
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
|
||||
'created-window#ok'
|
||||
must_fail $TMUX new-window -d -n 'created-window:bad'
|
||||
must_fail $TMUX new-window -d -n 'created-window.bad'
|
||||
'created-window:.#(ok)'
|
||||
|
||||
created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \
|
||||
-s 'new-session-#{pid}' -n 'new-window-#{pid}') || \
|
||||
-s 'new-session-#{pid}:.##(ok)' -n 'new-window-#{pid}:.##(ok)') || \
|
||||
fail "format in new-session name not expanded"
|
||||
created_session=${created%:*}
|
||||
created_window=${created#*:}
|
||||
must_equal "$($TMUX display-message -pt "$created_session" '#{session_name}')" \
|
||||
"new-session-$pid"
|
||||
"new-session-$pid:.#(ok)"
|
||||
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
|
||||
"new-window-$pid"
|
||||
"new-window-$pid:.#(ok)"
|
||||
$TMUX kill-session -t "$created_session"
|
||||
|
||||
created_window=$($TMUX new-window -dP -F '#{window_id}' -n '#{@name}') || \
|
||||
fail "format in new-window name not expanded"
|
||||
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
|
||||
'format#ok'
|
||||
must_fail $TMUX new-session -d -s 'new-session-#{pid}:bad'
|
||||
must_fail $TMUX new-session -d -n 'new-window-#{pid}.bad'
|
||||
must_fail $TMUX new-window -d -n '#{window_name}:bad'
|
||||
'format:.#(ok)'
|
||||
|
||||
# Invalid UTF-8 is never allowed for command names.
|
||||
invalid=$(printf '\302')
|
||||
@@ -124,9 +115,9 @@ must_equal "$($TMUX list-buffers -F '#{buffer_name}')" 'buffer#:.ok'
|
||||
# Window names from escape sequences allow '#' except in '#('.
|
||||
$TMUX send-keys "printf '\\033kescape#:.ok\\033\\\\'" Enter || exit 1
|
||||
sleep 1
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'escape#__ok'
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'escape#:.ok'
|
||||
|
||||
# Titles from escape sequences reject only '#'.
|
||||
# Titles from escape sequences allow '#' except in '#('.
|
||||
$TMUX send-keys "printf '\\033]2;escape#:.ok\\007'" Enter || exit 1
|
||||
sleep 1
|
||||
must_equal "$($TMUX display-message -p '#{pane_title}')" 'escape#:.ok'
|
||||
|
||||
199
regress/choose-buffer.sh
Normal file
199
regress/choose-buffer.sh
Normal file
@@ -0,0 +1,199 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of buffer mode (window-buffer.c) as driven by choose-buffer: that the
|
||||
# -f filter removes buffers that do not match (by name and by content), that
|
||||
# a filter matching nothing falls back to showing everything, that -O and -r
|
||||
# change the sort order, that d deletes the selected buffer and C-t and D
|
||||
# delete all tagged buffers, and that Enter runs the default command
|
||||
# (paste-buffer) with the selected buffer.
|
||||
#
|
||||
# The list is drawn on a mode screen which capture-pane does not show, so a
|
||||
# second server provides a client: an inner "tmux attach" runs inside a pane
|
||||
# of the second server, and that pane is captured to read what the inner
|
||||
# client rendered. Each choose-buffer call uses a distinct -F marker so a
|
||||
# capture can be tied to the call it belongs to.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# capture the screen rendered by the inner client
|
||||
capture()
|
||||
{
|
||||
$TMUX2 capture-pane -p -t out:0 2>/dev/null
|
||||
}
|
||||
|
||||
# wait_for $marker
|
||||
#
|
||||
# Wait (up to ~10s) until the rendered screen contains $marker, so the
|
||||
# capture is known to show the mode instance under test.
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
if capture | grep -q "$1"; then
|
||||
sleep 0.5
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$1'"
|
||||
}
|
||||
|
||||
# wait_buffers $n
|
||||
#
|
||||
# Wait (up to ~10s) until the test server has exactly $n paste buffers.
|
||||
wait_buffers()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
c=$($TMUX list-buffers -F x 2>/dev/null | grep -c x)
|
||||
[ "$c" -eq "$1" ] && return 0
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "expected $1 buffers, have $c"
|
||||
}
|
||||
|
||||
# wait_clients $n
|
||||
#
|
||||
# Wait (up to ~10s) until the test server has exactly $n clients attached.
|
||||
wait_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 10 ]; do
|
||||
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$c" -eq "$1" ] && return 0
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
$TMUX new-session -d -s aaa -x 80 -y 24 || exit 1
|
||||
|
||||
$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t aaa" || exit 1
|
||||
wait_clients 1 || fail "no client attached to test server"
|
||||
|
||||
# Two named buffers with distinct contents; bufa is created first.
|
||||
$TMUX set-buffer -b bufa "hello buffer" || exit 1
|
||||
$TMUX set-buffer -b bufz "other buffer" || exit 1
|
||||
|
||||
# --- filter by buffer name ---------------------------------------------------
|
||||
$TMUX choose-buffer -t aaa:0 -F 'B1' -f '#{==:#{buffer_name},bufa}' || exit 1
|
||||
wait_for 'B1'
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'bufa: B1' || fail "bufa missing when it matches"
|
||||
echo "$out" | grep -q 'bufz: B1' && fail "bufz shown but does not match"
|
||||
[ "$(echo "$out" | grep -c ': B1')" -eq 1 ] || fail "expected 1 buffer"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- filter by buffer content ------------------------------------------------
|
||||
$TMUX choose-buffer -t aaa:0 -F 'B2' -f '#{m:*hello*,#{buffer_sample}}' || \
|
||||
exit 1
|
||||
wait_for 'B2'
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'bufa: B2' || fail "bufa missing when content matches"
|
||||
echo "$out" | grep -q 'bufz: B2' && fail "bufz shown but content not matched"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- no filter shows both buffers ---------------------------------------------
|
||||
$TMUX choose-buffer -t aaa:0 -F 'B3' || exit 1
|
||||
wait_for 'B3'
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'bufa: B3' || fail "bufa missing with no filter"
|
||||
echo "$out" | grep -q 'bufz: B3' || fail "bufz missing with no filter"
|
||||
[ "$(echo "$out" | grep -c ': B3')" -eq 2 ] || fail "expected 2 buffers"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- filter matching nothing ---------------------------------------------------
|
||||
#
|
||||
# Everything is shown and the filter indicator reports no matches.
|
||||
$TMUX choose-buffer -t aaa:0 -F 'B4' -f '#{==:#{buffer_name},nosuch}' || \
|
||||
exit 1
|
||||
wait_for 'B4'
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'bufa: B4' || fail "bufa missing with no-match filter"
|
||||
echo "$out" | grep -q 'bufz: B4' || fail "bufz missing with no-match filter"
|
||||
echo "$out" | grep -q 'no matches' || fail "no matches indicator missing"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- sort orders ---------------------------------------------------------------
|
||||
#
|
||||
# By name bufa sorts first and -r reverses.
|
||||
$TMUX choose-buffer -t aaa:0 -F 'B5' -O name || exit 1
|
||||
wait_for 'B5'
|
||||
capture | grep ': B5' | head -1 | grep -q 'bufa: B5' || \
|
||||
fail "bufa not first with -O name"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
$TMUX choose-buffer -t aaa:0 -F 'B6' -O name -r || exit 1
|
||||
wait_for 'B6'
|
||||
capture | grep ': B6' | head -1 | grep -q 'bufz: B6' || \
|
||||
fail "bufz not first with -O name -r"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- d deletes the selected buffer --------------------------------------------
|
||||
#
|
||||
# The filter leaves only bufz listed and selected; d deletes it.
|
||||
$TMUX choose-buffer -t aaa:0 -F 'G1' -f '#{==:#{buffer_name},bufz}' || exit 1
|
||||
wait_for 'bufz: G1'
|
||||
$TMUX send-keys -t aaa:0 d
|
||||
wait_buffers 1
|
||||
$TMUX list-buffers -F '#{buffer_name}' | grep -q 'bufa' || \
|
||||
fail "wrong buffer deleted"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- C-t tags all buffers and D deletes the tagged ------------------------------
|
||||
$TMUX set-buffer -b bufz "other buffer" || exit 1
|
||||
$TMUX set-buffer -b bufb "third buffer" || exit 1
|
||||
$TMUX choose-buffer -t aaa:0 -F 'G2' || exit 1
|
||||
wait_for ': G2'
|
||||
$TMUX send-keys -t aaa:0 C-t D
|
||||
wait_buffers 0
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- Enter runs the default command (paste-buffer) ------------------------------
|
||||
#
|
||||
# The only buffer is listed and selected; Enter leaves the mode and pastes it
|
||||
# into the shell in the pane, where it appears on the screen.
|
||||
$TMUX set-buffer -b bufa "hello buffer" || exit 1
|
||||
$TMUX choose-buffer -t aaa:0 -F 'G3' || exit 1
|
||||
wait_for 'bufa: G3'
|
||||
$TMUX send-keys -t aaa:0 Enter
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
[ "$($TMUX display -p -t aaa:0 '#{pane_in_mode}')" = "0" ] && break
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "mode did not exit after Enter"
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX capture-pane -p -t aaa:0 | grep -q 'hello buffer' && break
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "buffer not pasted into pane"
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
140
regress/choose-client.sh
Normal file
140
regress/choose-client.sh
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of client mode (window-client.c) as driven by choose-client: that the
|
||||
# -f filter removes clients that do not match, that a filter matching nothing
|
||||
# falls back to showing everything, and that Enter runs the default command
|
||||
# (detach-client) on the selected client. Sort orders are not tested here
|
||||
# because clients are named after their ttys, which are not predictable.
|
||||
#
|
||||
# The list is drawn on a mode screen which capture-pane does not show, so a
|
||||
# second server provides the clients: two inner "tmux attach" commands run in
|
||||
# panes of the second server, and the pane holding the client the mode is
|
||||
# displayed on is captured. Each choose-client call uses a distinct -F marker
|
||||
# so a capture can be tied to the call it belongs to.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# capture the screen rendered by the inner client attached to aaa
|
||||
capture()
|
||||
{
|
||||
$TMUX2 capture-pane -p -t out:0 2>/dev/null
|
||||
}
|
||||
|
||||
# wait_for $marker
|
||||
#
|
||||
# Wait (up to ~10s) until the rendered screen contains $marker, so the
|
||||
# capture is known to show the mode instance under test.
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
if capture | grep -q "$1"; then
|
||||
sleep 0.5
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$1'"
|
||||
}
|
||||
|
||||
# wait_clients $n
|
||||
#
|
||||
# Wait (up to ~10s) until the test server has exactly $n clients attached.
|
||||
wait_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 10 ]; do
|
||||
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$c" -eq "$1" ] && return 0
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
# One client attached to each of two sessions; the mode is displayed on the
|
||||
# client attached to aaa (in window 0 of the outer server) and the filters
|
||||
# tell the clients apart by their attached session.
|
||||
$TMUX new-session -d -s aaa -x 80 -y 24 || exit 1
|
||||
$TMUX new-session -d -s bbb -x 80 -y 24 || exit 1
|
||||
|
||||
$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t aaa" || exit 1
|
||||
$TMUX2 new-window -d -t out: "$TMUX attach -t bbb" || exit 1
|
||||
wait_clients 2 || fail "expected two clients attached to test server"
|
||||
|
||||
# --- filter keeping only the aaa client -------------------------------------
|
||||
$TMUX choose-client -t aaa:0 -F 'C1=#{client_session}' \
|
||||
-f '#{==:#{client_session},aaa}' || exit 1
|
||||
wait_for 'C1='
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'C1=aaa' || fail "aaa client missing when it matches"
|
||||
echo "$out" | grep -q 'C1=bbb' && fail "bbb client shown but does not match"
|
||||
[ "$(echo "$out" | grep -c 'C1=')" -eq 1 ] || fail "expected 1 client"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- filter keeping only the bbb client -------------------------------------
|
||||
$TMUX choose-client -t aaa:0 -F 'C2=#{client_session}' \
|
||||
-f '#{==:#{client_session},bbb}' || exit 1
|
||||
wait_for 'C2='
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'C2=bbb' || fail "bbb client missing when it matches"
|
||||
echo "$out" | grep -q 'C2=aaa' && fail "aaa client shown but does not match"
|
||||
[ "$(echo "$out" | grep -c 'C2=')" -eq 1 ] || fail "expected 1 client"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- no filter shows both clients -------------------------------------------
|
||||
$TMUX choose-client -t aaa:0 -F 'C3=#{client_session}' || exit 1
|
||||
wait_for 'C3='
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'C3=aaa' || fail "aaa client missing with no filter"
|
||||
echo "$out" | grep -q 'C3=bbb' || fail "bbb client missing with no filter"
|
||||
[ "$(echo "$out" | grep -c 'C3=')" -eq 2 ] || fail "expected 2 clients"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- filter matching nothing ------------------------------------------------
|
||||
#
|
||||
# Everything is shown and the filter indicator reports no matches.
|
||||
$TMUX choose-client -t aaa:0 -F 'C4=#{client_session}' \
|
||||
-f '#{==:#{client_session},nosuch}' || exit 1
|
||||
wait_for 'C4='
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'C4=aaa' || fail "aaa client missing with no-match filter"
|
||||
echo "$out" | grep -q 'C4=bbb' || fail "bbb client missing with no-match filter"
|
||||
echo "$out" | grep -q 'no matches' || fail "no matches indicator missing"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- Enter runs the default command (detach-client) --------------------------
|
||||
#
|
||||
# The filter leaves only the bbb client listed and selected; Enter detaches
|
||||
# it, leaving only the aaa client attached.
|
||||
$TMUX choose-client -t aaa:0 -F 'G1=#{client_session}' \
|
||||
-f '#{==:#{client_session},bbb}' || exit 1
|
||||
wait_for 'G1=bbb'
|
||||
$TMUX send-keys -t aaa:0 Enter
|
||||
wait_clients 1 || fail "bbb client did not detach"
|
||||
[ "$($TMUX list-clients -F '#{client_session}')" = "aaa" ] || \
|
||||
fail "wrong client detached"
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
247
regress/choose-tree.sh
Normal file
247
regress/choose-tree.sh
Normal file
@@ -0,0 +1,247 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of tree mode (window-tree.c) as driven by choose-tree.
|
||||
#
|
||||
# Filtering: the -f filter is applied per pane and removes panes, windows and
|
||||
# sessions with no matching panes (a window with more than one pane and no
|
||||
# matching panes must disappear - GitHub issue 5326); -h keeps a window
|
||||
# listed when its only matching pane is the pane the tree is drawn in; a
|
||||
# filter matching nothing falls back to showing everything.
|
||||
#
|
||||
# Sorting: -O and -r change the sort order.
|
||||
#
|
||||
# Keys: h and l collapse and expand; f prompts for a filter and c clears it;
|
||||
# g goes to the top; Enter runs the default command (switch-client); x kills
|
||||
# the current item after a confirmation prompt.
|
||||
#
|
||||
# The tree is drawn on a mode screen which capture-pane does not show, so - as
|
||||
# in environ-update.sh - a second server provides a client: an inner "tmux
|
||||
# attach" runs inside a pane of the second server, and that pane is captured
|
||||
# to read what the inner client rendered. Each choose-tree call uses a
|
||||
# distinct -F marker so a capture can be tied to the call it belongs to.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# capture the screen rendered by the inner client
|
||||
capture()
|
||||
{
|
||||
$TMUX2 capture-pane -p -t out:0 2>/dev/null
|
||||
}
|
||||
|
||||
# wait_for $marker
|
||||
#
|
||||
# Wait (up to ~10s) until the rendered screen contains $marker, so the
|
||||
# capture is known to show the mode instance under test.
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
if capture | grep -q "$1"; then
|
||||
sleep 0.5
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$1'"
|
||||
}
|
||||
|
||||
# wait_count $marker $n
|
||||
#
|
||||
# Wait (up to ~10s) until exactly $n rendered lines contain $marker.
|
||||
wait_count()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
[ "$(capture | grep -c "$1")" -eq "$2" ] && return 0
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for $2 lines of '$1' (have $(capture | grep -c "$1"))"
|
||||
}
|
||||
|
||||
# wait_clients $n
|
||||
#
|
||||
# Wait (up to ~10s) until the test server has exactly $n clients attached.
|
||||
wait_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 10 ]; do
|
||||
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$c" -eq "$1" ] && return 0
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
# Session zzz is created first, so it sorts first by index, and has a
|
||||
# two-pane window 0 and a single-pane window 1. Session aaa has one window
|
||||
# with one pane and is where the tree is displayed. With everything expanded
|
||||
# and no filter the tree is nine lines:
|
||||
#
|
||||
# 0 zzz 1 window 0 2 pane 0 3 pane 1 4 window 1 5 pane 0
|
||||
# 6 aaa 7 window 0 8 pane 0
|
||||
$TMUX new-session -d -s zzz -x 80 -y 24 || exit 1
|
||||
$TMUX split-window -t zzz:0 || exit 1
|
||||
$TMUX new-window -t zzz || exit 1
|
||||
$TMUX new-session -d -s aaa -x 80 -y 24 || exit 1
|
||||
|
||||
$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t aaa" || exit 1
|
||||
wait_clients 1 || fail "no client attached to test server"
|
||||
|
||||
# --- filter keeping only aaa ------------------------------------------------
|
||||
#
|
||||
# zzz must disappear entirely: its single-pane window 1 and - the GitHub 5326
|
||||
# regression - its two-pane window 0. aaa contributes exactly three lines
|
||||
# (session, window, pane).
|
||||
$TMUX choose-tree -t aaa:0 -F 'F1' -f '#{==:#{session_name},aaa}' || exit 1
|
||||
wait_count 'F1' 3
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'aaa: F1' || fail "aaa missing when filter matches it"
|
||||
echo "$out" | grep -q 'zzz: F1' && fail "zzz shown but no pane matches"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- filter keeping only zzz ------------------------------------------------
|
||||
#
|
||||
# zzz contributes six lines (session, two windows, three panes); aaa must
|
||||
# disappear.
|
||||
$TMUX choose-tree -t aaa:0 -F 'F2' -f '#{==:#{session_name},zzz}' || exit 1
|
||||
wait_count 'F2' 6
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'zzz: F2' || fail "zzz missing when filter matches it"
|
||||
echo "$out" | grep -q 'aaa: F2' && fail "aaa shown but no pane matches"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- filter matching a single pane ------------------------------------------
|
||||
#
|
||||
# Only pane 1 of zzz:0 matches, so the tree is exactly session zzz, window 0
|
||||
# and that pane; zzz:1 and all of aaa must disappear.
|
||||
$TMUX choose-tree -t aaa:0 -F 'F3' -f '#{==:#{pane_index},1}' || exit 1
|
||||
wait_count 'F3' 3
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'zzz: F3' || fail "zzz missing when its pane matches"
|
||||
echo "$out" | grep -q 'aaa: F3' && fail "aaa shown but no pane matches"
|
||||
echo "$out" | grep -q '1: F3' || fail "matching pane missing"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- filter matching nothing ------------------------------------------------
|
||||
#
|
||||
# Everything is shown and the filter indicator reports no matches.
|
||||
$TMUX choose-tree -t aaa:0 -F 'F4' -f '#{==:#{session_name},nosuch}' || exit 1
|
||||
wait_for 'F4'
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'aaa: F4' || fail "aaa missing with no-match filter"
|
||||
echo "$out" | grep -q 'zzz: F4' || fail "zzz missing with no-match filter"
|
||||
echo "$out" | grep -q 'no matches' || fail "no matches indicator missing"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- -h with the tree pane as the only match --------------------------------
|
||||
#
|
||||
# With -h the pane the tree is drawn in is hidden, but it still counts as a
|
||||
# match, so session and window aaa stay listed: two lines, no pane line.
|
||||
$TMUX choose-tree -h -t aaa:0 -F 'F5' -f '#{==:#{session_name},aaa}' || \
|
||||
exit 1
|
||||
wait_count 'F5' 2
|
||||
capture | grep -q 'aaa: F5' || fail "aaa missing with -h"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- sort orders ------------------------------------------------------------
|
||||
#
|
||||
# By index zzz (created first) sorts first, by name aaa does, and -r reverses.
|
||||
$TMUX choose-tree -t aaa:0 -F 'F6' -O index || exit 1
|
||||
wait_for 'F6'
|
||||
capture | grep 'F6' | head -1 | grep -q 'zzz: F6' || \
|
||||
fail "zzz not first with -O index"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
$TMUX choose-tree -t aaa:0 -F 'F7' -O name || exit 1
|
||||
wait_for 'F7'
|
||||
capture | grep 'F7' | head -1 | grep -q 'aaa: F7' || \
|
||||
fail "aaa not first with -O name"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
$TMUX choose-tree -t aaa:0 -F 'F8' -O name -r || exit 1
|
||||
wait_for 'F8'
|
||||
capture | grep 'F8' | head -1 | grep -q 'zzz: F8' || \
|
||||
fail "zzz not first with -O name -r"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- collapse and expand with h and l -----------------------------------------
|
||||
#
|
||||
# g moves to the top (session zzz); h collapses it, hiding its five children;
|
||||
# l expands it again.
|
||||
$TMUX choose-tree -t aaa:0 -F 'G1' -O index || exit 1
|
||||
wait_count 'G1' 9
|
||||
$TMUX send-keys -t aaa:0 g h
|
||||
wait_count 'G1' 4
|
||||
$TMUX send-keys -t aaa:0 l
|
||||
wait_count 'G1' 9
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- filter entered at the prompt with f, cleared with c ----------------------
|
||||
$TMUX choose-tree -t aaa:0 -F 'G2' -O index || exit 1
|
||||
wait_count 'G2' 9
|
||||
$TMUX send-keys -t aaa:0 f
|
||||
$TMUX send-keys -t aaa:0 -l '#{==:#{session_name},aaa}'
|
||||
$TMUX send-keys -t aaa:0 Enter
|
||||
wait_count 'G2' 3
|
||||
out=$(capture)
|
||||
echo "$out" | grep -q 'aaa: G2' || fail "aaa missing with prompt filter"
|
||||
echo "$out" | grep -q 'zzz: G2' && fail "zzz shown with prompt filter"
|
||||
$TMUX send-keys -t aaa:0 c
|
||||
wait_count 'G2' 9
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
# --- Enter runs the default command (switch-client) ----------------------------
|
||||
#
|
||||
# g selects session zzz and Enter switches the client to it.
|
||||
$TMUX choose-tree -t aaa:0 -F 'G3' -O index || exit 1
|
||||
wait_count 'G3' 9
|
||||
$TMUX send-keys -t aaa:0 g Enter
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
[ "$($TMUX list-clients -F '#{client_session}')" = "zzz" ] && break
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "client did not switch to zzz"
|
||||
$TMUX switch-client -c "$($TMUX list-clients -F '#{client_name}')" -t aaa || \
|
||||
exit 1
|
||||
|
||||
# --- x kills the current item after confirmation -------------------------------
|
||||
#
|
||||
# g and four times j select window 1 of zzz; x asks for confirmation and y
|
||||
# kills it, leaving zzz with one window and the tree with seven lines.
|
||||
$TMUX choose-tree -t aaa:0 -F 'G4' -O index || exit 1
|
||||
wait_count 'G4' 9
|
||||
$TMUX send-keys -t aaa:0 g j j j j x
|
||||
wait_for 'Kill window 1'
|
||||
$TMUX send-keys -t aaa:0 y
|
||||
wait_count 'G4' 7
|
||||
[ "$($TMUX list-windows -t zzz -F x | grep -c x)" -eq 1 ] || \
|
||||
fail "window 1 of zzz not killed"
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
@@ -35,7 +35,7 @@ bind -n C-h run "(tmux display-message -p '#{pane_current_command}' | grep -iq v
|
||||
bind -n C-j run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-j) || tmux select-pane -D"
|
||||
bind -n C-k run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-k) || tmux select-pane -U"
|
||||
bind -n C-l run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-l) || tmux select-pane -R"
|
||||
bind -n C-\ run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys 'C-\\') || tmux select-pane -l"
|
||||
bind -n C-\\ run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys 'C-\\') || tmux select-pane -l"
|
||||
|
||||
# C-l is taken oer by vim style pane navigation
|
||||
bind C-l send-keys 'C-l'
|
||||
|
||||
@@ -183,7 +183,7 @@ bind t swap-window -t 1 # swap the current window's position with window # 1,
|
||||
unbind & # unbind default binding for `split-window -h`
|
||||
bind - split-window -v -c '#{pane_current_path}' # vertical split
|
||||
bind _ split-window -v -c '#{pane_current_path}' -f # full vertical split (v2.3+)
|
||||
bind \ split-window -h -c '#{pane_current_path}' # horizontal split
|
||||
bind \\ split-window -h -c '#{pane_current_path}' # horizontal split
|
||||
bind | split-window -h -c '#{pane_current_path}' -f # full horizontal split (v2.3+)
|
||||
# https://www.reddit.com/r/tmux/comments/3paqoi/tmux_21_has_been_released/cw5wy00
|
||||
bind w switch-client -Tsplit_wind
|
||||
|
||||
74
regress/control-client-layout.sh
Normal file
74
regress/control-client-layout.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Control clients must opt in to the current layout serialization.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Lcontrol-client-layout -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
IN1=$(mktemp -u)
|
||||
IN2=$(mktemp -u)
|
||||
OUT1=$(mktemp)
|
||||
OUT2=$(mktemp)
|
||||
mkfifo "$IN1" "$IN2" || exit 1
|
||||
trap '$TMUX kill-server 2>/dev/null; rm -f "$IN1" "$IN2" "$OUT1" "$OUT2"' 0 1 15
|
||||
|
||||
# Open both ends before starting either client so FIFO setup cannot block.
|
||||
exec 3<>"$IN1"
|
||||
exec 4<>"$IN2"
|
||||
|
||||
$TMUX new-session -d -x80 -y24 || exit 1
|
||||
$TMUX split-window -h || exit 1
|
||||
$TMUX new-pane -x20 -y8 -X5 -Y3 || exit 1
|
||||
|
||||
$TMUX -C attach <&3 >"$OUT1" &
|
||||
$TMUX -C attach -f window-layout-v2 <&4 >"$OUT2" &
|
||||
sleep 1
|
||||
|
||||
printf '%s\n' "list-windows -F 'layout:#{client_flags}:#{window_layout}'" >&3
|
||||
printf '%s\n' "list-panes -F 'layout:#{client_flags}:#{window_layout}'" >&3
|
||||
printf '%s\n' "list-sessions -F 'layout:#{client_flags}:#{window_layout}'" >&3
|
||||
printf '%s\n' "display-message -p 'layout:#{client_flags}:#{window_layout}'" >&3
|
||||
printf '%s\n' "list-windows -F 'layout:#{client_flags}:#{window_layout}'" >&4
|
||||
sleep 1
|
||||
|
||||
# Default control output is legacy; window-layout-v2 output is current.
|
||||
awk '/^layout:/ { print }' "$OUT1" | while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*window-layout-v2*|*%0,*|*';'*) exit 1 ;;
|
||||
esac
|
||||
done || exit 1
|
||||
[ "$(grep -ac '^layout:' "$OUT1")" -eq 6 ] || exit 1
|
||||
grep -a '^layout:.*window-layout-v2.*%0,' "$OUT2" >/dev/null || exit 1
|
||||
|
||||
# Toggling the flag changes subsequent format expansion for the same client.
|
||||
printf '%s\n' "refresh-client -f window-layout-v2" >&3
|
||||
printf '%s\n' "display-message -p 'v2:#{client_flags}:#{window_layout}'" >&3
|
||||
printf '%s\n' "refresh-client -f '!window-layout-v2'" >&3
|
||||
printf '%s\n' "display-message -p 'legacy:#{client_flags}:#{window_layout}'" >&3
|
||||
sleep 1
|
||||
grep -a '^v2:.*window-layout-v2.*%0,' "$OUT1" >/dev/null || exit 1
|
||||
grep -a '^legacy:' "$OUT1" | tail -1 | grep -v '%0,' >/dev/null || exit 1
|
||||
|
||||
# One layout change is formatted independently for each connected client.
|
||||
: >"$OUT1"
|
||||
: >"$OUT2"
|
||||
$TMUX resize-pane -t%0 -R || exit 1
|
||||
sleep 1
|
||||
LEGACY=$(grep -a '^%layout-change ' "$OUT1" | tail -1)
|
||||
CURRENT=$(grep -a '^%layout-change ' "$OUT2" | tail -1)
|
||||
[ -n "$LEGACY" ] || exit 1
|
||||
[ -n "$CURRENT" ] || exit 1
|
||||
case "$LEGACY" in *%0,*|*';'*) exit 1 ;; esac
|
||||
case "$CURRENT" in *%0,*) ;; *) exit 1 ;; esac
|
||||
[ "$(printf '%s' "$CURRENT" | awk '{ print gsub(/%0,/, "") }')" -ge 2 ] ||
|
||||
exit 1
|
||||
|
||||
exec 3>&-
|
||||
exec 4>&-
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
exit 0
|
||||
172
regress/control-subscriptions.sh
Normal file
172
regress/control-subscriptions.sh
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/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 -Ltest$$ -f/dev/null"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
IN="$TMPDIR/in"
|
||||
OUT="$TMPDIR/out"
|
||||
PID=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$PID" ] && kill "$PID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
wait_for()
|
||||
{
|
||||
pattern=$1
|
||||
timeout=${2:-6}
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt "$timeout" ]; do
|
||||
if grep -F -- "$pattern" "$OUT" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo "missing: $pattern"
|
||||
cat "$OUT"
|
||||
return 1
|
||||
}
|
||||
|
||||
reject_for()
|
||||
{
|
||||
pattern=$1
|
||||
timeout=${2:-3}
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt "$timeout" ]; do
|
||||
if grep -F -- "$pattern" "$OUT" >/dev/null 2>&1; then
|
||||
echo "unexpected: $pattern"
|
||||
cat "$OUT"
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
wait_tmux()
|
||||
{
|
||||
target=$1
|
||||
timeout=${2:-6}
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt "$timeout" ]; do
|
||||
if $TMUX display-message -p -t "$target" '#{window_id}' \
|
||||
>/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
window_fields()
|
||||
{
|
||||
$TMUX display-message -p -t "$1" '#{window_id} #{window_index}'
|
||||
}
|
||||
|
||||
pane_fields()
|
||||
{
|
||||
$TMUX display-message -p -t "$1" '#{window_id} #{window_index} #{pane_id}'
|
||||
}
|
||||
|
||||
send()
|
||||
{
|
||||
printf '%s\n' "$*" >&3
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s subs -x 80 -y 24 || exit 1
|
||||
sid=$($TMUX display-message -p -t subs '#{session_id}')
|
||||
|
||||
mkfifo "$IN"
|
||||
: >"$OUT"
|
||||
$TMUX -C attach-session -t subs <"$IN" >"$OUT" 2>&1 &
|
||||
PID=$!
|
||||
exec 3>"$IN"
|
||||
|
||||
send 'display-message -p ready'
|
||||
wait_for 'ready' 3 || exit 1
|
||||
|
||||
send "refresh-client -B 'sw::#{session_windows}'"
|
||||
wait_for "%subscription-changed sw $sid - - - : 1" || exit 1
|
||||
|
||||
send 'new-window'
|
||||
wait_for "%subscription-changed sw $sid - - - : 2" || exit 1
|
||||
|
||||
send 'refresh-client -B sw'
|
||||
send 'new-window'
|
||||
reject_for "%subscription-changed sw $sid - - - : 3" || exit 1
|
||||
|
||||
send 'new-window -n pane-test'
|
||||
wait_tmux subs:pane-test || exit 1
|
||||
set -- $(pane_fields subs:pane-test)
|
||||
wid=$1
|
||||
widx=$2
|
||||
pane=$3
|
||||
|
||||
send "refresh-client -B 'ap:%*:#{pane_id}'"
|
||||
wait_for "%subscription-changed ap $sid $wid $widx $pane : $pane" || exit 1
|
||||
|
||||
send 'split-window -t subs:pane-test'
|
||||
i=0
|
||||
while [ "$i" -lt 6 ]; do
|
||||
[ "$($TMUX list-panes -t subs:pane-test | wc -l)" -eq 2 ] && break
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 6 ] || exit 1
|
||||
newpane=$($TMUX list-panes -t subs:pane-test -F '#{pane_id}' | tail -n 1)
|
||||
wait_for "%subscription-changed ap $sid $wid $widx $newpane : $newpane" ||
|
||||
exit 1
|
||||
|
||||
$TMUX new-session -d -s other || exit 1
|
||||
$TMUX link-window -s subs:pane-test -t other:1 || exit 1
|
||||
|
||||
send "refresh-client -B 'sp:$newpane:#{pane_id}:#{window_id}'"
|
||||
wait_for "%subscription-changed sp $sid $wid $widx $newpane : $newpane:$wid" ||
|
||||
exit 1
|
||||
|
||||
send "refresh-client -B 'cw:$wid:#{window_id}:#{window_index}'"
|
||||
wait_for "%subscription-changed cw $sid $wid $widx - : $wid:$widx" ||
|
||||
exit 1
|
||||
|
||||
send "refresh-client -B 'aw:@*:#{window_id}'"
|
||||
send 'new-window -n window-test'
|
||||
wait_tmux subs:window-test || exit 1
|
||||
set -- $(window_fields subs:window-test)
|
||||
awid=$1
|
||||
awidx=$2
|
||||
wait_for "%subscription-changed aw $sid $awid $awidx - : $awid" || exit 1
|
||||
|
||||
send "refresh-client -B 'dup::#{session_windows}'"
|
||||
wcount=$($TMUX display-message -p -t subs '#{session_windows}')
|
||||
wait_for "%subscription-changed dup $sid - - - : $wcount" || exit 1
|
||||
send "refresh-client -B 'dup::#{session_name}'"
|
||||
wait_for "%subscription-changed dup $sid - - - : subs" || exit 1
|
||||
send 'new-window -n dup-test'
|
||||
wait_tmux subs:dup-test || exit 1
|
||||
wcount=$($TMUX display-message -p -t subs '#{session_windows}')
|
||||
reject_for "%subscription-changed dup $sid - - - : $wcount" || exit 1
|
||||
|
||||
send "refresh-client -B 'missing-pane:%999999:#{pane_id}'"
|
||||
send "refresh-client -B 'missing-window:@999999:#{window_id}'"
|
||||
reject_for '%subscription-changed missing-pane' || exit 1
|
||||
reject_for '%subscription-changed missing-window' || exit 1
|
||||
|
||||
exit 0
|
||||
126
regress/environ-update.sh
Normal file
126
regress/environ-update.sh
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of update-environment handling (environ_update() in environ.c), which
|
||||
# runs when a client attaches to a session: for each pattern in the session's
|
||||
# update-environment option, a matching variable in the attaching client's
|
||||
# environment is copied into the session environment, and a pattern that
|
||||
# matches nothing clears that name in the session (a NULL-valued entry).
|
||||
#
|
||||
# This needs a real attached client with a controllable environment, so - as in
|
||||
# format-variables.sh - a second server provides one: an inner "tmux attach"
|
||||
# runs inside a pane of the second server, and the variables to import are set
|
||||
# in that inner command's own environment.
|
||||
#
|
||||
# environ.sh covers the set-environment/show-environment commands themselves.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
# A second server on its own socket hosts the pane that runs the inner client.
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# check_value $var $expected
|
||||
#
|
||||
# Compare show-environment of $var on the session with $expected.
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show-environment -t main "$1" 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show-environment $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
cleanup
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# wait_clients $n
|
||||
#
|
||||
# Wait (up to ~10s) until the test server has exactly $n clients attached.
|
||||
wait_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 10 ]; do
|
||||
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$c" -eq "$1" ] && return 0
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# The session imports MYVAR and ABSENTVAR by exact name and anything matching
|
||||
# the glob TEST_*; nothing else is imported.
|
||||
$TMUX set -g update-environment "MYVAR ABSENTVAR TEST_*" || exit 1
|
||||
|
||||
# Seed the session so the effect of attaching is visible: MYVAR will be
|
||||
# overwritten by the client's value and ABSENTVAR will be cleared.
|
||||
$TMUX set-environment -t main MYVAR oldvalue || exit 1
|
||||
$TMUX set-environment -t main ABSENTVAR pre-existing || exit 1
|
||||
|
||||
# --- attach a client whose environment carries the imported variables ------
|
||||
#
|
||||
# MYVAR and TEST_GLOB are present in the inner client's environment; ABSENTVAR
|
||||
# is deliberately absent; OTHER is present but not named by update-environment.
|
||||
$TMUX2 new-session -d -x 90 -y 30 \
|
||||
"MYVAR=fromclient TEST_GLOB=globval OTHER=nope $TMUX attach -t main" \
|
||||
|| fail "could not start inner client"
|
||||
wait_clients 1 || fail "no client attached to test server"
|
||||
|
||||
# MYVAR matched by name and present in the client -> imported (overwrites).
|
||||
check_value MYVAR "MYVAR=fromclient"
|
||||
# TEST_GLOB matched by the TEST_* glob and present -> imported.
|
||||
check_value TEST_GLOB "TEST_GLOB=globval"
|
||||
# ABSENTVAR named but not in the client environment -> cleared (NULL value,
|
||||
# printed as -NAME).
|
||||
check_value ABSENTVAR "-ABSENTVAR"
|
||||
# OTHER is in the client environment but not named by update-environment, so it
|
||||
# is not imported at all.
|
||||
out=$($TMUX show-environment -t main OTHER 2>&1)
|
||||
[ "$out" = "unknown variable: OTHER" ] || \
|
||||
fail "OTHER was imported but should not have been: '$out'"
|
||||
|
||||
# --- -E disables the update-environment import -----------------------------
|
||||
#
|
||||
# Detach the client (kill its host server), reset the session variables, then
|
||||
# reattach with -E: the session values must be left untouched.
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
wait_clients 0 || fail "client did not detach"
|
||||
|
||||
$TMUX set-environment -t main MYVAR oldvalue2 || exit 1
|
||||
$TMUX set-environment -t main ABSENTVAR pre2 || exit 1
|
||||
|
||||
$TMUX2 new-session -d -x 90 -y 30 \
|
||||
"MYVAR=fromclientE $TMUX attach -E -t main" \
|
||||
|| fail "could not start inner -E client"
|
||||
wait_clients 1 || fail "no -E client attached to test server"
|
||||
|
||||
# With -E neither variable is touched by the attach.
|
||||
check_value MYVAR "MYVAR=oldvalue2"
|
||||
check_value ABSENTVAR "ABSENTVAR=pre2"
|
||||
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
fail "server died after update-environment tests"
|
||||
fi
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
187
regress/environ.sh
Normal file
187
regress/environ.sh
Normal file
@@ -0,0 +1,187 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the environment engine (environ.c) and its two commands,
|
||||
# set-environment/setenv (cmd-set-environment.c) and show-environment/showenv
|
||||
# (cmd-show-environment.c).
|
||||
#
|
||||
# The environment is a red-black tree of name/value entries held at two
|
||||
# scopes: the global environment and each session's environment. An entry
|
||||
# may be marked hidden (ENVIRON_HIDDEN) or "cleared" (a NULL value, which
|
||||
# masks an inherited variable rather than removing the entry). This
|
||||
# exercises: set and show at global and session scope; the shell (-s) output
|
||||
# form and its escaping of $ ` " and \; hidden variables (-h) and their
|
||||
# filtering; -r cleared entries printed as -NAME / "unset NAME;"; -u removal;
|
||||
# -F expansion of the value at set time; the plain "NAME=value" and "%hidden"
|
||||
# config-file assignment forms (environ_put); and the full set of argument and
|
||||
# target errors from both commands.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check_value $args $expected
|
||||
#
|
||||
# Run show-environment with $args and compare the single-line output.
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show-environment $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show-environment $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
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
|
||||
|
||||
# --- set and show at session scope ----------------------------------------
|
||||
check_ok set-environment FOO bar
|
||||
check_value "FOO" "FOO=bar"
|
||||
# setenv is an alias for set-environment; showenv for show-environment.
|
||||
check_ok setenv FOO2 bar2
|
||||
out=$($TMUX showenv FOO2 2>&1)
|
||||
[ "$out" = "FOO2=bar2" ] || { echo "setenv/showenv alias failed: '$out'"; exit 1; }
|
||||
|
||||
# --- set and show at global scope -----------------------------------------
|
||||
#
|
||||
# The global scope is separate from the session scope: a session variable is
|
||||
# not visible in the global environment.
|
||||
check_ok set-environment -g GVAR gval
|
||||
check_value "-g GVAR" "GVAR=gval"
|
||||
check_fail "unknown variable: FOO" show-environment -g FOO
|
||||
|
||||
# --- overwrite replaces the value -----------------------------------------
|
||||
check_ok set-environment FOO baz
|
||||
check_value "FOO" "FOO=baz"
|
||||
|
||||
# --- shell (-s) output form and escaping ----------------------------------
|
||||
#
|
||||
# With -s the value is printed as a shell assignment with export, and the
|
||||
# characters $ ` " and \ are backslash-escaped (POSIX double-quote rules).
|
||||
check_ok set-environment ESC 'a$b`c"d\e'
|
||||
check_value "-s ESC" 'ESC="a\$b\`c\"d\\e"; export ESC;'
|
||||
|
||||
# --- -F expands the value as a format at set time -------------------------
|
||||
#
|
||||
# With a resolvable target the value is expanded once when set; the stored
|
||||
# value is the result, not the format.
|
||||
check_ok set-environment -t main -F EXP '#{session_name}'
|
||||
check_value "EXP" "EXP=main"
|
||||
|
||||
# --- hidden variables (-h) ------------------------------------------------
|
||||
#
|
||||
# set-environment -h marks a variable hidden. show-environment hides it by
|
||||
# default and only prints it when -h is given; conversely a normal variable is
|
||||
# omitted when -h is given.
|
||||
check_ok set-environment -h SECRET s3cr
|
||||
check_value "SECRET" ""
|
||||
check_value "-h SECRET" "SECRET=s3cr"
|
||||
check_value "-h FOO" ""
|
||||
|
||||
# --- -r clears a variable (NULL value, masks inheritance) -----------------
|
||||
#
|
||||
# A cleared entry still exists but has no value: normal form prints "-NAME"
|
||||
# and shell form prints "unset NAME;".
|
||||
check_ok set-environment -r FOO
|
||||
check_value "FOO" "-FOO"
|
||||
check_value "-s FOO" "unset FOO;"
|
||||
|
||||
# --- -u removes a variable entirely ---------------------------------------
|
||||
check_ok set-environment -u FOO
|
||||
check_fail "unknown variable: FOO" show-environment FOO
|
||||
|
||||
# --- show with no name lists every (non-hidden) variable ------------------
|
||||
check_ok set-environment -g LISTA 1
|
||||
check_ok set-environment -g LISTB 2
|
||||
check_ok set-environment -gh LISTHID 3
|
||||
out=$($TMUX show-environment -g 2>&1)
|
||||
echo "$out" | grep -q '^LISTA=1$' || { echo "list missing LISTA"; exit 1; }
|
||||
echo "$out" | grep -q '^LISTB=2$' || { echo "list missing LISTB"; exit 1; }
|
||||
# A hidden variable is not listed without -h.
|
||||
echo "$out" | grep -q '^LISTHID' && { echo "list showed hidden var without -h"; exit 1; }
|
||||
# With -h only hidden variables are listed.
|
||||
$TMUX show-environment -gh 2>&1 | grep -q '^LISTHID=3$' || \
|
||||
{ echo "list -h missing LISTHID"; exit 1; }
|
||||
|
||||
# --- config-file assignment forms (environ_put) ---------------------------
|
||||
#
|
||||
# A bare NAME=value line in a config file sets a global variable; a "%hidden"
|
||||
# NAME=value line sets a hidden one. Start a second server from such a config
|
||||
# and read the values back.
|
||||
CONF=$(mktemp)
|
||||
cat > "$CONF" <<EOF
|
||||
CFGVAR=fromconfig
|
||||
%hidden CFGHID=hiddencfg
|
||||
EOF
|
||||
CTMUX="$TEST_TMUX -Ltest2 -f$CONF"
|
||||
$CTMUX kill-server 2>/dev/null
|
||||
$CTMUX new-session -d -s c -x 80 -y 24 || { rm -f "$CONF"; exit 1; }
|
||||
out=$($CTMUX show-environment -g CFGVAR 2>&1)
|
||||
[ "$out" = "CFGVAR=fromconfig" ] || \
|
||||
{ echo "config assignment failed: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
|
||||
out=$($CTMUX show-environment -gh CFGHID 2>&1)
|
||||
[ "$out" = "CFGHID=hiddencfg" ] || \
|
||||
{ echo "config %hidden failed: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
|
||||
# The %hidden variable is hidden from a plain show.
|
||||
out=$($CTMUX show-environment -g CFGHID 2>&1)
|
||||
[ "$out" = "" ] || \
|
||||
{ echo "config %hidden not hidden: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
|
||||
$CTMUX kill-server 2>/dev/null
|
||||
rm -f "$CONF"
|
||||
|
||||
# --- set-environment argument errors --------------------------------------
|
||||
check_fail "empty variable name" set-environment "" x
|
||||
check_fail "variable name contains =" set-environment "A=B" x
|
||||
check_fail "can't specify a value with -u" set-environment -u FOO val
|
||||
check_fail "can't specify a value with -r" set-environment -r FOO val
|
||||
check_fail "no value specified" set-environment NOVAL
|
||||
|
||||
# --- show-environment errors ----------------------------------------------
|
||||
check_fail "unknown variable: MISSING" show-environment MISSING
|
||||
|
||||
# --- unresolvable target errors -------------------------------------------
|
||||
check_fail "no such session: nosuch" show-environment -t nosuch FOO
|
||||
check_fail "no such session: nosuch" set-environment -t nosuch FOO bar
|
||||
|
||||
assert_alive "after environ tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
580
regress/format-modifiers.sh
Normal file
580
regress/format-modifiers.sh
Normal file
@@ -0,0 +1,580 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of format modifiers as described in tmux(1) FORMATS.
|
||||
#
|
||||
# This complements format-strings.sh (which covers escapes, conditionals,
|
||||
# boolean operators and the l: literal modifier). Here we exercise the
|
||||
# remaining modifiers: comparisons/matching (m, C, <, >, ==, ...), numeric
|
||||
# operations (e|op|), width/padding/truncation (=, p, n, w, a, R), basename
|
||||
# and dirname (b, d), time conversion (t), loops (S, W, P), colour (c) and
|
||||
# modifier nesting/limits.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
TZ=UTC
|
||||
LANG=C.UTF-8
|
||||
LC_ALL=C.UTF-8
|
||||
export TZ LANG LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
|
||||
ESC=$(printf '\033')
|
||||
|
||||
# test_format $format $expected [$target]
|
||||
#
|
||||
# Expand $format with display-message and compare with $expected. If $target
|
||||
# is given it is passed to display-message with -t.
|
||||
test_format()
|
||||
{
|
||||
fmt="$1"
|
||||
exp="$2"
|
||||
target="$3"
|
||||
|
||||
if [ -n "$target" ]; then
|
||||
out=$($TMUX display-message -t "$target" -p "$fmt")
|
||||
else
|
||||
out=$($TMUX display-message -p "$fmt")
|
||||
fi
|
||||
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Format test failed for '$fmt'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# test_expand $format $expected
|
||||
#
|
||||
# Expand $format in a plain format_expand context (list-windows -F on the
|
||||
# single-window "tf" session) rather than the format_expand_time context of
|
||||
# display-message. This matters for t/f: display-message runs the whole format
|
||||
# through strftime(3), so a strftime specifier there must be doubled (%%H); in a
|
||||
# format_expand context a single specifier (%H) is applied directly to the
|
||||
# variable's time.
|
||||
test_expand()
|
||||
{
|
||||
fmt="$1"
|
||||
exp="$2"
|
||||
|
||||
out=$($TMUX list-windows -t tf -F "$fmt")
|
||||
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Format test failed for '$fmt'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# assert_alive
|
||||
#
|
||||
# Check that the server is still responding (used after operations that could
|
||||
# in principle crash it, such as recursion and division by zero).
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server did not survive: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# Single-window session used by test_expand for format_expand-context tests.
|
||||
$TMUX new-session -d -s tf || exit 1
|
||||
|
||||
# User options used as inputs. Modifiers operate on variable names, so plain
|
||||
# literals must be provided via options (or a nested #{l:...}). They are set
|
||||
# globally (-g) so they are visible from every session, including the "tf"
|
||||
# session used by test_expand.
|
||||
$TMUX set -g @s 'abcdefghij' || exit 1
|
||||
$TMUX set -g @path '/usr/local/bin/foo' || exit 1
|
||||
$TMUX set -g @name 'window-name' || exit 1
|
||||
$TMUX set -g @greek 'αβγ' || exit 1 # 6 bytes, 3 columns wide
|
||||
$TMUX set -g @cjk '中文' || exit 1 # 6 bytes, 4 columns wide
|
||||
$TMUX set -g @host 'myhost' || exit 1
|
||||
$TMUX set -g @ts '1000000000' || exit 1 # 2001-09-09 01:46:40 UTC
|
||||
$TMUX set -g @sp 'a b$c' || exit 1 # shell-special characters for q:
|
||||
$TMUX set -g @hash 'a#b' || exit 1 # a "#" for q/e:
|
||||
$TMUX set -g @sq "a'b" || exit 1 # a single quote for q/s:
|
||||
$TMUX set -g @nl "$(printf 'a\nb')" || exit 1
|
||||
q_s_nl=$(printf "'a\nb'")
|
||||
|
||||
|
||||
# --- Comparisons and matching --------------------------------------------
|
||||
|
||||
# m: glob match, first argument is the pattern.
|
||||
test_format "#{m:*foo*,barfoobar}" "1"
|
||||
test_format "#{m:*foo*,barbar}" "0"
|
||||
test_format "#{m:abc,abc}" "1"
|
||||
# m/i: ignore case.
|
||||
test_format "#{m/i:*FOO*,barfoobar}" "1"
|
||||
test_format "#{m/i:*FOO*,barbar}" "0"
|
||||
# m/r: regular expression.
|
||||
test_format "#{m/r:^[0-9]+\$,12345}" "1"
|
||||
test_format "#{m/r:^[0-9]+\$,12a45}" "0"
|
||||
# m/ri: regular expression, ignore case.
|
||||
test_format "#{m/ri:^ab+\$,ABBB}" "1"
|
||||
test_format "#{m/ri:^ab+\$,ACCC}" "0"
|
||||
# m/z: fuzzy match, returns a boolean.
|
||||
test_format "#{m/z:foo,foobar}" "1"
|
||||
test_format "#{m/z:xyz,foobar}" "0"
|
||||
# m/p: fuzzy match, returns the matched (0-based) column positions.
|
||||
test_format "#{m/p:ac,abc}" "0,2"
|
||||
test_format "#{m/p:xyz,abc}" ""
|
||||
# Fuzzy match against empty text.
|
||||
test_format "#{m/p:x,}" ""
|
||||
test_format "#{m/z:x,}" "0"
|
||||
|
||||
# String comparisons.
|
||||
test_format "#{==:#{@host},myhost}" "1"
|
||||
test_format "#{==:#{@host},other}" "0"
|
||||
test_format "#{!=:abc,xyz}" "1"
|
||||
test_format "#{!=:abc,abc}" "0"
|
||||
test_format "#{<:3,5}" "1"
|
||||
test_format "#{<:5,3}" "0"
|
||||
test_format "#{>:5,3}" "1"
|
||||
test_format "#{>:3,5}" "0"
|
||||
test_format "#{<=:5,5}" "1"
|
||||
test_format "#{<=:6,5}" "0"
|
||||
test_format "#{>=:5,5}" "1"
|
||||
test_format "#{>=:4,5}" "0"
|
||||
|
||||
# Negation and canonical boolean.
|
||||
test_format "#{!:0}" "1"
|
||||
test_format "#{!:1}" "0"
|
||||
test_format "#{!!:}" "0"
|
||||
test_format "#{!!:0}" "0"
|
||||
test_format "#{!!:non-empty}" "1"
|
||||
|
||||
|
||||
# --- Quoting (q) ---------------------------------------------------------
|
||||
|
||||
# q: escapes shell special characters with a backslash.
|
||||
test_format "#{q:@sp}" 'a\ b\$c'
|
||||
# q/s quotes with POSIX shell single quotes.
|
||||
test_format "#{q/s:@sp}" "'a b\$c'"
|
||||
test_format "#{q/s:@sq}" "'a'\\''b'"
|
||||
test_format "#{q/s:@nl}" "$q_s_nl"
|
||||
# q/e and q/h escape "#" for the format/style parser by doubling it.
|
||||
test_format "#{q/e:@hash}" 'a##b'
|
||||
test_format "#{q/h:@hash}" 'a##b'
|
||||
# q/a quotes the value as a single shell argument.
|
||||
test_format "#{q/a:@sp}" '"a b\$c"'
|
||||
|
||||
|
||||
# --- Name existence (N) --------------------------------------------------
|
||||
|
||||
# N/w is true if a window with the (expanded) name exists in the session, N/s
|
||||
# if a session with that name exists. The default (no argument) is /w.
|
||||
$TMUX rename-window -t main:0 knownwin
|
||||
test_format "#{N/s:main}" "1"
|
||||
test_format "#{N/s:nosuchsession}" "0"
|
||||
test_format "#{N/w:knownwin}" "1" "main:"
|
||||
test_format "#{N/w:nosuchwindow}" "0" "main:"
|
||||
test_format "#{N:nosuchwindow}" "0" "main:"
|
||||
|
||||
|
||||
# --- Numeric operations (e) ----------------------------------------------
|
||||
|
||||
# Integer operators.
|
||||
test_format "#{e|+|:2,3}" "5"
|
||||
test_format "#{e|-|:10,4}" "6"
|
||||
test_format "#{e|-|:2,5}" "-3"
|
||||
test_format "#{e|*|:6,7}" "42"
|
||||
test_format "#{e|/|:20,4}" "5"
|
||||
# Modulus - both spellings (% must be doubled as it is a strftime specifier).
|
||||
test_format "#{e|m|:7,3}" "1"
|
||||
test_format "#{e|%%|:7,3}" "1"
|
||||
|
||||
# Numeric comparison operators.
|
||||
test_format "#{e|==|:5,5}" "1"
|
||||
test_format "#{e|!=|:5,5}" "0"
|
||||
test_format "#{e|<|:2,5}" "1"
|
||||
test_format "#{e|>|:9,2}" "1"
|
||||
test_format "#{e|<=|:5,5}" "1"
|
||||
test_format "#{e|>=|:5,5}" "1"
|
||||
|
||||
# Floating point with a decimal-place count.
|
||||
test_format "#{e|*|f|4:5.5,3}" "16.5000"
|
||||
test_format "#{e|/|f|3:1,3}" "0.333"
|
||||
test_format "#{e|/|f|2:10,3}" "3.33"
|
||||
# Default number of decimal places for float is two.
|
||||
test_format "#{e|*|f:2.5,2}" "5.00"
|
||||
|
||||
# Division by zero must not crash the server (result is unspecified).
|
||||
$TMUX display-message -p "#{e|/|:5,0}" >/dev/null 2>&1
|
||||
$TMUX display-message -p "#{e|/|f:5,0}" >/dev/null 2>&1
|
||||
assert_alive "division by zero"
|
||||
|
||||
|
||||
# --- ASCII and repeat ----------------------------------------------------
|
||||
|
||||
# a: numeric value to its ASCII character.
|
||||
test_format "#{a:98}" "b"
|
||||
test_format "#{a:65}" "A"
|
||||
# a: out-of-range or non-numeric input yields an empty string.
|
||||
test_format "#{a:200}" ""
|
||||
test_format "#{a:notanumber}" ""
|
||||
# R: repeat first argument second-argument times.
|
||||
test_format "#{R:a,3}" "aaa"
|
||||
test_format "#{R:ab,2}" "abab"
|
||||
# A long repeat exercises output-buffer growth during expansion.
|
||||
test_format "#{n:#{R:x,300}}" "300"
|
||||
|
||||
|
||||
# --- Width, padding and truncation ---------------------------------------
|
||||
|
||||
# =N truncates from the start, =-N from the end.
|
||||
test_format "#{=5:@s}" "abcde"
|
||||
test_format "#{=-5:@s}" "fghij"
|
||||
# = with no width, or a non-numeric width, does not truncate.
|
||||
test_format "#{=:@s}" "abcdefghij"
|
||||
test_format "#{=/x:@s}" "abcdefghij"
|
||||
# A marker is appended/prepended only when trimming actually occurs.
|
||||
test_format "#{=/5/...:@s}" "abcde..."
|
||||
test_format "#{=/5/...:@name}" "windo..."
|
||||
test_format "#{=/20/...:@s}" "abcdefghij"
|
||||
# Truncation is display-width (UTF-8) aware: a wide (2-column) character is only
|
||||
# included if it fits entirely within the limit.
|
||||
test_format "#{=3:@greek}" "αβγ"
|
||||
test_format "#{=2:@greek}" "αβ"
|
||||
test_format "#{=2:@cjk}" "中"
|
||||
test_format "#{=1:@cjk}" ""
|
||||
# Markers with wide characters: the marker is added when trimming occurs, and a
|
||||
# limit that splits a wide character drops it entirely.
|
||||
test_format "#{=/2/x:@cjk}" "中x"
|
||||
test_format "#{=/1/x:@cjk}" "x"
|
||||
|
||||
# p pads to a width: a positive width left-aligns (pads on the right), a
|
||||
# negative width right-aligns (pads on the left).
|
||||
test_format "#{p12:@name}" "window-name "
|
||||
test_format "#{p-12:@name}" " window-name"
|
||||
# No padding once the value already meets the width.
|
||||
test_format "#{p3:@name}" "window-name"
|
||||
# p with no width does nothing.
|
||||
test_format "#{p:@name}" "window-name"
|
||||
# Padding is display-width aware: @cjk is 4 columns wide, so p6/p-6 add two
|
||||
# spaces (not four).
|
||||
test_format "#{p6:@cjk}" "中文 "
|
||||
test_format "#{p-6:@cjk}" " 中文"
|
||||
|
||||
# n is byte length, w is display width.
|
||||
test_format "#{n:@s}" "10"
|
||||
test_format "#{w:@s}" "10"
|
||||
test_format "#{n:@greek}" "6"
|
||||
test_format "#{w:@greek}" "3"
|
||||
test_format "#{n:@cjk}" "6"
|
||||
test_format "#{w:@cjk}" "4"
|
||||
|
||||
|
||||
# --- basename and dirname ------------------------------------------------
|
||||
|
||||
test_format "#{b:@path}" "foo"
|
||||
test_format "#{d:@path}" "/usr/local/bin"
|
||||
|
||||
|
||||
# --- Time conversion -----------------------------------------------------
|
||||
|
||||
# t: converts an integer time to a ctime(3) string.
|
||||
test_format "#{t:@ts}" "Sun Sep 9 01:46:40 2001"
|
||||
# t/p: shorter format for times in the past.
|
||||
test_format "#{t/p:@ts}" "Sep01"
|
||||
# t/r: relative time depends on the current time, just check it is non-empty.
|
||||
if [ -z "$($TMUX display-message -p '#{t/r:@ts}')" ]; then
|
||||
echo "Format test failed for '#{t/r:@ts}': empty result"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# t/f: custom strftime format applied to the variable's time. Tested in a
|
||||
# format_expand context (list-windows -F), where a single strftime specifier is
|
||||
# applied directly. (In display-message, which additionally expands the format
|
||||
# through strftime, these would need to be doubled - %%Y etc.) The colon in the
|
||||
# format is escaped as '#:' because it is otherwise the modifier separator.
|
||||
test_expand "#{t/f/%Y:@ts}" "2001"
|
||||
test_expand "#{t/f/%Y-%m-%d:@ts}" "2001-09-09"
|
||||
test_expand "#{t/f/%H#:%M#:%S:@ts}" "01:46:40"
|
||||
# An escaped comma in the custom format is unescaped before strftime.
|
||||
test_expand "#{t/f/%Y#,end:@ts}" "2001,end"
|
||||
|
||||
# T: expands its argument and then runs the result through strftime with the
|
||||
# current time. A value with no strftime specifier is returned unchanged.
|
||||
test_format "#{T:@ts}" "1000000000"
|
||||
|
||||
# t/p (pretty) and t/r (relative) format times by age relative to now, with a
|
||||
# different branch per age band. Build options a known number of seconds in the
|
||||
# past and check each yields a non-empty result (the exact text depends on the
|
||||
# wall clock, so only non-emptiness is asserted).
|
||||
now=$(date +%s)
|
||||
for age in 30 300 4000 90000 200000 3000000 40000000; do
|
||||
$TMUX set -g @age "$((now - age))"
|
||||
if [ -z "$($TMUX display-message -p '#{t/r:@age}')" ]; then
|
||||
echo "Empty #{t/r:@age} for age ${age}s"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$($TMUX display-message -p '#{t/p:@age}')" ]; then
|
||||
echo "Empty #{t/p:@age} for age ${age}s"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# A time in the future has no relative form.
|
||||
$TMUX set -g @future "$((now + 100000))"
|
||||
test_format "#{t/r:@future}" ""
|
||||
|
||||
|
||||
# --- Content search (C) --------------------------------------------------
|
||||
|
||||
# Use a window running cat so the content is deterministic (no shell prompt).
|
||||
$TMUX new-session -d -s search -x 80 -y 10 'cat'
|
||||
sleep 1
|
||||
$TMUX send-keys -t search: 'Zebra_Marker_42' Enter
|
||||
sleep 1
|
||||
# C: returns the (1-based) line number of a match or 0 if not found.
|
||||
test_format "#{C:Zebra_Marker_42}" "1" "search:"
|
||||
test_format "#{C:Absent_String_999}" "0" "search:"
|
||||
test_format "#{C/r:Zebra_.*_42}" "1" "search:"
|
||||
test_format "#{C/i:zebra_marker_42}" "1" "search:"
|
||||
$TMUX kill-session -t search 2>/dev/null
|
||||
|
||||
|
||||
# --- Colour (c) ----------------------------------------------------------
|
||||
|
||||
# c: converts a colour to its six-digit hexadecimal RGB value.
|
||||
test_format "#{c:red}" "800000"
|
||||
test_format "#{c:colour4}" "000080"
|
||||
test_format "#{c:#7f7f7f}" "7f7f7f"
|
||||
# c/f and c/b produce the SGR escape sequence for fg/bg respectively.
|
||||
test_format "#{c/f:red}" "${ESC}[31m"
|
||||
test_format "#{c/b:red}" "${ESC}[41m"
|
||||
test_format "#{c/b:colour4}" "${ESC}[48;5;4m"
|
||||
# "none" gives a reset; an unknown colour gives an empty string.
|
||||
test_format "#{c/f:none}" "${ESC}[0m"
|
||||
test_format "#{c:notacolour}" ""
|
||||
test_format "#{c/f:notacolour}" ""
|
||||
|
||||
|
||||
# --- Nesting and limits --------------------------------------------------
|
||||
|
||||
# Modifier chaining: inner b: then outer truncation/padding/length.
|
||||
test_format "#{=5:#{b:@path}}" "foo"
|
||||
test_format "#{=2:#{b:@path}}" "fo"
|
||||
test_format "#{p6:#{b:@path}}" "foo "
|
||||
test_format "#{n:#{b:@path}}" "3"
|
||||
|
||||
# Nested l: literal expanded then truncated.
|
||||
test_format "#{=5:#{l:abcdefghij}}" "abcde"
|
||||
|
||||
# Deeper nesting: basename -> pad to 10 -> truncate to 5.
|
||||
test_format "#{=5:#{p10:#{b:@path}}}" "foo "
|
||||
# A substitution applied to a nested basename.
|
||||
test_format "#{s/o/O/:#{b:@path}}" "fOO"
|
||||
|
||||
# Unbounded self-recursion must hit the loop limit rather than crash.
|
||||
$TMUX set @rec '#{E:@rec}'
|
||||
$TMUX display-message -p '#{E:@rec}' >/dev/null 2>&1
|
||||
assert_alive "recursive expansion"
|
||||
|
||||
|
||||
# --- Missing, malformed and limit inputs ---------------------------------
|
||||
|
||||
# An undefined variable expands to empty; modifiers on it behave sensibly.
|
||||
test_format "#{@undefined}" ""
|
||||
test_format "#{=5:@undefined}" ""
|
||||
test_format "#{b:@undefined}" ""
|
||||
test_format "#{n:@undefined}" "0"
|
||||
|
||||
# Malformed numeric expressions expand to empty rather than erroring out.
|
||||
test_format "#{e|+|:notanumber,2}" "" # invalid left operand
|
||||
test_format "#{e|+|:2,notanumber}" "" # invalid right operand
|
||||
test_format "#{e|badop|:1,2}" "" # unknown operator
|
||||
test_format "#{e|+|f|x:1,2}" "" # invalid precision
|
||||
test_format "#{e|+|:1}" "" # too few operands
|
||||
test_format "#{e|+|f|2|extra:1,2}" "" # too many arguments (limit is 3)
|
||||
|
||||
# Repeat with a non-numeric or zero count yields an empty string.
|
||||
test_format "#{R:a,notanumber}" ""
|
||||
test_format "#{R:a,0}" ""
|
||||
|
||||
# Comparisons with too few arguments expand to empty.
|
||||
test_format "#{==:a}" ""
|
||||
test_format "#{<:a}" ""
|
||||
|
||||
# A substitution with fewer than two arguments is a no-op.
|
||||
test_format "#{s/onlyone:@s}" "abcdefghij"
|
||||
|
||||
# A non-numeric width for = or p is treated as no width (no change).
|
||||
test_format "#{=/x:@s}" "abcdefghij"
|
||||
test_format "#{p/x:@s}" "abcdefghij"
|
||||
|
||||
# The I (client terminal) modifier with no attached client is empty; this also
|
||||
# exercises its argument parsing (/c termcap, /f feature, default). The
|
||||
# non-empty terminal cases are covered with a real client in format-variables.sh.
|
||||
test_format "#{I/c:RGB}" ""
|
||||
test_format "#{I/f:overline}" ""
|
||||
test_format "#{I:x}" ""
|
||||
|
||||
|
||||
# --- Escaping inside modifiers -------------------------------------------
|
||||
|
||||
# A "," or "#" inside a modifier argument is escaped with "#".
|
||||
test_format "#{s/#,/-/:#{l:a,b,c}}" "a-b-c" # escaped comma in the pattern
|
||||
test_format "#{=/3/#,:@s}" "abc," # escaped comma in the marker
|
||||
# The truncation marker is itself expanded as a format.
|
||||
test_format "#{=/3/#{l:>}:@s}" "abc>"
|
||||
|
||||
# Substitution flags: a third argument of "i" is case-insensitive; an invalid
|
||||
# regular expression leaves the text unchanged.
|
||||
test_format "#{s/A/X/i:@s}" "Xbcdefghij"
|
||||
test_format "#{s/[/X/:@s}" "abcdefghij"
|
||||
|
||||
|
||||
# --- Unicode in modifier arguments ---------------------------------------
|
||||
|
||||
# Wide (CJK) and emoji text: matching, substitution, repeat and markers all
|
||||
# operate on characters, and n/w report bytes/columns.
|
||||
$TMUX set -g @emoji '😀😀' || exit 1 # 8 bytes, 4 columns
|
||||
test_format "#{m:*中*,#{@cjk}}" "1"
|
||||
test_format "#{s/文/X/:@cjk}" "中X"
|
||||
test_format "#{R:中,3}" "中中中"
|
||||
test_format "#{=/1/中:@s}" "a中"
|
||||
test_format "#{w:@emoji}" "4"
|
||||
test_format "#{n:@emoji}" "8"
|
||||
test_format "#{=2:@emoji}" "😀"
|
||||
|
||||
|
||||
# --- Server messages (show-messages) -------------------------------------
|
||||
|
||||
# show-messages formats each logged message (this exercises the message-time
|
||||
# formatting path); just check the server survives producing it.
|
||||
$TMUX show-messages >/dev/null 2>&1
|
||||
assert_alive "show-messages"
|
||||
|
||||
|
||||
# --- Verbose expansion (logging) -----------------------------------------
|
||||
|
||||
# display-message -v turns on format logging, so re-expanding a representative
|
||||
# set of formats with -v exercises the logging code paths. Only survival is
|
||||
# checked; the log text itself is not asserted.
|
||||
for f in \
|
||||
'#{=3:@s}' \
|
||||
'#{e|+|:2,3}' \
|
||||
'#{e|*|f|2:2.5,2}' \
|
||||
'#{m:*a*,abc}' \
|
||||
'#{<:3,5}' \
|
||||
'#{s/a/X/:@s}' \
|
||||
'#{b:@path}' \
|
||||
'#{t:@ts}' \
|
||||
'#{p6:@name}' \
|
||||
'#{=3:#{b:@path}}'; do
|
||||
$TMUX display-message -v -p "$f" >/dev/null 2>&1
|
||||
done
|
||||
assert_alive "verbose expansion"
|
||||
|
||||
|
||||
# --- Loops and sorting (S, W, P, L) --------------------------------------
|
||||
#
|
||||
# These need a fully controlled server so the set of sessions, windows and
|
||||
# panes (and their order) is known, so start from a clean server. This must be
|
||||
# the last section as it discards the setup above.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
|
||||
# Sessions, created in this order, so session ids (and hence creation order)
|
||||
# are zeta=$0, alpha=$1, mike=$2.
|
||||
$TMUX new-session -d -s zeta -x 80 -y 24 || exit 1
|
||||
$TMUX new-session -d -s alpha || exit 1
|
||||
$TMUX new-session -d -s mike || exit 1
|
||||
$TMUX set -g automatic-rename off
|
||||
|
||||
# S loops over every session. The default order is by session id (SORT_INDEX),
|
||||
# /i is the same, /n is by name, and the r suffix reverses.
|
||||
test_format "#{S:#{session_name} }" "zeta alpha mike "
|
||||
test_format "#{S/i:#{session_name} }" "zeta alpha mike "
|
||||
test_format "#{S/n:#{session_name} }" "alpha mike zeta "
|
||||
test_format "#{S/nr:#{session_name} }" "zeta mike alpha "
|
||||
test_format "#{S/ir:#{session_name} }" "mike alpha zeta "
|
||||
# /t sorts by activity time; the exact order is timing-dependent, so just check
|
||||
# every session is still iterated (this exercises the activity-sort branch).
|
||||
test_format "#{S/t:x}" "xxx"
|
||||
# An unrecognised sort letter falls back to the default order; /r on its own
|
||||
# reverses that default (this covers the fall-through branch).
|
||||
test_format "#{S/r:#{session_name} }" "mike alpha zeta "
|
||||
|
||||
# Windows in session zeta: window 0 renamed charlie, then alpha at 1, bravo at
|
||||
# 2. The default order is by index (SORT_ORDER), /n is by name, r reverses.
|
||||
$TMUX rename-window -t zeta:0 charlie
|
||||
$TMUX new-window -d -t zeta:1 -n alpha
|
||||
$TMUX new-window -d -t zeta:2 -n bravo
|
||||
test_format "#{W:#{window_name} }" "charlie alpha bravo " "zeta:"
|
||||
test_format "#{W/n:#{window_name} }" "alpha bravo charlie " "zeta:"
|
||||
test_format "#{W/nr:#{window_name} }" "charlie bravo alpha " "zeta:"
|
||||
test_format "#{W/ir:#{window_index}}" "210" "zeta:"
|
||||
# /i (by index) and /t (by activity); /i matches the default order here.
|
||||
test_format "#{W/i:#{window_name} }" "charlie alpha bravo " "zeta:"
|
||||
test_format "#{W/t:x}" "xxx" "zeta:"
|
||||
# An unrecognised sort letter falls back to the default order; /r reverses it.
|
||||
test_format "#{W/r:#{window_name} }" "bravo alpha charlie " "zeta:"
|
||||
|
||||
# Panes in window zeta:charlie. Splitting the active (newest) pane each time
|
||||
# makes pane index match creation order (0,1,2 left to right). The default
|
||||
# order is by creation (SORT_CREATION), r reverses.
|
||||
$TMUX split-window -h -t zeta:charlie
|
||||
$TMUX split-window -h -t zeta:charlie
|
||||
test_format "#{P:#{pane_index}}" "012" "zeta:charlie"
|
||||
test_format "#{P/r:#{pane_index}}" "210" "zeta:charlie"
|
||||
# Pane sort accepts i (pane-list order) and z (z-order). Other sort letters
|
||||
# fall back to the default creation order; r reverses whichever order is used.
|
||||
test_format "#{P/i:x}" "xxx" "zeta:charlie"
|
||||
test_format "#{P/i:#{pane_index}}" "012" "zeta:charlie"
|
||||
test_format "#{P/z:x}" "xxx" "zeta:charlie"
|
||||
test_format "#{P/n:x}" "xxx" "zeta:charlie"
|
||||
test_format "#{P/t:x}" "xxx" "zeta:charlie"
|
||||
$TMUX new-pane -d -t zeta:charlie -x 20 -y 10 -X 1 -Y 1
|
||||
test_format "#{P/i:#{pane_index}}" "0123" "zeta:charlie"
|
||||
test_format "#{P/z:#{pane_index}}" "3012" "zeta:charlie"
|
||||
test_format "#{P/zr:#{pane_index}}" "0123" "zeta:charlie"
|
||||
|
||||
# Verbose expansion of the loops, to exercise their logging paths.
|
||||
$TMUX display-message -v -p "#{S:#{session_name}}" >/dev/null 2>&1
|
||||
$TMUX display-message -v -t zeta: -p "#{W:#{window_name}}" >/dev/null 2>&1
|
||||
$TMUX display-message -v -t zeta:charlie -p "#{P:#{pane_index}}" >/dev/null 2>&1
|
||||
assert_alive "verbose loop expansion"
|
||||
|
||||
# L loops over attached clients. Attach two control-mode clients, each held
|
||||
# open by a background process keeping a FIFO's write end open.
|
||||
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
||||
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
mkfifo "$FIFO1" "$FIFO2" || exit 1
|
||||
# Hold the write ends open so the control clients stay attached.
|
||||
sleep 30 >"$FIFO1" &
|
||||
HOLD1=$!
|
||||
sleep 30 >"$FIFO2" &
|
||||
HOLD2=$!
|
||||
$TMUX -C attach -t zeta <"$FIFO1" >/dev/null 2>&1 &
|
||||
CC1=$!
|
||||
$TMUX -C attach -t alpha <"$FIFO2" >/dev/null 2>&1 &
|
||||
CC2=$!
|
||||
sleep 1
|
||||
# Two clients attached: L emits one item per client.
|
||||
test_format "#{L:x}" "xx"
|
||||
# The client sort orders (default, index, name, activity, reversed) are all
|
||||
# accepted; assert only the count so the test does not depend on client names or
|
||||
# timing.
|
||||
test_format "#{L/i:x}" "xx"
|
||||
test_format "#{L/n:x}" "xx"
|
||||
test_format "#{L/t:x}" "xx"
|
||||
test_format "#{L/nr:x}" "xx"
|
||||
test_format "#{L/r:x}" "xx"
|
||||
# Now detach one and confirm the count drops to one.
|
||||
kill $HOLD2 2>/dev/null
|
||||
sleep 1
|
||||
test_format "#{L:x}" "x"
|
||||
kill $HOLD1 $CC1 $CC2 2>/dev/null
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
|
||||
exit 0
|
||||
143
regress/format-mouse.sh
Normal file
143
regress/format-mouse.sh
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the mouse format variables (mouse_x, mouse_y, mouse_word,
|
||||
# mouse_line, ...). These are only populated while a mouse key binding is being
|
||||
# dispatched, so the test drives a real mouse event:
|
||||
#
|
||||
# - an inner client is attached inside a pane of a second ("outer") tmux
|
||||
# server, giving the inner server a genuine terminal;
|
||||
# - mouse mode is on and a MouseDown1Pane binding records the mouse format
|
||||
# variables into an option;
|
||||
# - an SGR mouse sequence is written to the outer pane, so the inner client
|
||||
# receives it as a real mouse click.
|
||||
#
|
||||
# This exercises the mouse callbacks and the grid word/line lookup code that
|
||||
# display-message cannot otherwise reach.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# click COL ROW
|
||||
#
|
||||
# Write an SGR mouse press then release (button 0) at 1-based COL/ROW 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
|
||||
|
||||
# Inner session with a single pane running cat, so its content is exactly what
|
||||
# we send it.
|
||||
$TMUX new-session -d -s cov -x 80 -y 24 'cat' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
sleep 1
|
||||
$TMUX send-keys -t cov:0.0 'alpha beta gamma' Enter
|
||||
sleep 1
|
||||
|
||||
# Record every pane mouse variable when the pane is clicked.
|
||||
$TMUX bind -n MouseDown1Pane run-shell \
|
||||
"$TMUX set -g @m 'x=#{mouse_x} y=#{mouse_y} word=#{mouse_word} line=#{mouse_line} pane=#{mouse_pane} hl=[#{mouse_hyperlink}]'"
|
||||
|
||||
# Attach a real client inside an outer tmux pane. Clicks all target the first
|
||||
# row, which lines up with the inner client regardless of the outer status line.
|
||||
$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t cov" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Click column 3, row 1: over the first word ("alpha") of the first line.
|
||||
click 3 1
|
||||
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
[ -n "$M" ] || fail "Mouse binding did not fire (no @m)."
|
||||
|
||||
# mouse_x is 0-based column (SGR column 3 -> x 2); mouse_y is 0-based row 0.
|
||||
case "$M" in
|
||||
*"x=2 "*) ;;
|
||||
*) fail "Unexpected mouse_x in: $M" ;;
|
||||
esac
|
||||
case "$M" in
|
||||
*"y=0 "*) ;;
|
||||
*) fail "Unexpected mouse_y in: $M" ;;
|
||||
esac
|
||||
# mouse_word is the word under the cursor, mouse_line the whole line.
|
||||
case "$M" in
|
||||
*"word=alpha "*) ;;
|
||||
*) fail "Unexpected mouse_word in: $M" ;;
|
||||
esac
|
||||
case "$M" in
|
||||
*"line=alpha beta gamma "*) ;;
|
||||
*) fail "Unexpected mouse_line in: $M" ;;
|
||||
esac
|
||||
|
||||
# A click in a different column selects a different word.
|
||||
click 8 1
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
case "$M" in
|
||||
*"word=beta "*) ;;
|
||||
*) fail "Unexpected mouse_word for second click in: $M" ;;
|
||||
esac
|
||||
|
||||
# The same variables have a separate path when the pane is in a mode (the word
|
||||
# and line come from the mode, not the live grid). A binding in the copy-mode
|
||||
# key table fires while copy mode is active.
|
||||
$TMUX bind -T copy-mode MouseDown1Pane run-shell \
|
||||
"$TMUX set -g @cm 'x=#{mouse_x} word=#{mouse_word} line=#{mouse_line}'"
|
||||
$TMUX copy-mode -t cov:0.0
|
||||
sleep 1
|
||||
click 8 1
|
||||
CM=$($TMUX show -gv @cm 2>/dev/null)
|
||||
case "$CM" in
|
||||
*"word=beta"*) ;;
|
||||
*) fail "Unexpected copy-mode mouse_word in: $CM" ;;
|
||||
esac
|
||||
$TMUX send-keys -t cov:0.0 -X cancel
|
||||
sleep 1
|
||||
|
||||
# Hyperlinks: a new window whose pane emits an OSC 8 hyperlink over the text
|
||||
# "LINKED". Clicking it reports the target URL via mouse_hyperlink (this drives
|
||||
# the grid hyperlink lookup). The emitter is written to a small script to keep
|
||||
# the escape sequence readable.
|
||||
LINKSH="${TMPDIR:-/tmp}/fmt-mouse-link-$$.sh"
|
||||
cat >"$LINKSH" <<'EOF'
|
||||
#!/bin/sh
|
||||
printf '\033]8;;http://example.com\033\\LINKED\033]8;;\033\\\n'
|
||||
exec cat
|
||||
EOF
|
||||
chmod +x "$LINKSH"
|
||||
$TMUX neww -t cov: -n link "$LINKSH"
|
||||
sleep 1
|
||||
$TMUX select-window -t cov:link
|
||||
sleep 1
|
||||
click 3 1
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
rm -f "$LINKSH"
|
||||
case "$M" in
|
||||
*"hl=[http://example.com]"*) ;;
|
||||
*) fail "Unexpected mouse_hyperlink in: $M" ;;
|
||||
esac
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
295
regress/format-render-contexts.sh
Normal file
295
regress/format-render-contexts.sh
Normal file
@@ -0,0 +1,295 @@
|
||||
#!/bin/sh
|
||||
# Exercise format and style rendering in live contexts.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LANG=C.UTF-8
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LANG LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMUX="$TEST_TMUX -Lformat-render-contexts-$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Lformat-render-contexts-outer-$$ -f/dev/null"
|
||||
LIMIT=20000
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_cmd()
|
||||
{
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout 10 "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
bounded()
|
||||
{
|
||||
name=$1
|
||||
text=$2
|
||||
|
||||
n=$(printf '%s' "$text" | wc -c)
|
||||
[ "$n" -le "$LIMIT" ] || fail "$name too large: $n bytes"
|
||||
}
|
||||
|
||||
tmux_run()
|
||||
{
|
||||
name=$1
|
||||
shift
|
||||
|
||||
out=$(run_cmd $TMUX "$@" 2>&1)
|
||||
rc=$?
|
||||
bounded "$name" "$out"
|
||||
[ "$rc" -eq 0 ] || fail "$name failed: $out"
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
capture()
|
||||
{
|
||||
out=$(run_cmd $TMUX2 capture-pane -p -t out:0 2>/dev/null)
|
||||
rc=$?
|
||||
bounded "capture-pane" "$out"
|
||||
[ "$rc" -eq 0 ] || fail "capture-pane failed"
|
||||
printf '%s\n' "$out"
|
||||
}
|
||||
|
||||
capture_esc()
|
||||
{
|
||||
out=$(run_cmd $TMUX2 capture-pane -pe -t out:0 2>/dev/null)
|
||||
rc=$?
|
||||
bounded "capture-pane -e" "$out"
|
||||
[ "$rc" -eq 0 ] || fail "capture-pane -e failed"
|
||||
printf '%s\n' "$out"
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
tmux_run "server alive ($1)" display-message -p alive >/dev/null
|
||||
}
|
||||
|
||||
wait_for()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
if capture | grep -F "$marker" >/dev/null 2>&1; then
|
||||
sleep 0.1
|
||||
return 0
|
||||
fi
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for $marker"
|
||||
}
|
||||
|
||||
render_status_options()
|
||||
{
|
||||
label=$1
|
||||
fmt=$2
|
||||
|
||||
tmux_run "$label status-left" \
|
||||
set-option -g status-left "SL$label: $fmt" >/dev/null
|
||||
tmux_run "$label clear status-right" \
|
||||
set-option -g status-right '' >/dev/null
|
||||
tmux_run "$label status-format left" \
|
||||
set-option -g status-format[0] '#{E:status-left}' >/dev/null
|
||||
tmux_run "$label refresh status-left" refresh-client -S >/dev/null
|
||||
wait_for "SL$label:"
|
||||
|
||||
tmux_run "$label clear status-left" \
|
||||
set-option -g status-left '' >/dev/null
|
||||
tmux_run "$label status-right" \
|
||||
set-option -g status-right "SR$label: $fmt" >/dev/null
|
||||
tmux_run "$label status-format right" \
|
||||
set-option -g status-format[0] \
|
||||
'#[align=right]#{E:status-right}' >/dev/null
|
||||
tmux_run "$label refresh status-right" refresh-client -S >/dev/null
|
||||
wait_for "SR$label:"
|
||||
assert_alive "$label status-left/right"
|
||||
}
|
||||
|
||||
render_status_format()
|
||||
{
|
||||
label=$1
|
||||
fmt=$2
|
||||
|
||||
tmux_run "$label status-format" \
|
||||
set-option -g status-format[0] "SF$label: $fmt" >/dev/null
|
||||
tmux_run "$label refresh status-format" refresh-client -S >/dev/null
|
||||
wait_for "SF$label:"
|
||||
assert_alive "$label status-format"
|
||||
}
|
||||
|
||||
render_message()
|
||||
{
|
||||
label=$1
|
||||
fmt=$2
|
||||
|
||||
tmux_run "$label display-message" \
|
||||
display-message -t fmt:0 -d 1000 "DM$label: $fmt" >/dev/null
|
||||
wait_for "DM$label:"
|
||||
assert_alive "$label display-message"
|
||||
}
|
||||
|
||||
render_choose_tree()
|
||||
{
|
||||
label=$1
|
||||
fmt=$2
|
||||
|
||||
tmux_run "$label choose-tree" \
|
||||
choose-tree -t fmt:0 -F "CT$label: $fmt" >/dev/null
|
||||
wait_for "CT$label:"
|
||||
tmux_run "$label quit choose-tree" send-keys -t fmt:0 q >/dev/null
|
||||
assert_alive "$label choose-tree"
|
||||
}
|
||||
|
||||
render_customize()
|
||||
{
|
||||
label=$1
|
||||
fmt=$2
|
||||
|
||||
tmux_run "$label customize-mode" \
|
||||
customize-mode -t fmt:0 -F "CM$label: $fmt" >/dev/null
|
||||
sleep 0.5
|
||||
out=$(capture)
|
||||
bounded "$label customize-mode capture" "$out"
|
||||
tmux_run "$label quit customize-mode" send-keys -t fmt:0 q >/dev/null
|
||||
assert_alive "$label customize-mode"
|
||||
}
|
||||
|
||||
render_list_output()
|
||||
{
|
||||
label=$1
|
||||
fmt=$2
|
||||
|
||||
tmux_run "$label bind-key" \
|
||||
bind-key -T root F12 display-message "LK$label: $fmt" >/dev/null
|
||||
out=$(tmux_run "$label list-keys" list-keys -T root F12)
|
||||
bounded "$label list-keys" "$out"
|
||||
printf '%s' "$out" | grep -F "LK$label:" >/dev/null 2>&1 ||
|
||||
fail "$label list-keys missing rendered command"
|
||||
|
||||
tmux_run "$label set show option" \
|
||||
set-option -g "@format_render_$label" "SO$label: $fmt" >/dev/null
|
||||
out=$(tmux_run "$label show-options" show-options -g "@format_render_$label")
|
||||
bounded "$label show-options" "$out"
|
||||
printf '%s' "$out" | grep -F "SO$label:" >/dev/null 2>&1 ||
|
||||
fail "$label show-options missing option"
|
||||
|
||||
assert_alive "$label list output"
|
||||
}
|
||||
|
||||
run_corpus()
|
||||
{
|
||||
label=$1
|
||||
fmt=$2
|
||||
|
||||
render_status_options "$label" "$fmt"
|
||||
render_status_format "$label" "$fmt"
|
||||
render_message "$label" "$fmt"
|
||||
render_choose_tree "$label" "$fmt"
|
||||
render_customize "$label" "$fmt"
|
||||
render_list_output "$label" "$fmt"
|
||||
}
|
||||
|
||||
check_sgr_sanity()
|
||||
{
|
||||
esc=$(printf '\033')
|
||||
|
||||
tmux_run "set message style fill" \
|
||||
set-option -g message-style \
|
||||
"fg=#080808,bg=#ffff00,fill=#ffff00,bold" >/dev/null
|
||||
tmux_run "show filled message" \
|
||||
display-message -t fmt:0 -d 1000 \
|
||||
'#[bg=blue,italics] hello, #[fg=#080808,bg=default] world!' \
|
||||
>/dev/null
|
||||
wait_for "hello, world!"
|
||||
|
||||
out=$(capture_esc)
|
||||
printf '%s' "$out" | grep -F 'world!' >/dev/null 2>&1 ||
|
||||
fail "message text missing from SGR capture"
|
||||
printf '%s' "$out" | grep "$esc" >/dev/null 2>&1 ||
|
||||
fail "SGR capture has no escapes"
|
||||
|
||||
world_sgr=$(
|
||||
printf '%s\n' "$out" |
|
||||
awk -v esc="$esc" '
|
||||
/world!/ {
|
||||
i = index($0, "world!")
|
||||
pre = substr($0, 1, i - 1)
|
||||
n = split(pre, parts, esc "\\[")
|
||||
print parts[n]
|
||||
exit
|
||||
}'
|
||||
)
|
||||
|
||||
case "$world_sgr" in
|
||||
*'48;'*) ;;
|
||||
*) fail "world segment missing explicit background SGR: $world_sgr" ;;
|
||||
esac
|
||||
case "$world_sgr" in
|
||||
*'49'*)
|
||||
fail "world segment used default background instead of message fill: $world_sgr"
|
||||
;;
|
||||
esac
|
||||
|
||||
assert_alive "message SGR sanity"
|
||||
}
|
||||
|
||||
wide_fmt()
|
||||
{
|
||||
printf 'wide: \0316\0225\0316\0273\0316\0273\0316\0267\0316\0275\0316\0271\0316\0272\0316\0254 \0344\0270\0255\0346\0226\0207 #{=12:\0344\0270\0255\0346\0226\0207abc}'
|
||||
}
|
||||
|
||||
long_style_fmt()
|
||||
{
|
||||
printf '%s' '#[fg=colour1,bg=colour2,us=colour3,acs,bright,dim,underscore,blink,reverse,hidden,italics,strikethrough,double-underscore,curly-underscore,dotted-underscore,dashed-underscore,overline,range=user|aaaaaaaaaaaaaaaa,align=absolute-centre,list=on,fill=colour200,width=4294967295,pad=4294967295]OVERLONG-STYLE#[default]'
|
||||
}
|
||||
|
||||
trap cleanup 0 1 15
|
||||
cleanup
|
||||
|
||||
tmux_run "new inner session" \
|
||||
new-session -d -s fmt -x 100 -y 30 "exec sleep 1000" >/dev/null
|
||||
tmux_run "new inner window" \
|
||||
new-window -t fmt -n second "exec sleep 1000" >/dev/null
|
||||
tmux_run "select first window" select-window -t fmt:0 >/dev/null
|
||||
tmux_run "status interval" set-option -g status-interval 1 >/dev/null
|
||||
tmux_run "base status" set-option -g status on >/dev/null
|
||||
tmux_run "nested option" \
|
||||
set-option -g @nested 'NESTED-#{session_name}-#{window_index}' >/dev/null
|
||||
|
||||
run_cmd $TMUX2 new-session -d -s out -x 100 -y 30 "$TMUX attach -t fmt" \
|
||||
>/dev/null 2>&1 || fail "failed to start outer client"
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
c=$(tmux_run "wait clients" list-clients -F x | grep -c x)
|
||||
[ "$c" -eq 1 ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "inner client did not attach"
|
||||
|
||||
run_corpus A 'plain #{session_name}:#{window_index} #{@nested}'
|
||||
run_corpus B "$(wide_fmt)"
|
||||
run_corpus C '#[fg=colour10,bg=colour17,bold,italics]styled #{pane_current_command}#[default]'
|
||||
run_corpus D '#[push-default]#[fg=red,bg=blue]push #{?pane_active,active,inactive}#[pop-default] after'
|
||||
run_corpus E "$(long_style_fmt)"
|
||||
|
||||
check_sgr_sanity
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
415
regress/format-variables.sh
Normal file
415
regress/format-variables.sh
Normal file
@@ -0,0 +1,415 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests that every format variable listed in tmux(1) (the format_table in
|
||||
# format.c) can be expanded without crashing the server, and checks the value
|
||||
# of a stable subset.
|
||||
#
|
||||
# The main point is coverage and crash-safety: each variable is expanded in a
|
||||
# rich context - a real attached client (from a nested tmux), a control-mode
|
||||
# client, a grouped session, two windows with a bell alert, a window with two
|
||||
# panes running cat, a paste buffer and options - so the per-variable callbacks
|
||||
# actually run. format-modifiers.sh covers the modifier machinery; this covers
|
||||
# the variable callbacks.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
# A second server on its own socket provides a real terminal (an inner client
|
||||
# attached inside one of its panes) so client terminal variables are populated.
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
# Every variable name in format_table[]. Kept as a plain word list so it can be
|
||||
# iterated with normal shell word splitting.
|
||||
NAMES="
|
||||
active_window_index
|
||||
alternate_on
|
||||
alternate_saved_x
|
||||
alternate_saved_y
|
||||
bracket_paste_flag
|
||||
buffer_created
|
||||
buffer_full
|
||||
buffer_mode_format
|
||||
buffer_name
|
||||
buffer_sample
|
||||
buffer_size
|
||||
client_activity
|
||||
client_cell_height
|
||||
client_cell_width
|
||||
client_colours
|
||||
client_control_mode
|
||||
client_created
|
||||
client_discarded
|
||||
client_flags
|
||||
client_height
|
||||
client_key_table
|
||||
client_last_session
|
||||
client_mode_format
|
||||
client_name
|
||||
client_pid
|
||||
client_prefix
|
||||
client_readonly
|
||||
client_session
|
||||
client_termfeatures
|
||||
client_termname
|
||||
client_termtype
|
||||
client_theme
|
||||
client_tty
|
||||
client_uid
|
||||
client_user
|
||||
client_utf8
|
||||
client_width
|
||||
client_written
|
||||
config_files
|
||||
cursor_blinking
|
||||
cursor_character
|
||||
cursor_colour
|
||||
cursor_flag
|
||||
cursor_shape
|
||||
cursor_very_visible
|
||||
cursor_x
|
||||
cursor_y
|
||||
history_all_bytes
|
||||
history_bytes
|
||||
history_limit
|
||||
history_size
|
||||
host
|
||||
host_short
|
||||
insert_flag
|
||||
keypad_cursor_flag
|
||||
keypad_flag
|
||||
last_window_index
|
||||
mouse_all_flag
|
||||
mouse_any_flag
|
||||
mouse_button_flag
|
||||
mouse_hyperlink
|
||||
mouse_line
|
||||
mouse_pane
|
||||
mouse_sgr_flag
|
||||
mouse_standard_flag
|
||||
mouse_status_line
|
||||
mouse_status_range
|
||||
mouse_utf8_flag
|
||||
mouse_word
|
||||
mouse_x
|
||||
mouse_y
|
||||
next_session_id
|
||||
origin_flag
|
||||
pane_active
|
||||
pane_at_bottom
|
||||
pane_at_left
|
||||
pane_at_right
|
||||
pane_at_top
|
||||
pane_bg
|
||||
pane_bottom
|
||||
pane_current_command
|
||||
pane_current_path
|
||||
pane_dead
|
||||
pane_dead_signal
|
||||
pane_dead_status
|
||||
pane_dead_time
|
||||
pane_fg
|
||||
pane_flags
|
||||
pane_floating_flag
|
||||
pane_format
|
||||
pane_height
|
||||
pane_id
|
||||
pane_in_mode
|
||||
pane_index
|
||||
pane_input_off
|
||||
pane_key_mode
|
||||
pane_last
|
||||
pane_left
|
||||
pane_marked
|
||||
pane_marked_set
|
||||
pane_mode
|
||||
pane_path
|
||||
pane_pb_progress
|
||||
pane_pb_state
|
||||
pane_pid
|
||||
pane_pipe
|
||||
pane_pipe_pid
|
||||
pane_right
|
||||
pane_search_string
|
||||
pane_start_command
|
||||
pane_start_command_list
|
||||
pane_start_path
|
||||
pane_synchronized
|
||||
pane_tabs
|
||||
pane_title
|
||||
pane_top
|
||||
pane_tty
|
||||
pane_unseen_changes
|
||||
pane_width
|
||||
pane_x
|
||||
pane_y
|
||||
pane_z
|
||||
pane_zoomed_flag
|
||||
pid
|
||||
scroll_region_lower
|
||||
scroll_region_upper
|
||||
server_sessions
|
||||
session_active
|
||||
session_activity
|
||||
session_activity_flag
|
||||
session_alert
|
||||
session_alerts
|
||||
session_attached
|
||||
session_attached_list
|
||||
session_bell_flag
|
||||
session_created
|
||||
session_format
|
||||
session_group
|
||||
session_group_attached
|
||||
session_group_attached_list
|
||||
session_group_list
|
||||
session_group_many_attached
|
||||
session_group_size
|
||||
session_grouped
|
||||
session_id
|
||||
session_last_attached
|
||||
session_many_attached
|
||||
session_marked
|
||||
session_name
|
||||
session_path
|
||||
session_silence_flag
|
||||
session_stack
|
||||
session_windows
|
||||
sixel_support
|
||||
socket_path
|
||||
start_time
|
||||
synchronized_output_flag
|
||||
tree_mode_format
|
||||
uid
|
||||
user
|
||||
version
|
||||
window_active
|
||||
window_active_clients
|
||||
window_active_clients_list
|
||||
window_active_sessions
|
||||
window_active_sessions_list
|
||||
window_activity
|
||||
window_activity_flag
|
||||
window_bell_flag
|
||||
window_bigger
|
||||
window_cell_height
|
||||
window_cell_width
|
||||
window_end_flag
|
||||
window_flags
|
||||
window_format
|
||||
window_height
|
||||
window_id
|
||||
window_index
|
||||
window_last_flag
|
||||
window_layout
|
||||
window_linked
|
||||
window_linked_sessions
|
||||
window_linked_sessions_list
|
||||
window_marked_flag
|
||||
window_name
|
||||
window_offset_x
|
||||
window_offset_y
|
||||
window_panes
|
||||
window_raw_flags
|
||||
window_silence_flag
|
||||
window_stack_index
|
||||
window_start_flag
|
||||
window_visible_layout
|
||||
window_width
|
||||
window_zoomed_flag
|
||||
wrap_flag
|
||||
"
|
||||
|
||||
# test_var $name $expected [$extra_args...]
|
||||
#
|
||||
# Expand a single #{name} and compare against $expected. Any extra arguments
|
||||
# are passed straight to display-message (e.g. -c or -t).
|
||||
test_var()
|
||||
{
|
||||
name="$1"
|
||||
exp="$2"
|
||||
shift 2
|
||||
|
||||
out=$($TMUX display-message "$@" -p "#{$name}")
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Variable test failed for '#{$name}'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server did not survive: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
FIFO="${TMPDIR:-/tmp}/fmt-vars-$$"
|
||||
HOLD=""
|
||||
CC=""
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$HOLD" ] && kill $HOLD 2>/dev/null
|
||||
[ -n "$CC" ] && kill $CC 2>/dev/null
|
||||
rm -f "$FIFO"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
# A session "cov" with a window "win0" holding two panes running cat, plus a
|
||||
# second window, an option and a paste buffer.
|
||||
$TMUX new-session -d -s cov -x 80 -y 24 -n win0 'cat' || exit 1
|
||||
$TMUX set -g automatic-rename off
|
||||
$TMUX set -g monitor-bell on
|
||||
$TMUX set -g monitor-activity on
|
||||
$TMUX split-window -t cov:win0 -d 'cat' || exit 1
|
||||
$TMUX new-window -d -t cov:1 -n win1 'cat' || exit 1
|
||||
$TMUX set -g @opt 'optionvalue' || exit 1
|
||||
$TMUX set-buffer -b buf0 'somebuffer' || exit 1
|
||||
|
||||
# A second session grouped with cov, so the session_group_* variables have real
|
||||
# data to report.
|
||||
$TMUX new-session -d -s cov2 -t cov || exit 1
|
||||
|
||||
sleep 1
|
||||
$TMUX send-keys -t cov:win0.0 'some pane content' Enter
|
||||
# Ring the bell in the non-current window so a bell alert is raised on the
|
||||
# session (this populates session_alert/session_alerts and window_bell_flag).
|
||||
$TMUX send-keys -t cov:win1.0 C-g
|
||||
sleep 1
|
||||
|
||||
# Attach a control-mode client, held open by a background process keeping the
|
||||
# write end of a FIFO open, so client_* variables have a client to read.
|
||||
rm -f "$FIFO"
|
||||
mkfifo "$FIFO" || exit 1
|
||||
sleep 30 >"$FIFO" &
|
||||
HOLD=$!
|
||||
$TMUX -C attach -t cov <"$FIFO" >/dev/null 2>&1 &
|
||||
CC=$!
|
||||
|
||||
# Attach a real client too: an inner tmux running inside a pane of the second
|
||||
# server gets a genuine terminal, which populates the terminal-dependent client
|
||||
# variables (client_termname, cursor_shape, the I modifier, ...).
|
||||
$TMUX2 new-session -d -x 90 -y 30 "$TMUX attach -t cov" || exit 1
|
||||
sleep 1
|
||||
|
||||
# The real (terminal) client, identified by not being in control mode.
|
||||
RC=$($TMUX list-clients -F '#{client_control_mode} #{client_name}' |
|
||||
awk '$1==0 { print $2; exit }')
|
||||
# The control client.
|
||||
CLIENT=$($TMUX list-clients -F '#{client_control_mode} #{client_name}' |
|
||||
awk '$1==1 { print $2; exit }')
|
||||
[ -n "$RC" ] || fail "No real client attached."
|
||||
[ -n "$CLIENT" ] || fail "No control client attached."
|
||||
|
||||
# Expand every variable at once, with the real terminal client and a target
|
||||
# pane in context, and confirm the server survives. This runs every callback.
|
||||
FMT=""
|
||||
for n in $NAMES; do
|
||||
FMT="$FMT#{$n}"
|
||||
done
|
||||
$TMUX display-message -c "$RC" -t cov:win0.0 -p "$FMT" >/dev/null 2>&1
|
||||
assert_alive "expanding all variables together"
|
||||
|
||||
# Expand each variable on its own too, so a crash can be pinned to one name.
|
||||
for n in $NAMES; do
|
||||
$TMUX display-message -c "$RC" -t cov:win0.0 -p "#{$n}" >/dev/null 2>&1
|
||||
assert_alive "expanding #{$n}"
|
||||
done
|
||||
|
||||
# Deterministic checks on stable variables (targeting pane 0 of window 0).
|
||||
TGT="cov:win0.0"
|
||||
test_var session_name "cov" -t "$TGT"
|
||||
test_var window_name "win0" -t "$TGT"
|
||||
test_var window_index "0" -t "$TGT"
|
||||
test_var window_panes "2" -t "$TGT"
|
||||
test_var session_windows "2" -t "$TGT"
|
||||
test_var pane_index "0" -t "$TGT"
|
||||
test_var pane_in_mode "0" -t "$TGT"
|
||||
test_var pane_at_top "1" -t "$TGT"
|
||||
test_var pane_at_left "1" -t "$TGT"
|
||||
test_var last_window_index "1" -t "$TGT"
|
||||
test_var pid "$($TMUX display-message -p '#{pid}')" -t "$TGT"
|
||||
test_var pane_start_command "cat" -t "$TGT"
|
||||
test_var pane_start_command_list "'cat'" -t "$TGT"
|
||||
|
||||
# The grouped session is reported as such.
|
||||
test_var session_grouped "1" -t "cov:"
|
||||
test_var session_group_size "2" -t "cov:"
|
||||
|
||||
# list-buffers -F formats each paste buffer (this fills in the paste-buffer
|
||||
# format defaults).
|
||||
if [ "$($TMUX list-buffers -F '#{buffer_name}=#{buffer_sample}')" != \
|
||||
"buf0=somebuffer" ]; then
|
||||
fail "Unexpected list-buffers format output."
|
||||
fi
|
||||
|
||||
# Version reported by the variable matches tmux -V.
|
||||
VER=$($TMUX -V | sed 's/^tmux //')
|
||||
test_var version "$VER" -t "$TGT"
|
||||
|
||||
# Client variables from each kind of client.
|
||||
test_var client_name "$CLIENT" -c "$CLIENT"
|
||||
test_var client_control_mode "1" -c "$CLIENT"
|
||||
test_var client_control_mode "0" -c "$RC"
|
||||
test_var socket_path "$($TMUX display-message -p '#{socket_path}')" -c "$CLIENT"
|
||||
# The real client has a terminal, so termcap/feature/environ queries work.
|
||||
test_var "I/e:TERM" "$($TMUX display-message -c "$RC" -p '#{client_termname}')" \
|
||||
-c "$RC"
|
||||
# Termcap and feature queries against a real terminal return a boolean.
|
||||
case "$($TMUX display-message -c "$RC" -p '#{I/c:colors}')" in
|
||||
0|1) ;;
|
||||
*) fail "Unexpected #{I/c:colors} for real client." ;;
|
||||
esac
|
||||
case "$($TMUX display-message -c "$RC" -p '#{I/f:256}')" in
|
||||
0|1) ;;
|
||||
*) fail "Unexpected #{I/f:256} for real client." ;;
|
||||
esac
|
||||
|
||||
# Time variables through the pretty and relative modifiers: start_time is the
|
||||
# recent server start, exercising the "last 24 hours" and "just now" paths.
|
||||
[ -n "$($TMUX display-message -p '#{t/p:start_time}')" ] ||
|
||||
fail "Empty #{t/p:start_time}."
|
||||
[ -n "$($TMUX display-message -p '#{t/r:start_time}')" ] ||
|
||||
fail "Empty #{t/r:start_time}."
|
||||
|
||||
# pane_start_command_list quotes each argv word for sh, so evaluating the
|
||||
# expansion reconstructs the original argv exactly - including words with
|
||||
# quotes, spaces, newlines and empty words. sh -c ignores the extra words
|
||||
# (they become positional parameters), so the pane stays alive. -u stops
|
||||
# the server sanitizing the newline away when printing to a non-UTF-8
|
||||
# client (the test runs without a locale in the environment).
|
||||
$TMUX new-session -d -s quot -x 80 -y 24 -- sh -c 'sleep 100' arg0 \
|
||||
"it's a 'test'" 'two words' '' 'new
|
||||
line' || fail "Failed to create quoting test session."
|
||||
LIST=$($TMUX -u display-message -t 'quot:0.0' -p '#{pane_start_command_list}')
|
||||
eval "set -- $LIST"
|
||||
GOT=$(for a; do printf '<%s>' "$a"; done)
|
||||
EXP=$(for a in sh -c 'sleep 100' arg0 "it's a 'test'" 'two words' '' 'new
|
||||
line'; do printf '<%s>' "$a"; done)
|
||||
if [ "$GOT" != "$EXP" ]; then
|
||||
echo "pane_start_command_list did not round-trip."
|
||||
echo "Expected: $EXP"
|
||||
echo "But got: $GOT"
|
||||
fail "Expansion was: $LIST"
|
||||
fi
|
||||
# A pane started with the default shell has an empty start command.
|
||||
$TMUX new-window -d -t 'quot:' || fail "Failed to create shell window."
|
||||
test_var pane_start_command_list "" -t "quot:1.0"
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
176
regress/input-common.inc
Normal file
176
regress/input-common.inc
Normal file
@@ -0,0 +1,176 @@
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
|
||||
TMP=$(mktemp)
|
||||
EXP=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
exit_status=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "FAIL: $1"
|
||||
diff -u "$EXP" "$TMP"
|
||||
exit_status=1
|
||||
}
|
||||
|
||||
start_pane()
|
||||
{
|
||||
start_pane_hlimit "$1" "$2" "$3" "$4" 0
|
||||
}
|
||||
|
||||
start_pane_history()
|
||||
{
|
||||
start_pane_hlimit "$1" "$2" "$3" "$4" 2000
|
||||
}
|
||||
|
||||
start_pane_hlimit()
|
||||
{
|
||||
name=$1
|
||||
sx=$2
|
||||
sy=$3
|
||||
seq=$4
|
||||
hlimit=$5
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1
|
||||
$TMUX set-option -g history-limit "$hlimit" || exit 1
|
||||
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
|
||||
"printf '$seq'; sleep 2" || exit 1
|
||||
$TMUX kill-session -t test-setup
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
start_cmd()
|
||||
{
|
||||
name=$1
|
||||
sx=$2
|
||||
sy=$3
|
||||
cmd=$4
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" "$cmd" || exit 1
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
normalize_capture()
|
||||
{
|
||||
sed 's/[ ]*$//' |
|
||||
awk '{ line[NR] = $0; if ($0 != "") last = NR }
|
||||
END { for (i = 1; i <= last; i++) print line[i] }'
|
||||
}
|
||||
|
||||
capture_grid()
|
||||
{
|
||||
$TMUX capture-pane -pN -t "$1:" -S 0 -E - | normalize_capture
|
||||
}
|
||||
|
||||
check_capture()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
capture_grid "$name" >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name"
|
||||
}
|
||||
|
||||
check_cursor()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
actual=$($TMUX display-message -p -t "$name:" '#{cursor_x},#{cursor_y}')
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
printf "%s\n" "$actual" >"$TMP"
|
||||
fail "$name cursor"
|
||||
fi
|
||||
}
|
||||
|
||||
check_flags()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
$TMUX capture-pane -pNF -t "$name:" -S 0 -E - |
|
||||
normalize_capture |
|
||||
awk '$0 != "-"' >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name flags"
|
||||
}
|
||||
|
||||
check_joined()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
$TMUX capture-pane -pNJ -t "$name:" -S 0 -E - |
|
||||
normalize_capture >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name joined"
|
||||
}
|
||||
|
||||
capture_raw()
|
||||
{
|
||||
$TMUX capture-pane -pR -t "$1:"
|
||||
}
|
||||
|
||||
capture_raw_used()
|
||||
{
|
||||
capture_raw "$1" |
|
||||
awk '/^(G| L)/ || /^ C/ && $3 !~ /^data=\(1,1, \)$/'
|
||||
}
|
||||
|
||||
check_raw()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
capture_raw "$name" >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name raw"
|
||||
}
|
||||
|
||||
check_raw_used()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
capture_raw_used "$name" >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name raw used"
|
||||
}
|
||||
|
||||
check_raw_has()
|
||||
{
|
||||
name=$1
|
||||
shift
|
||||
|
||||
capture_raw "$name" >"$TMP"
|
||||
for expected in "$@"; do
|
||||
if ! grep -Fqx "$expected" "$TMP"; then
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
fail "$name raw missing"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_raw_matches()
|
||||
{
|
||||
name=$1
|
||||
shift
|
||||
|
||||
capture_raw "$name" >"$TMP"
|
||||
for expected in "$@"; do
|
||||
if ! grep -Eq "$expected" "$TMP"; then
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
fail "$name raw missing"
|
||||
fi
|
||||
done
|
||||
}
|
||||
39
regress/input-cursor.sh
Normal file
39
regress/input-cursor.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane cursor 10 3 'ABCDE\r\033[2Cxy\033[1D!\033[4GZ\n'
|
||||
check_capture cursor 'ABxZE'
|
||||
check_cursor cursor '0,1'
|
||||
|
||||
start_pane saverc 10 3 'abc\0337\033[2;5HXY\0338Z\n'
|
||||
check_capture saverc 'abcZ
|
||||
XY'
|
||||
check_cursor saverc '0,1'
|
||||
|
||||
start_pane hvp 10 4 'A\033[3dB\033[5GC\033[2;2fD\n'
|
||||
check_capture hvp 'A
|
||||
D
|
||||
B C'
|
||||
check_cursor hvp '0,2'
|
||||
|
||||
start_pane cursorlines 8 4 'A\033[2BB\033[1FC\033[1AD\n'
|
||||
check_capture cursorlines 'AD
|
||||
C
|
||||
B'
|
||||
check_cursor cursorlines '0,1'
|
||||
|
||||
start_pane tabs 12 3 'a\tb\n'
|
||||
check_capture tabs 'a b'
|
||||
check_cursor tabs '0,1'
|
||||
|
||||
start_pane tabclear 12 3 '\033H\ta\033[3g\r\tb\n'
|
||||
check_capture tabclear ' a b'
|
||||
check_cursor tabclear '0,1'
|
||||
|
||||
start_pane cbt 16 3 '0123456789\r\033[10C\033[Zx\n'
|
||||
check_capture cbt '01234567x9'
|
||||
check_cursor cbt '0,1'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
54
regress/input-edit.sh
Normal file
54
regress/input-edit.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane dch 10 3 'abcdef\r\033[3C\033[2PXY\n'
|
||||
check_capture dch 'abcXY'
|
||||
|
||||
start_pane ich 10 3 'abcdef\r\033[3C\033[2@XY\n'
|
||||
check_capture ich 'abcXYdef'
|
||||
|
||||
start_pane erase 10 3 'abcdef\r\033[3C\033[KZ\n'
|
||||
check_capture erase 'abcZ'
|
||||
|
||||
start_pane el1 10 3 'abcdef\r\033[3C\033[1KZ\n'
|
||||
check_capture el1 ' Zef'
|
||||
|
||||
start_pane ech 10 3 'abcdef\r\033[3C\033[2XX\n'
|
||||
check_capture ech 'abcX f'
|
||||
|
||||
start_pane ed 10 3 'one\ntwo\033[2;2H\033[JX\n'
|
||||
check_capture ed 'one
|
||||
tX'
|
||||
|
||||
start_pane ed1 10 3 'one\ntwo\033[2;2H\033[1JX\n'
|
||||
check_capture ed1 '
|
||||
Xo'
|
||||
|
||||
start_pane ed2 10 3 'one\ntwo\033[2JZ\n'
|
||||
check_capture ed2 '
|
||||
Z'
|
||||
|
||||
start_pane il 8 4 '111\n222\n333\033[2;1H\033[LAAA\n'
|
||||
check_capture il '111
|
||||
AAA
|
||||
222
|
||||
333'
|
||||
|
||||
start_pane dl 8 4 '111\n222\n333\033[2;1H\033[MZZZ\n'
|
||||
check_capture dl '111
|
||||
ZZZ'
|
||||
|
||||
start_pane irm 10 3 'abcdef\r\033[4h\033[3CXY\033[4lZ\n'
|
||||
check_capture irm 'abcXYZef'
|
||||
|
||||
start_pane rep 10 3 'A\033[4bB\n'
|
||||
check_capture rep 'AAAAAB'
|
||||
|
||||
start_pane decaln 6 3 '\033#8'
|
||||
check_capture decaln 'EEEEEE
|
||||
EEEEEE
|
||||
EEEEEE'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
42
regress/input-malformed.sh
Normal file
42
regress/input-malformed.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_cmd csi-param-discard 8 3 \
|
||||
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
|
||||
check_capture csi-param-discard 'OK'
|
||||
|
||||
start_cmd csi-interm-discard 8 3 \
|
||||
"perl -e 'print qq{\e[ \030OK}'; sleep 2"
|
||||
check_capture csi-interm-discard 'OK'
|
||||
|
||||
start_cmd osc-discard 8 3 \
|
||||
"perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
|
||||
check_capture osc-discard 'OK'
|
||||
|
||||
start_cmd apc-discard 8 3 \
|
||||
"perl -e 'print qq{\e_}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
|
||||
check_capture apc-discard 'OK'
|
||||
|
||||
start_pane unknown-csi 8 3 '\033[?9999zOK'
|
||||
check_capture unknown-csi 'OK'
|
||||
|
||||
start_pane unknown-osc 8 3 '\033]999;bad\aOK'
|
||||
check_capture unknown-osc 'OK'
|
||||
|
||||
start_pane malformed-osc 8 3 '\033]8;id=a:id=b;http://bad\aX\033]8;id=no-separator\aY\033]9;4;5;200\a\033]9;4;z\a\033]10;notacolour\a\033]11;notacolour\a\033]12;notacolour\a\033]4;999;red\a\033]104;999\a\033]52bad\a\033]52;c;@@@\aOK'
|
||||
check_capture malformed-osc 'XYOK'
|
||||
check_raw_matches malformed-osc \
|
||||
'C 0,0 data=\(1,1,X\).* link=NONE linkid=NONE' \
|
||||
'C 0,1 data=\(1,1,Y\).* link=NONE linkid=NONE' \
|
||||
'C 0,2 data=\(1,1,O\).* link=NONE linkid=NONE' \
|
||||
'C 0,3 data=\(1,1,K\).* link=NONE linkid=NONE'
|
||||
|
||||
start_pane malformed-dcs 8 3 '\033P$qBAD\033\\OK'
|
||||
check_capture malformed-dcs 'OK^[P0$r
|
||||
^[\'
|
||||
|
||||
start_pane malformed-utf8 8 3 '\360\200\200\200A\355\240\200B'
|
||||
check_capture malformed-utf8 '<27>A<EFBFBD>B'
|
||||
|
||||
exit $exit_status
|
||||
15
regress/input-modes.sh
Normal file
15
regress/input-modes.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane alternate 10 3 'MAIN\033[?1049hALT\033[?1049lZ\n'
|
||||
check_capture alternate 'MAINZ'
|
||||
|
||||
start_pane osc133 10 4 '\033]133;A\007prompt\n\033]133;C\007output\n'
|
||||
check_capture osc133 'prompt
|
||||
output'
|
||||
check_flags osc133 'P prompt
|
||||
O output'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
37
regress/input-osc.sh
Normal file
37
regress/input-osc.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane hyperlink 20 3 '\033]8;id=1;https://example.com\033\\link\033]8;;\033\\ plain\n'
|
||||
check_capture hyperlink 'link plain'
|
||||
check_flags hyperlink 'HX link plain'
|
||||
$TMUX capture-pane -peH -t hyperlink: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane palette 20 3 '\033]4;1;rgb:11/22/33;2;red\007\033]104;1;2\007X\n'
|
||||
check_capture palette 'X'
|
||||
|
||||
start_pane osc-colours 20 3 '\033]10;rgb:11/22/33\007\033]11;rgb:44/55/66\007\033]12;rgb:77/88/99\007\033]110\007\033]111\007\033]112\007X\n'
|
||||
check_capture osc-colours 'X'
|
||||
|
||||
start_pane progress 20 3 '\033]9;4;1;25\007\033]9;4;0\007\033]9;4;5;200\007X\n'
|
||||
check_capture progress 'X'
|
||||
|
||||
start_pane rename 20 3 '\033krenamed\033\\X\n'
|
||||
check_capture rename 'X'
|
||||
|
||||
start_pane apc-title 20 3 '\033_test-title\033\\X\n'
|
||||
check_capture apc-title 'X'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 20 -y 3 -s osc52 "sleep 2" || exit 1
|
||||
$TMUX set-option -s set-clipboard on || exit 1
|
||||
$TMUX respawn-pane -k -t osc52: \
|
||||
"printf '\033]52;c;SGVsbG8=\007'; sleep 2" || exit 1
|
||||
sleep 0.3
|
||||
$TMUX save-buffer -b buffer0 - >"$TMP"
|
||||
printf "Hello" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "osc52"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
84
regress/input-raw-controls.sh
Normal file
84
regress/input-raw-controls.sh
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane bs 8 3 'abc\bd'
|
||||
check_capture bs 'abd'
|
||||
check_raw_matches bs \
|
||||
'C 0,0 data=\(1,1,a\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,b\) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,d\) flags=NONE\[0\]'
|
||||
|
||||
start_pane nel 8 3 'A\033EB'
|
||||
check_capture nel 'A
|
||||
B'
|
||||
check_raw_matches nel \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 1,0 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane tabstops 16 3 '\033H1\t2\033[3g\r\t3'
|
||||
check_raw_matches tabstops \
|
||||
'C 0,0 data=\(1,1,1\) flags=NONE\[0\]' \
|
||||
'C 0,8 data=\(1,1,2\) flags=NONE\[0\]' \
|
||||
'C 0,15 data=\(1,1,3\) flags=NONE\[0\]'
|
||||
|
||||
start_pane decaln 6 3 '\033#8'
|
||||
check_raw_matches decaln \
|
||||
'C 0,0 data=\(1,1,E\) flags=NONE\[0\]' \
|
||||
'C 1,5 data=\(1,1,E\) flags=NONE\[0\]' \
|
||||
'C 2,5 data=\(1,1,E\) flags=NONE\[0\]'
|
||||
|
||||
start_pane charset 8 3 '\033(0qxl\033(BZ'
|
||||
check_raw_matches charset \
|
||||
'C 0,0 data=\(1,1,q\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(1,1,x\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
|
||||
'C 0,2 data=\(1,1,l\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
|
||||
'C 0,3 data=\(1,1,Z\) flags=NONE\[0\] attr=NONE\[0\]'
|
||||
|
||||
start_pane g1charset 8 3 '\033)0\016q\017Z'
|
||||
check_raw_matches g1charset \
|
||||
'C 0,0 data=\(1,1,q\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(1,1,Z\) flags=NONE\[0\] attr=NONE\[0\]'
|
||||
|
||||
start_pane csisave 8 3 '\033[3;3HS\033[s\033[1;1HA\033[uR'
|
||||
check_raw_matches csisave \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 2,2 data=\(1,1,S\) flags=NONE\[0\]' \
|
||||
'C 2,3 data=\(1,1,R\) flags=NONE\[0\]'
|
||||
|
||||
start_pane alternate 8 3 'main\033[?1049halt\033[?1049lback'
|
||||
check_capture alternate 'mainback'
|
||||
check_raw_matches alternate \
|
||||
'C 0,0 data=\(1,1,m\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,b\) flags=NONE\[0\]' \
|
||||
'C 0,7 data=\(1,1,k\) flags=NONE\[0\]'
|
||||
|
||||
start_pane sync 8 3 '\033P=1signored\033\\A\033P=2s\033\\B'
|
||||
check_raw_matches sync \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane private 8 3 '\033[?25lA\033[?25hB\033[?1000hC\033[?1000lD'
|
||||
check_raw_matches private \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,C\) flags=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,D\) flags=NONE\[0\]'
|
||||
|
||||
start_pane ris 8 3 'A\033cB'
|
||||
check_capture ris 'B'
|
||||
check_raw_matches ris \
|
||||
'C 0,0 data=\(1,1,B\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\]'
|
||||
|
||||
start_pane keypad 8 3 '\033=A\033>B'
|
||||
check_raw_matches keypad \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane cursorstyle 8 3 '\033[5 qA\033[0 qB'
|
||||
check_raw_matches cursorstyle \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
33
regress/input-raw-cursor.sh
Normal file
33
regress/input-raw-cursor.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane absolute 8 4 'A\033[3;5HB\033[2GC\033[2D!'
|
||||
check_capture absolute 'A
|
||||
|
||||
!C B'
|
||||
check_cursor absolute '1,2'
|
||||
check_raw_matches absolute \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 2,4 data=\(1,1,B\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1,!\) flags=NONE\[0\]' \
|
||||
'C 2,1 data=\(1,1,C\) flags=NONE\[0\]'
|
||||
|
||||
start_pane savecursor 8 4 '\033[4;4HS\0337\033[1;1HA\0338R'
|
||||
check_capture savecursor 'A
|
||||
|
||||
|
||||
SR'
|
||||
check_cursor savecursor '5,3'
|
||||
check_raw_matches savecursor \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 3,3 data=\(1,1,S\) flags=NONE\[0\]' \
|
||||
'C 3,4 data=\(1,1,R\) flags=NONE\[0\]'
|
||||
|
||||
start_pane origin 8 5 '\033[2;4r\033[?6h\033[1;1HO\033[3;1HP\033[?6lQ'
|
||||
check_raw_matches origin \
|
||||
'C 1,0 data=\(1,1,O\) flags=NONE\[0\]' \
|
||||
'C 3,0 data=\(1,1,P\) flags=NONE\[0\]' \
|
||||
'C 0,0 data=\(1,1,Q\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
47
regress/input-raw-edit.sh
Normal file
47
regress/input-raw-edit.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane erasechars 8 3 'ABCDEFGH\r\033[3C\033[2X'
|
||||
check_capture erasechars 'ABC FGH'
|
||||
check_raw_matches erasechars \
|
||||
'C 0,3 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,5 data=\(1,1,F\) flags=NONE\[0\]'
|
||||
|
||||
start_pane deletechars 8 3 'ABCDEFGH\r\033[3C\033[3P'
|
||||
check_capture deletechars 'ABCGH'
|
||||
check_raw_matches deletechars \
|
||||
'C 0,3 data=\(1,1,G\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,H\) flags=NONE\[0\]' \
|
||||
'C 0,5 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
|
||||
|
||||
start_pane insertchars 8 3 'ABCDEF\r\033[3C\033[2@xy'
|
||||
check_capture insertchars 'ABCxyDEF'
|
||||
check_raw_matches insertchars \
|
||||
'C 0,3 data=\(1,1,x\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,y\) flags=NONE\[0\]' \
|
||||
'C 0,5 data=\(1,1,D\) flags=NONE\[0\]'
|
||||
|
||||
start_pane eraseline 8 3 'ABCDEFGH\r\033[4C\033[K'
|
||||
check_capture eraseline 'ABCD'
|
||||
check_raw_matches eraseline \
|
||||
'C 0,4 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,7 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
|
||||
|
||||
start_pane erasescreen 8 3 '1111111\033[2;1H2222222\033[H\033[JZ'
|
||||
check_capture erasescreen 'Z'
|
||||
check_raw_matches erasescreen \
|
||||
'^G 8x3 \(0/0\)$' \
|
||||
'C [0-9]+,0 data=\(1,1,Z\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
|
||||
|
||||
start_pane tabs 12 3 'A\tB\033[2g\r\033[IC'
|
||||
check_raw_matches tabs \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(7,7, \) flags=TAB\[[0-9a-f]+\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\]' \
|
||||
'C 0,8 data=\(1,1,B\) flags=NONE\[0\]' \
|
||||
'C 0,0 data=\(1,1,C\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
23
regress/input-raw-history.sh
Normal file
23
regress/input-raw-history.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane_hlimit trim 6 3 'one\ntwo\nthree\nfour\nfive\nsix' 2
|
||||
check_raw_matches trim \
|
||||
'^G 6x3 \(2/2\)$' \
|
||||
'L 0 \(-\) flags=NONE\[0\]' \
|
||||
'L 1 \(-\) flags=NONE\[0\]'
|
||||
|
||||
$TMUX clear-history -t trim:
|
||||
check_raw_matches trim \
|
||||
'^G 6x3 \(0/2\)$' \
|
||||
'C 0,0 data=\(1,1,f\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1,s\) flags=NONE\[0\]'
|
||||
|
||||
start_pane_hlimit edhistory 6 3 'one\ntwo\nthree\033[H\033[JZ' 5
|
||||
check_raw_matches edhistory \
|
||||
'^G 6x3 \([1-9][0-9]*/5\)$' \
|
||||
'L [0-9]+ \(-\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,0 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
21
regress/input-raw-reflow.sh
Normal file
21
regress/input-raw-reflow.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane_history reflow 8 4 'abcdefgh\nijklmnop\nqrstuvwx\nyz'
|
||||
$TMUX resize-window -t reflow: -x 4 -y 4
|
||||
sleep 0.2
|
||||
check_raw_matches reflow \
|
||||
'^G 4x4 \([0-9]+/2000\)$' \
|
||||
'L [0-9]+ \([0-9-]+\) flags=WRAPPED\[[0-9a-f]+\]' \
|
||||
'C [0-9]+,0 data=\(1,1,a\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,3 data=\(1,1,d\) flags=NONE\[0\]'
|
||||
|
||||
$TMUX resize-window -t reflow: -x 12 -y 4
|
||||
sleep 0.2
|
||||
check_raw_matches reflow \
|
||||
'^G 12x4 \([0-9]+/2000\)$' \
|
||||
'C [0-9]+,0 data=\(1,1,a\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,7 data=\(1,1,h\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
39
regress/input-raw-scroll.sh
Normal file
39
regress/input-raw-scroll.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane_history history 6 3 'one\ntwo\nthree\nfour\nfive'
|
||||
check_raw_matches history \
|
||||
'^G 6x3 \([1-9][0-9]*/2000\)$' \
|
||||
'L [0-9]+ \(-\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,0 data=\(1,1,o\) flags=NONE\[0\]'
|
||||
|
||||
start_pane_history index 6 4 'A\nB\nC\033[2;3r\033[2;1HX\033D\033DY'
|
||||
check_raw_matches index \
|
||||
'C [0-9]+,0 data=\(1,1,X\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,1 data=\(1,1,Y\) flags=NONE\[0\]'
|
||||
|
||||
start_pane reverse 6 4 'A\nB\nC\033[2;3r\033[2;1H\033MY'
|
||||
check_raw_matches reverse \
|
||||
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane insertline 6 4 'A\nB\nC\033[2;3r\033[2;1H\033[LY'
|
||||
check_raw_matches insertline \
|
||||
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane deleteline 6 4 'A\nB\nC\033[2;3r\033[2;1H\033[MY'
|
||||
check_raw_matches deleteline \
|
||||
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1, \) flags=NONE\[0\]'
|
||||
|
||||
start_pane region-edge 6 4 'top\033[2;3rmid\033[2;1H\033D\033Mbot'
|
||||
check_raw_matches region-edge \
|
||||
'C 0,0 data=\(1,1,m\) flags=NONE\[0\]' \
|
||||
'C 1,0 data=\(1,1,b\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1, \) flags=NONE\[0\]' \
|
||||
'C 3,0 data=\(1,1, \) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
39
regress/input-raw-sgr.sh
Normal file
39
regress/input-raw-sgr.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane attrs 16 3 '\033[1mB\033[2mD\033[3mI\033[4mU\033[5mK\033[7mR\033[8mH\033[9mS\033[53mO'
|
||||
check_raw_matches attrs \
|
||||
'C 0,0 data=\(1,1,B\) flags=NONE\[0\] attr=BRIGHT\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(1,1,D\) flags=NONE\[0\] attr=BRIGHT,DIM\[[0-9a-f]+\]' \
|
||||
'C 0,2 data=\(1,1,I\) flags=NONE\[0\] attr=BRIGHT,DIM,ITALICS\[[0-9a-f]+\]' \
|
||||
'C 0,3 data=\(1,1,U\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,ITALICS\[[0-9a-f]+\]' \
|
||||
'C 0,5 data=\(1,1,R\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,ITALICS\[[0-9a-f]+\]' \
|
||||
'C 0,7 data=\(1,1,S\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,HIDDEN,ITALICS,STRIKETHROUGH\[[0-9a-f]+\]' \
|
||||
'C 0,8 data=\(1,1,O\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,HIDDEN,ITALICS,STRIKETHROUGH,OVERLINE\[[0-9a-f]+\]'
|
||||
|
||||
start_pane colours 12 3 '\033[38;5;196;48;5;17mX\033[58;5;45mY'
|
||||
check_raw_matches colours \
|
||||
'C 0,0 data=\(1,1,X\) flags=FG256,BG256\[[0-9a-f]+\] attr=NONE\[0\] fg=colour196\[10000c4\] bg=colour17\[1000011\]' \
|
||||
'C 0,1 data=\(1,1,Y\) flags=FG256,BG256\[[0-9a-f]+\] attr=NONE\[0\] fg=colour196\[10000c4\] bg=colour17\[1000011\] us=colour45\[100002d\]'
|
||||
|
||||
start_pane bce 8 3 '\033[44mA\033[K'
|
||||
check_raw_matches bce \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\] attr=NONE\[0\].* bg=blue\[4\]' \
|
||||
'C 0,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\].* bg=blue\[4\]' \
|
||||
'C 0,7 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\].* bg=blue\[4\]'
|
||||
|
||||
start_pane underlines 12 3 '\033[4:2m2\033[4:3m3\033[4:4m4\033[4:5m5'
|
||||
check_raw_matches underlines \
|
||||
'C 0,0 data=\(1,1,2\) flags=NONE\[0\] attr=UNDERSCORE_2\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(1,1,3\) flags=NONE\[0\] attr=UNDERSCORE_3\[[0-9a-f]+\]' \
|
||||
'C 0,2 data=\(1,1,4\) flags=NONE\[0\] attr=UNDERSCORE_4\[[0-9a-f]+\]' \
|
||||
'C 0,3 data=\(1,1,5\) flags=NONE\[0\] attr=UNDERSCORE_5\[[0-9a-f]+\]'
|
||||
|
||||
start_pane hyperlink 12 3 '\033]8;id=id1;https://example.com/a\033\\A\033]8;;\033\\B'
|
||||
check_raw_matches hyperlink \
|
||||
'L 0 \(0\) flags=EXTENDED,HYPERLINK\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\].* link=https://example.com/a linkid=id1' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\].* link=NONE linkid=NONE'
|
||||
|
||||
exit $exit_status
|
||||
82
regress/input-raw-unicode.sh
Normal file
82
regress/input-raw-unicode.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane wide 8 3 'A\343\201\202B'
|
||||
check_capture wide 'AあB'
|
||||
check_raw_matches wide \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(2,3,あ\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane combining 8 3 'e\314\201x'
|
||||
check_raw_matches combining \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(1,[0-9]+,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,x\) flags=NONE\[0\]'
|
||||
|
||||
start_pane emoji 10 3 '\360\237\230\200Z'
|
||||
check_raw_matches emoji \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(2,4,😀\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
start_pane flag 10 3 '\360\237\207\254\360\237\207\247!'
|
||||
check_raw_matches flag \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(2,8,🇬🇧\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=NONE\[0\]'
|
||||
|
||||
start_pane variation 10 3 '*\357\270\217!'
|
||||
check_raw_matches variation \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(2,[0-9]+,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=NONE\[0\]'
|
||||
|
||||
start_pane invalid 10 3 '\377A'
|
||||
check_raw_matches invalid \
|
||||
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
|
||||
|
||||
start_pane trunc2 10 3 '\303A'
|
||||
check_raw_matches trunc2 \
|
||||
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
|
||||
|
||||
start_pane trunc3 10 3 '\342\202A'
|
||||
check_raw_matches trunc3 \
|
||||
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
|
||||
|
||||
start_pane overwrite-wide-left 10 3 'A\343\201\202B\r\033[1CX'
|
||||
check_raw_matches overwrite-wide-left \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,X\) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1, \) flags=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane overwrite-wide-pad 10 3 'A\343\201\202B\r\033[2CX'
|
||||
check_raw_matches overwrite-wide-pad \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1, \) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,X\) flags=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane overwrite-wide-with-wide 10 3 'A\343\201\202B\r\033[1C\347\225\214'
|
||||
check_raw_matches overwrite-wide-with-wide \
|
||||
'C 0,1 data=\(2,3,界\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane wide-right-edge 4 3 'ABC\343\201\202Z'
|
||||
check_raw_matches wide-right-edge \
|
||||
'L 0 \(0\) flags=WRAPPED\[[0-9a-f]+\]' \
|
||||
'C 1,0 data=\(2,3,あ\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 1,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 1,2 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
28
regress/input-raw-wrap.sh
Normal file
28
regress/input-raw-wrap.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane wrap 5 3 'ABCDEZ'
|
||||
check_capture wrap 'ABCDE
|
||||
Z'
|
||||
check_raw_matches wrap \
|
||||
'^G 5x3 \(0/0\)$' \
|
||||
'L 0 \(0\) flags=WRAPPED\[[0-9a-f]+\]' \
|
||||
'C 0,4 data=\(1,1,E\) flags=NONE\[0\]' \
|
||||
'C 1,0 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
start_pane nowrap 5 3 '\033[?7lABCDEZ'
|
||||
check_capture nowrap 'ABCDZ'
|
||||
check_raw_matches nowrap \
|
||||
'^G 5x3 \(0/0\)$' \
|
||||
'L 0 \(0\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
start_pane pending 5 3 'ABCD\r\033[4CZ'
|
||||
check_capture pending 'ABCDZ'
|
||||
check_cursor pending '5,0'
|
||||
check_raw_matches pending \
|
||||
'L 0 \(0\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
494
regress/input-reflow-stress.sh
Normal file
494
regress/input-reflow-stress.sh
Normal file
@@ -0,0 +1,494 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LANG=C.UTF-8
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LANG LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMUX="$TEST_TMUX -Linput-reflow-stress-$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Linput-reflow-stress-outer-$$ -f/dev/null"
|
||||
|
||||
TMP=$(mktemp "${TMPDIR:-/tmp}/input-reflow-stress.XXXXXX") || exit 1
|
||||
EXP=$(mktemp "${TMPDIR:-/tmp}/input-reflow-stress.XXXXXX") || exit 1
|
||||
exit_status=0
|
||||
|
||||
WIDTHS="80 40 20 10 7 5 4 3 2 1 2 3 4 5 7 10 20 40 80"
|
||||
LIVE_WIDTHS="68 24 80"
|
||||
HEIGHT=24
|
||||
HISTORY_LIMIT=220
|
||||
HISTORY_BOUND=12000
|
||||
JOINED_BOUND=500000
|
||||
RAW_BOUND=5000000
|
||||
COPY_BOUND=20000
|
||||
LIVE_BOUND=50000
|
||||
|
||||
cleanup()
|
||||
{
|
||||
rm -f "$TMP" "$EXP"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
record_fail()
|
||||
{
|
||||
echo "FAIL: $1" >&2
|
||||
exit_status=1
|
||||
}
|
||||
|
||||
u8()
|
||||
{
|
||||
printf "$1"
|
||||
}
|
||||
|
||||
wide_a()
|
||||
{
|
||||
u8 '\0343\0201\0202'
|
||||
}
|
||||
|
||||
wide_b()
|
||||
{
|
||||
u8 '\0347\0225\0214'
|
||||
}
|
||||
|
||||
combining_acute()
|
||||
{
|
||||
u8 '\0314\0201'
|
||||
}
|
||||
|
||||
zero_width_space()
|
||||
{
|
||||
u8 '\0342\0200\0213'
|
||||
}
|
||||
|
||||
zero_width_joiner()
|
||||
{
|
||||
u8 '\0342\0200\0215'
|
||||
}
|
||||
|
||||
replacement()
|
||||
{
|
||||
u8 '\0357\0277\0275'
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
$TMUX display-message -p -t stress: alive >/dev/null 2>&1 ||
|
||||
record_fail "server exited after $1"
|
||||
}
|
||||
|
||||
make_orphan_payload()
|
||||
{
|
||||
tag=$1
|
||||
|
||||
printf 'OP%s-01|ECH-half|left' "$tag"
|
||||
wide_b
|
||||
printf 'right'
|
||||
printf '\033[6D\033[1X\n'
|
||||
|
||||
printf 'OP%s-02|EL-inside|left' "$tag"
|
||||
wide_b
|
||||
printf 'right'
|
||||
printf '\033[6D\033[K\n'
|
||||
|
||||
printf 'OP%s-03|overwrite-leading|left' "$tag"
|
||||
wide_b
|
||||
printf 'right'
|
||||
printf '\rOP%s-03|overwrite-leading|left!\n' "$tag"
|
||||
|
||||
printf 'OP%s-04|right-edge|' "$tag"
|
||||
i=0
|
||||
while [ "$i" -lt 60 ]; do
|
||||
printf '.'
|
||||
i=$((i + 1))
|
||||
done
|
||||
wide_b
|
||||
printf 'Z'
|
||||
printf '\033[2D\033[1X\n'
|
||||
|
||||
printf 'OP%s-05|ERASED|left' "$tag"
|
||||
wide_b
|
||||
printf 'right\r\033[2KOP%s-05|ERASED|left clear\n' "$tag"
|
||||
|
||||
printf 'SENT-%s|ORPHAN-PHASE|complete\n' "$tag"
|
||||
}
|
||||
|
||||
make_payload()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 16 ]; do
|
||||
printf 'ASCII%02d|abcdefghijklmnopqrstuvwxyz\n' "$i"
|
||||
|
||||
printf 'WIDE%02d|' "$i"
|
||||
wide_a
|
||||
printf '|'
|
||||
wide_b
|
||||
printf '|tail\n'
|
||||
|
||||
printf 'COMB%02d|e' "$i"
|
||||
combining_acute
|
||||
printf '|zero'
|
||||
zero_width_space
|
||||
zero_width_joiner
|
||||
printf '|done\n'
|
||||
|
||||
printf 'STYLE%02d|\033[1;31mred\033[0m|\033[4munder\033[0m\n' \
|
||||
"$i"
|
||||
|
||||
printf 'LINK%02d|\033]8;;https://example.invalid/%d\007link%d\033]8;;\007|end\n' \
|
||||
"$i" "$i" "$i"
|
||||
|
||||
printf 'WRAP%02d|%064d\n' "$i" "$i"
|
||||
|
||||
printf 'CRBS%02d|abcdef\rCRBS%02d|XYZ\n' "$i" "$i"
|
||||
printf 'BS%02d|abc\bZ\n' "$i"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 6 ]; do
|
||||
printf 'L%04d|stable|abcdefghijklmnopqrstuvwxyz\n' "$i"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
printf 'SENT-A|SURVIVES|plain logical line\n'
|
||||
printf 'SENT-B|SURVIVES|wide-free after erases\n'
|
||||
make_orphan_payload PRE
|
||||
printf 'SENT-C|SURVIVES|tail logical line\n'
|
||||
}
|
||||
|
||||
make_alternate_payload()
|
||||
{
|
||||
printf '\033[?1049h'
|
||||
printf 'ALT-SENT|alternate screen|'
|
||||
wide_a
|
||||
printf '\nALT-SENT|resize target\n'
|
||||
}
|
||||
|
||||
exit_alternate_payload()
|
||||
{
|
||||
printf '\033[?1049l'
|
||||
}
|
||||
|
||||
load_and_paste()
|
||||
{
|
||||
buffer=$1
|
||||
shift
|
||||
|
||||
"$@" >"$EXP"
|
||||
$TMUX load-buffer -b "$buffer" "$EXP" || exit 1
|
||||
$TMUX paste-buffer -d -b "$buffer" -t stress:0.0 || exit 1
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
capture_joined()
|
||||
{
|
||||
$TMUX capture-pane -pNJ -t stress: -S -5000 -E - >"$TMP"
|
||||
}
|
||||
|
||||
assert_joined_sane()
|
||||
{
|
||||
label=$1
|
||||
|
||||
capture_joined || {
|
||||
record_fail "joined capture failed after $label"
|
||||
return
|
||||
}
|
||||
|
||||
bytes=$(wc -c <"$TMP")
|
||||
if [ "$bytes" -gt "$JOINED_BOUND" ]; then
|
||||
record_fail "joined capture too large after $label: $bytes"
|
||||
fi
|
||||
|
||||
if grep -q "$(replacement)" "$TMP"; then
|
||||
record_fail "replacement character in joined capture after $label"
|
||||
fi
|
||||
|
||||
out=$(
|
||||
awk -v label="$label" '
|
||||
{
|
||||
line = $0
|
||||
seen_on_line = 0
|
||||
while (match(line, /L[0-9][0-9][0-9][0-9]\|/)) {
|
||||
id = substr(line, RSTART + 1, 4) + 0
|
||||
seen_on_line++
|
||||
seen[id]++
|
||||
ids[++count] = id
|
||||
found = 1
|
||||
last = id
|
||||
line = substr(line, RSTART + RLENGTH)
|
||||
}
|
||||
if (seen_on_line > 1)
|
||||
printf("fused IDs after %s: %s\n", label, $0)
|
||||
}
|
||||
END {
|
||||
if (!found) {
|
||||
printf("no line IDs after %s\n", label)
|
||||
exit
|
||||
}
|
||||
for (i = 2; i <= count; i++) {
|
||||
if (ids[i] < ids[i - 1])
|
||||
drops++
|
||||
}
|
||||
if (drops > 1)
|
||||
printf("IDs out of order after %s\n", label)
|
||||
for (id in seen) {
|
||||
if (seen[id] > 2)
|
||||
printf("duplicate ID after %s: L%04d\n", label, id)
|
||||
}
|
||||
}' "$TMP"
|
||||
)
|
||||
[ -z "$out" ] || record_fail "$out"
|
||||
|
||||
for marker in \
|
||||
'SENT-A|SURVIVES|plain logical line' \
|
||||
'SENT-B|SURVIVES|wide-free after erases' \
|
||||
'SENT-C|SURVIVES|tail logical line'
|
||||
do
|
||||
grep -F "$marker" "$TMP" >/dev/null 2>&1 ||
|
||||
record_fail "missing sentinel after $label: $marker"
|
||||
done
|
||||
|
||||
if grep '^OP.*-05|ERASED|.*' "$TMP" | grep -q "$(wide_b)"; then
|
||||
record_fail "erased wide character visible after $label"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_final_logical_text()
|
||||
{
|
||||
label=$1
|
||||
|
||||
capture_joined || {
|
||||
record_fail "joined capture failed after $label"
|
||||
return
|
||||
}
|
||||
|
||||
for marker in \
|
||||
'SENT-A|SURVIVES|plain logical line' \
|
||||
'SENT-B|SURVIVES|wide-free after erases' \
|
||||
'SENT-C|SURVIVES|tail logical line' \
|
||||
'OPPRE-03|overwrite-leading|left!' \
|
||||
'OPPOST-03|overwrite-leading|left!' \
|
||||
'SENT-POST|ORPHAN-PHASE|complete'
|
||||
do
|
||||
grep -F "$marker" "$TMP" >/dev/null 2>&1 ||
|
||||
record_fail "missing expected logical text after $label: $marker"
|
||||
done
|
||||
}
|
||||
|
||||
assert_raw_sane()
|
||||
{
|
||||
label=$1
|
||||
|
||||
$TMUX capture-pane -pR -t stress: >"$TMP" ||
|
||||
record_fail "raw capture failed after $label"
|
||||
|
||||
bytes=$(wc -c <"$TMP")
|
||||
if [ "$bytes" -gt "$RAW_BOUND" ]; then
|
||||
record_fail "raw capture too large after $label: $bytes"
|
||||
fi
|
||||
|
||||
width=$($TMUX display-message -p -t stress: '#{pane_width}' 2>/dev/null)
|
||||
case "$width" in
|
||||
''|*[!0-9]*) width=0 ;;
|
||||
esac
|
||||
|
||||
out=$(
|
||||
awk -v label="$label" -v width="$width" '
|
||||
{
|
||||
sub(/^[ ]+/, "")
|
||||
if ($1 != "C")
|
||||
next
|
||||
|
||||
coord = $2
|
||||
sub(/^[0-9]*,/, "", coord)
|
||||
sub(/[^0-9].*$/, "", coord)
|
||||
col = coord + 0
|
||||
if (width > 0 && (col < 0 || col >= width))
|
||||
printf("cell column outside width after %s: width %d: %s\n", label, width, $0)
|
||||
|
||||
data = $0
|
||||
if (data !~ /data=\(/) {
|
||||
printf("cell without data after %s: %s\n", label, $0)
|
||||
next
|
||||
}
|
||||
sub(/^.*data=\(/, "", data)
|
||||
split(data, parts, ",")
|
||||
cell_width = parts[1] + 0
|
||||
padding = ($0 ~ /flags=[^ ]*PADDING/)
|
||||
if (!padding && cell_width == 0)
|
||||
printf("visible zero-width cell after %s: %s\n", label, $0)
|
||||
}' "$TMP"
|
||||
)
|
||||
[ -z "$out" ] || record_fail "$out"
|
||||
}
|
||||
|
||||
assert_history_sane()
|
||||
{
|
||||
label=$1
|
||||
size=$($TMUX display-message -p -t stress: '#{history_size}' 2>/dev/null)
|
||||
case "$size" in
|
||||
''|*[!0-9]*)
|
||||
record_fail "bad history size after $label: $size"
|
||||
;;
|
||||
*)
|
||||
if [ "$size" -gt "$HISTORY_BOUND" ]; then
|
||||
record_fail "history too large after $label: $size"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_copy_mode_sane()
|
||||
{
|
||||
label=$1
|
||||
|
||||
$TMUX capture-pane -pM -S -5000 -E - -t stress: >"$TMP" ||
|
||||
record_fail "copy-mode capture failed after $label"
|
||||
|
||||
bytes=$(wc -c <"$TMP")
|
||||
if [ "$bytes" -gt "$COPY_BOUND" ]; then
|
||||
record_fail "copy-mode capture too large after $label: $bytes"
|
||||
fi
|
||||
|
||||
if grep -q "$(replacement)" "$TMP"; then
|
||||
record_fail "replacement character in copy-mode after $label"
|
||||
fi
|
||||
|
||||
if ! grep -Eq 'L[0-9][0-9][0-9][0-9]\|' "$TMP"; then
|
||||
record_fail "no line ID in copy-mode after $label"
|
||||
fi
|
||||
}
|
||||
|
||||
run_resize_checks()
|
||||
{
|
||||
for width in $WIDTHS; do
|
||||
$TMUX resize-window -t stress: -x "$width" -y "$HEIGHT" || exit 1
|
||||
sleep 0.1
|
||||
assert_alive "resize to $width"
|
||||
assert_joined_sane "resize to $width"
|
||||
assert_raw_sane "resize to $width"
|
||||
assert_history_sane "resize to $width"
|
||||
done
|
||||
}
|
||||
|
||||
wait_outer_contains()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX2 capture-pane -p -t out:0 >"$TMP" 2>/dev/null &&
|
||||
grep -F "$marker" "$TMP" >/dev/null 2>&1 &&
|
||||
return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
assert_live_client_redraw()
|
||||
{
|
||||
$TMUX set-option -t stress: status on >/dev/null || exit 1
|
||||
$TMUX set-option -t stress: status-interval 1 >/dev/null || exit 1
|
||||
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -s out -x 100 -y 12 "$TMUX attach -t stress" ||
|
||||
exit 1
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
clients=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$clients" -ge 1 ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || {
|
||||
record_fail "nested client did not attach"
|
||||
return
|
||||
}
|
||||
|
||||
for width in $LIVE_WIDTHS; do
|
||||
$TMUX set-option -t stress: status-left "REDRAW-$width " >/dev/null ||
|
||||
exit 1
|
||||
$TMUX2 resize-window -t out: -x "$width" -y 12 || exit 1
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
wait_outer_contains 'REDRAW-80' ||
|
||||
record_fail "outer capture missing final redraw marker"
|
||||
|
||||
$TMUX2 capture-pane -p -t out:0 >"$TMP" 2>/dev/null ||
|
||||
record_fail "outer capture failed"
|
||||
|
||||
bytes=$(wc -c <"$TMP")
|
||||
if [ "$bytes" -gt "$LIVE_BOUND" ]; then
|
||||
record_fail "outer capture too large: $bytes"
|
||||
fi
|
||||
|
||||
if grep -q "$(replacement)" "$TMP"; then
|
||||
record_fail "replacement character in outer capture"
|
||||
fi
|
||||
|
||||
grep -F 'SENT-POST|ORPHAN-PHASE|complete' "$TMP" >/dev/null 2>&1 ||
|
||||
record_fail "outer capture missing expected sentinel"
|
||||
|
||||
grep -F 'REDRAW-24' "$TMP" >/dev/null 2>&1 &&
|
||||
record_fail "outer capture contains stale width marker"
|
||||
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX set-option -t stress: status off >/dev/null || exit 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
|
||||
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1
|
||||
$TMUX set-option -g history-limit "$HISTORY_LIMIT" || exit 1
|
||||
$TMUX new-session -d -x 80 -y "$HEIGHT" -s stress 'cat' || exit 1
|
||||
$TMUX kill-session -t test-setup || exit 1
|
||||
sleep 0.3
|
||||
|
||||
load_and_paste stress-data make_payload
|
||||
assert_joined_sane "initial payload"
|
||||
assert_raw_sane "initial payload"
|
||||
assert_history_sane "initial payload"
|
||||
|
||||
$TMUX resize-window -t stress: -x 40 -y "$HEIGHT" || exit 1
|
||||
sleep 0.1
|
||||
load_and_paste stress-orphan-post make_orphan_payload POST
|
||||
assert_joined_sane "post-resize orphan payload"
|
||||
assert_raw_sane "post-resize orphan payload"
|
||||
|
||||
run_resize_checks
|
||||
assert_final_logical_text "return to original width"
|
||||
|
||||
$TMUX copy-mode -H -t stress: || exit 1
|
||||
$TMUX send-keys -t stress: -X history-top
|
||||
sleep 0.1
|
||||
assert_alive "copy-mode"
|
||||
assert_copy_mode_sane "copy-mode history-top"
|
||||
$TMUX send-keys -t stress: -X cancel
|
||||
sleep 0.1
|
||||
|
||||
assert_live_client_redraw
|
||||
|
||||
load_and_paste stress-alt make_alternate_payload
|
||||
for width in 30 12 80; do
|
||||
$TMUX resize-window -t stress: -x "$width" -y "$HEIGHT" || exit 1
|
||||
sleep 0.1
|
||||
assert_alive "alternate resize to $width"
|
||||
assert_raw_sane "alternate resize to $width"
|
||||
done
|
||||
load_and_paste stress-alt-exit exit_alternate_payload
|
||||
sleep 0.3
|
||||
assert_alive "alternate screen exit"
|
||||
assert_joined_sane "alternate screen exit"
|
||||
assert_raw_sane "alternate screen exit"
|
||||
assert_final_logical_text "alternate screen exit"
|
||||
|
||||
cleanup
|
||||
exit $exit_status
|
||||
94
regress/input-replies.sh
Normal file
94
regress/input-replies.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.5
|
||||
|
||||
TMP=$(mktemp)
|
||||
EXP=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 80 -y 24 -s replies \; \
|
||||
set-window-option -t replies:0 remain-on-exit on || exit 1
|
||||
$TMUX set-option -s set-clipboard on || exit 1
|
||||
$TMUX set-option -s get-clipboard buffer || exit 1
|
||||
printf Hello | $TMUX load-buffer -
|
||||
sleep 0.5
|
||||
|
||||
exit_status=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "FAIL: $1"
|
||||
diff -u "$EXP" "$TMP"
|
||||
exit_status=1
|
||||
}
|
||||
|
||||
query()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
seq=$3
|
||||
count=$4
|
||||
setup=$5
|
||||
|
||||
$TMUX respawn-window -k -t replies:0 \
|
||||
"stty raw -echo min 1 time 20; printf '$setup'; printf '$seq'; dd bs=1 count=$count 2>/dev/null | cat -v >$TMP"
|
||||
sleep 0.5
|
||||
printf "%s" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name"
|
||||
}
|
||||
|
||||
query_timeout()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
seq=$3
|
||||
setup=$4
|
||||
|
||||
$TMUX respawn-window -k -t replies:0 \
|
||||
"stty raw -echo min 0 time 5; printf '$setup'; printf '$seq'; sleep 0.1; dd bs=1 count=128 2>/dev/null | cat -v >$TMP"
|
||||
sleep 1
|
||||
printf "%s" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name"
|
||||
}
|
||||
|
||||
query "dsr-ok" '^[[0n' '\033[5n' 4 ''
|
||||
query "dsr-cursor" '^[[1;1R' '\033[6n' 6 ''
|
||||
query "da-primary" '^[[?1;2c' '\033[c' 7 ''
|
||||
query "da-secondary" '^[[>84;0;0c' '\033[>c' 10 ''
|
||||
query "decrqm-irm-reset" '^[[4;2$y' '\033[4$p' 7 ''
|
||||
query "decrqm-irm-set" '^[[4;1$y' '\033[4$p' 7 '\033[4h'
|
||||
query "decrqm-cursor-keys-reset" '^[[?1;2$y' '\033[?1$p' 8 ''
|
||||
query "decrqm-cursor-keys-set" '^[[?1;1$y' '\033[?1$p' 8 '\033[?1h'
|
||||
query "decrqm-columns" '^[[?3;4$y' '\033[?3$p' 8 ''
|
||||
query "decrqm-origin-reset" '^[[?6;2$y' '\033[?6$p' 8 ''
|
||||
query "decrqm-origin-set" '^[[?6;1$y' '\033[?6$p' 8 '\033[?6h'
|
||||
query "decrqm-wrap-set" '^[[?7;1$y' '\033[?7$p' 8 ''
|
||||
query "decrqm-wrap-reset" '^[[?7;2$y' '\033[?7$p' 8 '\033[?7l'
|
||||
query "decrqm-cursor-visible-set" '^[[?25;1$y' '\033[?25$p' 9 ''
|
||||
query "decrqm-cursor-visible-reset" '^[[?25;2$y' '\033[?25$p' 9 '\033[?25l'
|
||||
query "decrqm-mouse-standard-set" '^[[?1000;1$y' '\033[?1000$p' 11 '\033[?1000h'
|
||||
query "decrqm-mouse-button-set" '^[[?1002;1$y' '\033[?1002$p' 11 '\033[?1002h'
|
||||
query "decrqm-mouse-all-set" '^[[?1003;1$y' '\033[?1003$p' 11 '\033[?1003h'
|
||||
query "decrqm-focus-set" '^[[?1004;1$y' '\033[?1004$p' 11 '\033[?1004h'
|
||||
query "decrqm-mouse-utf8-set" '^[[?1005;1$y' '\033[?1005$p' 11 '\033[?1005h'
|
||||
query "decrqm-mouse-sgr-set" '^[[?1006;1$y' '\033[?1006$p' 11 '\033[?1006h'
|
||||
query "decrqm-bracket-paste-set" '^[[?2004;1$y' '\033[?2004$p' 11 '\033[?2004h'
|
||||
query "decrqm-theme-updates-set" '^[[?2031;1$y' '\033[?2031$p' 11 '\033[?2031h'
|
||||
query "decrqss-cursor-style" '^[P1$r q0 q^[\' '\033P$q q\033\\' 12 ''
|
||||
|
||||
query_timeout "osc-10-query" '^[]10;rgb:ffff/0000/0000^G' '\033]10;?\007' '\033]10;red\007'
|
||||
query_timeout "osc-11-query" '^[]11;rgb:0000/0000/ffff^G' '\033]11;?\007' '\033]11;blue\007'
|
||||
query_timeout "osc-12-query" '^[]12;rgb:0000/ffff/0000^G' '\033]12;?\007' '\033]12;green\007'
|
||||
query_timeout "osc-4-query" '^[]4;1;rgb:ffff/0000/0000^G' '\033]4;1;?\007' '\033]4;1;red\007'
|
||||
query_timeout "osc-104-reset-query" '' '\033]4;1;?\007' '\033]4;1;red\007\033]104;1\007'
|
||||
query_timeout "osc-52-query" '^[]52;c;SGVsbG8=^G' '\033]52;c;?\007' ''
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
117
regress/input-requests.sh
Normal file
117
regress/input-requests.sh
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
python3 - "$TEST_TMUX" <<'PY'
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
tmux = sys.argv[1]
|
||||
server = [tmux, "-Ltest", "-f/dev/null"]
|
||||
|
||||
def run(*args, check=True):
|
||||
return subprocess.run(server + list(args), check=check,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
def attach():
|
||||
pid, fd = os.forkpty()
|
||||
if pid == 0:
|
||||
os.environ["TERM"] = "xterm-256color"
|
||||
os.execl(tmux, tmux, "-Ltest", "-f/dev/null", "attach-session",
|
||||
"-t", "requests")
|
||||
os.set_blocking(fd, False)
|
||||
return pid, fd
|
||||
|
||||
def read_until(fd, needle, timeout=5):
|
||||
end = time.time() + timeout
|
||||
data = b""
|
||||
while time.time() < end:
|
||||
r, _, _ = select.select([fd], [], [], 0.05)
|
||||
if fd in r:
|
||||
try:
|
||||
chunk = os.read(fd, 4096)
|
||||
except BlockingIOError:
|
||||
chunk = b""
|
||||
if chunk == b"":
|
||||
continue
|
||||
data += chunk
|
||||
if needle in data:
|
||||
return data
|
||||
raise RuntimeError("did not see terminal request %r in %r" %
|
||||
(needle, data))
|
||||
|
||||
def wait_file(path, timeout=5):
|
||||
end = time.time() + timeout
|
||||
while time.time() < end:
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
if data:
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
time.sleep(0.05)
|
||||
return b""
|
||||
|
||||
def respawn(command):
|
||||
run("respawn-window", "-k", "-t", "requests:0", command)
|
||||
time.sleep(0.2)
|
||||
|
||||
def cleanup(pid=None):
|
||||
if pid is not None:
|
||||
try:
|
||||
os.kill(pid, signal.SIGHUP)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
run("kill-server", check=False)
|
||||
|
||||
run("kill-server", check=False)
|
||||
run("new-session", "-d", "-x", "80", "-y", "24", "-s", "requests",
|
||||
"sleep 60")
|
||||
|
||||
pid, fd = attach()
|
||||
try:
|
||||
time.sleep(0.5)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
palette_out = f.name
|
||||
respawn("stty raw -echo min 1 time 50; "
|
||||
"printf '\\033]4;99;?\\033\\\\'; "
|
||||
"dd bs=1 count=27 2>/dev/null | cat -v >%s; sleep 1" %
|
||||
palette_out)
|
||||
read_until(fd, b"\033]4;99;?\033\\")
|
||||
os.write(fd, b"\033]4;99;rgb:0101/0202/0303\033\\")
|
||||
got = wait_file(palette_out)
|
||||
expected = b"^[]4;99;rgb:0101/0202/0303^[\\"
|
||||
if got != expected:
|
||||
raise AssertionError("palette reply: expected %r got %r" %
|
||||
(expected, got))
|
||||
|
||||
run("set-option", "-s", "set-clipboard", "on")
|
||||
run("set-option", "-s", "get-clipboard", "request")
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
clip_out = f.name
|
||||
respawn("stty raw -echo min 1 time 50; "
|
||||
"printf '\\033]52;c;?\\033\\\\'; "
|
||||
"dd bs=1 count=21 2>/dev/null | cat -v >%s; sleep 1" %
|
||||
clip_out)
|
||||
data = read_until(fd, b"]52;")
|
||||
if b"?" not in data:
|
||||
raise RuntimeError("clipboard request missing query in %r" % data)
|
||||
os.write(fd, b"\033]52;c;UmVxdWVzdA==\033\\")
|
||||
got = wait_file(clip_out)
|
||||
expected = b"^[]52;c;UmVxdWVzdA==^[\\"
|
||||
if got != expected:
|
||||
raise AssertionError("clipboard reply: expected %r got %r" %
|
||||
(expected, got))
|
||||
finally:
|
||||
cleanup(pid)
|
||||
PY
|
||||
73
regress/input-scroll.sh
Normal file
73
regress/input-scroll.sh
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane wrap 5 3 'abcdeF'
|
||||
check_capture wrap 'abcde
|
||||
F'
|
||||
check_cursor wrap '1,1'
|
||||
check_flags wrap 'W abcde
|
||||
- F'
|
||||
check_joined wrap 'abcdeF'
|
||||
|
||||
start_pane wraplast 5 3 'abcd\033[5GZQ'
|
||||
check_capture wraplast 'abcdZ
|
||||
Q'
|
||||
check_cursor wraplast '1,1'
|
||||
|
||||
start_pane nowrap 5 3 '\033[?7labcdeF'
|
||||
check_capture nowrap 'abcdF'
|
||||
check_cursor nowrap '4,0'
|
||||
|
||||
start_pane origin 6 4 '111111\n222222\n333333\n444444\033[2;3r\033[?6h\033[1;1HAA\033[?6l\033[r'
|
||||
check_capture origin '111111
|
||||
AA2222
|
||||
333333
|
||||
444444'
|
||||
|
||||
start_pane scrollup 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[3;1HAAAAA\nBBBBB\033[r'
|
||||
check_capture scrollup '11111
|
||||
AAAAA
|
||||
BBBBB
|
||||
44444'
|
||||
|
||||
start_pane scrolldown 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[2;1H\033[TZZZZZ\033[r'
|
||||
check_capture scrolldown '11111
|
||||
ZZZZZ
|
||||
22222
|
||||
44444'
|
||||
|
||||
start_pane ri 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[2;1H\033MZZZZZ\033[r'
|
||||
check_capture ri '11111
|
||||
ZZZZZ
|
||||
22222
|
||||
44444'
|
||||
|
||||
start_pane nel 5 3 'AA\033EBC\n'
|
||||
check_capture nel 'AA
|
||||
BC'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 5 -y 3 -s history \; \
|
||||
set-option -g history-limit 3 \; \
|
||||
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; sleep 2" || exit 1
|
||||
sleep 0.3
|
||||
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
|
||||
printf "%s\n" '01
|
||||
02
|
||||
03
|
||||
04
|
||||
05
|
||||
06' >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "history limit"
|
||||
|
||||
$TMUX clear-history -t history:
|
||||
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
|
||||
printf "%s\n" '04
|
||||
05
|
||||
06' >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "clear-history"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
26
regress/input-sgr.sh
Normal file
26
regress/input-sgr.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane sgr-basic 20 3 '\033[1;2;3;4;5;7;8;9mA\033[22;23;24;25;27;28;29mB\n'
|
||||
check_capture sgr-basic 'AB'
|
||||
$TMUX capture-pane -peN -t sgr-basic: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane sgr-colour 20 3 '\033[31;42mA\033[38;5;196;48;5;22mB\033[38;2;1;2;3;48;2;4;5;6mC\033[39;49mD\n'
|
||||
check_capture sgr-colour 'ABCD'
|
||||
$TMUX capture-pane -peN -t sgr-colour: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane sgr-underline 20 3 '\033[4:1mA\033[4:2mB\033[4:3mC\033[4:4mD\033[4:5mE\033[4:0mF\n'
|
||||
check_capture sgr-underline 'ABCDEF'
|
||||
$TMUX capture-pane -peN -t sgr-underline: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane sgr-uscolour 20 3 '\033[58;5;45;4mA\033[58:2::10:20:30mB\033[59mC\n'
|
||||
check_capture sgr-uscolour 'ABC'
|
||||
$TMUX capture-pane -peN -t sgr-uscolour: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane sgr-reset 20 3 '\033[90;100mA\033[0mB\033[91;101mC\033[39;49mD\n'
|
||||
check_capture sgr-reset 'ABCD'
|
||||
$TMUX capture-pane -peN -t sgr-reset: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
45
regress/input-unicode.sh
Normal file
45
regress/input-unicode.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane wide 10 3 '\343\201\202B\rX\n'
|
||||
check_capture wide 'X B'
|
||||
check_flags wide 'X X B'
|
||||
|
||||
start_pane widepad 10 3 'A\343\201\202B\r\033[2CX\n'
|
||||
check_capture widepad 'A XB'
|
||||
check_flags widepad 'X A XB'
|
||||
|
||||
start_pane wideedge 5 3 'abc\343\201\202Z\n'
|
||||
check_capture wideedge 'abcあ
|
||||
Z'
|
||||
check_cursor wideedge '0,2'
|
||||
check_joined wideedge 'abcあZ'
|
||||
|
||||
start_pane wideeol 5 3 'abcd\343\201\202Z\n'
|
||||
check_capture wideeol 'abcd
|
||||
あZ'
|
||||
check_cursor wideeol '0,2'
|
||||
|
||||
start_pane combine 10 3 'e\314\201\n'
|
||||
check_capture combine 'é'
|
||||
check_cursor combine '0,1'
|
||||
|
||||
start_pane combinewide 10 3 '\343\201\202\314\201X\n'
|
||||
check_capture combinewide 'あ́X'
|
||||
check_cursor combinewide '0,1'
|
||||
|
||||
start_pane variation 10 3 '\342\234\224\357\270\217X\n'
|
||||
check_capture variation '✔️X'
|
||||
check_cursor variation '0,1'
|
||||
|
||||
start_pane flag 10 3 '\360\237\207\254\360\237\207\247X\n'
|
||||
check_capture flag '🇬🇧X'
|
||||
check_cursor flag '0,1'
|
||||
|
||||
start_pane combining-left 10 3 '\314\201A\n'
|
||||
check_capture combining-left 'A'
|
||||
check_cursor combining-left '0,1'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
216
regress/lifecycle-deferred.sh
Normal file
216
regress/lifecycle-deferred.sh
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/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 -Llifecycle-deferred -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Llifecycle-deferred-outer -f/dev/null"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
IN="$TMPDIR/in"
|
||||
OUT="$TMPDIR/out"
|
||||
CONTROL_PID=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&- 2>/dev/null
|
||||
[ -n "$CONTROL_PID" ] && kill "$CONTROL_PID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" >&2
|
||||
[ -s "$OUT" ] && sed -n '1,120p' "$OUT" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_tmux()
|
||||
{
|
||||
out=
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
out=$(timeout 10 $TMUX "$@" 2>&1)
|
||||
else
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
fi
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "tmux $* failed ($rc): $out"
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
send_control()
|
||||
{
|
||||
printf '%s\n' "$1" >&3 || fail "failed to write control command: $1"
|
||||
}
|
||||
|
||||
wait_clients()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt 50 ]; do
|
||||
have=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$have" -eq "$want" ] && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_format()
|
||||
{
|
||||
target=$1
|
||||
format=$2
|
||||
want=$3
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt 50 ]; do
|
||||
have=$($TMUX display-message -p -t "$target" "$format" 2>/dev/null)
|
||||
[ "$have" = "$want" ] && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
run_tmux has-session -t life >/dev/null
|
||||
fields=$(run_tmux display-message -p -t life \
|
||||
'#{session_name}:#{window_id}:#{pane_id}:#{session_windows}:#{window_panes}')
|
||||
case "$fields" in
|
||||
life:@*:%*:*) ;;
|
||||
*) fail "bad current fields after $1: $fields" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_control_output()
|
||||
{
|
||||
sleep 1
|
||||
|
||||
if grep -E '(^%error |server exited|lost server|\(null\)|no current)' \
|
||||
"$OUT" >/dev/null 2>&1; then
|
||||
fail "control client reported an error or invalid object"
|
||||
fi
|
||||
|
||||
awk '
|
||||
$1 == "%session-window-changed" {
|
||||
if (NF != 3 || $2 !~ /^\$[0-9]+$/ || $3 !~ /^@[0-9]+$/)
|
||||
bad = 1
|
||||
}
|
||||
$1 == "%subscription-changed" {
|
||||
colon = 0
|
||||
for (i = 2; i <= NF; i++)
|
||||
if ($i == ":")
|
||||
colon = 1
|
||||
if (NF < 7 || !colon)
|
||||
bad = 1
|
||||
}
|
||||
END { exit bad }
|
||||
' "$OUT" || fail "control client received a malformed notification"
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
run_tmux new-session -d -s life -n prompt -x 80 -y 24 'sleep 1000' \
|
||||
>/dev/null
|
||||
run_tmux set-option -g detach-on-destroy off >/dev/null
|
||||
run_tmux new-window -t life -n tree 'sleep 1000' >/dev/null
|
||||
run_tmux new-window -t life -n work 'sleep 1000' >/dev/null
|
||||
run_tmux select-window -t life:prompt >/dev/null
|
||||
|
||||
run_tmux set-hook -g after-new-window \
|
||||
'display-message -p "hook-new #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g after-split-window \
|
||||
'display-message -p "hook-split #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g after-kill-pane \
|
||||
'display-message -p "hook-kill #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g pane-exited \
|
||||
'display-message -p "hook-exit #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g window-layout-changed \
|
||||
'display-message -p "hook-layout #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g session-window-changed \
|
||||
'display-message -p "hook-current #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux bind-key -n M-p command-prompt -P -p '(life)' \
|
||||
"set -g @lifecycle-prompt '%% #{session_name}:#{window_id}:#{pane_id}'" \
|
||||
>/dev/null
|
||||
|
||||
$TMUX2 new-session -d -s outer -n prompt -x 80 -y 24 "$TMUX attach -t life" \
|
||||
|| fail "failed to start first attached client"
|
||||
$TMUX2 new-window -t outer -n tree "$TMUX attach -t life" \
|
||||
|| fail "failed to start second attached client"
|
||||
wait_clients 2 || fail "normal clients did not attach"
|
||||
|
||||
$TMUX2 send-keys -t outer:prompt M-p || fail "failed to open command prompt"
|
||||
sleep 1
|
||||
run_tmux choose-tree -t life:tree.0 \
|
||||
-F 'tree #{session_name}:#{window_id}:#{pane_id}' >/dev/null
|
||||
wait_format life:tree.0 '#{pane_mode}' tree-mode || \
|
||||
fail "choose-tree did not enter tree-mode"
|
||||
|
||||
mkfifo "$IN" || fail "failed to create control fifo"
|
||||
(cat "$IN" | $TMUX -C attach -t life >"$OUT" 2>&1) &
|
||||
CONTROL_PID=$!
|
||||
exec 3>"$IN"
|
||||
wait_clients 3 || fail "control client did not attach"
|
||||
|
||||
CONTROL_CLIENT=$($TMUX list-clients -F '#{client_name} #{client_control_mode}' |
|
||||
awk '$2 == 1 { print $1; exit }')
|
||||
[ -n "$CONTROL_CLIENT" ] || fail "missing control client"
|
||||
|
||||
send_control "refresh-client -B 'all:%*:#{session_name}:#{window_id}:#{pane_id}:#{session_windows}:#{window_panes}'"
|
||||
send_control "refresh-client -B 'windows:@*:#{session_name}:#{window_id}:#{window_index}:#{window_panes}'"
|
||||
sleep 1
|
||||
|
||||
run_tmux kill-pane -t life:prompt.0 >/dev/null
|
||||
run_tmux kill-window -t life:tree >/dev/null
|
||||
assert_alive "killing prompt and tree panes"
|
||||
|
||||
i=1
|
||||
while [ "$i" -le 20 ]; do
|
||||
# Keep this sequence fixed: failures should reproduce on the same pass.
|
||||
s=ld$i
|
||||
ctl=ctl$i
|
||||
idx=$((50 + i))
|
||||
|
||||
run_tmux new-session -d -s "$s" -n base 'sleep 1000' >/dev/null
|
||||
run_tmux split-window -t "$s:base" 'sleep 1000' >/dev/null
|
||||
run_tmux respawn-pane -k -t "$s:base.1" 'sleep 1000' >/dev/null
|
||||
run_tmux kill-pane -t "$s:base.1" >/dev/null
|
||||
|
||||
run_tmux new-window -t "$s" -n second 'sleep 1000' >/dev/null
|
||||
run_tmux link-window -s "$s:second" -t "life:$idx" >/dev/null
|
||||
run_tmux unlink-window -t "life:$idx" >/dev/null
|
||||
|
||||
run_tmux new-window -t "$s" -n single 'sleep 1000' >/dev/null
|
||||
run_tmux kill-pane -t "$s:single.0" >/dev/null
|
||||
run_tmux kill-window -t "$s:base" >/dev/null
|
||||
|
||||
run_tmux new-session -d -s "$ctl" -n ctl 'sleep 1000' >/dev/null
|
||||
run_tmux switch-client -c "$CONTROL_CLIENT" -t "$ctl" >/dev/null
|
||||
run_tmux kill-session -t "$ctl" >/dev/null
|
||||
|
||||
run_tmux kill-session -t "$s" >/dev/null
|
||||
assert_alive "iteration $i"
|
||||
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
check_control_output
|
||||
cleanup
|
||||
exit 0
|
||||
297
regress/mode-mutation.sh
Normal file
297
regress/mode-mutation.sh
Normal file
@@ -0,0 +1,297 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise modes while their backing objects are changed from outside the
|
||||
# client displaying the mode. This catches stale selection indexes and pointers
|
||||
# after a mode list is shrunk or rebuilt.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
capture()
|
||||
{
|
||||
$TMUX2 capture-pane -p -t out:0 2>/dev/null
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
$TMUX display-message -p 'alive' >/dev/null 2>&1 || \
|
||||
fail "$1: server exited"
|
||||
}
|
||||
|
||||
wait_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$c" -eq "$1" ] && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "expected $1 clients, have $c"
|
||||
}
|
||||
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
capture | grep -q "$1" && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$1'"
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
t=$1
|
||||
want=$2
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($TMUX display-message -p -t "$t" '#{pane_in_mode}' \
|
||||
2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane $t mode state is $got, expected $want"
|
||||
}
|
||||
|
||||
repeat_key()
|
||||
{
|
||||
t=$1
|
||||
key=$2
|
||||
count=$3
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt "$count" ]; do
|
||||
$TMUX send-keys -t "$t" "$key" || \
|
||||
fail "failed to send $key to $t"
|
||||
i=$((i + 1))
|
||||
done
|
||||
}
|
||||
|
||||
start_client()
|
||||
{
|
||||
s=$1
|
||||
cmd=${2:-cat}
|
||||
|
||||
cleanup
|
||||
$TMUX new-session -d -s "$s" -n main -x 80 -y 24 "$cmd" || \
|
||||
fail "$s: new-session failed"
|
||||
$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t $s" || \
|
||||
fail "$s: outer client failed"
|
||||
wait_clients 1
|
||||
}
|
||||
|
||||
new_outer_client()
|
||||
{
|
||||
s=$1
|
||||
|
||||
$TMUX2 new-window -d -t out: "$TMUX attach -t $s" || \
|
||||
fail "$s: outer client failed"
|
||||
}
|
||||
|
||||
client_for_session()
|
||||
{
|
||||
$TMUX list-clients -F '#{client_name} #{client_session}' |
|
||||
awk -v s="$1" '$2 == s { print $1; exit }'
|
||||
}
|
||||
|
||||
test_choose_tree()
|
||||
{
|
||||
start_client tree-a
|
||||
$TMUX new-session -d -s tree-b -n one 'cat' || fail "tree-b failed"
|
||||
$TMUX new-window -d -t tree-b -n two 'cat' || fail "tree-b:1 failed"
|
||||
$TMUX new-session -d -s tree-c -n one 'cat' || fail "tree-c failed"
|
||||
$TMUX new-session -d -s tree-d -n one 'cat' || fail "tree-d failed"
|
||||
$TMUX split-window -d -t tree-a:0 'cat' || fail "tree split failed"
|
||||
|
||||
$TMUX choose-tree -t tree-a:0 -O index -F 'MT #{session_name}:#{window_index}.#{pane_index}' || \
|
||||
fail "choose-tree failed"
|
||||
wait_for 'MT '
|
||||
repeat_key tree-a:0 j 40
|
||||
|
||||
$TMUX kill-session -t tree-d || fail "tree kill-session failed"
|
||||
$TMUX kill-session -t tree-c || fail "tree kill-session failed"
|
||||
$TMUX rename-session -t tree-b tree-renamed || fail "tree rename failed"
|
||||
$TMUX rename-window -t tree-renamed:0 renamed || fail "tree rename-window failed"
|
||||
$TMUX kill-window -t tree-renamed:1 || fail "tree kill-window failed"
|
||||
side=$($TMUX split-window -d -P -F '#{pane_id}' -t tree-a:0 'cat') || \
|
||||
fail "tree side split failed"
|
||||
$TMUX break-pane -d -s "$side" || fail "tree break-pane failed"
|
||||
$TMUX join-pane -d -s "$side" -t tree-a:0.0 || \
|
||||
fail "tree join-pane failed"
|
||||
i=0
|
||||
while [ "$i" -lt 12 ]; do
|
||||
$TMUX new-window -d -t tree-a -n "new$i" 'cat' || \
|
||||
fail "tree new-window failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
assert_alive "choose-tree mutation"
|
||||
$TMUX send-keys -t tree-a:0 k j l h Enter || \
|
||||
fail "choose-tree keys failed"
|
||||
wait_mode tree-a:0 0
|
||||
assert_alive "choose-tree exit"
|
||||
}
|
||||
|
||||
test_choose_buffer()
|
||||
{
|
||||
start_client buffer-a
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 30 ]; do
|
||||
$TMUX set-buffer -b "mbuf$i" "buffer mutation $i" || \
|
||||
fail "set-buffer failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
$TMUX choose-buffer -t buffer-a:0 -F 'MB #{buffer_name}' || \
|
||||
fail "choose-buffer failed"
|
||||
wait_for 'MB '
|
||||
repeat_key buffer-a:0 j 40
|
||||
|
||||
i=8
|
||||
while [ "$i" -lt 30 ]; do
|
||||
$TMUX delete-buffer -b "mbuf$i" || fail "delete-buffer failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
i=30
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX set-buffer -b "mbuf$i" "new buffer mutation $i" || \
|
||||
fail "new set-buffer failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
assert_alive "choose-buffer mutation"
|
||||
$TMUX send-keys -t buffer-a:0 k j Enter || \
|
||||
fail "choose-buffer keys failed"
|
||||
wait_mode buffer-a:0 0
|
||||
assert_alive "choose-buffer exit"
|
||||
}
|
||||
|
||||
test_choose_client()
|
||||
{
|
||||
start_client client-a
|
||||
$TMUX new-session -d -s client-b -n main 'cat' || fail "client-b failed"
|
||||
$TMUX new-session -d -s client-c -n main 'cat' || fail "client-c failed"
|
||||
new_outer_client client-b
|
||||
new_outer_client client-c
|
||||
wait_clients 3
|
||||
|
||||
$TMUX choose-client -t client-a:0 -F 'MC #{client_session}' || \
|
||||
fail "choose-client failed"
|
||||
wait_for 'MC '
|
||||
repeat_key client-a:0 j 20
|
||||
|
||||
c=$(client_for_session client-c)
|
||||
[ -n "$c" ] || fail "client-c client not found"
|
||||
$TMUX detach-client -t "$c" || fail "detach client-c failed"
|
||||
c=$(client_for_session client-b)
|
||||
[ -n "$c" ] || fail "client-b client not found"
|
||||
$TMUX detach-client -t "$c" || fail "detach client-b failed"
|
||||
|
||||
$TMUX new-session -d -s client-d -n main 'cat' || fail "client-d failed"
|
||||
new_outer_client client-d
|
||||
wait_clients 2
|
||||
|
||||
assert_alive "choose-client mutation"
|
||||
$TMUX send-keys -t client-a:0 k j Enter || \
|
||||
fail "choose-client keys failed"
|
||||
wait_mode client-a:0 0
|
||||
assert_alive "choose-client exit"
|
||||
}
|
||||
|
||||
test_customize_mode()
|
||||
{
|
||||
start_client option-a
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 30 ]; do
|
||||
$TMUX set-option -g "@mode_mut_$i" "$i" || \
|
||||
fail "set option failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
$TMUX customize-mode -t option-a:0 -F 'MO #{option_name}=#{option_value}' || \
|
||||
fail "customize-mode failed"
|
||||
wait_mode option-a:0 1
|
||||
repeat_key option-a:0 j 80
|
||||
|
||||
i=10
|
||||
while [ "$i" -lt 30 ]; do
|
||||
$TMUX set-option -gu "@mode_mut_$i" || fail "unset option failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
i=30
|
||||
while [ "$i" -lt 55 ]; do
|
||||
$TMUX set-option -g "@mode_mut_$i" "$i" || \
|
||||
fail "new option failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
$TMUX set-option -g status-left 'mutated' || fail "status-left failed"
|
||||
$TMUX rename-session -t option-a option-renamed || fail "option rename failed"
|
||||
|
||||
assert_alive "customize-mode mutation"
|
||||
$TMUX send-keys -t option-renamed:0 k j C-d C-u q || \
|
||||
fail "customize-mode keys failed"
|
||||
wait_mode option-renamed:0 0
|
||||
assert_alive "customize-mode exit"
|
||||
}
|
||||
|
||||
test_copy_mode()
|
||||
{
|
||||
start_client copy-a 'i=0; while [ $i -lt 200 ]; do echo "copy mutation line $i"; i=$((i + 1)); done; cat'
|
||||
$TMUX set-window-option -g mode-keys vi || fail "mode-keys failed"
|
||||
$TMUX split-window -d -t copy-a:0 'cat' || fail "copy split failed"
|
||||
|
||||
$TMUX copy-mode -t copy-a:0 || fail "copy-mode failed"
|
||||
wait_mode copy-a:0 1
|
||||
repeat_key copy-a:0 k 20
|
||||
|
||||
$TMUX rename-window -t copy-a:0 renamed || fail "copy rename-window failed"
|
||||
side=$($TMUX split-window -d -P -F '#{pane_id}' -t copy-a:renamed 'cat') || \
|
||||
fail "copy side split failed"
|
||||
$TMUX break-pane -d -s "$side" || fail "copy break-pane failed"
|
||||
$TMUX join-pane -d -s "$side" -t copy-a:renamed.0 || \
|
||||
fail "copy join-pane failed"
|
||||
$TMUX kill-pane -t copy-a:renamed.1 || fail "copy kill-pane failed"
|
||||
$TMUX new-window -d -t copy-a -n extra 'cat' || fail "copy new-window failed"
|
||||
$TMUX kill-window -t copy-a:extra || fail "copy kill-window failed"
|
||||
$TMUX rename-session -t copy-a copy-renamed || fail "copy rename failed"
|
||||
|
||||
assert_alive "copy-mode mutation"
|
||||
$TMUX send-keys -t copy-renamed:renamed.0 j k C-d C-u q || \
|
||||
fail "copy-mode keys failed"
|
||||
wait_mode copy-renamed:renamed.0 0
|
||||
assert_alive "copy-mode exit"
|
||||
}
|
||||
|
||||
cleanup
|
||||
test_choose_tree
|
||||
test_choose_buffer
|
||||
test_choose_client
|
||||
test_customize_mode
|
||||
test_copy_mode
|
||||
cleanup
|
||||
exit 0
|
||||
161
regress/options-array.sh
Normal file
161
regress/options-array.sh
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of array options in the options engine (options_array_* in options.c
|
||||
# and the array handling in cmd-set-option.c / cmd-show-options.c).
|
||||
#
|
||||
# Array options are indexed by integer. This exercises: setting a whole array
|
||||
# from a separator-delimited string; per-index set with option[N]; -a append
|
||||
# (which lands at the next free index); show ordering by ascending index and
|
||||
# preservation of gaps; per-index unset with -u; show -v of a single index and
|
||||
# of a missing index; and per-option separators (user-keys splits only on
|
||||
# comma, update-environment on space or comma).
|
||||
#
|
||||
# update-environment (session), status-format (session), user-keys (server)
|
||||
# and command-alias (server) are used as representative array options.
|
||||
#
|
||||
# options-scope.sh covers scoping/inheritance and options-values.sh covers
|
||||
# value validation.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_array $args $expected
|
||||
#
|
||||
# Compare the full (multi-line) show output for an array option with a
|
||||
# newline-separated $expected string.
|
||||
check_array()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$(printf '%s' "$2")" ]; then
|
||||
echo "show $1 (array) 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
|
||||
}
|
||||
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
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
|
||||
|
||||
# --- whole-array assignment splits on the separator -----------------------
|
||||
#
|
||||
# update-environment has the default " ," separator, so a single string value
|
||||
# is split into consecutive indices starting at 0.
|
||||
check_ok set -g update-environment "AAA BBB,CCC"
|
||||
check_array "-g update-environment" "update-environment[0] AAA
|
||||
update-environment[1] BBB
|
||||
update-environment[2] CCC"
|
||||
|
||||
# --- -a append goes to the next free index --------------------------------
|
||||
check_ok set -ga update-environment "DDD"
|
||||
check_array "-g update-environment" "update-environment[0] AAA
|
||||
update-environment[1] BBB
|
||||
update-environment[2] CCC
|
||||
update-environment[3] DDD"
|
||||
|
||||
# --- per-index unset leaves a gap; show preserves order and gaps ----------
|
||||
check_ok set -gu update-environment[1]
|
||||
check_array "-g update-environment" "update-environment[0] AAA
|
||||
update-environment[2] CCC
|
||||
update-environment[3] DDD"
|
||||
# show -v of an existing index returns its value; a missing index is empty.
|
||||
check_value "-gv update-environment[0]" "AAA"
|
||||
check_value "-gv update-environment[1]" ""
|
||||
|
||||
# --- explicit indexed set, including out-of-order and gaps ----------------
|
||||
#
|
||||
# status-format is a session array; assigning an empty string first clears its
|
||||
# multi-index default, then set specific indices out of order and confirm show
|
||||
# sorts by ascending index and keeps the gap at [1].
|
||||
check_ok set -g status-format ""
|
||||
check_array "-g status-format" "status-format"
|
||||
check_ok set -g status-format[5] "five"
|
||||
check_ok set -g status-format[0] "zero"
|
||||
check_ok set -g status-format[2] "two"
|
||||
check_array "-g status-format" "status-format[0] zero
|
||||
status-format[2] two
|
||||
status-format[5] five"
|
||||
|
||||
# --- comma-only separator (user-keys) -------------------------------------
|
||||
#
|
||||
# user-keys splits only on comma, so an embedded space stays within one entry
|
||||
# (and show quotes a value containing a space).
|
||||
check_ok set -g user-keys "One,Two Three"
|
||||
check_array "-g user-keys" 'user-keys[0] One
|
||||
user-keys[1] "Two Three"'
|
||||
|
||||
# --- command-type array (a hook) ------------------------------------------
|
||||
#
|
||||
# Hooks are command arrays: an indexed value is parsed as a command when set
|
||||
# and re-printed from the parsed command list; a syntax error is reported.
|
||||
check_ok set -g alert-bell[0] "display-message hi"
|
||||
check_value "-gv alert-bell[0]" "display-message hi"
|
||||
check_fail "syntax error" set -g alert-bell[0] "if -x {"
|
||||
|
||||
# --- colour-type array ----------------------------------------------------
|
||||
#
|
||||
# pane-colours is a colour array; an indexed value is validated as a colour.
|
||||
check_ok set -w pane-colours[0] red
|
||||
check_value "-wv pane-colours[0]" "red"
|
||||
check_fail "bad colour: xxxyyy" set -w pane-colours[1] xxxyyy
|
||||
|
||||
# --- -o refuses to overwrite an already-set index -------------------------
|
||||
check_ok set -g command-alias[9] "x=list-keys"
|
||||
check_fail "already set: command-alias[9]" set -go command-alias[9] "y=list-keys"
|
||||
|
||||
# --- non-array option rejects index syntax --------------------------------
|
||||
#
|
||||
# status-left is a plain string; indexing it is an error.
|
||||
check_fail "not an array: status-left[0]" set -g status-left[0] "x"
|
||||
|
||||
assert_alive "after options-array tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
208
regress/options-scope.sh
Normal file
208
regress/options-scope.sh
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the options engine scoping and inheritance, as described in the
|
||||
# OPTIONS section of tmux(1) and implemented in options.c, cmd-set-option.c and
|
||||
# cmd-show-options.c.
|
||||
#
|
||||
# This exercises: global vs session vs window vs pane precedence; -u to remove
|
||||
# an option (revealing the inherited value); -gu to restore a global option to
|
||||
# its compiled default; scope inference from the option name (-w/-p and the
|
||||
# set-window-option alias); show -v (which does NOT walk parents) versus show -A
|
||||
# (which does, marking inherited values with a trailing *); unknown/ambiguous
|
||||
# option errors and -q suppression; and user options (@foo) at every scope.
|
||||
#
|
||||
# options-values.sh covers value validation and options-array.sh covers arrays.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check_value $args $expected
|
||||
#
|
||||
# Run show-option with $args and compare the single-line output with $expected.
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
# Run a command and require that it succeeds.
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
#
|
||||
# Run a command and require that it fails with the given error message.
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
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
|
||||
|
||||
# --- global vs session precedence -----------------------------------------
|
||||
#
|
||||
# status-left is a session option. A value set at the session scope shadows
|
||||
# the global one; show -v at each scope reports that scope's own value.
|
||||
check_ok set -g status-left "GLOBAL"
|
||||
check_ok set status-left "SESSION"
|
||||
check_value "-v status-left" "SESSION"
|
||||
check_value "-gv status-left" "GLOBAL"
|
||||
|
||||
# show -v does NOT inherit: -u removes the session entry, after which the
|
||||
# session-scope show -v is empty even though the global value still exists.
|
||||
check_ok set -u status-left
|
||||
check_value "-v status-left" ""
|
||||
check_value "-gv status-left" "GLOBAL"
|
||||
|
||||
# show -A walks the parent scopes and marks an inherited value with a "*".
|
||||
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: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- -gu restores the compiled default ------------------------------------
|
||||
#
|
||||
# Removing a global option with -u restores its built-in default rather than
|
||||
# deleting it; status-left's default is the format "[#{session_name}] ".
|
||||
check_ok set -g status-left "GLOBAL2"
|
||||
check_value "-gv status-left" "GLOBAL2"
|
||||
check_ok set -gu status-left
|
||||
check_value "-gv status-left" "[#{session_name}] "
|
||||
|
||||
# --- scope inference from the option name ---------------------------------
|
||||
#
|
||||
# mode-keys is a window option, so a bare set-option infers the window scope;
|
||||
# set-window-option (setw) is an explicit alias for the same thing, and -g w
|
||||
# targets the global window options.
|
||||
check_ok set mode-keys vi
|
||||
check_value "-wv mode-keys" "vi"
|
||||
check_ok setw mode-keys emacs
|
||||
check_value "-wv mode-keys" "emacs"
|
||||
check_ok set -gw mode-keys vi
|
||||
check_value "-gwv mode-keys" "vi"
|
||||
|
||||
# cursor-colour is a window-and-pane option. A pane-scope value overrides a
|
||||
# window-scope one for that pane.
|
||||
check_ok set -w cursor-colour blue
|
||||
check_ok set -p cursor-colour red
|
||||
check_value "-pv cursor-colour" "red"
|
||||
out=$($TMUX show -Ap 2>/dev/null | grep '^cursor-colour ')
|
||||
if [ "$out" != "cursor-colour red" ]; then
|
||||
echo "pane cursor-colour did not override window value."
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- -U unsets a window option and clears pane copies ----------------------
|
||||
#
|
||||
# When a window option also has per-pane copies, -u on the window scope leaves
|
||||
# those pane copies in place; -U additionally removes the option from every
|
||||
# pane in the window, so all panes fall back to inheritance.
|
||||
$TMUX split-window -t main || exit 1
|
||||
panes=$($TMUX list-panes -t main -F '#{pane_id}')
|
||||
set -- $panes
|
||||
pa=$1
|
||||
pb=$2
|
||||
check_ok set -p -t "$pa" cursor-colour red
|
||||
check_ok set -p -t "$pb" cursor-colour blue
|
||||
check_ok set -w -t main cursor-colour green
|
||||
check_value "-pv -t $pa cursor-colour" "red"
|
||||
check_value "-pv -t $pb cursor-colour" "blue"
|
||||
check_value "-wv -t main cursor-colour" "green"
|
||||
check_ok set -Uw -t main cursor-colour
|
||||
check_value "-pv -t $pa cursor-colour" ""
|
||||
check_value "-pv -t $pb cursor-colour" ""
|
||||
check_value "-wv -t main cursor-colour" ""
|
||||
|
||||
# --- unknown, ambiguous and -q --------------------------------------------
|
||||
check_fail "invalid option: no-such-option" set -g no-such-option x
|
||||
check_fail "ambiguous option: status-l" set -g status-l x
|
||||
# A unique prefix resolves to the full option name.
|
||||
check_ok set -g status-inte 5
|
||||
check_value "-gv status-interval" "5"
|
||||
# -q suppresses the error and exits successfully.
|
||||
check_ok set -gq no-such-option x
|
||||
check_ok show -gqv no-such-option
|
||||
|
||||
# --- errors from unresolvable targets -------------------------------------
|
||||
#
|
||||
# A -t target that does not resolve produces a scope-specific error from
|
||||
# options_scope_from_name()/options_scope_from_flags().
|
||||
check_fail "no such session: nosuch" show -t nosuch status-left
|
||||
check_fail "no such window: nosuch" show -w -t nosuch mode-keys
|
||||
check_fail "no such pane: nosuch" set -p -t nosuch cursor-colour red
|
||||
|
||||
# --- show with no option name lists every option --------------------------
|
||||
#
|
||||
# show without a specific option walks the whole table (cmd_show_options_all).
|
||||
# Hooks are hidden unless -H is given.
|
||||
$TMUX set -g @listme "here" || exit 1
|
||||
if ! $TMUX show -g | grep -q '^@listme here$'; then
|
||||
echo "show -g did not list @listme."
|
||||
exit 1
|
||||
fi
|
||||
# alert-bell is a hook: only shown with -H.
|
||||
if $TMUX show -g | grep -q '^alert-bell'; then
|
||||
echo "show -g listed a hook without -H."
|
||||
exit 1
|
||||
fi
|
||||
if ! $TMUX show -gH | grep -q '^alert-bell'; then
|
||||
echo "show -gH did not list the alert-bell hook."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- user options at every scope ------------------------------------------
|
||||
#
|
||||
# @-prefixed user options can be created freely at any scope and do not
|
||||
# inherit type checking.
|
||||
check_ok set -g @u "global-user"
|
||||
check_ok set @u "session-user"
|
||||
check_ok set -w @u "window-user"
|
||||
check_ok set -p @u "pane-user"
|
||||
check_value "-gv @u" "global-user"
|
||||
check_value "-v @u" "session-user"
|
||||
check_value "-wv @u" "window-user"
|
||||
check_value "-pv @u" "pane-user"
|
||||
|
||||
assert_alive "after options-scope tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
193
regress/options-values.sh
Normal file
193
regress/options-values.sh
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of options engine value validation, as implemented by
|
||||
# options_from_string() and friends in options.c.
|
||||
#
|
||||
# Each option table entry has a type (string, number, key, colour, flag,
|
||||
# choice, command) with type-specific parsing and validation. This exercises:
|
||||
# number range limits; choice options rejecting unknown values; flag options
|
||||
# toggling with no value and rejecting garbage; colour and key options
|
||||
# rejecting invalid input; string append with -a; -F expansion at set time;
|
||||
# and -o refusing to overwrite an option that is already set.
|
||||
#
|
||||
# options-scope.sh covers scoping/inheritance and options-array.sh covers
|
||||
# arrays.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
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
|
||||
|
||||
# --- number options -------------------------------------------------------
|
||||
#
|
||||
# display-time is a number with a minimum of 0; a negative value and a
|
||||
# non-numeric value are both rejected via strtonum(3).
|
||||
check_ok set -g display-time 4000
|
||||
check_value "-gv display-time" "4000"
|
||||
check_fail "value is too small: -5" set -g display-time -5
|
||||
check_fail "value is invalid: abc" set -g display-time abc
|
||||
# A missing value is rejected for a non-flag, non-choice option.
|
||||
check_fail "empty value" set -g display-time
|
||||
|
||||
# --- choice options -------------------------------------------------------
|
||||
#
|
||||
# status-keys accepts only its listed choices (vi/emacs); anything else is an
|
||||
# "unknown value" error and the option keeps its previous value.
|
||||
check_ok set -g status-keys vi
|
||||
check_value "-gv status-keys" "vi"
|
||||
check_fail "unknown value: bogus" set -g status-keys bogus
|
||||
check_value "-gv status-keys" "vi"
|
||||
|
||||
# --- flag options ---------------------------------------------------------
|
||||
#
|
||||
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
||||
# on/off/yes/no/1/0 are accepted (case-insensitively); anything else fails.
|
||||
check_ok set -g focus-events off
|
||||
check_value "-gv focus-events" "off"
|
||||
check_ok set -g focus-events # toggle
|
||||
check_value "-gv focus-events" "on"
|
||||
check_ok set -g focus-events # toggle back
|
||||
check_value "-gv focus-events" "off"
|
||||
check_ok set -g focus-events yes
|
||||
check_value "-gv focus-events" "on"
|
||||
check_ok set -g focus-events NO
|
||||
check_value "-gv focus-events" "off"
|
||||
check_fail "bad value: maybe" set -g focus-events maybe
|
||||
|
||||
# --- colour options -------------------------------------------------------
|
||||
#
|
||||
# status-bg is a colour; named colours, numbers and #rrggbb are accepted,
|
||||
# garbage is rejected.
|
||||
check_ok set -g status-bg red
|
||||
check_value "-gv status-bg" "red"
|
||||
check_ok set -g status-bg colour123
|
||||
check_value "-gv status-bg" "colour123"
|
||||
check_ok set -g status-bg "#00ff00"
|
||||
check_value "-gv status-bg" "#00ff00"
|
||||
check_fail "bad colour: xxxyyy" set -g status-bg xxxyyy
|
||||
|
||||
# --- style options --------------------------------------------------------
|
||||
#
|
||||
# status-style is a style string, validated when set; a bogus style keyword is
|
||||
# rejected and the old value is retained.
|
||||
check_ok set -g status-style "fg=red,bg=black"
|
||||
check_value "-gv status-style" "fg=red,bg=black"
|
||||
check_fail "invalid style: bg=xxxyyy" set -g status-style "bg=xxxyyy"
|
||||
check_value "-gv status-style" "fg=red,bg=black"
|
||||
|
||||
# --- key options ----------------------------------------------------------
|
||||
#
|
||||
# prefix is a key; a valid key name is stored in canonical form, a bad one is
|
||||
# rejected.
|
||||
check_ok set -g prefix C-a
|
||||
check_value "-gv prefix" "C-a"
|
||||
check_fail "bad key: boguskey" set -g prefix boguskey
|
||||
|
||||
# --- string options with extra validation ---------------------------------
|
||||
#
|
||||
# default-shell is a string but is checked to be an executable shell; a bogus
|
||||
# path is rejected and the old value kept.
|
||||
old=$($TMUX show -gv default-shell)
|
||||
check_fail "not a suitable shell: /not/a/shell" set -g default-shell /not/a/shell
|
||||
check_value "-gv default-shell" "$old"
|
||||
|
||||
# --- user options require a value ------------------------------------------
|
||||
#
|
||||
# A user option set with no value at all is an error.
|
||||
check_fail "empty value" set -g @novalue
|
||||
|
||||
# --- command options ------------------------------------------------------
|
||||
#
|
||||
# default-client-command is a command option: the value is parsed as a tmux
|
||||
# command when set and re-printed from the parsed command list. A syntax
|
||||
# error is reported and the option is left unchanged.
|
||||
check_ok set -g default-client-command "new-window"
|
||||
check_value "-gv default-client-command" "new-window"
|
||||
check_fail "syntax error" set -g default-client-command "if -x {"
|
||||
check_value "-gv default-client-command" "new-window"
|
||||
|
||||
# --- renamed option aliases -----------------------------------------------
|
||||
#
|
||||
# Historical option names are mapped to their current spelling, so setting
|
||||
# cursor-color updates cursor-colour.
|
||||
check_ok set -w cursor-color red
|
||||
check_value "-wv cursor-colour" "red"
|
||||
|
||||
# --- string append (-a) ---------------------------------------------------
|
||||
#
|
||||
# -a appends to the current string value rather than replacing it.
|
||||
check_ok set -g @str "foo"
|
||||
check_ok set -ga @str "bar"
|
||||
check_value "-gv @str" "foobar"
|
||||
|
||||
# --- -F expands at set time -----------------------------------------------
|
||||
#
|
||||
# With -F the value is expanded as a format once, at set time; without -F it is
|
||||
# stored literally.
|
||||
check_ok set -gF @expanded "#{session_name}"
|
||||
check_value "-gv @expanded" "main"
|
||||
check_ok set -g @literal "#{session_name}"
|
||||
check_value "-gv @literal" "#{session_name}"
|
||||
|
||||
# --- -o refuses to overwrite ----------------------------------------------
|
||||
#
|
||||
# -o makes set-option fail if the option is already set, leaving it unchanged;
|
||||
# it succeeds for an option that is not yet set.
|
||||
check_ok set -g @once "first"
|
||||
check_fail "already set: @once" set -go @once "second"
|
||||
check_value "-gv @once" "first"
|
||||
check_ok set -go @fresh "value"
|
||||
check_value "-gv @fresh" "value"
|
||||
|
||||
assert_alive "after options-values tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
620
regress/pane-ops.sh
Normal file
620
regress/pane-ops.sh
Normal file
@@ -0,0 +1,620 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of pane management command semantics (not parsing), as implemented in
|
||||
# cmd-split-window.c, cmd-break-pane.c, cmd-join-pane.c (join-pane and
|
||||
# move-pane), cmd-swap-pane.c, cmd-kill-pane.c, cmd-respawn-pane.c,
|
||||
# cmd-respawn-window.c, cmd-resize-pane.c and cmd-select-pane.c.
|
||||
#
|
||||
# This exercises:
|
||||
# - split-window -h/-v with -l in cells and percent, -b placing the new pane
|
||||
# before (left/top of) the target and -f spanning the full window size;
|
||||
# - break-pane moving a pane into a new window (-d, -n name, -a after, -P -F
|
||||
# printing the new location);
|
||||
# - join-pane moving a window's only pane into another window (destroying the
|
||||
# source window), -b before, -l size, and the identical-panes error;
|
||||
# - move-pane as an alias for join-pane;
|
||||
# - swap-pane -U/-D/-s/-t, -d keeping the active pane, and the marked pane
|
||||
# (select-pane -m/-M) as the default swap source;
|
||||
# - kill-pane, kill-pane -a keeping only the target;
|
||||
# - 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).
|
||||
#
|
||||
# window-ops.sh covers window-level commands and buffers.sh paste buffers.
|
||||
|
||||
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 -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
# Run a command and require that it succeeds.
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
#
|
||||
# Run a command and require that it fails with the given error message.
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fmt $target $format $expected
|
||||
#
|
||||
# Expand a format in a target's context and compare with $expected.
|
||||
check_fmt()
|
||||
{
|
||||
out=$($TMUX display-message -p -t "$1" "$2" 2>&1)
|
||||
if [ "$out" != "$3" ]; then
|
||||
echo "Format '$2' for '$1' wrong."
|
||||
echo "Expected: '$3'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_panes $target $expected
|
||||
#
|
||||
# Compare the pane list of a window (as "index:id ...", in index order) with
|
||||
# $expected.
|
||||
check_panes()
|
||||
{
|
||||
out=$(echo $($TMUX list-panes -t "$1" -F '#{pane_index}:#{pane_id}'))
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "Pane list of '$1' wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# split-window geometry.
|
||||
|
||||
check_ok new-session -d -s P -x 80 -y 24 -n main
|
||||
p0=$($TMUX display-message -p -t P:0.0 '#{pane_id}')
|
||||
|
||||
# Horizontal split with -l in cells: new pane gets exactly that width and the
|
||||
# old pane the rest minus the separator line.
|
||||
check_ok split-window -d -h -l 20 -t "$p0"
|
||||
p1=$($TMUX display-message -p -t P:0.1 '#{pane_id}')
|
||||
check_fmt "$p1" '#{pane_width}x#{pane_height}' '20x24'
|
||||
check_fmt "$p0" '#{pane_width}x#{pane_height}' '59x24'
|
||||
|
||||
# Vertical split with a percentage of the pane being split.
|
||||
check_ok split-window -d -v -l 25% -t "$p0"
|
||||
p2=$($TMUX display-message -p -t P:0.1 '#{pane_id}')
|
||||
check_fmt "$p2" '#{pane_width}x#{pane_height}' '59x6'
|
||||
check_fmt "$p0" '#{pane_width}x#{pane_height}' '59x17'
|
||||
|
||||
# -b puts the new pane to the left of the target; -f makes it span the full
|
||||
# window height.
|
||||
check_ok split-window -d -h -b -f -l 10 -t "$p0"
|
||||
p3=$($TMUX display-message -p -t P:0.0 '#{pane_id}')
|
||||
check_fmt "$p3" '#{pane_width}x#{pane_height}' '10x24'
|
||||
check_fmt "$p3" '#{pane_left},#{pane_top}' '0,0'
|
||||
check_fmt "$p0" '#{pane_width}x#{pane_height}' '50x17'
|
||||
check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
|
||||
|
||||
# The new pane becomes active unless -d is given.
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok split-window -d -v -t "$p0"
|
||||
check_fmt 'P:0' '#{pane_id}' "$p0"
|
||||
p4=$($TMUX display-message -p -t P:0.2 '#{pane_id}')
|
||||
check_ok split-window -v -t "$p4"
|
||||
p5=$($TMUX display-message -p -t 'P:0' '#{pane_id}')
|
||||
check_ok kill-pane -t "$p5"
|
||||
check_ok kill-pane -t "$p4"
|
||||
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.
|
||||
out=$($TMUX break-pane -d -P -F '#{window_index}:#{pane_id}' -n broken \
|
||||
-s "$p1" -t P:)
|
||||
if [ "$out" != "1:$p1" ]; then
|
||||
echo "break-pane -P output wrong: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
check_fmt 'P:1' '#{window_name}:#{window_panes}' 'broken:1'
|
||||
check_fmt 'P:0' '#{window_panes}' '3'
|
||||
|
||||
# join-pane -v moves it back (the source window, left empty, is destroyed).
|
||||
check_ok join-pane -d -v -s P:broken.0 -t "$p2"
|
||||
check_fmt 'P:0' '#{window_panes}' '4'
|
||||
if $TMUX has-session -t P:broken 2>/dev/null; then
|
||||
echo "Window 'broken' still exists after join-pane."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The joined pane is below the target (-v, no -b).
|
||||
top=$($TMUX display-message -p -t "$p2" '#{pane_bottom}')
|
||||
joined=$($TMUX display-message -p -t "$p1" '#{pane_top}')
|
||||
if [ "$joined" -le "$top" ]; then
|
||||
echo "Joined pane is not below target ($joined <= $top)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# join-pane -h -b puts the source to the left of the target; -l sets size.
|
||||
check_ok break-pane -d -n broken -s "$p1" -t P:
|
||||
check_ok join-pane -d -h -b -l 30 -s P:broken.0 -t "$p2"
|
||||
check_fmt "$p1" '#{pane_width}' '30'
|
||||
l1=$($TMUX display-message -p -t "$p1" '#{pane_left}')
|
||||
l2=$($TMUX display-message -p -t "$p2" '#{pane_left}')
|
||||
if [ "$l1" -ge "$l2" ]; then
|
||||
echo "Joined pane is not left of target ($l1 >= $l2)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Joining a pane to itself is an error.
|
||||
check_fail 'source and target panes must be different' \
|
||||
join-pane -d -s "$p0" -t "$p0"
|
||||
|
||||
# break-pane to an occupied window index or with an invalid (non-UTF-8) name
|
||||
# is an error.
|
||||
check_fail 'index in use: 0' break-pane -d -s "$p1" -t P:0
|
||||
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.)
|
||||
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_fmt 'P:0' '#{window_panes}' '4'
|
||||
check_fmt 'P:5' '#{window_panes}' '1'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# swap-pane.
|
||||
|
||||
check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
|
||||
|
||||
# -s/-t swap two panes.
|
||||
check_ok swap-pane -d -s "$p3" -t "$p1"
|
||||
check_panes P:0 "0:$p1 1:$p0 2:$p2 3:$p3"
|
||||
check_ok swap-pane -d -s "$p3" -t "$p1"
|
||||
check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
|
||||
|
||||
# -U swaps the target pane with the previous pane, -D with the next; without
|
||||
# -s the target is the active pane.
|
||||
check_ok swap-pane -d -U -t "$p0"
|
||||
check_panes P:0 "0:$p0 1:$p3 2:$p2 3:$p1"
|
||||
check_ok swap-pane -d -D -t "$p0"
|
||||
check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
|
||||
|
||||
# Without -d the target pane becomes the active pane (it arrives at the
|
||||
# source pane's position).
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok swap-pane -s "$p0" -t "$p2"
|
||||
check_fmt 'P:0' '#{pane_id}' "$p2"
|
||||
check_panes P:0 "0:$p3 1:$p2 2:$p0 3:$p1"
|
||||
check_ok swap-pane -s "$p2" -t "$p0"
|
||||
check_fmt 'P:0' '#{pane_id}' "$p0"
|
||||
check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
|
||||
|
||||
# With a marked pane and no -s, the marked pane is the swap source.
|
||||
check_ok select-pane -m -t "$p3"
|
||||
check_fmt "$p3" '#{pane_marked}' '1'
|
||||
check_ok swap-pane -d -t "$p1"
|
||||
check_panes P:0 "0:$p1 1:$p0 2:$p2 3:$p3"
|
||||
check_ok swap-pane -d -t "$p1"
|
||||
check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
|
||||
|
||||
# select-pane -M clears the mark.
|
||||
check_ok select-pane -M
|
||||
check_fmt "$p3" '#{pane_marked}' '0'
|
||||
check_fmt 'P:0' '#{pane_marked_set}' '0'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# resize-pane and zoom.
|
||||
|
||||
# Absolute -x on a horizontal split and percentage.
|
||||
check_ok resize-pane -t "$p3" -x 20
|
||||
check_fmt "$p3" '#{pane_width}' '20'
|
||||
check_ok resize-pane -t "$p3" -x 25%
|
||||
check_fmt "$p3" '#{pane_width}' '20'
|
||||
check_ok resize-pane -t "$p3" -x 10
|
||||
check_fmt "$p3" '#{pane_width}' '10'
|
||||
|
||||
# Relative adjustments: -R grows a left pane, -L shrinks it back; a count may
|
||||
# be given.
|
||||
check_ok resize-pane -t "$p3" -R
|
||||
check_fmt "$p3" '#{pane_width}' '11'
|
||||
check_ok resize-pane -t "$p3" -L
|
||||
check_fmt "$p3" '#{pane_width}' '10'
|
||||
check_ok resize-pane -t "$p3" -R 5
|
||||
check_fmt "$p3" '#{pane_width}' '15'
|
||||
check_ok resize-pane -t "$p3" -L 5
|
||||
check_fmt "$p3" '#{pane_width}' '10'
|
||||
|
||||
# -y on a vertical split.
|
||||
check_ok resize-pane -t "$p2" -y 10
|
||||
check_fmt "$p2" '#{pane_height}' '10'
|
||||
|
||||
# p2 is the bottom pane, so its bottom border cannot move down: -D instead
|
||||
# grows it by taking lines from the pane above and -U gives them back.
|
||||
check_ok resize-pane -t "$p2" -D 2
|
||||
check_fmt "$p2" '#{pane_height}' '12'
|
||||
check_ok resize-pane -t "$p2" -U 2
|
||||
check_fmt "$p2" '#{pane_height}' '10'
|
||||
|
||||
# Bad adjustment, width and height are errors.
|
||||
check_fail 'adjustment invalid' resize-pane -t "$p2" -U nonsense
|
||||
check_fail 'width invalid' resize-pane -t "$p2" -x nonsense
|
||||
check_fail 'height invalid' resize-pane -t "$p2" -y nonsense
|
||||
|
||||
# -Z zooms: the pane temporarily fills the window and the flags show it.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_width}x#{pane_height}' \
|
||||
'1:80x24'
|
||||
|
||||
# Zoom is transparent to pane commands on other panes, and -Z again unzooms.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_fmt "$p0" '#{window_zoomed_flag}' '0'
|
||||
|
||||
# Splitting while zoomed unzooms first.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_fmt 'P:0' '#{window_zoomed_flag}' '1'
|
||||
check_ok split-window -d -v -t "$p0"
|
||||
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
|
||||
check_fmt 'P:0' '#{window_panes}' '5'
|
||||
p6=$($TMUX display-message -p -t P:0.2 '#{pane_id}')
|
||||
check_ok kill-pane -t "$p6"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# kill-pane.
|
||||
|
||||
check_fmt 'P:0' '#{window_panes}' '4'
|
||||
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 kill-pane -a -t "$p0"
|
||||
check_panes P:0 "0:$p0"
|
||||
|
||||
# Killing the last pane in a window kills the window.
|
||||
check_ok new-window -d -t P:7 -n goner
|
||||
check_ok kill-pane -t P:7.0
|
||||
if $TMUX has-session -t P:goner 2>/dev/null; then
|
||||
echo "Window 'goner' still exists after killing its only pane."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# respawn-pane and respawn-window.
|
||||
|
||||
# Respawning a pane whose process is alive fails without -k.
|
||||
check_fail "respawn pane failed: pane P:0.0 still active" \
|
||||
respawn-pane -t P:0.0
|
||||
check_fail "respawn window failed: window P:0 still active" \
|
||||
respawn-window -t P:0
|
||||
|
||||
# With remain-on-exit a pane whose command exited stays as a dead pane and
|
||||
# may be respawned without -k.
|
||||
check_ok set-option -g remain-on-exit on
|
||||
check_ok new-window -d -t P:8 -n dead 'true'
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t P:8.0 '#{pane_dead}')" != "1" ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "Pane did not die."; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
check_ok respawn-pane -t P:8.0 'sleep 100'
|
||||
check_fmt 'P:8.0' '#{pane_dead}' '0'
|
||||
|
||||
# -k kills the live process and respawns.
|
||||
check_ok respawn-pane -k -t P:8.0 'sleep 200'
|
||||
check_fmt 'P:8.0' '#{pane_dead}' '0'
|
||||
|
||||
# respawn-window -k replaces the whole window (all panes) with one pane.
|
||||
check_ok split-window -d -t P:8
|
||||
check_fmt 'P:8' '#{window_panes}' '2'
|
||||
check_ok respawn-window -k -t P:8 'sleep 300'
|
||||
check_fmt 'P:8' '#{window_panes}' '1'
|
||||
check_fmt 'P:8.0' '#{pane_dead}' '0'
|
||||
check_ok set-option -g remain-on-exit off
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# select-pane.
|
||||
|
||||
# A 2x2-ish arrangement: q0 on top, q1 bottom-left, q2 bottom-right.
|
||||
check_ok new-window -d -t P:2 -n sel
|
||||
q0=$($TMUX display-message -p -t P:2.0 '#{pane_id}')
|
||||
check_ok split-window -d -v -t "$q0"
|
||||
q1=$($TMUX display-message -p -t P:2.1 '#{pane_id}')
|
||||
check_ok split-window -d -h -t "$q1"
|
||||
q2=$($TMUX display-message -p -t P:2.2 '#{pane_id}')
|
||||
|
||||
# Directional selection: -D, -R and -U move by pane position.
|
||||
check_ok select-pane -t "$q0"
|
||||
check_ok select-pane -D -t P:2
|
||||
check_fmt 'P:2' '#{pane_id}' "$q1"
|
||||
check_ok select-pane -R -t P:2
|
||||
check_fmt 'P:2' '#{pane_id}' "$q2"
|
||||
check_ok select-pane -U -t P:2
|
||||
check_fmt 'P:2' '#{pane_id}' "$q0"
|
||||
|
||||
# -l returns to the previously active pane; a window that never had another
|
||||
# active pane has no last pane.
|
||||
check_ok select-pane -l -t P:2
|
||||
check_fmt 'P:2' '#{pane_id}' "$q2"
|
||||
check_fail 'no last pane' select-pane -l -t P:8.0
|
||||
|
||||
# -d disables input to a pane, -e enables it again and -T sets the title.
|
||||
check_ok select-pane -d -t "$q0"
|
||||
check_fmt "$q0" '#{pane_input_off}' '1'
|
||||
check_ok select-pane -e -t "$q0"
|
||||
check_fmt "$q0" '#{pane_input_off}' '0'
|
||||
check_ok select-pane -T mytitle -t "$q0"
|
||||
check_fmt "$q0" '#{pane_title}' 'mytitle'
|
||||
check_ok kill-window -t P:2
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# more split-window variants.
|
||||
|
||||
check_ok new-window -d -t P:2 -n splits
|
||||
|
||||
# new-window -E creates an empty initial pane, running no command.
|
||||
check_ok new-window -d -E -t P:9 -n empty
|
||||
check_fmt 'P:9.0' '#{pane_dead}' '0'
|
||||
check_fmt 'P:9.0' '#{pane_pid}' ''
|
||||
check_fail 'command cannot be given for empty pane' \
|
||||
new-window -d -E -t P:10 -n empty 'true'
|
||||
|
||||
# respawn-pane -E stores the command and cwd without starting it.
|
||||
tmp=${TMPDIR:-/tmp}/tmux-pane-ops-empty-$$
|
||||
rm -f "$tmp"
|
||||
check_ok new-window -d -E -t P:10 -n empty-respawn
|
||||
check_ok respawn-pane -E -c /tmp -t P:10.0 "pwd > $tmp"
|
||||
if [ -e "$tmp" ]; then
|
||||
echo "respawn-pane -E started command unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
check_ok respawn-pane -t P:10.0
|
||||
i=0
|
||||
while [ ! -e "$tmp" ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && echo "respawn-pane did not start stored command" && \
|
||||
exit 1
|
||||
sleep 0.1
|
||||
done
|
||||
if [ "$(cat "$tmp")" != "/tmp" ]; then
|
||||
echo "respawn-pane did not use stored cwd"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
check_ok kill-window -t P:9
|
||||
|
||||
# respawn-window -E stores the command and cwd without starting it.
|
||||
tmp=${TMPDIR:-/tmp}/tmux-window-ops-empty-$$
|
||||
rm -f "$tmp"
|
||||
check_ok new-window -d -E -t P:11 -n empty-respawn-window
|
||||
check_ok respawn-window -E -c /tmp -t P:11 "pwd > $tmp"
|
||||
check_fmt 'P:11.0' '#{pane_pid}' ''
|
||||
if [ -e "$tmp" ]; then
|
||||
echo "respawn-window -E started command unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
check_ok respawn-window -t P:11
|
||||
i=0
|
||||
while [ ! -e "$tmp" ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && echo "respawn-window did not start stored command" && \
|
||||
exit 1
|
||||
sleep 0.1
|
||||
done
|
||||
if [ "$(cat "$tmp")" != "/tmp" ]; then
|
||||
echo "respawn-window did not use stored cwd"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
|
||||
# -E splits with an empty pane, running no command; giving one is an error.
|
||||
check_ok split-window -d -E -t P:2.0
|
||||
check_fmt 'P:2' '#{window_panes}' '2'
|
||||
check_fail 'command cannot be given for empty pane' \
|
||||
split-window -d -E -t P:2.0 'sleep 5'
|
||||
|
||||
# -e adds to the new pane's environment.
|
||||
eid=$($TMUX split-window -d -P -F '#{pane_id}' -e GREETING=hello -t P:2.0 \
|
||||
'echo $GREETING; exec cat')
|
||||
i=0
|
||||
while out=$($TMUX capture-pane -p -t "$eid" | sed -n 1p) && \
|
||||
[ "$out" != "hello" ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "split-window -e wrong: '$out'"; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
# A bad -l size is an error.
|
||||
check_fail 'invalid tiled geometry invalid' \
|
||||
split-window -d -v -l invalid -t P:2.0
|
||||
|
||||
# -Z zooms the new pane.
|
||||
check_ok split-window -d -Z -t P:2.0
|
||||
check_fmt 'P:2' '#{window_zoomed_flag}' '1'
|
||||
check_ok kill-window -t P:2
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# more swap-pane: wrapping, cross-window, self and zoomed swaps.
|
||||
|
||||
check_ok new-window -d -t P:3 -n swaps
|
||||
check_ok split-window -d -v -t P:3.0
|
||||
check_ok split-window -d -v -t P:3.0
|
||||
r0=$($TMUX display-message -p -t P:3.0 '#{pane_id}')
|
||||
r1=$($TMUX display-message -p -t P:3.1 '#{pane_id}')
|
||||
r2=$($TMUX display-message -p -t P:3.2 '#{pane_id}')
|
||||
o0=$($TMUX display-message -p -t P:5.0 '#{pane_id}')
|
||||
|
||||
# -D on the last pane and -U on the first wrap around to the other end.
|
||||
check_ok swap-pane -d -D -t "$r2"
|
||||
check_panes P:3 "0:$r2 1:$r1 2:$r0"
|
||||
check_ok swap-pane -d -s "$r0" -t "$r2"
|
||||
check_ok swap-pane -d -U -t "$r0"
|
||||
check_panes P:3 "0:$r2 1:$r1 2:$r0"
|
||||
check_ok swap-pane -d -s "$r0" -t "$r2"
|
||||
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"
|
||||
|
||||
# Panes can be swapped between different windows.
|
||||
check_ok swap-pane -d -s "$o0" -t "$r1"
|
||||
check_panes P:3 "0:$r0 1:$o0 2:$r2"
|
||||
check_panes P:5 "0:$r1"
|
||||
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"
|
||||
|
||||
# -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_ok resize-pane -Z -t P:3
|
||||
check_panes P:3 "0:$r1 1:$r0 2:$r2"
|
||||
|
||||
# kill-pane -a -f only kills other panes matching the filter.
|
||||
check_ok kill-pane -a -f '#{==:#{pane_id},'"$r2"'}' -t "$r1"
|
||||
check_panes P:3 "0:$r1 1:$r0"
|
||||
check_ok kill-window -t P:3
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# split-window -I and -s.
|
||||
|
||||
check_ok new-window -d -t P:3 -n splits2
|
||||
|
||||
# -I fills the new (empty) pane from standard input.
|
||||
printf 'stdin-stuff' | $TMUX split-window -d -I -t P:3.0
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "split-window -I failed."
|
||||
exit 1
|
||||
fi
|
||||
i=0
|
||||
while out=$($TMUX capture-pane -p -t P:3.1 | sed -n 1p) && \
|
||||
[ "$out" != "stdin-stuff" ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "split-window -I wrong: '$out'"; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
# -s sets the new pane's window-style.
|
||||
sid=$($TMUX split-window -d -P -F '#{pane_id}' -s 'bg=red' -t P:3.0)
|
||||
out=$($TMUX show-options -v -p -t "$sid" window-style)
|
||||
if [ "$out" != "bg=red" ]; then
|
||||
echo "split-window -s style wrong: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
check_ok kill-window -t P:3
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# more break-pane: -a insertion, selection and single-pane windows.
|
||||
|
||||
# check_windows $session $expected
|
||||
#
|
||||
# Compare the window list of a session (as "index:name ...") with $expected.
|
||||
check_windows()
|
||||
{
|
||||
out=$(echo $($TMUX list-windows -t "$1" -F \
|
||||
'#{window_index}:#{window_name}'))
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "Window list of '$1' wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok new-session -d -s Q -x 80 -y 24 -n q0
|
||||
|
||||
# -a breaks into a new window inserted after the target index, shuffling the
|
||||
# following windows up.
|
||||
check_ok new-window -d -t Q:1 -n q1
|
||||
check_ok split-window -d -t Q:1
|
||||
check_ok break-pane -d -a -s Q:1.1 -n qa -t Q:0
|
||||
check_windows Q '0:q0 1:qa 2:q1'
|
||||
|
||||
# Without -d the new window is selected.
|
||||
check_ok split-window -d -t Q:2
|
||||
check_ok select-window -t Q:0
|
||||
check_ok break-pane -s Q:2.1 -n qcur -t Q:
|
||||
check_fmt 'Q:' '#{window_name}' 'qcur'
|
||||
check_windows Q '0:q0 1:qa 2:q1 3:qcur'
|
||||
|
||||
# Breaking the only pane of a window relinks the window at a new index; -n
|
||||
# still renames it.
|
||||
check_ok new-window -d -t Q:5 -n qsolo
|
||||
out=$($TMUX break-pane -d -P -F '#{window_name}' -s Q:5.0 -n qmoved -t Q:)
|
||||
if [ "$out" != "qmoved" ]; then
|
||||
echo "single-pane break-pane output wrong: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
if $TMUX has-session -t Q:qsolo 2>/dev/null; then
|
||||
echo "Window 'qsolo' still exists after single-pane break-pane."
|
||||
exit 1
|
||||
fi
|
||||
check_ok has-session -t Q:qmoved
|
||||
check_ok kill-session -t Q
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# resize-pane -T.
|
||||
|
||||
# -T trims the history: lines below the cursor position are removed and the
|
||||
# cursor moves to the bottom. seq writes 100 lines (leaving 77 in history on
|
||||
# a 24-line screen) and the escape sequence puts the cursor on line 5.
|
||||
check_ok new-window -d -t P:2 'seq 1 100; printf "\033[5;1H"; exec cat'
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t P:2.0 '#{history_size}')" != "77" ]
|
||||
do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "History did not fill."; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
check_fmt 'P:2.0' '#{cursor_y}' '4'
|
||||
check_ok resize-pane -T -t P:2.0
|
||||
check_fmt 'P:2.0' '#{history_size}' '58'
|
||||
check_fmt 'P:2.0' '#{cursor_y}' '23'
|
||||
check_ok kill-window -t P:2
|
||||
|
||||
assert_alive
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
@@ -131,11 +131,14 @@ $IN send-keys -l "Z" || exit 1
|
||||
settle
|
||||
search_is "hello Z" "C-w did not kill a word"
|
||||
|
||||
# C-a then C-k kills the whole line.
|
||||
# C-a then C-k kills the whole line. The mode prompt no longer fills the rest
|
||||
# of the row, so insert a marker to distinguish prompt input from tree content
|
||||
# that may remain visible after the prompt.
|
||||
$IN send-keys C-a || exit 1
|
||||
$IN send-keys C-k || exit 1
|
||||
$IN send-keys -l "X" || exit 1
|
||||
settle
|
||||
search_row | grep -q '(search) [^ ]' && fail "C-a C-k did not clear the line"
|
||||
search_is "X" "C-a C-k did not clear the line"
|
||||
|
||||
# --- 3. Editing kept the prompt open the whole time. ---
|
||||
in_tree_mode || fail "editing keys closed the mode"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
base [30m[47m
|
||||
[39m[49m [30m[47m
|
||||
[39m[49m [30m[47m
|
||||
[39m[49m [38;2;143;188;143m┌──────────────┐[39m [30m[47m
|
||||
[39m[49m [38;2;143;188;143m│[39mOVERSB [30m[47m [38;2;143;188;143m[49m│[39m [30m[47m
|
||||
[39m[49m [38;2;143;188;143m│[39m [30m[47m [38;2;143;188;143m[49m│[39m [30m[47m
|
||||
[39m[49m [38;2;143;188;143m│[39m [30m[47m [38;2;143;188;143m[49m│[39m [30m[47m
|
||||
[39m[49m [38;2;143;188;143m│[39m [30m[47m [38;2;143;188;143m[49m│[39m [30m[47m
|
||||
[39m[49m [38;2;143;188;143m└──────────────┘[39m [30m[47m
|
||||
[39m[49m [38;2;154;205;50m┌──────────────┐[39m [30m[47m
|
||||
[39m[49m [38;2;154;205;50m│[39mOVERSB [30m[47m [38;2;154;205;50m[49m│[39m [30m[47m
|
||||
[39m[49m [38;2;154;205;50m│[39m [30m[47m [38;2;154;205;50m[49m│[39m [30m[47m
|
||||
[39m[49m [38;2;154;205;50m│[39m [30m[47m [38;2;154;205;50m[49m│[39m [30m[47m
|
||||
[39m[49m [38;2;154;205;50m│[39m [30m[47m [38;2;154;205;50m[49m│[39m [30m[47m
|
||||
[39m[49m [38;2;154;205;50m└──────────────┘[39m [30m[47m
|
||||
[39m[49m [30m[47m
|
||||
[39m[49m [30m[47m
|
||||
[39m[49m [30m[47m
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[38;2;143;188;143m── 0:left ──────────[7m┬[38;2;179;179;179m──[0m[38;2;179;179;179m 1:right ────────
|
||||
[39m [7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[7m[38;2;143;188;143m│[0m
|
||||
[38;2;154;205;50m── 0:left ──────────[7m┬[38;2;179;179;179m──[0m[38;2;179;179;179m 1:right ────────
|
||||
[39m [7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
[7m[38;2;154;205;50m│[0m
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
|
||||
[7m[38;2;143;188;143m────────────────────────────────────────
|
||||
[7m[38;2;154;205;50m────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;143;188;143m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
[7m[38;2;154;205;50m│[0m [7m[38;2;179;179;179m│[0m
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
SB00 abcdefghij [30m[47m
|
||||
[39m[49mSB01 abcdefghij [30m[47m
|
||||
[39m[49mSB02 abcdefghij [30m[47m
|
||||
[39m[49mSB03 abc[38;2;143;188;143m┌──────────────────┐[39m [30m[47m
|
||||
[39m[49mSB04 abc[38;2;143;188;143m│[39mFLOAT02 abcdef [37m[40m [38;2;143;188;143m[49m│[39m [30m[47m
|
||||
[39m[49mSB05 abc[38;2;143;188;143m│[39mFLOAT03 abcdef [37m[40m [38;2;143;188;143m[49m│[39m [30m[47m
|
||||
[39m[49mSB06 abc[38;2;143;188;143m│[39mFLOAT04 abcdef [30m[47m [38;2;143;188;143m[49m│[39m [30m[47m
|
||||
[39m[49mSB07 abc[38;2;143;188;143m│[39m [30m[47m [38;2;143;188;143m[49m│[39m [30m[47m
|
||||
[39m[49mSB08 abc[38;2;143;188;143m└──────────────────┘[39m [30m[47m
|
||||
[39m[49mSB03 abc[38;2;154;205;50m┌──────────────────┐[39m [30m[47m
|
||||
[39m[49mSB04 abc[38;2;154;205;50m│[39mFLOAT02 abcdef [37m[40m [38;2;154;205;50m[49m│[39m [30m[47m
|
||||
[39m[49mSB05 abc[38;2;154;205;50m│[39mFLOAT03 abcdef [37m[40m [38;2;154;205;50m[49m│[39m [30m[47m
|
||||
[39m[49mSB06 abc[38;2;154;205;50m│[39mFLOAT04 abcdef [30m[47m [38;2;154;205;50m[49m│[39m [30m[47m
|
||||
[39m[49mSB07 abc[38;2;154;205;50m│[39m [30m[47m [38;2;154;205;50m[49m│[39m [30m[47m
|
||||
[39m[49mSB08 abc[38;2;154;205;50m└──────────────────┘[39m [30m[47m
|
||||
[39m[49mSB09 abcdefghij [30m[47m
|
||||
[39m[49mSB10 abcdefghij [30m[47m
|
||||
[39m[49m [30m[47m
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[30m[47m [39m[49mSB00 abcdefghij [38;2;143;188;143m│[30m[47m [39m[49mSBL00 abcdefghij
|
||||
[30m[47m [39m[49mSB01 abcdefghij [38;2;143;188;143m│[30m[47m [39m[49mSBL01 abcdefghij
|
||||
[30m[47m [39m[49mSB02 abcdefghij [38;2;143;188;143m│[30m[47m [39m[49mSBL02 abcdefghij
|
||||
[30m[47m [39m[49mSB03 abcdefghij [38;2;143;188;143m│[30m[47m [39m[49mSBL03 abcdefghij
|
||||
[30m[47m [39m[49mSB04 abcdefghij [38;2;143;188;143m│[30m[47m [39m[49mSBL04 abcdefghij
|
||||
[30m[47m [39m[49mSB05 abcdefghij [38;2;143;188;143m│[30m[47m [39m[49mSBL05 abcdefghij
|
||||
[30m[47m [39m[49mSB06 abcdefghij [38;2;143;188;143m│[30m[47m [39m[49mSBL06 abcdefghij
|
||||
[30m[47m [39m[49mSB00 abcdefghij [38;2;154;205;50m│[30m[47m [39m[49mSBL00 abcdefghij
|
||||
[30m[47m [39m[49mSB01 abcdefghij [38;2;154;205;50m│[30m[47m [39m[49mSBL01 abcdefghij
|
||||
[30m[47m [39m[49mSB02 abcdefghij [38;2;154;205;50m│[30m[47m [39m[49mSBL02 abcdefghij
|
||||
[30m[47m [39m[49mSB03 abcdefghij [38;2;154;205;50m│[30m[47m [39m[49mSBL03 abcdefghij
|
||||
[30m[47m [39m[49mSB04 abcdefghij [38;2;154;205;50m│[30m[47m [39m[49mSBL04 abcdefghij
|
||||
[30m[47m [39m[49mSB05 abcdefghij [38;2;154;205;50m│[30m[47m [39m[49mSBL05 abcdefghij
|
||||
[30m[47m [39m[49mSB06 abcdefghij [38;2;154;205;50m│[30m[47m [39m[49mSBL06 abcdefghij
|
||||
[30m[47m [39m[49mSB07 abcdefghij [38;2;179;179;179m│[30m[47m [39m[49mSBL07 abcdefghij
|
||||
[30m[47m [39m[49mSB08 abcdefghij [38;2;179;179;179m│[30m[47m [39m[49mSBL08 abcdefghij
|
||||
[30m[47m [39m[49mSB09 abcdefghij [38;2;179;179;179m│[30m[47m [39m[49mSBL09 abcdefghij
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
SB00 abcdefghij [30m[47m [38;2;143;188;143m[49m│[39mSBR00 abcdefghij [30m[47m
|
||||
[39m[49mSB01 abcdefghij [30m[47m [38;2;143;188;143m[49m│[39mSBR01 abcdefghij [30m[47m
|
||||
[39m[49mSB02 abcdefghij [30m[47m [38;2;143;188;143m[49m│[39mSBR02 abcdefghij [30m[47m
|
||||
[39m[49mSB03 abcdefghij [30m[47m [38;2;143;188;143m[49m│[39mSBR03 abcdefghij [30m[47m
|
||||
[39m[49mSB04 abcdefghij [30m[47m [38;2;143;188;143m[49m│[39mSBR04 abcdefghij [30m[47m
|
||||
[39m[49mSB05 abcdefghij [30m[47m [38;2;143;188;143m[49m│[39mSBR05 abcdefghij [30m[47m
|
||||
[39m[49mSB06 abcdefghij [30m[47m [38;2;143;188;143m[49m│[39mSBR06 abcdefghij [30m[47m
|
||||
SB00 abcdefghij [30m[47m [38;2;154;205;50m[49m│[39mSBR00 abcdefghij [30m[47m
|
||||
[39m[49mSB01 abcdefghij [30m[47m [38;2;154;205;50m[49m│[39mSBR01 abcdefghij [30m[47m
|
||||
[39m[49mSB02 abcdefghij [30m[47m [38;2;154;205;50m[49m│[39mSBR02 abcdefghij [30m[47m
|
||||
[39m[49mSB03 abcdefghij [30m[47m [38;2;154;205;50m[49m│[39mSBR03 abcdefghij [30m[47m
|
||||
[39m[49mSB04 abcdefghij [30m[47m [38;2;154;205;50m[49m│[39mSBR04 abcdefghij [30m[47m
|
||||
[39m[49mSB05 abcdefghij [30m[47m [38;2;154;205;50m[49m│[39mSBR05 abcdefghij [30m[47m
|
||||
[39m[49mSB06 abcdefghij [30m[47m [38;2;154;205;50m[49m│[39mSBR06 abcdefghij [30m[47m
|
||||
[39m[49mSB07 abcdefghij [30m[47m [38;2;179;179;179m[49m│[39mSBR07 abcdefghij [30m[47m
|
||||
[39m[49mSB08 abcdefghij [30m[47m [38;2;179;179;179m[49m│[39mSBR08 abcdefghij [30m[47m
|
||||
[39m[49mSB09 abcdefghij [30m[47m [38;2;179;179;179m[49m│[39mSBR09 abcdefghij [30m[47m
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[41mSTYLE00 abcdefghij [38;2;143;188;143m[49m│[39m[44mSTYLE00 abcdefghij
|
||||
[41mSTYLE01 abcdefghij [38;2;143;188;143m[49m│[39m[44mSTYLE01 abcdefghij
|
||||
[41mSTYLE02 abcdefghij [38;2;143;188;143m[49m│[39m[44mSTYLE02 abcdefghij
|
||||
[41mSTYLE03 abcdefghij [38;2;143;188;143m[49m│[39m[44mSTYLE03 abcdefghij
|
||||
[41mSTYLE04 abcdefghij [38;2;143;188;143m[49m│[39m[44mSTYLE04 abcdefghij
|
||||
[41mSTYLE00 abcdefghij [38;2;154;205;50m[49m│[39m[44mSTYLE00 abcdefghij
|
||||
[41mSTYLE01 abcdefghij [38;2;154;205;50m[49m│[39m[44mSTYLE01 abcdefghij
|
||||
[41mSTYLE02 abcdefghij [38;2;154;205;50m[49m│[39m[44mSTYLE02 abcdefghij
|
||||
[41mSTYLE03 abcdefghij [38;2;154;205;50m[49m│[39m[44mSTYLE03 abcdefghij
|
||||
[41mSTYLE04 abcdefghij [38;2;154;205;50m[49m│[39m[44mSTYLE04 abcdefghij
|
||||
[41mSTYLE05 abcdefghij [38;2;179;179;179m[49m│[39m[44mSTYLE05 abcdefghij
|
||||
[41mSTYLE06 abcdefghij [38;2;179;179;179m[49m│[39m[44mSTYLE06 abcdefghij
|
||||
[41m [38;2;179;179;179m[49m│[39m[44m
|
||||
|
||||
0
regress/session-group-resize.sh
Executable file → Normal file
0
regress/session-group-resize.sh
Executable file → Normal file
217
regress/session-ops.sh
Normal file
217
regress/session-ops.sh
Normal file
@@ -0,0 +1,217 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of session management command semantics, as implemented in
|
||||
# cmd-new-session.c, cmd-rename-session.c, cmd-kill-session.c and
|
||||
# cmd-has-session.c, plus grouped sessions (new-session -t).
|
||||
#
|
||||
# This exercises:
|
||||
# - new-session naming: explicit -s, invalid and duplicate names, automatic
|
||||
# numeric names, -n naming the initial window and -A attaching to (here:
|
||||
# not duplicating) an existing session;
|
||||
# - session_id/session_name/session_windows formats and has-session;
|
||||
# - rename-session, including duplicate and invalid names, and that the
|
||||
# session keeps its id when renamed;
|
||||
# - kill-session, kill-session -a (all but target), the "-f only valid with
|
||||
# -a" guard, and that killing the last session stops the server;
|
||||
# - grouped sessions: new-session -t shares the window list (a window made
|
||||
# in one session appears in the other; killed windows disappear), current
|
||||
# windows are tracked independently and destroying one grouped session
|
||||
# leaves the windows in the other.
|
||||
#
|
||||
# session-group-resize.sh covers sizing of grouped sessions.
|
||||
|
||||
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 -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
# Run a command and require that it succeeds.
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@" </dev/null; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
#
|
||||
# Run a command and require that it fails with the given error message.
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" </dev/null 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fmt $target $format $expected
|
||||
#
|
||||
# Expand a format in a target's context and compare with $expected.
|
||||
check_fmt()
|
||||
{
|
||||
out=$($TMUX display-message -p -t "$1" "$2" 2>&1)
|
||||
if [ "$out" != "$3" ]; then
|
||||
echo "Format '$2' for '$1' wrong."
|
||||
echo "Expected: '$3'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_sessions $expected
|
||||
#
|
||||
# Compare the session list (as "name name ...", sorted by name) with
|
||||
# $expected.
|
||||
check_sessions()
|
||||
{
|
||||
out=$(echo $($TMUX list-sessions -F '#{session_name}' | LC_ALL=C sort))
|
||||
if [ "$out" != "$1" ]; then
|
||||
echo "Session list wrong."
|
||||
echo "Expected: '$1'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_windows $session $expected
|
||||
#
|
||||
# Compare the window list of a session (as "index:name ...") with $expected.
|
||||
check_windows()
|
||||
{
|
||||
out=$(echo $($TMUX list-windows -t "$1" -F \
|
||||
'#{window_index}:#{window_name}'))
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "Window list of '$1' wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# new-session and has-session.
|
||||
|
||||
check_ok new-session -d -s S1 -x 80 -y 24 -n first
|
||||
check_fmt 'S1:' '#{session_name}:#{window_name}:#{session_windows}' \
|
||||
'S1:first:1'
|
||||
|
||||
# A duplicate name is an error. Only invalid UTF-8 is rejected as a name:
|
||||
# colons, periods and even an empty string are allowed (such sessions can
|
||||
# only be targeted by id).
|
||||
check_fail 'duplicate session: S1' new-session -d -s S1
|
||||
badname=$(printf 'a\377b')
|
||||
check_fail "invalid session name: $badname" new-session -d -s "$badname"
|
||||
oddid=$($TMUX new-session -d -s 'a:b.c' -x 80 -y 24 -P -F '#{session_id}')
|
||||
check_fmt "$oddid" '#{session_name}' 'a:b.c'
|
||||
check_ok kill-session -t "$oddid"
|
||||
emptyid=$($TMUX new-session -d -s '' -x 80 -y 24 -P -F '#{session_id}')
|
||||
check_fmt "$emptyid" '#{session_name}' ''
|
||||
check_ok kill-session -t "$emptyid"
|
||||
|
||||
# Without -s, sessions get a numeric name matching their id counter.
|
||||
autoname=$($TMUX new-session -d -x 80 -y 24 -P -F '#{session_name}')
|
||||
autoid=$($TMUX display-message -p -t "=$autoname:" '#{session_id}')
|
||||
if [ "\$$autoname" != "$autoid" ]; then
|
||||
echo "Automatic session name '$autoname' does not match id '$autoid'."
|
||||
exit 1
|
||||
fi
|
||||
check_ok has-session -t "=$autoname"
|
||||
check_ok rename-session -t "=$autoname" S2
|
||||
check_sessions 'S1 S2'
|
||||
|
||||
# -A creates the session only if it does not exist; if it does, -A means
|
||||
# attach, which a detached client without a terminal cannot do.
|
||||
check_ok new-session -d -A -s S3
|
||||
check_sessions 'S1 S2 S3'
|
||||
check_fail 'open terminal failed: not a terminal' new-session -d -A -s S3
|
||||
check_sessions 'S1 S2 S3'
|
||||
check_ok kill-session -t S3
|
||||
|
||||
# has-session fails for a missing session.
|
||||
check_fail "can't find session: nosuch" has-session -t nosuch
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# rename-session.
|
||||
|
||||
# The id survives a rename and the old name is gone.
|
||||
id=$($TMUX display-message -p -t S2: '#{session_id}')
|
||||
check_ok rename-session -t S2 newname
|
||||
check_sessions 'S1 newname'
|
||||
check_fmt "$id" '#{session_name}' 'newname'
|
||||
check_fail "can't find session: S2" has-session -t S2
|
||||
|
||||
# Renaming to an existing or invalid name is an error.
|
||||
check_fail 'duplicate session: S1' rename-session -t newname S1
|
||||
check_fail "invalid session name: $badname" rename-session -t newname \
|
||||
"$badname"
|
||||
check_ok rename-session -t newname S2
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# grouped sessions (new-session -t).
|
||||
|
||||
check_ok new-session -d -s G1 -x 80 -y 24 -n shared
|
||||
check_ok new-session -d -s G2 -t G1
|
||||
check_fmt 'G1:' '#{session_grouped}:#{session_group_size}' '1:2'
|
||||
check_fmt 'G2:' '#{session_grouped}:#{session_group_list}' '1:G1,G2'
|
||||
|
||||
# The window list is shared: windows created or killed in one session
|
||||
# appear and disappear in the other.
|
||||
check_ok new-window -d -t G2: -n added
|
||||
check_windows G1 '0:shared 1:added'
|
||||
check_windows G2 '0:shared 1:added'
|
||||
check_ok kill-window -t G1:added
|
||||
check_windows G2 '0:shared'
|
||||
|
||||
# The current window is tracked per session.
|
||||
check_ok new-window -d -t G2:1 -n other
|
||||
check_ok select-window -t G1:0
|
||||
check_ok select-window -t G2:1
|
||||
check_fmt 'G1:' '#{window_name}' 'shared'
|
||||
check_fmt 'G2:' '#{window_name}' 'other'
|
||||
|
||||
# Killing one grouped session leaves the windows in the other (the group
|
||||
# itself survives with a single member).
|
||||
check_ok kill-session -t G2
|
||||
check_windows G1 '0:shared 1:other'
|
||||
check_fmt 'G1:' '#{session_grouped}:#{session_group_size}' '1:1'
|
||||
check_ok kill-window -t G1:other
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# kill-session.
|
||||
|
||||
check_sessions 'G1 S1 S2'
|
||||
check_fail '-f only valid with -a' kill-session -f 'x' -t S1
|
||||
|
||||
# -C only clears alerts; the session survives.
|
||||
check_ok kill-session -C -t S2
|
||||
check_ok has-session -t S2
|
||||
|
||||
# -a kills every other session.
|
||||
check_ok kill-session -a -t S1
|
||||
check_sessions 'S1'
|
||||
|
||||
# Killing the last session stops the server.
|
||||
check_ok kill-session -t S1
|
||||
if $TMUX has-session -t S1 2>/dev/null; then
|
||||
echo "Server still up after killing the last session."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
150
regress/set-hook-B.sh
Executable file
150
regress/set-hook-B.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/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)
|
||||
OUT=$(mktemp -d)
|
||||
TMUX_TMPDIR="$OUT"
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -Ltest-hook-B-$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
$TMUX kill-server 2>/dev/null || true
|
||||
rm -rf "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null || true
|
||||
rm -rf "$OUT"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
wait_for()
|
||||
{
|
||||
option=$1
|
||||
expected=$2
|
||||
i=0
|
||||
|
||||
while [ $i -lt 30 ]; do
|
||||
value=$($TMUX show -gqv "$option" 2>/dev/null || true)
|
||||
[ "$value" = "$expected" ] && return 0
|
||||
i=$((i + 1))
|
||||
sleep 0.2
|
||||
done
|
||||
fail "expected $option to be '$expected' but got '$value'"
|
||||
}
|
||||
|
||||
assert_unchanged()
|
||||
{
|
||||
option=$1
|
||||
expected=$2
|
||||
i=0
|
||||
|
||||
while [ $i -lt 15 ]; do
|
||||
value=$($TMUX show -gqv "$option" 2>/dev/null || true)
|
||||
[ "$value" = "$expected" ] || \
|
||||
fail "expected $option to remain '$expected' but got '$value'"
|
||||
i=$((i + 1))
|
||||
sleep 0.2
|
||||
done
|
||||
}
|
||||
|
||||
$TMUX new -d -s one || fail "new-session failed"
|
||||
|
||||
$TMUX set -g @seen 0 || fail "set @seen failed"
|
||||
$TMUX set-hook -g -B '@session-name::#{session_name}' \
|
||||
'set -g @seen "#{hook}:#{hook_value}"' ||
|
||||
fail "set-hook -B failed"
|
||||
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"
|
||||
assert_unchanged @seen 0
|
||||
|
||||
$TMUX rename-session two || fail "rename-session two failed"
|
||||
wait_for @seen '@session-name:two'
|
||||
|
||||
$TMUX set -g @seen-last 0 || fail "set @seen-last failed"
|
||||
$TMUX set-hook -g -B '@session-name::#{session_name}' \
|
||||
'set -g @seen-last "#{hook_last}->#{hook_value}"' ||
|
||||
fail "set-hook -B replacement failed"
|
||||
assert_unchanged @seen-last 0
|
||||
$TMUX rename-session one || fail "rename-session one failed"
|
||||
wait_for @seen-last 'two->one'
|
||||
|
||||
$TMUX set-hook -gu -B @session-name || fail "set-hook -gu -B failed"
|
||||
shown=$($TMUX show-hooks -g -B @session-name) ||
|
||||
fail "show-hooks -B after remove failed"
|
||||
[ -z "$shown" ] || fail "show-hooks -B showed removed monitor: $shown"
|
||||
last=$($TMUX show -gqv @seen-last)
|
||||
$TMUX rename-session three || fail "rename-session three failed"
|
||||
assert_unchanged @seen-last "$last"
|
||||
|
||||
$TMUX set -gu @value || fail "unset @value failed"
|
||||
$TMUX set -g @empty-seen 0 || fail "set @empty-seen failed"
|
||||
$TMUX set-hook -g -B '@empty::#{@value}' \
|
||||
'set -g @empty-seen "#{hook_last}->#{hook_value}"' ||
|
||||
fail "set-hook -B empty failed"
|
||||
assert_unchanged @empty-seen 0
|
||||
$TMUX set -g @value changed || fail "set @value failed"
|
||||
wait_for @empty-seen '->changed'
|
||||
|
||||
if $TMUX set-hook -g -B 'bad::#{session_name}' 'display-message x' \
|
||||
>"$OUT/bad.out" 2>"$OUT/bad.err"; then
|
||||
fail "non-@ monitor hook name was accepted"
|
||||
fi
|
||||
|
||||
session=$($TMUX display -p '#{session_id}')
|
||||
window=$($TMUX display -p '#{window_id}')
|
||||
pane=$($TMUX display -p '#{pane_id}')
|
||||
pane_number=${pane#%}
|
||||
|
||||
$TMUX set -gu @pane-value || fail "unset @pane-value failed"
|
||||
$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"
|
||||
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}"' ||
|
||||
fail "set-hook -B pane replacement failed"
|
||||
assert_unchanged @pane-seen 0
|
||||
$TMUX set -g @pane-value changed || fail "set @pane-value failed"
|
||||
wait_for @pane-seen "$session:$window:0:$pane:changed"
|
||||
|
||||
$TMUX set -g @exact-value one || fail "set @exact-value failed"
|
||||
$TMUX set -g @exact-seen 0 || fail "set @exact-seen failed"
|
||||
$TMUX set -gw @foo 'set -g @exact-seen inherited' ||
|
||||
fail "set global @foo failed"
|
||||
$TMUX set-hook -w -B '@foo::#{@exact-value}' ||
|
||||
fail "set-hook -B exact scope monitor failed"
|
||||
assert_unchanged @exact-seen 0
|
||||
$TMUX set -g @exact-value two || fail "set @exact-value two failed"
|
||||
assert_unchanged @exact-seen 0
|
||||
$TMUX set-hook -w -B '@foo::#{@exact-value}' \
|
||||
'set -g @exact-seen "#{hook_value}"' ||
|
||||
fail "set-hook -B exact scope command failed"
|
||||
assert_unchanged @exact-seen 0
|
||||
$TMUX set -g @exact-value three || fail "set @exact-value three failed"
|
||||
wait_for @exact-seen three
|
||||
|
||||
target_pane=$($TMUX splitw -P -F '#{pane_id}') ||
|
||||
fail "split-window failed"
|
||||
$TMUX set -g @target-pane 0 || fail "set @target-pane failed"
|
||||
$TMUX set-hook -g -B '@target:%*:#{@target-value}' \
|
||||
'set -g @target-pane "#{pane_id}"' ||
|
||||
fail "set-hook -B target pane failed"
|
||||
assert_unchanged @target-pane 0
|
||||
$TMUX set -pt "$target_pane" @target-value changed ||
|
||||
fail "set pane @target-value failed"
|
||||
wait_for @target-pane "$target_pane"
|
||||
|
||||
exit 0
|
||||
150
regress/targets-panes.sh
Normal file
150
regress/targets-panes.sh
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of pane target resolution in cmd-find.c.
|
||||
#
|
||||
# Building on targets.sh (session/window resolution), this exercises the pane
|
||||
# half of cmd_find_target() in a known 2x2 split:
|
||||
#
|
||||
# - pane ids (%n), pane indices, and the +/- offset and ! last-pane tokens;
|
||||
# - positional tokens {top-left}/{top-right}/{bottom-left}/{bottom-right}
|
||||
# and {top}/{bottom}/{left}/{right};
|
||||
# - directional tokens {up-of}/{down-of}/{left-of}/{right-of} relative to
|
||||
# the active pane;
|
||||
# - the ".pane" and "sess:win.pane" combined forms;
|
||||
# - the marked pane, reached with ~ / {marked} and cleared with -M;
|
||||
# - and the error paths: a pane id in the wrong window, a directional token
|
||||
# with no neighbour, and an unset marked pane.
|
||||
#
|
||||
# The 2x2 split is created in a fixed order so pane ids are deterministic:
|
||||
#
|
||||
# +--------+--------+
|
||||
# | %0 | %1 | top-left = %0 top-right = %1
|
||||
# +--------+--------+ bottom-left = %2 bottom-right = %3
|
||||
# | %2 | %3 |
|
||||
# +--------+--------+
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check $target $expected [format]
|
||||
#
|
||||
# The default format is the pane id.
|
||||
check()
|
||||
{
|
||||
fmt=${3:-'#{pane_id}'}
|
||||
out=$($TMUX display-message -p -t "$1" "$fmt" 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "target '$1' resolved wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_fail()
|
||||
{
|
||||
out=$($TMUX has-session -t "$2" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "target '$2' resolved (expected failure)."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$1" ]; then
|
||||
echo "Wrong error for target '$2'."
|
||||
echo "Expected: '$1'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- fixture: a 2x2 split plus a single-pane window -----------------------
|
||||
check_ok new-session -d -s p -x 80 -y 24
|
||||
check_ok split-window -h -t p:0 # %0 left, %1 right
|
||||
check_ok split-window -v -t p:0.%0 # split left: %0 top, %2 bottom
|
||||
check_ok split-window -v -t p:0.%1 # split right: %1 top, %3 bottom
|
||||
check_ok new-window -d -t p: -n solo # a second, single-pane window
|
||||
|
||||
# --- pane ids, index, offsets ---------------------------------------------
|
||||
check "p:0.%3" "%3" # exact pane id
|
||||
check "p:0.3" "%3" # pane by index
|
||||
check "p:0.%1" "%1" # sess:win.pane form
|
||||
check ".%1" "%1" # .pane form (current window)
|
||||
# "sess:.pane" (empty window part) resolves the pane in the session's current
|
||||
# window. Make window 0 current first.
|
||||
check_ok select-window -t p:0
|
||||
check "p:.%1" "%1"
|
||||
check "p:.{top-left}" "%0"
|
||||
# Offsets are relative to the active pane; make %0 active first.
|
||||
check_ok select-pane -t p:0.%0
|
||||
check "p:0.+" "1" '#{pane_index}' # next pane
|
||||
check "p:0.-" "3" '#{pane_index}' # previous pane (wraps)
|
||||
|
||||
# --- last pane (!) --------------------------------------------------------
|
||||
check_ok select-pane -t p:0.%2
|
||||
check_ok select-pane -t p:0.%0 # now the last pane is %2
|
||||
check "p:0.!" "%2"
|
||||
|
||||
# --- positional tokens (absolute geometry) --------------------------------
|
||||
check "p:0.{top-left}" "%0"
|
||||
check "p:0.{top-right}" "%1"
|
||||
check "p:0.{bottom-left}" "%2"
|
||||
check "p:0.{bottom-right}" "%3"
|
||||
check "p:0.{top}" "%0" # leftmost of the top row
|
||||
check "p:0.{bottom}" "%2" # leftmost of the bottom row
|
||||
check "p:0.{left}" "%0" # top of the left column
|
||||
check "p:0.{right}" "%1" # top of the right column
|
||||
|
||||
# --- directional tokens (relative to the active pane) ---------------------
|
||||
#
|
||||
# From the top-left pane the real neighbours are below and to the right.
|
||||
check_ok select-pane -t p:0.%0
|
||||
check "p:0.{down-of}" "%2"
|
||||
check "p:0.{right-of}" "%1"
|
||||
# From the bottom-right pane the real neighbours are above and to the left.
|
||||
check_ok select-pane -t p:0.%3
|
||||
check "p:0.{up-of}" "%1"
|
||||
check "p:0.{left-of}" "%2"
|
||||
|
||||
# --- pane error paths -----------------------------------------------------
|
||||
check_fail "can't find pane: %0" "p:solo.%0" # pane id, wrong window
|
||||
check_fail "can't find pane: {up-of}" "p:solo.{up-of}" # no neighbour
|
||||
check_fail "can't find pane: 9" "p:0.9" # no such index
|
||||
|
||||
# --- marked pane ----------------------------------------------------------
|
||||
#
|
||||
# ~ / {marked} resolve to the marked pane from anywhere; -M clears it.
|
||||
check_fail "no marked target" "~" # nothing marked yet
|
||||
check_ok select-pane -m -t p:0.%1
|
||||
check "~" "%1"
|
||||
check "{marked}" "%1"
|
||||
# The mark is global: it resolves even with a different current window.
|
||||
check_ok select-window -t p:solo
|
||||
check "~" "%1"
|
||||
check_ok select-window -t p:0
|
||||
check_ok select-pane -M # clear the mark
|
||||
check_fail "no marked target" "~"
|
||||
check_fail "no marked target" "{marked}"
|
||||
|
||||
assert_alive "after pane target tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
226
regress/targets.sh
Normal file
226
regress/targets.sh
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of target (session and window) resolution in cmd-find.c.
|
||||
#
|
||||
# A target string like "session:window.pane" is parsed by cmd_find_target()
|
||||
# and resolved to a concrete session/window/pane. This exercises the session
|
||||
# and window halves of that machinery:
|
||||
#
|
||||
# - session and window ids ($n, @n) and names;
|
||||
# - exact (=name), prefix and fnmatch matching, and the ambiguous/missing
|
||||
# error paths for each;
|
||||
# - the combined "sess:", "sess:win", ":win" and "sess:win.pane" forms and
|
||||
# the empty (current) target;
|
||||
# - the offset and special window tokens (^ $ ! + - and their {start},
|
||||
# {end}, {last}, {next}, {previous} spellings), including +N/-N with
|
||||
# wrap-around;
|
||||
# - the special whole-target tokens {active}/@/{current} and {mouse}/=;
|
||||
# - the CMD_FIND_WINDOW_INDEX "can't specify pane here" guard; and
|
||||
# - -s versus -t resolution on link-window/move-window.
|
||||
#
|
||||
# Positive cases are asserted with display-message -p -t (which renders the
|
||||
# resolved target); error cases with has-session -t, which resolves strictly
|
||||
# and prints the cmd-find error text.
|
||||
#
|
||||
# Pane resolution (directional/positional tokens, marked pane) is covered by
|
||||
# targets-panes.sh.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check $target $expected [format]
|
||||
#
|
||||
# Resolve $target and compare the rendered value. The default format is the
|
||||
# window index; pass a third argument to override.
|
||||
check()
|
||||
{
|
||||
fmt=${3:-'#{window_index}'}
|
||||
out=$($TMUX display-message -p -t "$1" "$fmt" 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "target '$1' resolved wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $target
|
||||
#
|
||||
# has-session resolves the target strictly and prints the cmd-find error.
|
||||
check_fail()
|
||||
{
|
||||
out=$($TMUX has-session -t "$2" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "target '$2' resolved (expected failure)."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$1" ]; then
|
||||
echo "Wrong error for target '$2'."
|
||||
echo "Expected: '$1'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- fixture --------------------------------------------------------------
|
||||
#
|
||||
# Session alpha with four named windows (0 editor, 1 editing, 2 shell,
|
||||
# 3 logs); "editor"/"editing" share a prefix for the ambiguity tests. Two
|
||||
# grp* sessions share a prefix for the session ambiguity tests.
|
||||
check_ok new-session -d -s alpha -x 80 -y 24
|
||||
check_ok rename-window -t alpha:0 editor
|
||||
check_ok new-window -d -t alpha: -n editing
|
||||
check_ok new-window -d -t alpha: -n shell
|
||||
check_ok new-window -d -t alpha: -n logs
|
||||
check_ok new-session -d -s beta -x 80 -y 24
|
||||
check_ok new-window -d -t beta: -n bw1
|
||||
check_ok new-session -d -s grp1 -x 80 -y 24
|
||||
check_ok new-session -d -s grp2 -x 80 -y 24
|
||||
|
||||
# Give alpha a last-window (2) with the current window left at 0.
|
||||
check_ok select-window -t alpha:2
|
||||
check_ok select-window -t alpha:0
|
||||
|
||||
# --- session ids and names ------------------------------------------------
|
||||
sid=$($TMUX display-message -p -t alpha: '#{session_id}')
|
||||
check "$sid:" "alpha" '#{session_name}'
|
||||
check "=alpha:" "alpha" '#{session_name}' # exact
|
||||
check "alpha:" "alpha" '#{session_name}' # full name
|
||||
check "al:" "alpha" '#{session_name}' # prefix
|
||||
check "al*:" "alpha" '#{session_name}' # fnmatch
|
||||
|
||||
# --- session error paths --------------------------------------------------
|
||||
check_fail "can't find session: grp" "grp:" # ambiguous prefix
|
||||
check_fail "can't find session: grp*" "grp*:" # ambiguous fnmatch
|
||||
check_fail "can't find session: al" "=al:" # exact-only, no such session
|
||||
check_fail "can't find session: nosuch" "nosuch:"
|
||||
|
||||
# --- window ids and names -------------------------------------------------
|
||||
wid=$($TMUX display-message -p -t alpha:editing '#{window_id}')
|
||||
# A bare @id (no session) resolves both window and session.
|
||||
check "$wid" "alpha:1" '#{session_name}:#{window_index}'
|
||||
check "alpha:shell" "2" # exact name
|
||||
check "alpha:edito" "0" # prefix
|
||||
check "alpha:=editor" "0" # exact match flag
|
||||
check "alpha:sh*" "2" # fnmatch
|
||||
check "alpha:1" "1" # index
|
||||
|
||||
# A window id qualified by a session resolves within that session; a window id
|
||||
# belonging to a different session is rejected.
|
||||
w2=$($TMUX display-message -p -t alpha:shell '#{window_id}')
|
||||
check "alpha:$w2" "2"
|
||||
bw=$($TMUX display-message -p -t beta: '#{window_id}')
|
||||
check_fail "can't find window: $bw" "alpha:$bw" # window id, wrong session
|
||||
|
||||
# --- window error paths ---------------------------------------------------
|
||||
check_fail "can't find window: edit" "alpha:edit" # ambiguous prefix
|
||||
check_fail "can't find window: e*" "alpha:e*" # ambiguous fnmatch
|
||||
check_fail "can't find window: nope" "alpha:nope" # missing
|
||||
check_fail "can't find window: @999" "@999" # missing window id
|
||||
|
||||
# --- offset and special window tokens -------------------------------------
|
||||
#
|
||||
# alpha's current window is 0; offsets wrap around the four windows.
|
||||
check "alpha:^" "0" # start
|
||||
check "alpha:\$" "3" # end
|
||||
check "alpha:+" "1" # next
|
||||
check "alpha:-" "3" # previous (wraps)
|
||||
check "alpha:+2" "2"
|
||||
check "alpha:-2" "2" # wraps
|
||||
check "alpha:{start}" "0"
|
||||
check "alpha:{end}" "3"
|
||||
check "alpha:{next}" "1"
|
||||
check "alpha:{previous}" "3"
|
||||
check "alpha:!" "2" # last window
|
||||
check "alpha:{last}" "2"
|
||||
|
||||
# --- combined and empty forms ---------------------------------------------
|
||||
#
|
||||
# Empty targets use the current pane from TMUX_PANE when there is no client.
|
||||
# This keeps the test independent of the best-session fallback.
|
||||
check_ok select-window -t alpha:0
|
||||
pane=$($TMUX display-message -p -t alpha:0 '#{pane_id}')
|
||||
TMUX_PANE=$pane check "" "alpha" '#{session_name}' # empty target is current
|
||||
TMUX_PANE=$pane check "" "alpha:0" '#{session_name}:#{window_index}'
|
||||
TMUX_PANE=$pane check ":shell" "alpha:2" '#{session_name}:#{window_index}'
|
||||
check "alpha:shell.0" "alpha:2" '#{session_name}:#{window_index}'
|
||||
TMUX_PANE=$pane check "alpha:.0" "alpha:0" '#{session_name}:#{window_index}' # empty window part
|
||||
|
||||
# --- bare-name fallbacks --------------------------------------------------
|
||||
#
|
||||
# A bare pane target that is not a pane falls back to a window, then to a
|
||||
# session, using the current session (alpha).
|
||||
TMUX_PANE=$pane check "editor" "0" '#{window_index}' # bare window name
|
||||
check "beta" "beta" '#{session_name}' # bare session name
|
||||
|
||||
# --- whole-target special tokens ------------------------------------------
|
||||
#
|
||||
# {active}/@/{current} need a client with a session; with only a detached
|
||||
# command client they must error cleanly (regression: this used to crash the
|
||||
# server via a NULL session dereference). {mouse}/= need a mouse event.
|
||||
check_fail "no current client" "{active}"
|
||||
check_fail "no current client" "@"
|
||||
check_fail "no current client" "{current}"
|
||||
check_fail "no mouse target" "{mouse}"
|
||||
check_fail "no mouse target" "="
|
||||
assert_alive "after whole-target special tokens"
|
||||
|
||||
# --- CMD_FIND_WINDOW_INDEX rejects a pane part ----------------------------
|
||||
out=$($TMUX new-window -d -t 'alpha:1.%0' 2>&1)
|
||||
[ $? -ne 0 ] || { echo "new-window with pane target succeeded"; exit 1; }
|
||||
[ "$out" = "can't specify pane here" ] || \
|
||||
{ echo "wrong pane-here error: '$out'"; exit 1; }
|
||||
|
||||
# --- window index targets: offsets resolve to an index --------------------
|
||||
#
|
||||
# new-window's -t is a window index (CMD_FIND_WINDOW_INDEX); an offset from
|
||||
# the current window (0) picks the numeric index rather than an existing
|
||||
# window, so "+6" creates window 6.
|
||||
check_ok select-window -t alpha:0
|
||||
check_ok new-window -d -t 'alpha:+6' -n offwin
|
||||
check "alpha:6" "offwin" '#{window_name}'
|
||||
check_ok kill-window -t alpha:6
|
||||
|
||||
# --- -s versus -t resolution ----------------------------------------------
|
||||
#
|
||||
# link-window takes a source window (-s) and a destination index (-t); each
|
||||
# side is resolved independently by cmd-find.
|
||||
check_ok new-session -d -s src -x 80 -y 24
|
||||
check_ok new-window -d -t src: -n payload
|
||||
check_ok link-window -s src:payload -t alpha:9
|
||||
check "alpha:9" "payload" '#{window_name}'
|
||||
# move-window relocates it; the old index must be gone.
|
||||
check_ok move-window -s alpha:9 -t alpha:5
|
||||
check "alpha:5" "payload" '#{window_name}'
|
||||
check_fail "can't find window: 9" "alpha:9"
|
||||
|
||||
# --- default state with no client -----------------------------------------
|
||||
#
|
||||
# run-shell with no target and no attached client has cmd-find build the
|
||||
# current state from nothing (the best session).
|
||||
check_ok run-shell 'true'
|
||||
|
||||
assert_alive "after target tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
@@ -90,7 +90,6 @@ You should see the Greek word 'kosme': "κόσμε"
|
||||
2.3.2 U-0000E000 = ee 80 80 = "" |
|
||||
2.3.3 U-0000FFFD = ef bf bd = "<22>" |
|
||||
2.3.4 U-0010FFFF = f4 8f bf bf = "" |
|
||||
2.3.5 U-00110000 = f4 90 80 80 = "<22>" |
|
||||
|
|
||||
3 Malformed sequences |
|
||||
|
|
||||
|
||||
406
regress/window-ops.sh
Normal file
406
regress/window-ops.sh
Normal file
@@ -0,0 +1,406 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of window management command semantics (not parsing), as implemented
|
||||
# in cmd-new-window.c, cmd-move-window.c (move-window and link-window),
|
||||
# cmd-unlink-window.c, cmd-swap-window.c, cmd-rotate-window.c,
|
||||
# cmd-kill-window.c and cmd-select-window.c.
|
||||
#
|
||||
# This exercises:
|
||||
# - new-window placement: next free index, explicit index, index in use with
|
||||
# and without -k, -a (after) and -b (before) insertion with shuffling, and
|
||||
# -S selecting an existing window by name instead of creating;
|
||||
# - move-window to a free index, to an occupied index with and without -k,
|
||||
# -a insertion and -r renumbering (including base-index);
|
||||
# - renumber-windows closing gaps;
|
||||
# - link-window sharing a window between two sessions (window_linked and
|
||||
# window_linked_sessions), unlink-window removing one link and refusing to
|
||||
# unlink the last link without -k;
|
||||
# - swap-window within and between sessions, -d keeping the active window,
|
||||
# and the grouped-sessions error;
|
||||
# - rotate-window -U/-D rotating pane positions;
|
||||
# - kill-window switching to the last (previously current) window, kill-window
|
||||
# -a killing all other windows and the "-f only valid with -a" guard.
|
||||
#
|
||||
# pane-ops.sh covers pane-level commands and buffers.sh covers paste buffers.
|
||||
|
||||
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 -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
# Run a command and require that it succeeds.
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
#
|
||||
# Run a command and require that it fails with the given error message.
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_windows $session $expected
|
||||
#
|
||||
# Compare the window list of a session (as "index:name index:name ...", in
|
||||
# index order) with $expected.
|
||||
check_windows()
|
||||
{
|
||||
out=$(echo $($TMUX list-windows -t "$1" -F \
|
||||
'#{window_index}:#{window_name}'))
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "Window list of '$1' wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fmt $target $format $expected
|
||||
#
|
||||
# Expand a format in a target's context and compare with $expected.
|
||||
check_fmt()
|
||||
{
|
||||
out=$($TMUX display-message -p -t "$1" "$2" 2>&1)
|
||||
if [ "$out" != "$3" ]; then
|
||||
echo "Format '$2' for '$1' wrong."
|
||||
echo "Expected: '$3'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# new-window placement.
|
||||
|
||||
check_ok new-session -d -s W -x 80 -y 24 -n w0
|
||||
|
||||
# Next free index.
|
||||
check_ok new-window -d -t W: -n w1
|
||||
check_ok new-window -d -t W: -n w2
|
||||
check_windows W '0:w0 1:w1 2:w2'
|
||||
|
||||
# Explicit index, then the next new window fills the first free index, not
|
||||
# one past the highest.
|
||||
check_ok new-window -d -t W:9 -n w9
|
||||
check_ok new-window -d -t W: -n w3
|
||||
check_windows W '0:w0 1:w1 2:w2 3:w3 9:w9'
|
||||
|
||||
# Occupied index fails without -k and replaces with -k.
|
||||
check_fail 'create window failed: index 9 in use' \
|
||||
new-window -d -t W:9 -n dup
|
||||
check_ok new-window -d -k -t W:9 -n w9k
|
||||
check_windows W '0:w0 1:w1 2:w2 3:w3 9:w9k'
|
||||
|
||||
# -a inserts after the target, shuffling the following windows up.
|
||||
check_ok new-window -d -a -t W:1 -n wA
|
||||
check_windows W '0:w0 1:w1 2:wA 3:w2 4:w3 9:w9k'
|
||||
|
||||
# -b inserts before the target, shuffling the target and followers up.
|
||||
check_ok new-window -d -b -t W:0 -n wB
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
|
||||
# -S selects an existing window with the same name instead of creating (with
|
||||
# -d it would not switch, so no -d here).
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -t W: -n w3
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '5:w3'
|
||||
|
||||
# Clean up to a known arrangement.
|
||||
check_ok kill-window -t W:wB
|
||||
check_ok kill-window -t W:wA
|
||||
check_ok kill-window -t W:w9k
|
||||
check_ok move-window -r -t W:
|
||||
check_windows W '0:w0 1:w1 2:w2 3:w3'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# move-window.
|
||||
|
||||
# To a free index.
|
||||
check_ok move-window -d -s W:2 -t W:7
|
||||
check_windows W '0:w0 1:w1 3:w3 7:w2'
|
||||
|
||||
# To an occupied index, without and with -k.
|
||||
check_fail 'index in use: 7' move-window -d -s W:3 -t W:7
|
||||
check_ok move-window -d -k -s W:3 -t W:7
|
||||
check_windows W '0:w0 1:w1 7:w3'
|
||||
|
||||
# -a inserts after the target and shuffles.
|
||||
check_ok move-window -d -a -s W:7 -t W:0
|
||||
check_windows W '0:w0 1:w3 2:w1'
|
||||
|
||||
# -r renumbers in order, respecting base-index.
|
||||
check_ok move-window -d -s W:2 -t W:8
|
||||
check_ok set-option -t W base-index 5
|
||||
check_ok move-window -r -t W:
|
||||
check_windows W '5:w0 6:w3 7:w1'
|
||||
check_ok set-option -t W base-index 0
|
||||
check_ok move-window -r -t W:
|
||||
check_windows W '0:w0 1:w3 2:w1'
|
||||
|
||||
# With the renumber-windows option on, killing a window renumbers the rest
|
||||
# automatically.
|
||||
check_ok set-option -t W renumber-windows on
|
||||
check_ok new-window -d -t W:9 -n wtmp
|
||||
check_windows W '0:w0 1:w3 2:w1 9:wtmp'
|
||||
check_ok kill-window -t W:1
|
||||
check_windows W '0:w0 1:w1 2:wtmp'
|
||||
check_ok kill-window -t W:2
|
||||
check_ok set-option -t W renumber-windows off
|
||||
check_ok new-window -d -t W:2 -n w3
|
||||
check_windows W '0:w0 1:w1 2:w3'
|
||||
|
||||
# Without -s, the current window of the client/session moves.
|
||||
check_ok select-window -t W:2
|
||||
check_ok move-window -d -t W:6
|
||||
check_windows W '0:w0 1:w1 6:w3'
|
||||
check_ok move-window -r -t W:
|
||||
check_windows W '0:w0 1:w1 2:w3'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# link-window and unlink-window.
|
||||
|
||||
check_ok new-session -d -s L -x 80 -y 24 -n l0
|
||||
|
||||
# Link a window from W into L and check it is shared.
|
||||
check_ok link-window -d -s W:w1 -t L:5
|
||||
check_windows L '0:l0 5:w1'
|
||||
check_fmt 'W:w1' '#{window_linked}' '1'
|
||||
check_fmt 'L:5' '#{window_linked_sessions}' '2'
|
||||
|
||||
# The linked window is the same window: renaming in one session shows in the
|
||||
# other.
|
||||
check_ok rename-window -t L:5 shared
|
||||
check_windows W '0:w0 1:shared 2:w3'
|
||||
check_ok rename-window -t W:1 w1
|
||||
|
||||
# Linking again to an occupied index fails without -k.
|
||||
check_fail 'index in use: 0' link-window -d -s W:w3 -t L:0
|
||||
|
||||
# Unlink removes one link; the window survives in the other session.
|
||||
check_ok unlink-window -t L:5
|
||||
check_windows L '0:l0'
|
||||
check_windows W '0:w0 1:w1 2:w3'
|
||||
check_fmt 'W:w1' '#{window_linked}' '0'
|
||||
|
||||
# Unlinking a window linked to only one session needs -k.
|
||||
check_fail 'window only linked to one session' unlink-window -t W:w3
|
||||
check_ok unlink-window -k -t W:w3
|
||||
check_windows W '0:w0 1:w1'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# swap-window.
|
||||
|
||||
check_ok new-window -d -t W:2 -n w2
|
||||
check_ok new-window -d -t W:3 -n w3
|
||||
|
||||
# Swap within a session: indices are exchanged.
|
||||
check_ok swap-window -d -s W:0 -t W:3
|
||||
check_windows W '0:w3 1:w1 2:w2 3:w0'
|
||||
check_ok swap-window -d -s W:0 -t W:3
|
||||
check_windows W '0:w0 1:w1 2:w2 3:w3'
|
||||
|
||||
# Without -d the current index does not change, so the window that arrives
|
||||
# there becomes current; with -d the swapped windows are selected, so the
|
||||
# source window stays current at its new index.
|
||||
check_ok select-window -t W:0
|
||||
check_ok swap-window -s W:0 -t W:3
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '0:w3'
|
||||
check_ok swap-window -s W:3 -t W:0
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '0:w0'
|
||||
check_ok swap-window -d -s W:0 -t W:3
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '3:w0'
|
||||
check_ok swap-window -d -s W:3 -t W:0
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '0:w0'
|
||||
|
||||
# Swap between two different sessions.
|
||||
check_ok swap-window -d -s W:w2 -t L:l0
|
||||
check_windows W '0:w0 1:w1 2:l0 3:w3'
|
||||
check_windows L '0:w2'
|
||||
check_ok swap-window -d -s W:2 -t L:0
|
||||
check_windows W '0:w0 1:w1 2:w2 3:w3'
|
||||
check_windows L '0:l0'
|
||||
|
||||
# Swapping between two sessions in the same group is an error.
|
||||
check_ok new-session -d -s WG -t W
|
||||
check_fail "can't move window, sessions are grouped" \
|
||||
swap-window -d -s W:0 -t WG:1
|
||||
check_ok kill-session -t WG
|
||||
check_windows W '0:w0 1:w1 2:w2 3:w3'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# rotate-window.
|
||||
|
||||
check_ok new-session -d -s R -x 80 -y 24
|
||||
check_ok split-window -d -t R:0
|
||||
check_ok split-window -d -t R:0
|
||||
p0=$($TMUX display-message -p -t R:0.0 '#{pane_id}')
|
||||
p1=$($TMUX display-message -p -t R:0.1 '#{pane_id}')
|
||||
p2=$($TMUX display-message -p -t R:0.2 '#{pane_id}')
|
||||
|
||||
# check_panes $target $expected
|
||||
#
|
||||
# Compare the pane list of a window (as "index:id ...") with $expected.
|
||||
check_panes()
|
||||
{
|
||||
out=$(echo $($TMUX list-panes -t "$1" -F \
|
||||
'#{pane_index}:#{pane_id}'))
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "Pane list of '$1' wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_panes R:0 "0:$p0 1:$p1 2:$p2"
|
||||
|
||||
# -U rotates panes up (each pane moves to the previous position); -D rotates
|
||||
# down. -U then -D restores the original order.
|
||||
check_ok rotate-window -U -t R:0
|
||||
check_panes R:0 "0:$p1 1:$p2 2:$p0"
|
||||
check_ok rotate-window -D -t R:0
|
||||
check_panes R:0 "0:$p0 1:$p1 2:$p2"
|
||||
check_ok rotate-window -D -t R:0
|
||||
check_panes R:0 "0:$p2 1:$p0 2:$p1"
|
||||
check_ok rotate-window -U -t R:0
|
||||
|
||||
# The active position is preserved across rotation: the pane that arrives at
|
||||
# the active position becomes the active pane.
|
||||
check_ok select-pane -t R:0.0
|
||||
check_ok rotate-window -U -t R:0
|
||||
check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p1"
|
||||
check_ok rotate-window -D -t R:0
|
||||
check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p0"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# kill-window.
|
||||
|
||||
# Killing the current window switches to the last (previously current)
|
||||
# window.
|
||||
check_ok select-window -t W:1
|
||||
check_ok select-window -t W:3
|
||||
check_fmt 'W:' '#{window_index}' '3'
|
||||
check_ok kill-window -t W:3
|
||||
check_fmt 'W:' '#{window_index}' '1'
|
||||
check_windows W '0:w0 1:w1 2:w2'
|
||||
|
||||
# -f is only valid with -a.
|
||||
check_fail '-f only valid with -a' kill-window -f 'x' -t W:0
|
||||
|
||||
# -a kills every window except the target.
|
||||
check_ok kill-window -a -t W:w1
|
||||
check_windows W '1:w1'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# select-window, next-window, previous-window.
|
||||
|
||||
# -P prints where the new window went; an invalid (non-UTF-8) name is an
|
||||
# error.
|
||||
out=$($TMUX new-window -d -t W:0 -n wa -P -F '#{window_index}:#{window_name}')
|
||||
if [ "$out" != "0:wa" ]; then
|
||||
echo "new-window -P output wrong: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
check_fail "invalid window name: $(printf 'a\377b')" \
|
||||
new-window -d -t W: -n "$(printf 'a\377b')"
|
||||
check_ok new-window -d -t W:2 -n wc
|
||||
check_windows W '0:wa 1:w1 2:wc'
|
||||
|
||||
# -n and -p select the next and previous window, wrapping at the ends.
|
||||
check_ok select-window -t W:0
|
||||
check_ok select-window -n -t W:
|
||||
check_fmt 'W:' '#{window_index}' '1'
|
||||
check_ok select-window -n -t W:
|
||||
check_fmt 'W:' '#{window_index}' '2'
|
||||
check_ok select-window -n -t W:
|
||||
check_fmt 'W:' '#{window_index}' '0'
|
||||
check_ok select-window -p -t W:
|
||||
check_fmt 'W:' '#{window_index}' '2'
|
||||
|
||||
# next-window and previous-window are the same code.
|
||||
check_ok next-window -t W:
|
||||
check_fmt 'W:' '#{window_index}' '0'
|
||||
check_ok previous-window -t W:
|
||||
check_fmt 'W:' '#{window_index}' '2'
|
||||
|
||||
# -l selects the previously current window and select-window -T on the
|
||||
# already-current window does the same.
|
||||
check_ok select-window -t W:1
|
||||
check_ok select-window -l -t W:
|
||||
check_fmt 'W:' '#{window_index}' '2'
|
||||
check_ok select-window -T -t W:2
|
||||
check_fmt 'W:' '#{window_index}' '1'
|
||||
check_ok select-window -T -t W:2
|
||||
check_fmt 'W:' '#{window_index}' '2'
|
||||
|
||||
# With -a, next-window looks for a window with an alert and fails if there
|
||||
# is none; a fresh session has no last window.
|
||||
check_fail 'no next window' next-window -a -t W:
|
||||
check_fail 'no previous window' previous-window -a -t W:
|
||||
check_ok new-session -d -s F -x 80 -y 24
|
||||
check_fail 'no last window' select-window -l -t F:
|
||||
check_fail 'no last window' select-window -T -t F:0
|
||||
check_ok kill-session -t F
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# more kill-window -a: no-op, filters and multiply-linked windows.
|
||||
|
||||
# -a with a single window in the session does nothing.
|
||||
check_ok kill-window -a -t L:0
|
||||
check_windows L '0:l0'
|
||||
|
||||
# -a -f only kills other windows matching the filter.
|
||||
check_ok kill-window -a -f '#{==:#{window_name},wc}' -t W:0
|
||||
check_windows W '0:wa 1:w1'
|
||||
|
||||
# If the current window is linked into the session more than once, -a kills
|
||||
# it too - taking the whole session with it here.
|
||||
check_ok new-session -d -s D -x 80 -y 24 -n d0
|
||||
check_ok link-window -d -s D:0 -t D:5
|
||||
check_ok new-window -d -t D:1 -n dx
|
||||
check_ok select-window -t D:0
|
||||
check_ok kill-window -a -t D:0
|
||||
if $TMUX has-session -t D 2>/dev/null; then
|
||||
echo "Session D survived kill-window -a on multiply-linked window."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_fmt 'R:0' '#{window_panes}' '3'
|
||||
assert_alive
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
@@ -1655,12 +1655,14 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
if (wp != NULL) {
|
||||
if (wp->base.mode & MODE_SYNC)
|
||||
screen_write_stop_sync(wp);
|
||||
screen_write_clear_dirty(wp);
|
||||
} else {
|
||||
TAILQ_FOREACH(loop, &scene->w->panes, entry) {
|
||||
if (!window_pane_is_visible(loop))
|
||||
continue;
|
||||
if (loop->base.mode & MODE_SYNC)
|
||||
screen_write_stop_sync(loop);
|
||||
screen_write_clear_dirty(loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
211
screen-write.c
211
screen-write.c
@@ -38,6 +38,7 @@ static int screen_write_overwrite(struct screen_write_ctx *,
|
||||
struct grid_cell *, u_int);
|
||||
static int screen_write_combine(struct screen_write_ctx *,
|
||||
const struct grid_cell *);
|
||||
static void screen_write_flush_dirty(struct window_pane *);
|
||||
|
||||
struct screen_write_citem {
|
||||
u_int x;
|
||||
@@ -214,6 +215,48 @@ screen_write_pane_is_obscured(struct screen_write_ctx *ctx)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Should we draw to the TTY? */
|
||||
static int
|
||||
screen_write_should_draw_lines(struct screen_write_ctx *ctx, u_int y, u_int ny)
|
||||
{
|
||||
struct window_pane *wp = ctx->wp;
|
||||
struct screen *s = ctx->s;
|
||||
u_int sy = screen_size_y(s);
|
||||
bitstr_t *bs;
|
||||
|
||||
if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP)))
|
||||
return (0);
|
||||
if (s->mode & MODE_SYNC) {
|
||||
if (wp != NULL && y < sy && ny != 0) {
|
||||
bs = wp->sync_dirty;
|
||||
if (ny > sy - y)
|
||||
ny = sy - y;
|
||||
if (bs == NULL || wp->sync_dirty_size != sy) {
|
||||
if (bs != NULL && wp->sync_dirty_size != sy) {
|
||||
y = 0;
|
||||
ny = sy;
|
||||
}
|
||||
free(bs);
|
||||
|
||||
bs = wp->sync_dirty = bit_alloc(sy);
|
||||
if (bs == NULL)
|
||||
fatal("bit_alloc failed");
|
||||
wp->sync_dirty_size = sy;
|
||||
}
|
||||
bit_nset(bs, y, y + ny - 1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Should we draw this line to the TTY? */
|
||||
static int
|
||||
screen_write_should_draw_line(struct screen_write_ctx *ctx, u_int y)
|
||||
{
|
||||
return (screen_write_should_draw_lines(ctx, y, 1));
|
||||
}
|
||||
|
||||
/* Set up context for TTY command. */
|
||||
static void
|
||||
screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
|
||||
@@ -979,7 +1022,7 @@ screen_write_sync_callback(__unused int fd, __unused short events, void *arg)
|
||||
|
||||
if (wp->base.mode & MODE_SYNC) {
|
||||
wp->base.mode &= ~MODE_SYNC;
|
||||
wp->flags |= PANE_REDRAW;
|
||||
screen_write_flush_dirty(wp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1004,14 +1047,14 @@ screen_write_start_sync(struct window_pane *wp)
|
||||
void
|
||||
screen_write_stop_sync(struct window_pane *wp)
|
||||
{
|
||||
if (wp == NULL)
|
||||
if (wp == NULL || (~wp->base.mode & MODE_SYNC))
|
||||
return;
|
||||
|
||||
if (event_initialized(&wp->sync_timer))
|
||||
evtimer_del(&wp->sync_timer);
|
||||
wp->base.mode &= ~MODE_SYNC;
|
||||
|
||||
wp->flags |= PANE_REDRAW;
|
||||
screen_write_flush_dirty(wp);
|
||||
|
||||
log_debug("%s: %%%u stopped sync mode", __func__, wp->id);
|
||||
}
|
||||
@@ -1166,9 +1209,6 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
return;
|
||||
|
||||
r = window_visible_ranges(wp, xoff, yoff + yy, sx, NULL);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
ri = &r->ranges[i];
|
||||
@@ -1207,6 +1247,44 @@ screen_write_redraw_line(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
|
||||
}
|
||||
}
|
||||
|
||||
/* Redraw dirty lines. */
|
||||
static void
|
||||
screen_write_flush_dirty(struct window_pane *wp)
|
||||
{
|
||||
struct screen_write_ctx ctx;
|
||||
struct tty_ctx ttyctx;
|
||||
struct screen *s = &wp->base;
|
||||
u_int y, sy = screen_size_y(s), lines = 0;
|
||||
|
||||
if (wp->sync_dirty == NULL)
|
||||
return;
|
||||
|
||||
screen_write_start_pane(&ctx, wp, s);
|
||||
screen_write_initctx(&ctx, &ttyctx, 1, 1);
|
||||
|
||||
for (y = 0; y < sy; y++) {
|
||||
if (bit_test(wp->sync_dirty, y)) {
|
||||
screen_write_redraw_line(&ctx, &ttyctx, y);
|
||||
lines++;
|
||||
}
|
||||
}
|
||||
log_debug("%s: %%%u had %u dirty lines", __func__, wp->id, lines);
|
||||
|
||||
screen_write_stop(&ctx);
|
||||
screen_write_clear_dirty(wp);
|
||||
}
|
||||
|
||||
/* Clear any dirty lines. */
|
||||
void
|
||||
screen_write_clear_dirty(struct window_pane *wp)
|
||||
{
|
||||
if (wp != NULL && wp->sync_dirty != NULL) {
|
||||
free(wp->sync_dirty);
|
||||
wp->sync_dirty = NULL;
|
||||
wp->sync_dirty_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Redraw all visible cells in a pane. */
|
||||
static void
|
||||
screen_write_redraw_pane(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
|
||||
@@ -1249,7 +1327,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_lines(ctx, 0, screen_size_y(s)))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_alignmenttest, &ttyctx);
|
||||
@@ -1290,7 +1368,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = nx;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_line(ctx, s->cy))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_insertcharacter, &ttyctx);
|
||||
@@ -1331,7 +1409,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = nx;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_line(ctx, s->cy))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_deletecharacter, &ttyctx);
|
||||
@@ -1372,7 +1450,7 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = nx;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_line(ctx, s->cy))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_clearcharacter, &ttyctx);
|
||||
@@ -1413,7 +1491,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = ny;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_lines(ctx, s->cy, sy - s->cy))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_insertline, &ttyctx);
|
||||
@@ -1440,7 +1518,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = ny;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_lines(ctx, s->cy, s->rlower + 1 - s->cy))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_insertline, &ttyctx);
|
||||
@@ -1457,7 +1535,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct tty_ctx ttyctx;
|
||||
u_int sy = screen_size_y(s);
|
||||
u_int sy = screen_size_y(s), ry;
|
||||
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
@@ -1481,7 +1559,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = ny;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
ry = s->rlower + 1 - s->rupper;
|
||||
if (!screen_write_should_draw_lines(ctx, s->rupper, ry))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_deleteline, &ttyctx);
|
||||
@@ -1492,8 +1571,9 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ny > s->rlower + 1 - s->cy)
|
||||
ny = s->rlower + 1 - s->cy;
|
||||
ry = s->rlower + 1 - s->cy;
|
||||
if (ny > ry)
|
||||
ny = ry;
|
||||
if (ny == 0)
|
||||
return;
|
||||
|
||||
@@ -1508,7 +1588,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = ny;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_lines(ctx, s->cy, ry))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_deleteline, &ttyctx);
|
||||
@@ -1642,29 +1722,34 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
|
||||
{
|
||||
struct screen *s = ctx->s;
|
||||
struct tty_ctx ttyctx;
|
||||
u_int ry;
|
||||
|
||||
if (s->cy != s->rupper) {
|
||||
if (s->cy > 0)
|
||||
screen_write_set_cursor(ctx, -1, s->cy - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->cy == s->rupper) {
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
ttyctx.bg = bg;
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
ttyctx.bg = bg;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_reverseindex, &ttyctx);
|
||||
return;
|
||||
}
|
||||
ry = s->rlower + 1 - s->rupper;
|
||||
if (!screen_write_should_draw_lines(ctx, s->rupper, ry))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_reverseindex, &ttyctx);
|
||||
return;
|
||||
}
|
||||
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
} else if (s->cy > 0)
|
||||
screen_write_set_cursor(ctx, -1, s->cy - 1);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
}
|
||||
|
||||
/* Set scroll region. */
|
||||
@@ -1714,20 +1799,24 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
ctx->bg = bg;
|
||||
}
|
||||
|
||||
if (s->cy == s->rlower) {
|
||||
if (s->cy != s->rlower) {
|
||||
if (s->cy < screen_size_y(s) - 1)
|
||||
screen_write_set_cursor(ctx, -1, s->cy + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (rlower == screen_size_y(s) - 1)
|
||||
redraw = image_scroll_up(s, 1);
|
||||
else
|
||||
redraw = image_check_line(s, rupper, rlower - rupper);
|
||||
if (redraw && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
if (rlower == screen_size_y(s) - 1)
|
||||
redraw = image_scroll_up(s, 1);
|
||||
else
|
||||
redraw = image_check_line(s, rupper, rlower - rupper);
|
||||
if (redraw && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
|
||||
screen_write_collect_scroll(ctx, bg);
|
||||
ctx->scrolled++;
|
||||
} else if (s->cy < screen_size_y(s) - 1)
|
||||
screen_write_set_cursor(ctx, -1, s->cy + 1);
|
||||
|
||||
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
|
||||
screen_write_collect_scroll(ctx, bg);
|
||||
ctx->scrolled++;
|
||||
}
|
||||
|
||||
/* Scroll up. */
|
||||
@@ -1767,7 +1856,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct tty_ctx ttyctx;
|
||||
u_int i;
|
||||
u_int i, ry;
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
ttyctx.bg = bg;
|
||||
@@ -1788,7 +1877,8 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = lines;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
ry = s->rlower + 1 - s->rupper;
|
||||
if (!screen_write_should_draw_lines(ctx, s->rupper, ry))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED || ctx->wp == NULL) {
|
||||
tty_write(tty_cmd_scrolldown, &ttyctx);
|
||||
@@ -1841,7 +1931,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1));
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_lines(ctx, s->cy, sy - s->cy))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) {
|
||||
tty_write(tty_cmd_clearendofscreen, &ttyctx);
|
||||
@@ -1916,7 +2006,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
screen_write_collect_clear(ctx, 0, s->cy);
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_lines(ctx, 0, s->cy + 1))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) {
|
||||
tty_write(tty_cmd_clearstartofscreen, &ttyctx);
|
||||
@@ -1989,7 +2079,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
|
||||
screen_write_collect_clear(ctx, 0, sy);
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_lines(ctx, 0, sy))
|
||||
return;
|
||||
if (~ttyctx.flags & TTY_CTX_PANE_OBSCURED) {
|
||||
tty_write(tty_cmd_clearscreen, &ttyctx);
|
||||
@@ -2303,12 +2393,21 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only,
|
||||
const char *from)
|
||||
{
|
||||
struct screen *s = ctx->s;
|
||||
struct window_pane *wp = ctx->wp;
|
||||
u_int y, cx, cy, items = 0;
|
||||
struct screen_write_citem *ci, *tmp;
|
||||
struct screen_write_cline *cl;
|
||||
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (wp != NULL && (wp->flags & (PANE_REDRAW|PANE_DROP)))
|
||||
goto discard;
|
||||
if (s->mode & MODE_SYNC) {
|
||||
for (y = 0; y < screen_size_y(s); y++) {
|
||||
cl = &s->write_list[y];
|
||||
if (!TAILQ_EMPTY(&cl->items))
|
||||
screen_write_should_draw_line(ctx, y);
|
||||
}
|
||||
goto discard;
|
||||
}
|
||||
|
||||
if (ctx->scrolled != 0) {
|
||||
if (!screen_write_collect_flush_scrolled(ctx))
|
||||
@@ -2646,12 +2745,12 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
if (s->mode & MODE_INSERT) {
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
ttyctx.n = width;
|
||||
if (~s->mode & MODE_SYNC)
|
||||
if (screen_write_should_draw_line(ctx, s->cy))
|
||||
tty_write(tty_cmd_insertcharacter, &ttyctx);
|
||||
}
|
||||
|
||||
/* If not writing, done now. */
|
||||
if (skip || s->mode & MODE_SYNC)
|
||||
if (skip || !screen_write_should_draw_line(ctx, s->cy))
|
||||
return;
|
||||
|
||||
/* Do a full line redraw if needed. */
|
||||
@@ -2671,7 +2770,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
for (i = 0, vis = 0; i < r->used; i++)
|
||||
vis += r->ranges[i].nx;
|
||||
if (vis >= width) {
|
||||
if (~s->mode & MODE_SYNC)
|
||||
if (screen_write_should_draw_line(ctx, s->cy))
|
||||
tty_write(tty_cmd_cell, &ttyctx);
|
||||
return;
|
||||
}
|
||||
@@ -2681,7 +2780,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
* spaces in the visible regions.
|
||||
*/
|
||||
utf8_set(&tmp_gc.data, ' ');
|
||||
if (s->mode & MODE_SYNC)
|
||||
if (!screen_write_should_draw_line(ctx, s->cy))
|
||||
return;
|
||||
for (i = 0; i < r->used; i++) {
|
||||
ri = &r->ranges[i];
|
||||
@@ -2824,7 +2923,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
ttyctx.cell = &last;
|
||||
if (force_wide)
|
||||
ttyctx.flags |= TTY_CTX_CELL_INVALIDATE;
|
||||
if (~s->mode & MODE_SYNC)
|
||||
if (screen_write_should_draw_line(ctx, cy))
|
||||
tty_write(tty_cmd_cell, &ttyctx);
|
||||
screen_write_set_cursor(ctx, cx, cy);
|
||||
|
||||
|
||||
7
screen.c
7
screen.c
@@ -99,12 +99,12 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
|
||||
s->write_list = NULL;
|
||||
s->hyperlinks = NULL;
|
||||
|
||||
screen_reinit(s);
|
||||
screen_reinit(s, 1);
|
||||
}
|
||||
|
||||
/* Reinitialise screen. */
|
||||
void
|
||||
screen_reinit(struct screen *s)
|
||||
screen_reinit(struct screen *s, int check)
|
||||
{
|
||||
s->cx = 0;
|
||||
s->cy = 0;
|
||||
@@ -123,7 +123,8 @@ screen_reinit(struct screen *s)
|
||||
s->saved_cy = UINT_MAX;
|
||||
|
||||
screen_reset_tabs(s);
|
||||
|
||||
if (check)
|
||||
grid_check_is_clear(s->grid);
|
||||
grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8);
|
||||
|
||||
screen_clear_selection(s);
|
||||
|
||||
@@ -1193,6 +1193,8 @@ server_client_update_theme_colours(struct client *c)
|
||||
format_defaults(ft, c, NULL, NULL, NULL);
|
||||
|
||||
theme = c->theme;
|
||||
if (theme == THEME_UNKNOWN)
|
||||
theme = colour_totheme(c->tty.bg);
|
||||
if (option == 2)
|
||||
theme = THEME_LIGHT;
|
||||
else if (option == 3)
|
||||
@@ -2048,19 +2050,18 @@ server_client_reset_state(struct client *c)
|
||||
if (!window_position_is_visible(r, cx))
|
||||
cursor = 0;
|
||||
|
||||
if (window_pane_scrollbar_overlay_visible(wp)) {
|
||||
sb_w = wp->scrollbar_style.width;
|
||||
if (sb_w > wp->sx)
|
||||
sb_w = wp->sx;
|
||||
if (sb_w != 0 &&
|
||||
w->sb_pos ==
|
||||
PANE_SCROLLBARS_LEFT) {
|
||||
if (s->cx < sb_w)
|
||||
if (window_pane_scrollbar_overlay_visible(wp)) {
|
||||
sb_w = wp->scrollbar_style.width;
|
||||
if (sb_w > wp->sx)
|
||||
sb_w = wp->sx;
|
||||
if (sb_w != 0 &&
|
||||
w->sb_pos == PANE_SCROLLBARS_LEFT) {
|
||||
if (s->cx < sb_w)
|
||||
cursor = 0;
|
||||
} else if (sb_w != 0 &&
|
||||
s->cx >= wp->sx - sb_w)
|
||||
cursor = 0;
|
||||
} else if (sb_w != 0 &&
|
||||
s->cx >= wp->sx - sb_w)
|
||||
cursor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (status_at_line(c) == 0)
|
||||
cy += status_line_size(c);
|
||||
@@ -2089,10 +2090,10 @@ server_client_reset_state(struct client *c)
|
||||
mode |= MODE_MOUSE_ALL;
|
||||
}
|
||||
}
|
||||
if (options_get_number(oo, "focus-follows-mouse") ||
|
||||
w->sb == PANE_SCROLLBARS_MODAL ||
|
||||
w->sb == PANE_SCROLLBARS_AUTOHIDE)
|
||||
mode |= MODE_MOUSE_ALL;
|
||||
if (options_get_number(oo, "focus-follows-mouse") ||
|
||||
w->sb == PANE_SCROLLBARS_MODAL ||
|
||||
w->sb == PANE_SCROLLBARS_AUTOHIDE)
|
||||
mode |= MODE_MOUSE_ALL;
|
||||
else if (~mode & MODE_MOUSE_ALL)
|
||||
mode |= MODE_MOUSE_BUTTON;
|
||||
}
|
||||
|
||||
17
spawn.c
17
spawn.c
@@ -104,7 +104,7 @@ spawn_window(struct spawn_context *sc, char **cause)
|
||||
sc->wp0 = TAILQ_FIRST(&w->panes);
|
||||
TAILQ_REMOVE(&w->panes, sc->wp0, entry);
|
||||
|
||||
layout_free(w);
|
||||
layout_free(w, 0);
|
||||
window_destroy_panes(w);
|
||||
|
||||
TAILQ_INSERT_HEAD(&w->panes, sc->wp0, entry);
|
||||
@@ -264,14 +264,20 @@ spawn_pane(struct spawn_context *sc, char **cause)
|
||||
free(cwd);
|
||||
return (NULL);
|
||||
}
|
||||
if (sc->wp0->fd != -1) {
|
||||
if (sc->wp0->event != NULL) {
|
||||
bufferevent_free(sc->wp0->event);
|
||||
sc->wp0->event = NULL;
|
||||
}
|
||||
if (sc->wp0->fd != -1) {
|
||||
close(sc->wp0->fd);
|
||||
sc->wp0->fd = -1;
|
||||
}
|
||||
window_pane_reset_mode_all(sc->wp0);
|
||||
screen_reinit(&sc->wp0->base);
|
||||
input_free(sc->wp0->ictx);
|
||||
sc->wp0->ictx = NULL;
|
||||
screen_reinit(&sc->wp0->base, 0);
|
||||
if (sc->wp0->ictx != NULL) {
|
||||
input_free(sc->wp0->ictx);
|
||||
sc->wp0->ictx = NULL;
|
||||
}
|
||||
new_wp = sc->wp0;
|
||||
new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
|
||||
} else {
|
||||
@@ -387,6 +393,7 @@ spawn_pane(struct spawn_context *sc, char **cause)
|
||||
new_wp->base.mode |= MODE_CRLF;
|
||||
goto complete;
|
||||
}
|
||||
new_wp->flags &= ~PANE_EMPTY;
|
||||
|
||||
/* Store current working directory and change to new one. */
|
||||
if (getcwd(path, sizeof path) != NULL) {
|
||||
|
||||
2
status.c
2
status.c
@@ -488,7 +488,7 @@ status_message_redraw(struct client *c)
|
||||
status_message_area(c, &ax, &aw);
|
||||
|
||||
ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
|
||||
memcpy(&gc, &grid_default_cell, sizeof gc);
|
||||
style_apply(&gc, s->options, "message-style", ft);
|
||||
|
||||
/*
|
||||
* Set #{message} in the format tree. If styles should be ignored in
|
||||
|
||||
119
tmux.1
119
tmux.1
@@ -2773,7 +2773,7 @@ The pane must not already be floating or hidden, and the window must not
|
||||
be zoomed.
|
||||
.Tg capturep
|
||||
.It Xo Ic capture\-pane
|
||||
.Op Fl aeFHLpPqCJMN
|
||||
.Op Fl aeFHLpPRqCJMN
|
||||
.Op Fl b Ar buffer\-name
|
||||
.Op Fl E Ar end\-line
|
||||
.Op Fl S Ar start\-line
|
||||
@@ -2833,6 +2833,8 @@ With
|
||||
.Fl H ,
|
||||
only hyperlinks in the specified lines are captured.
|
||||
Multiple hyperlinks on the same line are separated by spaces.
|
||||
.Fl R
|
||||
dumps the internal grid data for diagnostics.
|
||||
.Pp
|
||||
.Fl S
|
||||
and
|
||||
@@ -3250,6 +3252,15 @@ is omitted and a marked pane is present (see
|
||||
.Ic select\-pane
|
||||
.Fl m ) ,
|
||||
the marked pane is used rather than the current pane.
|
||||
.Pp
|
||||
If
|
||||
.Ar src\-pane
|
||||
is floating and
|
||||
.Ar dst\-pane
|
||||
is either unspecified or equal to
|
||||
.Ar src\-pane ,
|
||||
.Ar src\-pane
|
||||
is returned to its previous position in the layout.
|
||||
.Tg killp
|
||||
.It Xo Ic kill\-pane
|
||||
.Op Fl a
|
||||
@@ -3546,7 +3557,7 @@ the
|
||||
option.
|
||||
.Tg neww
|
||||
.It Xo Ic new\-window
|
||||
.Op Fl abdkPS
|
||||
.Op Fl abdEkPS
|
||||
.Op Fl c Ar start\-directory
|
||||
.Op Fl e Ar environment
|
||||
.Op Fl F Ar format
|
||||
@@ -3570,6 +3581,9 @@ is the new window location.
|
||||
If
|
||||
.Fl d
|
||||
is given, the session does not make the new window the current window.
|
||||
If
|
||||
.Fl E
|
||||
is given, the initial pane is created without a running command.
|
||||
.Ar target\-window
|
||||
represents the window to be created; if the target already exists an error is
|
||||
shown, unless the
|
||||
@@ -3869,7 +3883,7 @@ This command will automatically set
|
||||
to manual in the window options.
|
||||
.Tg respawnp
|
||||
.It Xo Ic respawn\-pane
|
||||
.Op Fl k
|
||||
.Op Fl \&Ek
|
||||
.Op Fl c Ar start\-directory
|
||||
.Op Fl e Ar environment
|
||||
.Op Fl t Ar target\-pane
|
||||
@@ -3886,6 +3900,9 @@ executed.
|
||||
The pane must be already inactive, unless
|
||||
.Fl k
|
||||
is given, in which case any existing command is killed.
|
||||
If
|
||||
.Fl E
|
||||
is given, the pane is left without a running command.
|
||||
.Fl c
|
||||
specifies a new working directory for the pane.
|
||||
The
|
||||
@@ -3895,7 +3912,7 @@ option has the same meaning as for the
|
||||
command.
|
||||
.Tg respawnw
|
||||
.It Xo Ic respawn\-window
|
||||
.Op Fl k
|
||||
.Op Fl \&Ek
|
||||
.Op Fl c Ar start\-directory
|
||||
.Op Fl e Ar environment
|
||||
.Op Fl t Ar target\-window
|
||||
@@ -3912,6 +3929,9 @@ executed.
|
||||
The window must be already inactive, unless
|
||||
.Fl k
|
||||
is given, in which case any existing command is killed.
|
||||
If
|
||||
.Fl E
|
||||
is given, the window is left with one pane and without a running command.
|
||||
.Fl c
|
||||
specifies a new working directory for the window.
|
||||
The
|
||||
@@ -6558,6 +6578,7 @@ Hooks are managed with these commands:
|
||||
.Bl -tag -width Ds
|
||||
.It Xo Ic set\-hook
|
||||
.Op Fl agpRuw
|
||||
.Op Fl B Ar name:what:format
|
||||
.Op Fl t Ar target\-pane
|
||||
.Ar hook\-name
|
||||
.Op Ar command
|
||||
@@ -6574,18 +6595,53 @@ The flags are the same as for
|
||||
.Ic set\-option .
|
||||
.Pp
|
||||
With
|
||||
.Fl B ,
|
||||
.Ar name:what:format
|
||||
uses the same subscription syntax as
|
||||
.Ic refresh\-client
|
||||
.Fl B :
|
||||
.Ar name
|
||||
is the hook to run,
|
||||
.Ar what
|
||||
selects the session, pane, all panes, window, or all windows, and
|
||||
.Ar format
|
||||
is expanded once a second.
|
||||
For monitor hooks,
|
||||
.Ar name
|
||||
must begin with
|
||||
.Ql @ .
|
||||
If
|
||||
.Ar command
|
||||
is given, it is stored as the
|
||||
.Ql @
|
||||
hook command; otherwise only the monitor is created or replaced.
|
||||
Note that monitor hooks are not inherited, the hook is only run from
|
||||
the scope where it is created.
|
||||
With
|
||||
.Fl u ,
|
||||
the subscription named by
|
||||
.Fl B
|
||||
is removed.
|
||||
.Pp
|
||||
With
|
||||
.Fl R ,
|
||||
run
|
||||
.Ar hook\-name
|
||||
immediately.
|
||||
.It Xo Ic show\-hooks
|
||||
.Op Fl gpw
|
||||
.Op Fl Bgpw
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar hook
|
||||
.Xc
|
||||
Shows hooks.
|
||||
The flags are the same as for
|
||||
.Ic show\-options .
|
||||
.Pp
|
||||
With
|
||||
.Fl B ,
|
||||
shows the subscriptions installed with
|
||||
.Em set\-hook
|
||||
.Fl B .
|
||||
.El
|
||||
.Sh MOUSE SUPPORT
|
||||
If the
|
||||
@@ -6877,6 +6933,12 @@ results in
|
||||
replaces a
|
||||
.Nm
|
||||
colour by its six-digit hexadecimal RGB value.
|
||||
If an argument of
|
||||
.Ql f
|
||||
or
|
||||
.Ql b
|
||||
is given, it will instead produce the SGR escape sequence to set the foreground
|
||||
or background colour respectively.
|
||||
.Pp
|
||||
A limit may be placed on the length of the resultant string by prefixing it
|
||||
by an
|
||||
@@ -6928,7 +6990,7 @@ will use shorter but less accurate time format for times in the past.
|
||||
.Ql r
|
||||
.Pq Ql t/r
|
||||
will show the time relative to the current time, for example
|
||||
.Ql \1m
|
||||
.Ql \&1m
|
||||
or
|
||||
.Ql 2m23s .
|
||||
A custom format may be given using an
|
||||
@@ -6986,7 +7048,9 @@ or with
|
||||
.Ql a
|
||||
escape
|
||||
.Nm
|
||||
command arguments.
|
||||
command arguments; with
|
||||
.Ql s
|
||||
use single quotes.
|
||||
.Ql E:\&
|
||||
will expand the format twice, for example
|
||||
.Ql #{E:status\-left}
|
||||
@@ -7020,7 +7084,13 @@ to sort in reverse order.
|
||||
.Ql /r\&
|
||||
can also be used with
|
||||
.Ql P:\&
|
||||
to reverse the sort order by pane index.
|
||||
to reverse the sort order; by default panes are sorted by creation order.
|
||||
.Ql P:\&
|
||||
can also take
|
||||
.Ql /i\&
|
||||
to sort by pane index or
|
||||
.Ql /z\&
|
||||
to sort by z-index.
|
||||
For example,
|
||||
.Ql S/nr:\&
|
||||
to sort sessions by name in reverse order.
|
||||
@@ -7046,6 +7116,33 @@ prefix, for example a user option
|
||||
on the next window is available as
|
||||
.Ql next_@color .
|
||||
.Pp
|
||||
.Ql O:\&
|
||||
will loop over each option;
|
||||
array options are looped once for each array item.
|
||||
.Ql O:\&
|
||||
may be given a flag to choose the options table:
|
||||
.Bl -column "Flag" "Table" -offset indent
|
||||
.It Sy "Flag" Ta Sy "Table"
|
||||
.It Li "s" Ta "session"
|
||||
.It Li "w" Ta "window"
|
||||
.It Li "p" Ta "pane"
|
||||
.It Li "v" Ta "server"
|
||||
.El
|
||||
.Pp
|
||||
.Ql g
|
||||
chooses global options.
|
||||
The default is
|
||||
.Ql s .
|
||||
.Ql V:\&
|
||||
will loop over each environment variable.
|
||||
Its flags are:
|
||||
.Bl -column "Flag" "Environment" -offset indent
|
||||
.It Sy "Flag" Ta Sy "Environment"
|
||||
.It Li "s" Ta "session environment"
|
||||
.It Li "g" Ta "global environment"
|
||||
.It Li "c" Ta "client environment"
|
||||
.El
|
||||
.Pp
|
||||
.Ql N:\&
|
||||
checks if a window (without any suffix or with the
|
||||
.Ql w
|
||||
@@ -7182,10 +7279,13 @@ The following variables are available, where appropriate:
|
||||
.It Li "history_size" Ta "" Ta "Size of history in lines"
|
||||
.It Li "hook" Ta "" Ta "Name of running hook, if any"
|
||||
.It Li "hook_client" Ta "" Ta "Name of client where hook was run, if any"
|
||||
.It Li "hook_last" Ta "" Ta "Previous value for a monitor hook"
|
||||
.It Li "hook_pane" Ta "" Ta "ID of pane where hook was run, if any"
|
||||
.It Li "hook_session" Ta "" Ta "ID of session where hook was run, if any"
|
||||
.It Li "hook_session_name" Ta "" Ta "Name of session where hook was run, if any"
|
||||
.It Li "hook_value" Ta "" Ta "New value for a monitor hook"
|
||||
.It Li "hook_window" Ta "" Ta "ID of window where hook was run, if any"
|
||||
.It Li "hook_window_index" Ta "" Ta "Index of window where hook was run, if any"
|
||||
.It Li "hook_window_name" Ta "" Ta "Name of window where hook was run, if any"
|
||||
.It Li "host" Ta "#H" Ta "Hostname of local host"
|
||||
.It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)"
|
||||
@@ -7259,6 +7359,7 @@ The following variables are available, where appropriate:
|
||||
.It Li "pane_right" Ta "" Ta "Right of pane"
|
||||
.It Li "pane_search_string" Ta "" Ta "Last search string in copy mode"
|
||||
.It Li "pane_start_command" Ta "" Ta "Command pane started with"
|
||||
.It Li "pane_start_command_list" Ta "" Ta "Command pane started with, quoted"
|
||||
.It Li "pane_start_path" Ta "" Ta "Path pane started with"
|
||||
.It Li "pane_synchronized" Ta "" Ta "1 if pane is synchronized"
|
||||
.It Li "pane_tabs" Ta "" Ta "Pane tab positions"
|
||||
@@ -7344,6 +7445,8 @@ The following variables are available, where appropriate:
|
||||
.It Li "window_linked" Ta "" Ta "1 if window is linked across sessions"
|
||||
.It Li "window_linked_sessions" Ta "" Ta "Number of sessions this window is linked to"
|
||||
.It Li "window_linked_sessions_list" Ta "" Ta "List of sessions this window is linked to"
|
||||
.It Li "window_manual_height" Ta "" Ta "Manual height of window, if set"
|
||||
.It Li "window_manual_width" Ta "" Ta "Manual width of window, if set"
|
||||
.It Li "window_marked_flag" Ta "" Ta "1 if window contains the marked pane"
|
||||
.It Li "window_name" Ta "#W" Ta "Name of window"
|
||||
.It Li "window_offset_x" Ta "" Ta "X offset into window if larger than client"
|
||||
|
||||
67
tmux.h
67
tmux.h
@@ -99,6 +99,9 @@ struct winlink;
|
||||
#ifndef TMUX_LOCK_CMD
|
||||
#define TMUX_LOCK_CMD "lock -np"
|
||||
#endif
|
||||
#ifndef TMUX_MOUSE
|
||||
#define TMUX_MOUSE 0
|
||||
#endif
|
||||
|
||||
/* Minimum and maximum layout cell size, NOT including border lines. */
|
||||
#define PANE_MINIMUM 1
|
||||
@@ -1294,6 +1297,9 @@ struct window_pane {
|
||||
#define PANE_UNSEENCHANGES 0x4000
|
||||
#define PANE_REDRAWSCROLLBAR 0x8000
|
||||
|
||||
bitstr_t *sync_dirty;
|
||||
u_int sync_dirty_size;
|
||||
|
||||
u_int sb_slider_y;
|
||||
u_int sb_slider_h;
|
||||
int sb_auto_visible;
|
||||
@@ -2318,14 +2324,26 @@ struct client {
|
||||
};
|
||||
TAILQ_HEAD(clients, client);
|
||||
|
||||
/* Control mode subscription type. */
|
||||
enum control_sub_type {
|
||||
CONTROL_SUB_SESSION,
|
||||
CONTROL_SUB_PANE,
|
||||
CONTROL_SUB_ALL_PANES,
|
||||
CONTROL_SUB_WINDOW,
|
||||
CONTROL_SUB_ALL_WINDOWS
|
||||
/* Monitor. */
|
||||
enum monitor_type {
|
||||
MONITOR_SESSION,
|
||||
MONITOR_PANE,
|
||||
MONITOR_ALL_PANES,
|
||||
MONITOR_WINDOW,
|
||||
MONITOR_ALL_WINDOWS
|
||||
};
|
||||
#define MONITOR_NOTIFY_INITIAL 0x1
|
||||
struct monitor_change {
|
||||
const char *name;
|
||||
const char *value;
|
||||
const char *last;
|
||||
|
||||
struct client *c;
|
||||
struct session *s;
|
||||
struct winlink *wl;
|
||||
struct window_pane *wp;
|
||||
};
|
||||
typedef void (*monitor_cb)(struct monitor_change *, void *);
|
||||
|
||||
/* Key binding and key table. */
|
||||
struct key_binding {
|
||||
@@ -2646,6 +2664,12 @@ char *format_trim_right(const char *, u_int);
|
||||
|
||||
/* notify.c */
|
||||
void notify_hook(struct cmdq_item *, const char *);
|
||||
void notify_monitor_add(struct cmdq_item *, struct options *,
|
||||
const char *, enum monitor_type, int, const char *,
|
||||
struct cmd_find_state *, struct session *);
|
||||
void notify_monitor_remove(struct options *, const char *);
|
||||
void notify_monitor_free(void *);
|
||||
char *notify_monitor_to_string(struct options_entry *);
|
||||
void notify_client(const char *, struct client *);
|
||||
void notify_session(const char *, struct session *);
|
||||
void notify_winlink(const char *, struct winlink *);
|
||||
@@ -2668,6 +2692,8 @@ struct options_entry *options_default(struct options *,
|
||||
char *options_default_to_string(const struct options_table_entry *);
|
||||
const char *options_name(struct options_entry *);
|
||||
struct options *options_owner(struct options_entry *);
|
||||
void *options_get_monitor_data(struct options_entry *);
|
||||
void options_set_monitor_data(struct options_entry *, void *);
|
||||
const struct options_table_entry *options_table_entry(struct options_entry *);
|
||||
struct options_entry *options_get_only(struct options *, const char *);
|
||||
struct options_entry *options_get(struct options *, const char *);
|
||||
@@ -3305,6 +3331,7 @@ void colour_split_rgb(int, u_char *, u_char *, u_char *);
|
||||
int colour_force_rgb(int);
|
||||
int colour_dim(int, u_int);
|
||||
const char *colour_tostring(int);
|
||||
const char *colour_toescape(struct client *, int, int);
|
||||
enum client_theme colour_totheme(int);
|
||||
int colour_fromstring(const char *);
|
||||
const char *colour_theme_option(u_int, enum client_theme);
|
||||
@@ -3329,6 +3356,7 @@ bitstr_t *fuzzy_match(const char *, const char *, u_int, u_int *);
|
||||
|
||||
/* grid.c */
|
||||
extern const struct grid_cell grid_default_cell;
|
||||
void grid_check_is_clear(struct grid *);
|
||||
void grid_empty_line(struct grid *, u_int, u_int);
|
||||
void grid_set_tab(struct grid_cell *, u_int);
|
||||
int grid_cells_equal(const struct grid_cell *, const struct grid_cell *);
|
||||
@@ -3338,6 +3366,9 @@ struct grid *grid_create(u_int, u_int, u_int);
|
||||
void grid_destroy(struct grid *);
|
||||
void grid_free_lines(struct grid *, u_int, u_int);
|
||||
int grid_compare(struct grid *, struct grid *);
|
||||
const char *grid_line_flags_string(int);
|
||||
const char *grid_cell_flags_string(int);
|
||||
const char *grid_cell_attr_string(int);
|
||||
void grid_collect_history(struct grid *, int);
|
||||
void grid_remove_history(struct grid *, u_int );
|
||||
void grid_scroll_history(struct grid *, u_int);
|
||||
@@ -3446,6 +3477,7 @@ void screen_write_mode_set(struct screen_write_ctx *, int);
|
||||
void screen_write_mode_clear(struct screen_write_ctx *, int);
|
||||
void screen_write_start_sync(struct window_pane *);
|
||||
void screen_write_stop_sync(struct window_pane *);
|
||||
void screen_write_clear_dirty(struct window_pane *);
|
||||
void screen_write_cursorup(struct screen_write_ctx *, u_int);
|
||||
void screen_write_cursordown(struct screen_write_ctx *, u_int);
|
||||
void screen_write_cursorright(struct screen_write_ctx *, u_int);
|
||||
@@ -3499,7 +3531,7 @@ int redraw_get_status_border_cell_type(struct redraw_span **, u_int);
|
||||
|
||||
/* screen.c */
|
||||
void screen_init(struct screen *, u_int, u_int, u_int);
|
||||
void screen_reinit(struct screen *);
|
||||
void screen_reinit(struct screen *, int);
|
||||
void screen_free(struct screen *);
|
||||
void screen_reset_tabs(struct screen *);
|
||||
void screen_reset_hyperlinks(struct screen *);
|
||||
@@ -3681,7 +3713,7 @@ struct visible_ranges *window_visible_ranges(struct window_pane *, int, int,
|
||||
u_int layout_count_cells(struct layout_cell *);
|
||||
int layout_has_tiled(struct layout_cell *);
|
||||
struct layout_cell *layout_create_cell(struct layout_cell *);
|
||||
void layout_free_cell(struct layout_cell *);
|
||||
void layout_free_cell(struct layout_cell *, int);
|
||||
void layout_print_cell(struct layout_cell *, const char *, u_int);
|
||||
void layout_destroy_cell(struct window *, struct layout_cell *,
|
||||
struct layout_cell **);
|
||||
@@ -3692,6 +3724,7 @@ void layout_set_size(struct layout_cell *, u_int, u_int, int, int);
|
||||
void layout_make_leaf(struct layout_cell *, struct window_pane *);
|
||||
void layout_make_node(struct layout_cell *, enum layout_type);
|
||||
void layout_fix_zindexes(struct window *, struct layout_cell *);
|
||||
int layout_cell_is_tiled(struct layout_cell *);
|
||||
void layout_fix_offsets(struct window *);
|
||||
void layout_fix_panes(struct window *, struct window_pane *);
|
||||
void layout_resize_adjust(struct window *, struct layout_cell *,
|
||||
@@ -3700,7 +3733,7 @@ void layout_resize_set_size(struct window *, struct layout_cell *,
|
||||
enum layout_type, u_int);
|
||||
struct layout_cell *layout_cell_get_neighbour(struct layout_cell *);
|
||||
void layout_init(struct window *, struct window_pane *);
|
||||
void layout_free(struct window *);
|
||||
void layout_free(struct window *, int);
|
||||
void layout_resize(struct window *, u_int, u_int);
|
||||
void layout_resize_pane(struct window_pane *, enum layout_type,
|
||||
int, int);
|
||||
@@ -3845,6 +3878,16 @@ void check_window_name(struct window *);
|
||||
char *default_window_name(struct window *);
|
||||
char *parse_window_name(const char *);
|
||||
|
||||
/* monitor.c */
|
||||
struct monitor_set *monitor_create_client(struct client *, monitor_cb, void *);
|
||||
struct monitor_set *monitor_create_session(struct session *, monitor_cb, void *);
|
||||
void monitor_destroy(struct monitor_set *);
|
||||
int monitor_parse(const char *, char **, enum monitor_type *, int *,
|
||||
char **);
|
||||
void monitor_add(struct monitor_set *, const char *, enum monitor_type, int,
|
||||
const char *, u_int);
|
||||
void monitor_remove(struct monitor_set *, const char *);
|
||||
|
||||
/* control.c */
|
||||
void control_discard(struct client *);
|
||||
void control_start(struct client *);
|
||||
@@ -3860,8 +3903,8 @@ void control_reset_offsets(struct client *);
|
||||
void printflike(2, 3) control_write(struct client *, const char *, ...);
|
||||
void control_write_output(struct client *, struct window_pane *);
|
||||
int control_all_done(struct client *);
|
||||
void control_add_sub(struct client *, const char *, enum control_sub_type,
|
||||
int, const char *);
|
||||
void control_add_sub(struct client *, const char *, enum monitor_type, int,
|
||||
const char *);
|
||||
void control_remove_sub(struct client *, const char *);
|
||||
|
||||
/* control-notify.c */
|
||||
|
||||
@@ -589,6 +589,7 @@ tty_default_features(int *feat, const char *name, u_int version)
|
||||
"cstyle,"
|
||||
"extkeys,"
|
||||
"focus,"
|
||||
"overline,"
|
||||
"hyperlinks,"
|
||||
"osc7,"
|
||||
"sync,"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user