mirror of
https://github.com/tmux/tmux.git
synced 2026-09-05 13:10:45 +00:00
More bugfixes and test updates.
This commit is contained in:
104
layout-custom.c
104
layout-custom.c
@@ -93,14 +93,14 @@ static struct layout_cell *layout_parse_json_layout(struct json_node *,
|
||||
struct layout_cell *,
|
||||
struct layout_parse_ctx *);
|
||||
|
||||
/* Compare cell contexts in descending order of index. */
|
||||
/* Compare cell contexts in ascending order of index. */
|
||||
static int
|
||||
layout_parse_index_cmp(const void *a, const void *b)
|
||||
{
|
||||
const struct layout_parse_cell_ctx *cca = a;
|
||||
const struct layout_parse_cell_ctx *ccb = b;
|
||||
|
||||
return (ccb->index - cca->index);
|
||||
return (cca->index - ccb->index);
|
||||
}
|
||||
|
||||
/* Compare cell contexts in descending order of z-index. */
|
||||
@@ -114,7 +114,7 @@ layout_parse_zindex_cmp(const void *a, const void *b)
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare cell contexts in descending order of z-index. The active pane has
|
||||
* Compare cell contexts in descending order of last. The active pane has
|
||||
* index of -1.
|
||||
*/
|
||||
static int
|
||||
@@ -196,7 +196,7 @@ layout_parse_remove_cctx(struct layout_parse_ctx *pctx, struct layout_cell *lc)
|
||||
for (i = 0; i < pctx->clen; i++) {
|
||||
if (lc == pctx->cctxs[i].lc) {
|
||||
cctx = &pctx->cctxs[--pctx->clen];
|
||||
memcpy(&pctx->cctxs[i], cctx, sizeof *cctx);
|
||||
memmove(&pctx->cctxs[i], cctx, sizeof *cctx);
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
@@ -461,7 +461,8 @@ layout_parse(struct window *w, const char *layout, char **cause)
|
||||
* remain.
|
||||
*/
|
||||
lcchild = layout_find_bottomright(lc);
|
||||
if (layout_parse_remove_cctx(&pctx, lc) != 0)
|
||||
if (pctx.version != 1 && layout_parse_remove_cctx(&pctx,
|
||||
lcchild) != 0)
|
||||
goto fail;
|
||||
layout_destroy_cell(w, lcchild, &lc);
|
||||
}
|
||||
@@ -516,7 +517,8 @@ layout_parse(struct window *w, const char *layout, char **cause)
|
||||
|
||||
/* Assign the panes into the cells. */
|
||||
layout_assign(w, &pctx);
|
||||
layout_parse_apply_ctx(w, &pctx);
|
||||
if (pctx.version != 1)
|
||||
layout_parse_apply_ctx(w, &pctx);
|
||||
|
||||
/* Update pane offsets and sizes. */
|
||||
layout_fix_offsets(w);
|
||||
@@ -524,7 +526,8 @@ layout_parse(struct window *w, const char *layout, char **cause)
|
||||
recalculate_sizes();
|
||||
layout_print_cell(lc, __func__, 0);
|
||||
|
||||
events_fire_window("window-layout-changed", w);
|
||||
if (pctx.version == 1) /* backwards compatibility. */
|
||||
events_fire_window("window-layout-changed", w);
|
||||
|
||||
return (0);
|
||||
|
||||
@@ -581,7 +584,7 @@ layout_assign_fallback(struct window_pane **wp, struct layout_cell *lc)
|
||||
static void
|
||||
layout_assign(struct window *w, struct layout_parse_ctx *pctx)
|
||||
{
|
||||
struct window_pane *wp = NULL;
|
||||
struct window_pane *wp = TAILQ_FIRST(&w->panes);
|
||||
|
||||
if (pctx->clen > 0)
|
||||
return (layout_assign_from_ctx(w, pctx));
|
||||
@@ -716,7 +719,7 @@ layout_parse_json(struct json_node *json, struct layout_parse_ctx *pctx)
|
||||
}
|
||||
pctx->root = layout_parse_json_layout(field,
|
||||
NULL, pctx);
|
||||
if (pctx->root== NULL)
|
||||
if (pctx->root == NULL)
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
@@ -724,8 +727,10 @@ layout_parse_json(struct json_node *json, struct layout_parse_ctx *pctx)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pctx->root == NULL)
|
||||
if (pctx->root == NULL) {
|
||||
*cause = xstrdup("missing layout");
|
||||
goto fail;
|
||||
}
|
||||
json_destroy_node(json);
|
||||
|
||||
return (0);
|
||||
@@ -733,9 +738,10 @@ fail:
|
||||
json_destroy_node(json);
|
||||
if (pctx->root != NULL)
|
||||
layout_free_cell(pctx->root, 0);
|
||||
pctx->root = NULL;
|
||||
return (-1);
|
||||
}
|
||||
// TODO: add causes for failures.
|
||||
|
||||
/* Parse nodes into layout cells. */
|
||||
static struct layout_cell *
|
||||
layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent,
|
||||
@@ -745,7 +751,7 @@ layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent,
|
||||
struct layout_cell *lc = layout_create_cell(lcparent), *lcchild;
|
||||
enum layout_type type = LAYOUT_WINDOWPANE;
|
||||
const char *numstr;
|
||||
char *endptr;
|
||||
char *endptr, *tmp, **cause = pctx->cause;
|
||||
u_int sx = UINT_MAX, sy = UINT_MAX;
|
||||
int xoff = INT_MAX, yoff = INT_MAX;
|
||||
int active = -1, last = -1, id = -1, index = -1;
|
||||
@@ -764,6 +770,8 @@ layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent,
|
||||
yoff = field->val.num;
|
||||
else if (json_key_is_eq(field, "l"))
|
||||
last = field->val.num;
|
||||
else if (json_key_is_eq(field, "i"))
|
||||
index = field->val.num;
|
||||
else if (json_key_is_eq(field, "z")) {
|
||||
zindex = field->val.num;
|
||||
lc->flags |= LAYOUT_CELL_FLOATING;
|
||||
@@ -781,25 +789,32 @@ layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent,
|
||||
type = LAYOUT_TOPBOTTOM;
|
||||
else if (json_val_is_eq(field, "p"))
|
||||
type = LAYOUT_WINDOWPANE;
|
||||
else
|
||||
else {
|
||||
tmp = xstrndup(field->val.jstr.ptr,
|
||||
field->val.jstr.len);
|
||||
xasprintf(cause, "invalid cell type %s",
|
||||
tmp);
|
||||
free(tmp);
|
||||
goto fail;
|
||||
}
|
||||
lc->type = type;
|
||||
} else if (json_key_is_eq(field, "I")) {
|
||||
errno = 0;
|
||||
numstr = field->val.jstr.ptr;
|
||||
numlen = field->val.jstr.len;
|
||||
if (*numstr != '%')
|
||||
if (*numstr != '%') {
|
||||
xasprintf(cause, "pane id must be "
|
||||
"prefixed by '%%'");
|
||||
goto fail;
|
||||
}
|
||||
id = strtol(numstr + 1, &endptr, 10);
|
||||
if (errno != 0 || endptr != numstr + numlen)
|
||||
goto fail;
|
||||
} else if (json_key_is_eq(field, "i")) {
|
||||
errno = 0;
|
||||
numstr = field->val.jstr.ptr;
|
||||
numlen = field->val.jstr.len;
|
||||
index = strtol(numstr, &endptr, 10);
|
||||
if (errno != 0 || endptr != numstr + numlen)
|
||||
if (errno != 0 || endptr != numstr + numlen) {
|
||||
tmp = xstrndup(numstr, numlen);
|
||||
xasprintf(cause, "invalid pane id: %s",
|
||||
tmp);
|
||||
free(tmp);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NODE_ARRAY:
|
||||
@@ -817,14 +832,19 @@ layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (type == LAYOUT_WINDOWPANE && !TAILQ_EMPTY(&lc->cells))
|
||||
if (type == LAYOUT_WINDOWPANE && !TAILQ_EMPTY(&lc->cells)) {
|
||||
xasprintf(cause, "pane cells cannot have children");
|
||||
goto fail;
|
||||
if (type != LAYOUT_WINDOWPANE && TAILQ_EMPTY(&lc->cells))
|
||||
} else if (type != LAYOUT_WINDOWPANE && TAILQ_EMPTY(&lc->cells)) {
|
||||
xasprintf(cause, "non-pane cells must have children");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (sx == UINT_MAX || sy == UINT_MAX || xoff == INT_MAX ||
|
||||
yoff == INT_MAX)
|
||||
yoff == INT_MAX) {
|
||||
xasprintf(cause, "cell geometry must be fully specified");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
layout_set_size(lc, sx, sy, xoff, yoff);
|
||||
if (lc->type == LAYOUT_WINDOWPANE)
|
||||
@@ -843,7 +863,6 @@ fail:
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
|
||||
/* Construct a layout root from a formatted string. */
|
||||
static int
|
||||
layout_construct(const char *layout, struct layout_parse_ctx *pctx)
|
||||
@@ -865,14 +884,22 @@ layout_construct(const char *layout, struct layout_parse_ctx *pctx)
|
||||
*pctx->cause = xstrdup("invalid layout checksum");
|
||||
return (-1);
|
||||
}
|
||||
pctx->root = layout_construct_v1(NULL, &layout);
|
||||
if ((pctx->root = layout_construct_v1(NULL, &layout)) == NULL) {
|
||||
*pctx->cause = xstrdup("invalid layout");
|
||||
return (-1);
|
||||
}
|
||||
pctx->version = 1;
|
||||
} else {
|
||||
if ((json = json_parse(layout, pctx->cause)) == NULL)
|
||||
return (-1);
|
||||
|
||||
if (layout_parse_json(json, pctx) != 0)
|
||||
return (-1);
|
||||
if (pctx->version != 2)
|
||||
|
||||
if (pctx->version != 2) {
|
||||
*pctx->cause = xstrdup("version mismatch.");
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
return (0);
|
||||
@@ -886,6 +913,9 @@ layout_parse_apply_ctx(struct window *w, struct layout_parse_ctx *pctx)
|
||||
struct window_pane *wp;
|
||||
int i;
|
||||
|
||||
if (pctx->clen == 0)
|
||||
return;
|
||||
|
||||
/* Apply z-indexes. */
|
||||
while (!TAILQ_EMPTY(&w->z_index)) {
|
||||
wp = TAILQ_FIRST(&w->z_index);
|
||||
@@ -901,10 +931,19 @@ layout_parse_apply_ctx(struct window *w, struct layout_parse_ctx *pctx)
|
||||
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
|
||||
}
|
||||
|
||||
/* Apply active/last. */
|
||||
/* Set the active pane. */
|
||||
for (i = 0; i < pctx->clen; i++) {
|
||||
cctx = &pctx->cctxs[i];
|
||||
if (cctx->active == 1) {
|
||||
window_set_active_pane(w, cctx->lc->wp, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply last panes. */
|
||||
while (!TAILQ_EMPTY(&w->last_panes)) {
|
||||
wp = TAILQ_FIRST(&w->last_panes);
|
||||
TAILQ_REMOVE(&w->last_panes, wp, sentry);
|
||||
window_pane_stack_remove(&w->last_panes, wp);
|
||||
}
|
||||
|
||||
qsort(pctx->cctxs, pctx->clen, sizeof pctx->cctxs[0],
|
||||
@@ -913,10 +952,9 @@ layout_parse_apply_ctx(struct window *w, struct layout_parse_ctx *pctx)
|
||||
for (i = 0; i < pctx->clen; i++) {
|
||||
cctx = &pctx->cctxs[i];
|
||||
wp = cctx->lc->wp;
|
||||
if (cctx->active) {
|
||||
window_set_active_pane(w, wp, 1);
|
||||
if (cctx->last < 0 || cctx->active == 1) {
|
||||
continue;
|
||||
}
|
||||
TAILQ_INSERT_HEAD(&w->last_panes, wp, sentry);
|
||||
window_pane_stack_push(&w->last_panes, wp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the custom layout dumper and parser in layout-custom.c. layout_dump
|
||||
# is reached through the #{window_layout} and #{window_visible_layout} formats
|
||||
# and layout_parse through "select-layout <layout>".
|
||||
# Tests of the custom layout dumper and evaluator in layout-custom.c, and of
|
||||
# the JSON tokenizer and parser in json.c that the current layout format is
|
||||
# built on.
|
||||
#
|
||||
# Both formats are covered:
|
||||
# layout_dump is reached through the #{window_layout} and
|
||||
# #{window_visible_layout} formats and layout_parse through
|
||||
# "select-layout <layout>". json.c has no command of its own either:
|
||||
# layout_construct sniffs the first non-blank character and hands anything
|
||||
# starting with '{' to json_parse, so select-layout is the only way into it
|
||||
# from the shell as well.
|
||||
#
|
||||
# Both layout formats are covered:
|
||||
# - the current (v2) JSON format, which is what every client except an old
|
||||
# control client sees;
|
||||
# - the legacy (v1) format, which is still produced for a control client that
|
||||
@@ -15,20 +22,29 @@
|
||||
# - dumping a single pane, a split, the "a" (active) and "l" (last pane) keys
|
||||
# and the "z" key of a floating pane;
|
||||
# - #{window_visible_layout} agreeing with #{window_layout};
|
||||
# - the JSON syntax itself: insignificant whitespace, backslash escapes inside
|
||||
# strings, the number and boolean forms, and one failure for each error
|
||||
# json.c can report;
|
||||
# - a dump being parsed back to exactly the same layout (round trip), after
|
||||
# another layout has been applied in between;
|
||||
# - parsing a hand-written v2 layout, including insignificant whitespace;
|
||||
# - parsing a hand-written v2 layout and the panes being assigned to its cells
|
||||
# in order;
|
||||
# - the same layouts with their fields in reversed and scrambled orders,
|
||||
# including "c" before "t" (children parsed before the cell type is known)
|
||||
# and "V" after "L";
|
||||
# - parsing a v1 layout, with the checksum computed here independently of
|
||||
# layout_checksum(), and dumping the same layout as v1 to a control client;
|
||||
# - a layout with more cells than the window has panes having the bottom right
|
||||
# cells dropped;
|
||||
# - failures: a bad v1 header or checksum, a malformed v1 body, unterminated
|
||||
# and malformed JSON, a wrong version, a missing or duplicated root cell,
|
||||
# missing sizes, bad cell types and pane ids, leaf cells with children and
|
||||
# node cells without, too few cells for the panes and inconsistent sizes.
|
||||
# cells dropped, in both formats;
|
||||
# - a layout naming no active or last pane leaving both as they were;
|
||||
# - parsing a v1 layout and dumping it back as v1 through a control client,
|
||||
# with the checksum computed here independently of layout_checksum(), and a
|
||||
# v1 layout leaving the active pane and last pane stack untouched;
|
||||
# - the %layout-change notification, in both formats at once: two control
|
||||
# clients watching one layout change, only one of which has asked for new
|
||||
# layouts, and the number of notifications a change produces in each format;
|
||||
# - failures: a bad v1 header, checksum or body, a wrong version, a missing or
|
||||
# duplicated root cell, missing sizes, bad cell types and pane ids, leaf
|
||||
# cells with children and node cells without, too few cells for the panes and
|
||||
# inconsistent sizes.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
@@ -140,7 +156,14 @@ v1()
|
||||
}'
|
||||
}
|
||||
|
||||
ONE='{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"a":true,"i":"%N"}}'
|
||||
# A pane cell is dumped as its geometry, then "a" if it is the active pane or
|
||||
# "l" with its position on the last pane stack if it is on it, then "i" with
|
||||
# its pane index, then "z" if it is floating, then "I" with its pane id.
|
||||
ONE='{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"a":true,"i":0,"I":"%N"}}'
|
||||
|
||||
# A single leaf cell filling the window, without the keys that only the dumper
|
||||
# writes. Used by the JSON checks, which care about the syntax around it.
|
||||
LEAF='{"t":"p","w":80,"h":24,"x":0,"y":0}'
|
||||
|
||||
$TMUX new-session -d -s L -x 80 -y 24 -n one || exit 1
|
||||
|
||||
@@ -159,11 +182,145 @@ must_equal 'Single pane visible layout' "$(visible_layout L:one)" "$ONE"
|
||||
|
||||
# The bottom right cells are closed until as many are left as there are panes,
|
||||
# so a two cell layout applied to a one pane window collapses back to the
|
||||
# single pane filling the window.
|
||||
# single pane filling the window: the cell that is left takes the space of the
|
||||
# one that was closed.
|
||||
check_ok select-layout -t L:one \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0},{"t":"p","w":80,"h":12,"x":0,"y":12}]}}'
|
||||
must_equal 'Trimmed layout' "$(layout L:one)" "$ONE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The JSON syntax.
|
||||
#
|
||||
# These run on the one pane window and are written so that what they prove
|
||||
# depends on json.c rather than on the layout evaluation in layout-custom.c:
|
||||
# an accepted layout is only required to leave the window as its single pane,
|
||||
# and values that are not part of the layout format are carried on keys
|
||||
# layout-custom.c never looks at ("n", "b" and so on), which it skips, so
|
||||
# numbers, booleans and escapes can be exercised on their own.
|
||||
#
|
||||
# Objects nested in an array nested in an object are not checked here: every
|
||||
# split layout below is one.
|
||||
#
|
||||
# Two of json.c's messages cannot be reached from the shell and so are not
|
||||
# covered. "expected object" is unreachable because layout_construct() only
|
||||
# calls json_parse() once the string already starts with '{', and "invalid
|
||||
# boolean" is unreachable because json_parse_boolean() is only called after the
|
||||
# value has already matched "true" or "false".
|
||||
|
||||
# check_json_ok $what $layout
|
||||
#
|
||||
# select-layout must parse $layout and leave the window as its single pane.
|
||||
check_json_ok()
|
||||
{
|
||||
check_ok select-layout -t L:one "$2"
|
||||
must_equal "Layout after '$1'" "$(layout L:one)" "$ONE"
|
||||
}
|
||||
|
||||
# check_json_fail $what $reason $layout
|
||||
#
|
||||
# select-layout must reject $layout with an error beginning with $reason.
|
||||
# json_error() appends up to ERROR_CTX_LEN characters of context from the point
|
||||
# of failure and cmd-select-layout.c then appends the layout itself, so only
|
||||
# the reason is matched.
|
||||
check_json_fail()
|
||||
{
|
||||
out=$($TMUX select-layout -t L:one "$3" 2>&1) &&
|
||||
fail "$1: select-layout succeeded (expected failure)"
|
||||
case "$out" in
|
||||
"$2"*) ;;
|
||||
*) fail "$1: expected '$2...' but got '$out'";;
|
||||
esac
|
||||
}
|
||||
|
||||
# Whitespace between tokens is skipped. A number is scanned up to the ',', ']',
|
||||
# '}' or whitespace that ends it, so a space after a number is fine but one
|
||||
# inside it is not.
|
||||
check_json_ok 'Spaces between tokens' \
|
||||
'{ "V" : 2 , "L" : { "t" : "p" , "w" : 80 , "h" : 24 , "x" : 0 , "y" : 0 } }'
|
||||
|
||||
check_json_ok 'Newlines and tabs between tokens' "$(printf '{
|
||||
\t"V": 2,
|
||||
\t"L": {
|
||||
\t\t"t": "p",
|
||||
\t\t"w": 80,
|
||||
\t\t"h": 24,
|
||||
\t\t"x": 0,
|
||||
\t\t"y": 0
|
||||
\t}
|
||||
}')"
|
||||
|
||||
check_json_ok 'Carriage returns between tokens' \
|
||||
"$(printf '{\r"V":2,\r"L":%s\r}' "$LEAF")"
|
||||
|
||||
# A backslash makes the tokenizer consume the next character whatever it is, so
|
||||
# an escaped quote does not end the string. The key is not one that
|
||||
# layout-custom.c looks at, so all that is being checked is that the string
|
||||
# ended in the right place and the object still parsed.
|
||||
check_json_ok 'Escaped quote in a string' \
|
||||
'{"V":2,"a\"b":0,"L":'"$LEAF"'}'
|
||||
|
||||
# An escaped backslash immediately before the closing quote: the escape has to
|
||||
# be cleared again so that the quote after it does end the string.
|
||||
check_json_ok 'Escaped backslash before the closing quote' \
|
||||
'{"V":2,"a\\":0,"L":'"$LEAF"'}'
|
||||
|
||||
# Numbers and booleans, again on keys layout-custom.c ignores, so only json.c
|
||||
# decides whether they are accepted.
|
||||
check_json_ok 'Zero' '{"V":2,"n":0,"L":'"$LEAF"'}'
|
||||
check_json_ok 'Several digits' '{"V":2,"n":1234567,"L":'"$LEAF"'}'
|
||||
check_json_ok 'Negative number' '{"V":2,"n":-42,"L":'"$LEAF"'}'
|
||||
check_json_ok 'Booleans' '{"V":2,"b":true,"d":false,"L":'"$LEAF"'}'
|
||||
|
||||
# Tokenizer failures. A value that runs to the end of the input has no
|
||||
# terminator, so it is the tokenizer rather than the parser that gives up. Both
|
||||
# the number scan and the string scan have to notice this, and with the closing
|
||||
# quote escaped there is no terminator left either.
|
||||
check_json_fail 'Unterminated number' 'tokenization error' '{"V":2'
|
||||
check_json_fail 'Unterminated string' 'tokenization error' '{"V":"x'
|
||||
check_json_fail 'Escaped closing quote' 'tokenization error' \
|
||||
'{"V":2,"L":{"t":"p\"}}'
|
||||
|
||||
# Something that is not a quoted string where a key belongs.
|
||||
check_json_fail 'Missing key' 'invalid key' '{"V":2,,"L":'"$LEAF"'}'
|
||||
|
||||
# A key not followed by ':'.
|
||||
check_json_fail 'Missing colon' 'missing colon' '{"V","L":2}'
|
||||
|
||||
# A bare word that is neither "true", "false" nor a number. This is where
|
||||
# "null" ends up.
|
||||
check_json_fail 'Unknown literal' 'invalid value' '{"V":null,"L":'"$LEAF"'}'
|
||||
|
||||
# A ':' with no value after it, so the token where the value belongs is one the
|
||||
# object parser has no case for.
|
||||
check_json_fail 'Missing value' 'unsupported object token' '{"V":}'
|
||||
|
||||
# A ',' with nothing after it, and a value with no ',' before the next key.
|
||||
check_json_fail 'Trailing comma in an object' 'invalid object' \
|
||||
'{"V":2,"L":'"$LEAF"',}'
|
||||
check_json_fail 'Missing comma in an object' 'invalid object' \
|
||||
'{"V":2 "L":'"$LEAF"'}'
|
||||
|
||||
# Arrays hold objects and nothing else.
|
||||
check_json_fail 'Non-object in an array' 'invalid array member' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":["x"]}}'
|
||||
check_json_fail 'Trailing comma in an array' 'invalid array' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0},]}}'
|
||||
|
||||
# An empty string is two adjacent quotes with no value token between them,
|
||||
# which the string parser does not accept.
|
||||
check_json_fail 'Empty string' 'invalid string' '{"V":2,"L":""}'
|
||||
|
||||
# A number token that strtoll does not consume all of.
|
||||
check_json_fail 'Number with trailing characters' 'invalid number' \
|
||||
'{"V":8a,"L":'"$LEAF"'}'
|
||||
|
||||
# Anything after the top level object.
|
||||
check_json_fail 'Data after the top level object' 'unexpected trailing data' \
|
||||
'{"V":2,"L":'"$LEAF"'}{}'
|
||||
|
||||
# None of the rejections touched the layout.
|
||||
must_equal 'Layout after rejected parses' "$(layout L:one)" "$ONE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dumping a split.
|
||||
|
||||
@@ -174,8 +331,11 @@ q0=$($TMUX display-message -p -t L:two.0 '#{pane_id}')
|
||||
# one for the border between them. With -d the top pane stays active.
|
||||
check_ok split-window -d -v -l 12 -t L:two.0
|
||||
q1=$($TMUX display-message -p -t L:two.1 '#{pane_id}')
|
||||
|
||||
# Nothing has changed the active pane, so the last pane stack is still empty
|
||||
# and the bottom pane has neither "a" nor "l".
|
||||
must_equal 'Split layout' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"a":true,"i":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"i":"%N"}]}}'
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The active and last pane keys.
|
||||
@@ -184,11 +344,12 @@ must_equal 'Split layout' "$(layout L:two)" \
|
||||
# last pane stack, where it is at index 0.
|
||||
check_ok select-pane -t "$q1"
|
||||
must_equal 'Layout after select-pane' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"l":0,"i":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"a":true,"i":"%N"}]}}'
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"l":0,"i":0,"I":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"a":true,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# Selecting the top pane again swaps the two keys over.
|
||||
# Selecting the top pane again swaps the two keys over. "i" and "I" do not
|
||||
# move: they are the pane's position in the window and its id.
|
||||
check_ok select-pane -t "$q0"
|
||||
SPLIT='{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"a":true,"i":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"l":0,"i":"%N"}]}}'
|
||||
SPLIT='{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":12,"x":0,"y":12,"l":0,"i":1,"I":"%N"}]}}'
|
||||
must_equal 'Layout after select-pane back' "$(layout L:two)" "$SPLIT"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -199,32 +360,40 @@ must_equal 'Layout after select-pane back' "$(layout L:two)" "$SPLIT"
|
||||
# The zoomed case is deliberately not covered here. While a pane is zoomed
|
||||
# #{window_layout} dumps the saved (unzoomed) layout and
|
||||
# #{window_visible_layout} the zoomed one, but that depends on how zooming
|
||||
# stashes the layout root rather than on anything in layout-custom.c. Add it
|
||||
# back once zooming has settled.
|
||||
# stashes the layout root rather than on anything in layout-custom.c.
|
||||
must_equal 'Visible layout' "$(visible_layout L:two)" "$SPLIT"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Round trip.
|
||||
|
||||
# Make the two panes obviously uneven so that the layout applied in between
|
||||
# cannot be mistaken for the saved one.
|
||||
# cannot be mistaken for the saved one. A resize shows up in the dump as the
|
||||
# new cell sizes and offsets.
|
||||
check_ok resize-pane -t "$q0" -y 5
|
||||
saved=$(raw_layout L:two)
|
||||
must_equal 'Resized layout' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":5,"x":0,"y":0,"a":true,"i":"%N"},{"t":"p","w":80,"h":18,"x":0,"y":6,"l":0,"i":"%N"}]}}'
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":5,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":18,"x":0,"y":6,"l":0,"i":1,"I":"%N"}]}}'
|
||||
|
||||
check_ok select-layout -t L:two even-vertical
|
||||
must_differ 'Layout after even-vertical' "$(raw_layout L:two)" "$saved"
|
||||
|
||||
# Parsing a dump gives back exactly the same dump, pane ids included.
|
||||
# Parsing a dump gives back exactly the same dump, pane ids included. The panes
|
||||
# go back into the cells that named them: the cells are ordered by "i" and then
|
||||
# given the window's panes in order, so a cell dumped with "i":k must come back
|
||||
# the k'th.
|
||||
check_ok select-layout -t L:two "$saved"
|
||||
must_equal 'Round tripped layout' "$(raw_layout L:two)" "$saved"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parsing a hand-written layout.
|
||||
|
||||
# Whitespace between tokens is skipped. Note that a number is scanned up to the
|
||||
# ',', ']' or '}' that ends it, so there is deliberately no space there.
|
||||
# Laid out over several lines to keep it readable; that the whitespace is
|
||||
# skipped at all is json.c's business, what matters here is that the cells come
|
||||
# out of it in the right shape.
|
||||
#
|
||||
# "a" and "l" are given on the cells so that the active pane and the last pane
|
||||
# stack are pinned by the layout rather than left to whatever a layout that
|
||||
# names neither happens to produce.
|
||||
check_ok select-layout -t L:two '{
|
||||
"V": 2,
|
||||
"L": {
|
||||
@@ -234,13 +403,13 @@ check_ok select-layout -t L:two '{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"c": [
|
||||
{"t": "p", "w": 30, "h": 24, "x": 0, "y": 0},
|
||||
{"t": "p", "w": 49, "h": 24, "x": 31, "y": 0}
|
||||
{"t": "p", "w": 30, "h": 24, "x": 0, "y": 0, "a": true},
|
||||
{"t": "p", "w": 49, "h": 24, "x": 31, "y": 0, "l": 0}
|
||||
]
|
||||
}
|
||||
}'
|
||||
must_equal 'Hand-written layout' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"h","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":30,"h":24,"x":0,"y":0,"a":true,"i":"%N"},{"t":"p","w":49,"h":24,"x":31,"y":0,"l":0,"i":"%N"}]}}'
|
||||
'{"V":2,"L":{"t":"h","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":30,"h":24,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":49,"h":24,"x":31,"y":0,"l":0,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# The panes are assigned to the cells in order.
|
||||
must_equal 'First pane width' \
|
||||
@@ -255,58 +424,31 @@ must_equal 'Second pane width' \
|
||||
# every object has its keys reversed: "c" comes before "t", so the children are
|
||||
# evaluated while the cell type is still the default, and "V" comes after "L",
|
||||
# so the version is only known once the layout has been built.
|
||||
check_ok select-layout -t L:two '{"L":{"c":[{"y":0,"x":0,"h":8,"w":80,"t":"p"},{"y":9,"x":0,"h":15,"w":80,"t":"p"}],"y":0,"x":0,"h":24,"w":80,"t":"v"},"V":2}'
|
||||
check_ok select-layout -t L:two '{"L":{"c":[{"a":true,"y":0,"x":0,"h":8,"w":80,"t":"p"},{"l":0,"y":9,"x":0,"h":15,"w":80,"t":"p"}],"y":0,"x":0,"h":24,"w":80,"t":"v"},"V":2}'
|
||||
must_equal 'Reversed field order' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":8,"x":0,"y":0,"a":true,"i":"%N"},{"t":"p","w":80,"h":15,"x":0,"y":9,"l":0,"i":"%N"}]}}'
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":8,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":15,"x":0,"y":9,"l":0,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# Keys interleaved rather than simply reversed, with "c" in the middle. The
|
||||
# "a" here is on the second cell but is ignored on parsing, so the dump still
|
||||
# marks the first cell active: the active pane comes from the window, not the
|
||||
# layout.
|
||||
# Keys interleaved rather than simply reversed, with "c" in the middle. This
|
||||
# time "a" is on the second cell, so the second pane becomes the active one:
|
||||
# which pane is active comes from the layout, while "i" and "I" still come from
|
||||
# the window. The first cell names neither "a" nor "l", so its pane is neither
|
||||
# active nor on the last pane stack and the dump gives it neither key.
|
||||
check_ok select-layout -t L:two '{"V":2,"L":{"h":24,"c":[{"w":40,"t":"p","y":0,"h":24,"x":0},{"a":true,"h":24,"w":39,"y":0,"t":"p","x":41}],"w":80,"y":0,"t":"h","x":0}}'
|
||||
must_equal 'Scrambled field order' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"h","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":40,"h":24,"x":0,"y":0,"a":true,"i":"%N"},{"t":"p","w":39,"h":24,"x":41,"y":0,"l":0,"i":"%N"}]}}'
|
||||
|
||||
# The same rejections apply whatever order the fields are in: a leaf with
|
||||
# children when "c" is seen first, a node with no children when "t" is last, a
|
||||
# bad cell type when "t" is last, and a bad pane id when "i" is first.
|
||||
check_fail 'invalid layout: {"V":2,"L":{"c":[{"t":"p","w":80,"h":24,"x":0,"y":0}],"t":"p","w":80,"h":24,"x":0,"y":0}}' \
|
||||
select-layout -t L:two '{"V":2,"L":{"c":[{"t":"p","w":80,"h":24,"x":0,"y":0}],"t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_fail 'invalid layout: {"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"v"}}' \
|
||||
select-layout -t L:two '{"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"v"}}'
|
||||
check_fail 'invalid layout: {"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"q"}}' \
|
||||
select-layout -t L:two '{"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"q"}}'
|
||||
check_fail 'invalid layout: {"V":2,"L":{"i":"0","t":"p","w":80,"h":24,"x":0,"y":0}}' \
|
||||
select-layout -t L:two '{"V":2,"L":{"i":"0","t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# A child that fails after some children have already been added, with "c"
|
||||
# before "t" so the parent's type is still the default when it gives up. This
|
||||
# is the case the cleanup at the end of layout_evaluate_layout exists for: the
|
||||
# already-built children have to be freed even though the parent does not yet
|
||||
# look like a node. The second child has no "y".
|
||||
check_fail 'invalid layout: {"V":2,"L":{"c":[{"t":"p","w":80,"h":11,"x":0,"y":0},{"t":"p","w":80,"h":12,"x":0}],"t":"v","w":80,"h":24,"x":0,"y":0}}' \
|
||||
select-layout -t L:two '{"V":2,"L":{"c":[{"t":"p","w":80,"h":11,"x":0,"y":0},{"t":"p","w":80,"h":12,"x":0}],"t":"v","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# A wrong version after a layout that is otherwise fine, so the built cells
|
||||
# have to be thrown away once "V" is finally seen.
|
||||
check_fail 'invalid layout: {"L":{"t":"p","w":80,"h":24,"x":0,"y":0},"V":1}' \
|
||||
select-layout -t L:two '{"L":{"t":"p","w":80,"h":24,"x":0,"y":0},"V":1}'
|
||||
'{"V":2,"L":{"t":"h","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":40,"h":24,"x":0,"y":0,"i":0,"I":"%N"},{"t":"p","w":39,"h":24,"x":41,"y":0,"a":true,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The legacy (v1) format.
|
||||
|
||||
body="80x24,0,0[80x11,0,0,${q0#%},80x12,0,12,${q1#%}]"
|
||||
# The layout just applied, in v1: a left/right cell is written with braces and
|
||||
# a top/bottom cell with brackets, and each leaf carries its pane id without
|
||||
# the leading %.
|
||||
v1body="80x24,0,0{40x24,0,0,${q0#%},39x24,41,0,${q1#%}}"
|
||||
|
||||
# A v1 layout with a correct checksum is accepted and gives the same layout as
|
||||
# the equivalent v2 one.
|
||||
check_ok select-layout -t L:two "$(v1 "$body")"
|
||||
must_equal 'Layout parsed from v1' "$(layout L:two)" "$SPLIT"
|
||||
|
||||
# A control client that has not asked for new layouts is dumped v1, which must
|
||||
# be the string that was just parsed. Its output is wrapped in %begin/%end
|
||||
# guard lines.
|
||||
# A control client that has not asked for new layouts is dumped v1. Its output
|
||||
# is wrapped in %begin/%end guard lines.
|
||||
got=$($TMUX -C display-message -p -t L:two '#{window_layout}' | grep -v '^%')
|
||||
must_equal 'v1 dump' "$got" "$(v1 "$body")"
|
||||
must_equal 'v1 dump' "$got" "$(v1 "$v1body")"
|
||||
|
||||
# With the new-layouts flag the same client is dumped v2 instead. The flag is
|
||||
# set with "attach -f" rather than refresh-client because refresh-client needs
|
||||
@@ -315,8 +457,54 @@ got=$(printf "display-message -p -t L:two '#{window_layout}'\n" |
|
||||
$TMUX -C attach -f new-layouts -t L 2>&1 | grep -v '^%')
|
||||
must_contain 'v2 dump for control client' "$got" '{"V":2,"L":'
|
||||
|
||||
# A v1 layout with a correct checksum is parsed, and dumping v1 again gives
|
||||
# back the same string. That is the whole of what v1 carries: the cells take
|
||||
# the sizes and offsets from the body, and the panes are assigned to them in
|
||||
# order, which is what puts the same two ids back in the same two places. It is
|
||||
# checked in v1 rather than against a v2 dump so that nothing v1 has no opinion
|
||||
# on - the active pane, the last pane stack, the pane index - comes into it.
|
||||
v1vsplit="80x24,0,0[80x11,0,0,${q0#%},80x12,0,12,${q1#%}]"
|
||||
check_ok select-layout -t L:two "$(v1 "$v1vsplit")"
|
||||
got=$($TMUX -C display-message -p -t L:two '#{window_layout}' | grep -v '^%')
|
||||
must_equal 'v1 round trip' "$got" "$(v1 "$v1vsplit")"
|
||||
|
||||
# v1 names no active pane, last pane or z-index and must disturb none of them.
|
||||
# Applying the v1 form of the layout the window already has therefore leaves
|
||||
# even the v2 dump the same byte for byte, last pane stack included.
|
||||
check_ok select-pane -t "$q1"
|
||||
check_ok select-pane -t "$q0"
|
||||
before=$(raw_layout L:two)
|
||||
check_ok select-layout -t L:two "$(v1 "$v1vsplit")"
|
||||
must_equal 'v1 leaves the active and last panes alone' \
|
||||
"$(raw_layout L:two)" "$before"
|
||||
|
||||
# A v1 layout with more cells than the window has panes is trimmed like any
|
||||
# other: the bottom right cell is closed and the cell above it takes its eight
|
||||
# rows and the border between them, leaving 16. Pane ids in a v1 body are not
|
||||
# used to place panes, so the third cell can carry any id.
|
||||
v1three="80x24,0,0[80x7,0,0,${q0#%},80x7,0,8,${q1#%},80x8,0,16,999]"
|
||||
check_ok select-layout -t L:two "$(v1 "$v1three")"
|
||||
got=$($TMUX -C display-message -p -t L:two '#{window_layout}' | grep -v '^%')
|
||||
must_equal 'v1 layout trimmed' "$got" \
|
||||
"$(v1 "80x24,0,0[80x7,0,0,${q0#%},80x16,0,8,${q1#%}]")"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Failures.
|
||||
# Cells that name no active or last pane.
|
||||
|
||||
# "a" and "l" are the only things that decide which pane is active and what is
|
||||
# on the last pane stack, so a layout naming neither leaves the active pane
|
||||
# where it was and puts nothing on the stack. Here the top pane is active and
|
||||
# the bottom one is at index 0 of the stack beforehand; afterwards the top pane
|
||||
# is still active and the bottom pane is on no stack, so it has no "l".
|
||||
check_ok select-pane -t "$q1"
|
||||
check_ok select-pane -t "$q0"
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0},{"t":"p","w":80,"h":14,"x":0,"y":10}]}}'
|
||||
must_equal 'Layout naming no active pane' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":14,"x":0,"y":10,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Failures with a message.
|
||||
|
||||
# check_layout_fail $cause $layout
|
||||
#
|
||||
@@ -326,6 +514,9 @@ check_layout_fail()
|
||||
check_fail "$1: $2" select-layout -t L:two "$2"
|
||||
}
|
||||
|
||||
# A rejected layout must leave the window alone, whatever it was.
|
||||
unchanged=$(raw_layout L:two)
|
||||
|
||||
# Not JSON and not a checksum.
|
||||
check_layout_fail 'malformed layout header' 'garbage'
|
||||
|
||||
@@ -333,47 +524,12 @@ check_layout_fail 'malformed layout header' 'garbage'
|
||||
good=$(v1 '80x24,0,0')
|
||||
check_layout_fail 'invalid layout checksum' "${good%%,*},80x24,0,1"
|
||||
|
||||
# A correct checksum over a body that is not a layout.
|
||||
# A correct checksum over a body that is not a layout: a cell with no offsets,
|
||||
# and a top to bottom cell closed with '}' instead of ']'. layout_construct_v1
|
||||
# returns NULL for both and layout_construct() reports it.
|
||||
check_layout_fail 'invalid layout' "$(v1 '80x24')"
|
||||
check_layout_fail 'invalid layout' "$(v1 '80x24,0,0[80x11,0,0,80x12,0,12}')"
|
||||
|
||||
# The value of "V" runs to the end of the string without a terminator, so
|
||||
# tokenizing fails.
|
||||
check_layout_fail 'invalid layout characters' '{"V":2'
|
||||
|
||||
# Structurally invalid JSON: an unclosed object, a trailing comma, a second
|
||||
# object after the layout, a number that is not one, and an array member that
|
||||
# is not an object.
|
||||
check_layout_fail 'invalid layout json' '{"V":2,"L":{"t":"p"'
|
||||
check_layout_fail 'invalid layout json' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0},}'
|
||||
check_layout_fail 'invalid layout json' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0}}{}'
|
||||
check_layout_fail 'invalid layout json' \
|
||||
'{"V":2,"L":{"t":"p","w":8a,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail 'invalid layout json' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"c":["x"]}}'
|
||||
|
||||
# Valid JSON that is not a valid layout: the wrong version, no root cell, two
|
||||
# root cells, a missing "y", an unknown cell type and a pane id without its %.
|
||||
check_layout_fail 'invalid layout' \
|
||||
'{"V":1,"L":{"t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail 'invalid layout' '{"V":2}'
|
||||
check_layout_fail 'invalid layout' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0},"L":{"t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail 'invalid layout' '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0}}'
|
||||
check_layout_fail 'invalid layout' \
|
||||
'{"V":2,"L":{"t":"q","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail 'invalid layout' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":"0"}}'
|
||||
|
||||
# A node cell must have children and a leaf cell must not.
|
||||
check_layout_fail 'invalid layout' '{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail 'invalid layout' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[]}}'
|
||||
check_layout_fail 'invalid layout' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":24,"x":0,"y":0}]}}'
|
||||
|
||||
# Fewer cells than the window has panes; unlike the other way around this
|
||||
# cannot be fixed up.
|
||||
check_layout_fail 'have 2 panes but need 1' \
|
||||
@@ -383,8 +539,72 @@ check_layout_fail 'have 2 panes but need 1' \
|
||||
check_layout_fail 'size mismatch after applying layout' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0},{"t":"p","w":40,"h":12,"x":0,"y":12}]}}'
|
||||
|
||||
# The rest are valid JSON, so it is layout_parse_json() and
|
||||
# layout_parse_json_layout() doing the rejecting rather than json.c, and their
|
||||
# own cause reaches the client.
|
||||
|
||||
# Two root cells.
|
||||
check_layout_fail 'duplicate layout' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0},"L":{"t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# A missing "y". A cell needs all four of "w", "h", "x" and "y".
|
||||
check_layout_fail 'cell geometry must be fully specified' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0}}'
|
||||
|
||||
# An unknown cell type: only "h", "v" and "p" exist.
|
||||
check_layout_fail 'invalid cell type q' \
|
||||
'{"V":2,"L":{"t":"q","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# A pane id without its %, and one with trailing rubbish after the number. Note
|
||||
# it is "I" that carries the pane id and requires the %; "i" is the pane index
|
||||
# and takes a plain number.
|
||||
check_layout_fail "pane id must be prefixed by '%'" \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"I":"0"}}'
|
||||
check_layout_fail 'invalid pane id: %1x' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"I":"%1x"}}'
|
||||
|
||||
# A node cell must have children and a leaf cell must not.
|
||||
check_layout_fail 'non-pane cells must have children' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail 'non-pane cells must have children' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[]}}'
|
||||
check_layout_fail 'pane cells cannot have children' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":24,"x":0,"y":0}]}}'
|
||||
|
||||
# The same rejections apply whatever order the fields are in: a leaf with
|
||||
# children when "c" is seen first, a node with no children when "t" is last, a
|
||||
# bad cell type when "t" is last, and a bad pane id when "I" is first.
|
||||
check_layout_fail 'pane cells cannot have children' \
|
||||
'{"V":2,"L":{"c":[{"t":"p","w":80,"h":24,"x":0,"y":0}],"t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail 'non-pane cells must have children' \
|
||||
'{"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"v"}}'
|
||||
check_layout_fail 'invalid cell type q' '{"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"q"}}'
|
||||
check_layout_fail "pane id must be prefixed by '%'" \
|
||||
'{"V":2,"L":{"I":"0","t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# A child that fails after some children have already been added, with "c"
|
||||
# before "t" so the parent's type is still the default when it gives up. This
|
||||
# is the case the cleanup at the end of layout_parse_json_layout exists for:
|
||||
# the already-built children have to be freed even though the parent does not
|
||||
# yet look like a node. The second child has no "y", and its cause is the one
|
||||
# that comes back.
|
||||
check_layout_fail 'cell geometry must be fully specified' \
|
||||
'{"V":2,"L":{"c":[{"t":"p","w":80,"h":11,"x":0,"y":0},{"t":"p","w":80,"h":12,"x":0}],"t":"v","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# No root cell at all. Every other rejection above comes from a cell that
|
||||
# failed to parse; this one is the check after the loop, reached when no "L"
|
||||
# was seen at all.
|
||||
check_layout_fail 'missing layout' '{"V":2}'
|
||||
|
||||
# The wrong version, and the wrong version after a layout that is otherwise
|
||||
# fine so that the built cells have to be thrown away once "V" is finally seen.
|
||||
check_layout_fail 'version mismatch.' \
|
||||
'{"V":1,"L":{"t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail 'version mismatch.' \
|
||||
'{"L":{"t":"p","w":80,"h":24,"x":0,"y":0},"V":1}'
|
||||
|
||||
# None of that touched the layout.
|
||||
must_equal 'Layout after failures' "$(layout L:two)" "$SPLIT"
|
||||
must_equal 'Layout after failures' "$(raw_layout L:two)" "$unchanged"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Floating panes.
|
||||
@@ -399,6 +619,111 @@ must_contain 'Floating layout' "$(layout L:float)" '"z":'
|
||||
check_ok select-layout -t L:float "$(raw_layout L:float)"
|
||||
must_contain 'Floating layout after round trip' "$(layout L:float)" '"z":'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Control mode notifications.
|
||||
#
|
||||
# %layout-change is what a control client actually reads a layout from, and it
|
||||
# carries both #{window_layout} and #{window_visible_layout}. Its template is
|
||||
# expanded once per client (control-notify.c), so two clients watching the same
|
||||
# window must be told about the same change in different formats: v1 for the
|
||||
# one that has not asked for new layouts, v2 for the one that has.
|
||||
#
|
||||
# The dumps above go through "-C display-message", which only ever reaches the
|
||||
# format callbacks for the client asking. This needs clients that stay
|
||||
# attached while something else changes the layout, so they go on the end of
|
||||
# fifos and the change is made from outside.
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
OLDIN="$DIR/old-in"
|
||||
OLDOUT="$DIR/old-out"
|
||||
NEWIN="$DIR/new-in"
|
||||
NEWOUT="$DIR/new-out"
|
||||
OLDPID=
|
||||
NEWPID=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$OLDPID" ] && kill "$OLDPID" 2>/dev/null
|
||||
[ -n "$NEWPID" ] && kill "$NEWPID" 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# wait_for $file $text
|
||||
#
|
||||
# Wait for $text to appear in a control client's output.
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 6 ]; do
|
||||
grep -F -- "$2" "$1" >/dev/null 2>&1 && return 0
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo "missing from $1: $2" >&2
|
||||
cat "$1" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
mkfifo "$OLDIN" "$NEWIN" || exit 1
|
||||
: >"$OLDOUT"
|
||||
: >"$NEWOUT"
|
||||
|
||||
$TMUX -C attach -t L <"$OLDIN" >"$OLDOUT" 2>&1 &
|
||||
OLDPID=$!
|
||||
exec 4>"$OLDIN"
|
||||
$TMUX -C attach -f new-layouts -t L <"$NEWIN" >"$NEWOUT" 2>&1 &
|
||||
NEWPID=$!
|
||||
exec 5>"$NEWIN"
|
||||
|
||||
# Both clients have to be attached before the layout changes, or they miss the
|
||||
# notification entirely.
|
||||
printf 'display-message -p ready\n' >&4
|
||||
printf 'display-message -p ready\n' >&5
|
||||
wait_for "$OLDOUT" ready || fail 'Control client without new-layouts did not attach'
|
||||
wait_for "$NEWOUT" ready || fail 'Control client with new-layouts did not attach'
|
||||
|
||||
wid=$($TMUX display-message -p -t L:two '#{window_id}')
|
||||
|
||||
# One layout change, made by a third client so that neither of the two is the
|
||||
# one running the command. 8 lines for the top pane leaves 15 for the bottom
|
||||
# and one for the border.
|
||||
check_ok resize-pane -t "$q0" -y 8
|
||||
|
||||
# Nothing is zoomed, so both fields of the notification carry the same layout.
|
||||
# The v2 one is compared against the dump rather than a literal so that it is
|
||||
# the two formats being checked and not the geometry again.
|
||||
v2now=$(raw_layout L:two)
|
||||
v1now=$(v1 "80x24,0,0[80x8,0,0,${q0#%},80x15,0,9,${q1#%}]")
|
||||
wait_for "$NEWOUT" "%layout-change $wid $v2now $v2now " ||
|
||||
fail 'No v2 %layout-change for the client with new-layouts'
|
||||
wait_for "$OLDOUT" "%layout-change $wid $v1now $v1now " ||
|
||||
fail 'No v1 %layout-change for the client without new-layouts'
|
||||
|
||||
# How many notifications one layout change produces, which differs by format
|
||||
# on purpose. cmd_select_layout_exec() fires window-layout-changed for any
|
||||
# layout it applies, and layout_parse() fires it again for a v1 one, so v1
|
||||
# arrives twice - which is what master does for every layout, and what control
|
||||
# clients written against it expect. v2 is new and has no such clients, so it
|
||||
# gets the single notification. Counting the delta rather than the total, with
|
||||
# a settle in between, keeps this independent of what has already been sent.
|
||||
n1=$(grep -c "%layout-change $wid " "$OLDOUT")
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0},{"t":"p","w":80,"h":14,"x":0,"y":10}]}}'
|
||||
sleep 2
|
||||
n2=$(grep -c "%layout-change $wid " "$OLDOUT")
|
||||
must_equal 'Notifications for a v2 layout' "$((n2 - n1))" '1'
|
||||
|
||||
check_ok select-layout -t L:two "$(v1 "$v1vsplit")"
|
||||
sleep 2
|
||||
n3=$(grep -c "%layout-change $wid " "$OLDOUT")
|
||||
must_equal 'Notifications for a v1 layout' "$((n3 - n2))" '2'
|
||||
|
||||
# And the client that did not ask for new layouts must never have been sent
|
||||
# one, in that notification or any other.
|
||||
grep -F '{"V":2,' "$OLDOUT" >/dev/null 2>&1 &&
|
||||
fail 'Control client without new-layouts was sent a v2 layout'
|
||||
|
||||
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
|
||||
echo "Server died." >&2
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user