mirror of
https://github.com/tmux/tmux.git
synced 2026-09-05 21:20:45 +00:00
cleanups and bug fixes
This commit is contained in:
89
json.c
89
json.c
@@ -26,7 +26,21 @@
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
#define INPUT_MAX 8192
|
||||
/*
|
||||
* Parse a subset of JSON.
|
||||
*
|
||||
* The subset accepted is:
|
||||
*
|
||||
* - Arrays may only hold objects.
|
||||
* - Numbers are 64-bit signed integers in base 10; there are no
|
||||
* fractions and no exponents.
|
||||
* - There is no null, and a string may not be empty.
|
||||
* - Escapes are not decoded. '\' is only used to skip past \" pairs.
|
||||
* - A key may not appear twice in the same object. Note that because escapes
|
||||
* are not decoded, duplicate keys may go undetected.
|
||||
*/
|
||||
|
||||
#define INPUT_MAX (1 << 14)
|
||||
#define TOKENS_MAX (INPUT_MAX)
|
||||
#define ERROR_CTX_LEN 8
|
||||
|
||||
@@ -95,14 +109,14 @@ static void json_free_tokens(struct json_tokens *);
|
||||
static int json_add_token(struct json_tokens *,
|
||||
enum json_token_type, const char *, int);
|
||||
static const struct json_token *json_tokens_tail(struct json_tokens *);
|
||||
static void json_error(char **, const char *, const char *);
|
||||
|
||||
static struct json_node *json_create_node(struct json_node *,
|
||||
enum json_node_type, const char *, const char *,
|
||||
const void *);
|
||||
static void json_assign_value(struct json_node *, const void *);
|
||||
static struct json_node *json_parse_tokens(struct json_tokens **, char **);
|
||||
static int json_parse_key(struct json_token **, const char **,
|
||||
char **);
|
||||
static const char *json_parse_key(struct json_token **, char **);
|
||||
static struct json_node *json_parse_object(struct json_token **, const char *,
|
||||
struct json_node *, char **);
|
||||
static struct json_node *json_parse_array(struct json_token **, const char *,
|
||||
@@ -137,15 +151,15 @@ json_parse(const char *input, char **cause)
|
||||
|
||||
/* Returns a field node from an object node. */
|
||||
struct json_node *
|
||||
json_find(struct json_node *jn, const char *key)
|
||||
json_find(const struct json_node *jn, const char *key)
|
||||
{
|
||||
struct json_node tmp = { 0 };
|
||||
struct json_node *node = (struct json_node *)jn, tmp = { 0 };
|
||||
|
||||
if (jn->type != NODE_OBJECT)
|
||||
return (NULL);
|
||||
|
||||
tmp.key = key;
|
||||
return (RB_FIND(json_fields, &jn->fields, &tmp));
|
||||
return (RB_FIND(json_fields, &node->fields, &tmp));
|
||||
}
|
||||
|
||||
/* Returns the first member of an array node. */
|
||||
@@ -160,12 +174,12 @@ json_array_first(const struct json_node *jn)
|
||||
|
||||
/* Returns the next member of an array's member node. */
|
||||
struct json_node *
|
||||
json_array_next(const struct json_node *jn)
|
||||
json_array_next(const struct json_node *member)
|
||||
{
|
||||
if (jn->parent->type != NODE_ARRAY)
|
||||
if (member == NULL || member->parent->type != NODE_ARRAY)
|
||||
return (NULL);
|
||||
|
||||
return (TAILQ_NEXT(jn, aentry));
|
||||
return (TAILQ_NEXT(member, aentry));
|
||||
}
|
||||
|
||||
/* Returns the string value from a node. */
|
||||
@@ -230,12 +244,11 @@ json_find_string(const struct json_node *jn, const char *key, char **cause)
|
||||
struct json_node *field;
|
||||
static char ret[INPUT_MAX];
|
||||
|
||||
if ((field = json_find((struct json_node *)jn, key)) == NULL) {
|
||||
if ((field = json_find(jn, key)) == NULL) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "key \"%s\" not found", key);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (field->type != NODE_STRING) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "key \"%s\" expected STRING value",
|
||||
@@ -243,7 +256,7 @@ json_find_string(const struct json_node *jn, const char *key, char **cause)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (xsnprintf(ret, sizeof ret, "%s", field->str) >= (int)sizeof ret) {
|
||||
if (snprintf(ret, sizeof ret, "%s", field->str) >= (int)sizeof ret) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "string overflow for key \"%s\"", key);
|
||||
return (NULL);
|
||||
@@ -259,7 +272,7 @@ json_find_number(const struct json_node *jn, const char *key, char **cause)
|
||||
struct json_node *field;
|
||||
static int64_t ret;
|
||||
|
||||
if ((field = json_find((struct json_node *)jn, key)) == NULL) {
|
||||
if ((field = json_find(jn, key)) == NULL) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "key \"%s\" not found", key);
|
||||
return (NULL);
|
||||
@@ -282,7 +295,7 @@ json_find_boolean(const struct json_node *jn, const char *key, char **cause)
|
||||
struct json_node *field;
|
||||
static int ret;
|
||||
|
||||
if ((field = json_find((struct json_node *)jn, key)) == NULL) {
|
||||
if ((field = json_find(jn, key)) == NULL) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "key \"%s\" not found", key);
|
||||
return (NULL);
|
||||
@@ -304,7 +317,7 @@ json_find_object(const struct json_node *jn, const char *key, char **cause)
|
||||
{
|
||||
struct json_node *field;
|
||||
|
||||
if ((field = json_find((struct json_node *)jn, key)) == NULL) {
|
||||
if ((field = json_find(jn, key)) == NULL) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "key \"%s\" not found", key);
|
||||
return (NULL);
|
||||
@@ -325,7 +338,7 @@ json_find_array(const struct json_node *jn, const char *key, char **cause)
|
||||
{
|
||||
struct json_node *field;
|
||||
|
||||
if ((field = json_find((struct json_node *)jn, key)) == NULL) {
|
||||
if ((field = json_find(jn, key)) == NULL) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "key \"%s\" not found", key);
|
||||
return (NULL);
|
||||
@@ -352,7 +365,7 @@ json_error(char **cause, const char *reason, const char *input)
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ERROR_CTX_LEN; i++) {
|
||||
for (i = 0; i < ERROR_CTX_LEN + 1; i++) {
|
||||
if (input[i] == '\0') {
|
||||
ellipsis = "";
|
||||
break;
|
||||
@@ -522,7 +535,8 @@ json_create_node(struct json_node *parent, enum json_node_type type,
|
||||
|
||||
node = xcalloc(1, sizeof *node);
|
||||
node->parent = parent;
|
||||
node->key = key;
|
||||
if (key != NULL)
|
||||
node->key = xstrdup(key);
|
||||
node->loc = loc;
|
||||
node->type = type;
|
||||
if (type == NODE_ARRAY)
|
||||
@@ -556,6 +570,7 @@ json_destroy_node(struct json_node *node)
|
||||
RB_REMOVE(json_fields, &node->fields, field);
|
||||
json_destroy_node(field);
|
||||
}
|
||||
break;
|
||||
case NODE_ARRAY:
|
||||
while (!TAILQ_EMPTY(&node->members)) {
|
||||
member = TAILQ_FIRST(&node->members);
|
||||
@@ -632,10 +647,10 @@ fail:
|
||||
}
|
||||
|
||||
/* Parse and return a key string, and advance the token pointer. */
|
||||
static int
|
||||
json_parse_key(struct json_token **toks, const char **key, char **cause)
|
||||
static const char *
|
||||
json_parse_key(struct json_token **toks, char **cause)
|
||||
{
|
||||
const char *loc;
|
||||
const char *key, *loc = (*toks)->loc;
|
||||
int len;
|
||||
|
||||
if ((*toks)->type != TOK_QUOTE)
|
||||
@@ -651,14 +666,14 @@ json_parse_key(struct json_token **toks, const char **key, char **cause)
|
||||
if ((*toks)->type != TOK_QUOTE)
|
||||
goto fail;
|
||||
|
||||
*key = xstrndup(loc, len);
|
||||
key = xstrndup(loc, len);
|
||||
(*toks)++;
|
||||
|
||||
return (0);
|
||||
return (key);
|
||||
|
||||
fail:
|
||||
json_error(cause, "invalid key", loc);
|
||||
return (-1);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Parse an object value, return the node, and advance the token pointer. */
|
||||
@@ -677,8 +692,12 @@ json_parse_object(struct json_token **toks, const char *key,
|
||||
|
||||
object = json_create_node(parent, NODE_OBJECT, key, loc, NULL);
|
||||
while ((*toks)->type != TOK_CLOSEOBJECT) {
|
||||
if (json_parse_key(toks, &fkey, cause) != 0)
|
||||
if ((fkey = json_parse_key(toks, cause)) == NULL)
|
||||
goto fail;
|
||||
if (json_find(object, fkey) != NULL) {
|
||||
json_error(cause, "duplicate key", (*toks)->loc);
|
||||
goto fail;
|
||||
}
|
||||
if ((*toks)->type != TOK_COLON) {
|
||||
json_error(cause, "missing colon", (*toks)->loc);
|
||||
goto fail;
|
||||
@@ -725,11 +744,14 @@ json_parse_object(struct json_token **toks, const char *key,
|
||||
json_error(cause, "invalid object", (*toks)->loc);
|
||||
goto fail;
|
||||
}
|
||||
free((char *)fkey);
|
||||
}
|
||||
(*toks)++;
|
||||
return (object);
|
||||
|
||||
fail:
|
||||
if (fkey != NULL)
|
||||
free((char *)fkey);
|
||||
json_destroy_node(object);
|
||||
return (NULL);
|
||||
}
|
||||
@@ -739,14 +761,15 @@ static struct json_node *
|
||||
json_parse_array(struct json_token **toks, const char *key,
|
||||
struct json_node *parent, char **cause)
|
||||
{
|
||||
struct json_node *array;
|
||||
struct json_node *member;
|
||||
struct json_node *array, *member;
|
||||
const char *loc;
|
||||
|
||||
if ((*toks)->type != TOK_OPENARRAY)
|
||||
return (NULL);
|
||||
loc = (*toks)->loc;
|
||||
(*toks)++;
|
||||
|
||||
array = json_create_node(parent, NODE_ARRAY, key, (*toks)->loc, NULL);
|
||||
array = json_create_node(parent, NODE_ARRAY, key, loc, NULL);
|
||||
while ((*toks)->type != TOK_CLOSEARRAY) {
|
||||
switch ((*toks)->type) {
|
||||
case TOK_OPENOBJECT:
|
||||
@@ -815,20 +838,20 @@ static struct json_node *
|
||||
json_parse_number(struct json_token **toks, const char *key,
|
||||
struct json_node *parent, char **cause)
|
||||
{
|
||||
const char *numstr = (*toks)->loc;
|
||||
const char *start = (*toks)->loc;
|
||||
char *endptr;
|
||||
int64_t num;
|
||||
|
||||
errno = 0;
|
||||
num = strtoll(numstr, &endptr, 10);
|
||||
if (errno != 0 || endptr != numstr + (*toks)->len)
|
||||
num = strtoll(start, &endptr, 10);
|
||||
if (errno != 0 || endptr != start + (*toks)->len)
|
||||
goto fail;
|
||||
(*toks)++;
|
||||
|
||||
return (json_create_node(parent, NODE_NUMBER, key, (*toks)->loc, &num));
|
||||
return (json_create_node(parent, NODE_NUMBER, key, start, &num));
|
||||
|
||||
fail:
|
||||
json_error(cause, "invalid number", numstr);
|
||||
json_error(cause, "invalid number", start);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
|
||||
182
layout-custom.c
182
layout-custom.c
@@ -48,16 +48,18 @@
|
||||
* If the cell is a leaf cell (that is, containing a pane and no child cells),
|
||||
* it additionally has:
|
||||
* "I": pane ID as %n
|
||||
* "l": index into last panes list, if not the active pane
|
||||
* "l": index into last panes list if visited and not the active pane
|
||||
* "a": true if the active pane
|
||||
* "i": pane index
|
||||
* "z": z-index, if a floating pane
|
||||
*/
|
||||
|
||||
#define LAYOUT_STRING_MAX (1 << 14)
|
||||
|
||||
/* Layout string. */
|
||||
struct layout_string {
|
||||
char *write;
|
||||
char dat[8192];
|
||||
char dat[LAYOUT_STRING_MAX];
|
||||
};
|
||||
|
||||
struct layout_parse_cell_ctx {
|
||||
@@ -71,22 +73,23 @@ struct layout_parse_cell_ctx {
|
||||
|
||||
struct layout_parse_ctx {
|
||||
int64_t version;
|
||||
int num_active;
|
||||
struct layout_cell *root;
|
||||
char **cause;
|
||||
|
||||
#define CCTX_MAX 512
|
||||
#define CCTX_MAX (LAYOUT_STRING_MAX >> 5) /* min 32 bytes per pane */
|
||||
int clen;
|
||||
struct layout_parse_cell_ctx cctxs[CCTX_MAX];
|
||||
};
|
||||
|
||||
static struct layout_cell *layout_find_bottomright(struct layout_cell *);
|
||||
static u_short layout_checksum(const char *);
|
||||
static int layout_append(struct layout_cell *,
|
||||
struct layout_string *, int);
|
||||
static struct layout_cell *layout_find_bottomright(struct layout_cell *);
|
||||
static u_short layout_checksum(const char *);
|
||||
static int layout_append(struct layout_cell *,
|
||||
struct layout_string *, int);
|
||||
static int layout_construct(const char *,
|
||||
struct layout_parse_ctx *);
|
||||
static void layout_assign(struct window *,
|
||||
struct layout_parse_ctx *);
|
||||
static void layout_assign(struct window *,
|
||||
struct layout_parse_ctx *);
|
||||
static void layout_parse_apply_ctx(struct window *,
|
||||
struct layout_parse_ctx *);
|
||||
static struct layout_cell *layout_parse_json_layout(
|
||||
@@ -100,8 +103,13 @@ 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;
|
||||
int retval = 0;
|
||||
|
||||
return (cca->index - ccb->index);
|
||||
if (cca->index < ccb->index)
|
||||
retval = -1;
|
||||
if (cca->index > ccb->index)
|
||||
retval = 1;
|
||||
return (retval);
|
||||
}
|
||||
|
||||
/* Compare cell contexts in descending order of z-index. */
|
||||
@@ -110,21 +118,28 @@ layout_parse_zindex_cmp(const void *a, const void *b)
|
||||
{
|
||||
const struct layout_parse_cell_ctx *cca = a;
|
||||
const struct layout_parse_cell_ctx *ccb = b;
|
||||
int retval = 0;
|
||||
|
||||
return (ccb->zindex - cca->zindex);
|
||||
if (cca->zindex > ccb->zindex)
|
||||
retval = -1;
|
||||
if (cca->zindex < ccb->zindex)
|
||||
retval = 1;
|
||||
return (retval);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare cell contexts in descending order of last. The active pane has
|
||||
* index of -1.
|
||||
*/
|
||||
/* Compare cell contexts in descending order of last. */
|
||||
static int
|
||||
layout_parse_last_cmp(const void *a, const void *b)
|
||||
{
|
||||
const struct layout_parse_cell_ctx *cca = a;
|
||||
const struct layout_parse_cell_ctx *ccb = b;
|
||||
int retval = 0;
|
||||
|
||||
return (ccb->last - cca->last);
|
||||
if (cca->last > ccb->last)
|
||||
retval = -1;
|
||||
if (cca->last < ccb->last)
|
||||
retval = 1;
|
||||
return (retval);
|
||||
}
|
||||
|
||||
/* Initialize a layout string. */
|
||||
@@ -158,6 +173,7 @@ static void
|
||||
layout_parse_init_ctx(struct layout_parse_ctx *pctx, char **cause)
|
||||
{
|
||||
pctx->version = -1;
|
||||
pctx->num_active = 0;
|
||||
pctx->root = NULL;
|
||||
pctx->cause = cause;
|
||||
pctx->clen = 0;
|
||||
@@ -191,9 +207,6 @@ layout_parse_remove_cctx(struct layout_parse_ctx *pctx, struct layout_cell *lc)
|
||||
struct layout_parse_cell_ctx *cctx;
|
||||
int i;
|
||||
|
||||
if (pctx->clen == 0)
|
||||
return (-1);
|
||||
|
||||
for (i = 0; i < pctx->clen; i++) {
|
||||
if (lc == pctx->cctxs[i].lc) {
|
||||
cctx = &pctx->cctxs[--pctx->clen];
|
||||
@@ -304,12 +317,12 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
if (window_pane_index(wp, &i) != -1) {
|
||||
if (window_pane_index(wp, &i) == 0) {
|
||||
if (layout_string_write(ls, ",\"i\":%u", i) != 0)
|
||||
return (-1);
|
||||
}
|
||||
if (lc->flags & LAYOUT_CELL_FLOATING) {
|
||||
if (window_pane_zindex(wp, &i) != -1) {
|
||||
if (window_pane_zindex(wp, &i) == 0) {
|
||||
if (layout_string_write(ls, ",\"z\":%u", i)
|
||||
!= 0)
|
||||
return (-1);
|
||||
@@ -443,12 +456,11 @@ layout_parse(struct window *w, const char *layout, char **cause)
|
||||
layout_free_cell(pctx.root, 0);
|
||||
return (-1);
|
||||
}
|
||||
lc = pctx.root;
|
||||
|
||||
/* Check this window will fit into the layout. */
|
||||
npanes = window_count_panes(w, 1);
|
||||
for (;;) {
|
||||
ncells = layout_count_cells(lc);
|
||||
ncells = layout_count_cells(pctx.root);
|
||||
if (npanes > ncells) {
|
||||
xasprintf(cause, "have %u panes but need %u", npanes,
|
||||
ncells);
|
||||
@@ -461,12 +473,15 @@ layout_parse(struct window *w, const char *layout, char **cause)
|
||||
* Fewer panes than cells, close the bottom right until none
|
||||
* remain.
|
||||
*/
|
||||
lcchild = layout_find_bottomright(lc);
|
||||
lcchild = layout_find_bottomright(pctx.root);
|
||||
if (pctx.version != 1 && layout_parse_remove_cctx(&pctx,
|
||||
lcchild) != 0)
|
||||
lcchild) != 0) {
|
||||
*cause = xstrdup("empty/missing layout parse context");
|
||||
goto fail;
|
||||
layout_destroy_cell(w, lcchild, &lc);
|
||||
}
|
||||
layout_destroy_cell(w, lcchild, &pctx.root);
|
||||
}
|
||||
lc = pctx.root;
|
||||
|
||||
/*
|
||||
* It appears older versions of tmux were able to generate layouts with
|
||||
@@ -518,16 +533,17 @@ layout_parse(struct window *w, const char *layout, char **cause)
|
||||
|
||||
/* Assign the panes into the cells. */
|
||||
layout_assign(w, &pctx);
|
||||
if (pctx.version != 1)
|
||||
layout_parse_apply_ctx(w, &pctx);
|
||||
|
||||
/* Update pane offsets and sizes. */
|
||||
/* Update pane attributes. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
if (pctx.version != 1)
|
||||
layout_parse_apply_ctx(w, &pctx);
|
||||
recalculate_sizes();
|
||||
layout_print_cell(lc, __func__, 0);
|
||||
|
||||
if (pctx.version == 1) /* backwards compatibility. */
|
||||
/* Backwards compatibility. */
|
||||
if (pctx.version == 1)
|
||||
events_fire_window("window-layout-changed", w);
|
||||
|
||||
return (0);
|
||||
@@ -588,8 +604,9 @@ layout_assign(struct window *w, struct layout_parse_ctx *pctx)
|
||||
struct window_pane *wp = TAILQ_FIRST(&w->panes);
|
||||
|
||||
if (pctx->clen > 0)
|
||||
return (layout_assign_from_ctx(w, pctx));
|
||||
return (layout_assign_fallback(&wp, pctx->root));
|
||||
layout_assign_from_ctx(w, pctx);
|
||||
else
|
||||
layout_assign_fallback(&wp, pctx->root);
|
||||
}
|
||||
|
||||
/* Construct a cell from the legacy (v1) format. */
|
||||
@@ -713,14 +730,12 @@ layout_parse_json(struct json_node *jnroot, struct layout_parse_ctx *pctx)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((num = json_find_number(jn, "V", cause)) == NULL) {
|
||||
if ((num = json_find_number(jn, "V", cause)) == NULL)
|
||||
goto fail;
|
||||
}
|
||||
pctx->version = *num;
|
||||
|
||||
if ((object = json_find_object(jn, "L", cause)) == NULL) {
|
||||
if ((object = json_find_object(jn, "L", cause)) == NULL)
|
||||
goto fail;
|
||||
}
|
||||
pctx->root = layout_parse_json_layout(object, NULL, pctx);
|
||||
if (pctx->root == NULL)
|
||||
goto fail;
|
||||
@@ -749,7 +764,7 @@ layout_parse_json_layout(const struct json_node *node,
|
||||
const int64_t *num;
|
||||
const int *boolean;
|
||||
char *endptr, **cause = pctx->cause;
|
||||
u_int id, index, zindex, active = -1, last = -1;
|
||||
int id, index, zindex, active = -1, last = -1;
|
||||
|
||||
if ((str = json_find_string(node, "t", cause)) == NULL)
|
||||
goto fail;
|
||||
@@ -759,65 +774,106 @@ layout_parse_json_layout(const struct json_node *node,
|
||||
lc->type = LAYOUT_TOPBOTTOM;
|
||||
else if (strcmp(str, "h") == 0)
|
||||
lc->type = LAYOUT_LEFTRIGHT;
|
||||
else
|
||||
else {
|
||||
xasprintf(cause, "unknown cell type \"%s\"", str);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((num = json_find_number(node, "w", cause)) == NULL)
|
||||
goto fail;
|
||||
if (*num < PANE_MINIMUM || *num > PANE_MAXIMUM) {
|
||||
xasprintf(cause, "invalid width %lld", (long long)*num);
|
||||
goto fail;
|
||||
}
|
||||
lc->g.sx = *num;
|
||||
|
||||
if ((num = json_find_number(node, "h", cause)) == NULL)
|
||||
goto fail;
|
||||
if (*num < PANE_MINIMUM || *num > PANE_MAXIMUM) {
|
||||
xasprintf(cause, "invalid height %lld", (long long)*num);
|
||||
goto fail;
|
||||
}
|
||||
lc->g.sy = *num;
|
||||
|
||||
if ((num = json_find_number(node, "x", cause)) == NULL)
|
||||
goto fail;
|
||||
if (*num < -WINDOW_MAXIMUM || *num > WINDOW_MAXIMUM) {
|
||||
xasprintf(cause, "invalid x-offset %lld", (long long)*num);
|
||||
goto fail;
|
||||
}
|
||||
lc->g.xoff = *num;
|
||||
|
||||
if ((num = json_find_number(node, "y", cause)) == NULL)
|
||||
goto fail;
|
||||
if (*num < -WINDOW_MAXIMUM || *num > WINDOW_MAXIMUM) {
|
||||
xasprintf(cause, "invalid y-offset %lld", (long long)*num);
|
||||
goto fail;
|
||||
}
|
||||
lc->g.yoff = *num;
|
||||
|
||||
if (lc->type == LAYOUT_WINDOWPANE) {
|
||||
if (json_find(node, "c") != NULL) {
|
||||
*cause = xstrdup("panes cannot have children");
|
||||
goto fail;
|
||||
}
|
||||
if ((str = json_find_string(node, "I", cause)) == NULL)
|
||||
goto fail;
|
||||
errno = 0;
|
||||
if (*str != '%') {
|
||||
*cause = xstrdup("pane id must begin with '%'");
|
||||
goto fail;
|
||||
}
|
||||
errno = 0;
|
||||
id = strtol(str + 1, &endptr, 10);
|
||||
if (errno != 0 || endptr != str + strlen(str)) {
|
||||
*cause = xstrdup("invalid number string '%s'");
|
||||
xasprintf(cause, "invalid number string '%s'", str);
|
||||
goto fail;
|
||||
}
|
||||
if ((num = json_find_number(node, "i", cause)) == NULL)
|
||||
goto fail;
|
||||
if (*num < 0 || *num > INT_MAX) {
|
||||
xasprintf(cause, "invalid index %lld", (long long)*num);
|
||||
goto fail;
|
||||
}
|
||||
index = *num;
|
||||
|
||||
if (json_find((struct json_node *)node, "a") != NULL) {
|
||||
if (json_find(node, "a") != NULL) {
|
||||
boolean = json_find_boolean(node, "a", cause);
|
||||
if (boolean == NULL)
|
||||
goto fail;
|
||||
active = *boolean;
|
||||
} else if (json_find((struct json_node *)node, "l") != NULL) {
|
||||
if (active)
|
||||
pctx->num_active++;
|
||||
} else if (json_find(node, "l") != NULL) {
|
||||
num = json_find_number(node, "l", cause);
|
||||
if (num == NULL)
|
||||
goto fail;
|
||||
if (*num < 0 || *num > INT_MAX) {
|
||||
xasprintf(cause, "invalid last %lld",
|
||||
(long long)*num);
|
||||
goto fail;
|
||||
}
|
||||
last = *num;
|
||||
}
|
||||
|
||||
if (json_find((struct json_node *)node, "z") != NULL) {
|
||||
if (json_find(node, "z") != NULL) {
|
||||
num = json_find_number(node, "z", cause);
|
||||
if (num == NULL)
|
||||
goto fail;
|
||||
if (*num < 0 || *num > INT_MAX - 1) {
|
||||
xasprintf(cause, "invalid floating zindex %lld",
|
||||
(long long)*num);
|
||||
goto fail;
|
||||
}
|
||||
zindex = *num;
|
||||
lc->flags |= LAYOUT_CELL_FLOATING;
|
||||
} else
|
||||
zindex = INT_MAX;
|
||||
|
||||
layout_parse_add_cctx(pctx, lc, active, last, id, index,
|
||||
zindex);
|
||||
if (layout_parse_add_cctx(pctx, lc, active, last, id, index,
|
||||
zindex) != 0) {
|
||||
*cause = xstrdup("too many panes");
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
if ((array = json_find_array(node, "c", cause)) == NULL)
|
||||
goto fail;
|
||||
@@ -848,7 +904,7 @@ layout_construct(const char *layout, struct layout_parse_ctx *pctx)
|
||||
{
|
||||
struct json_node *json;
|
||||
u_short csum;
|
||||
int n;
|
||||
int n = 0;
|
||||
|
||||
while (isspace((u_char) *layout))
|
||||
layout++;
|
||||
@@ -876,7 +932,15 @@ layout_construct(const char *layout, struct layout_parse_ctx *pctx)
|
||||
return (-1);
|
||||
|
||||
if (pctx->version != 2) {
|
||||
*pctx->cause = xstrdup("version mismatch.");
|
||||
*pctx->cause = xstrdup("version mismatch");
|
||||
return (-1);
|
||||
}
|
||||
if (pctx->num_active > 1) {
|
||||
*pctx->cause = xstrdup("more than one active pane");
|
||||
return (-1);
|
||||
}
|
||||
if (pctx->clen == 0) {
|
||||
*pctx->cause = xstrdup("no panes");
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
@@ -888,17 +952,17 @@ layout_construct(const char *layout, struct layout_parse_ctx *pctx)
|
||||
static void
|
||||
layout_parse_apply_ctx(struct window *w, struct layout_parse_ctx *pctx)
|
||||
{
|
||||
struct layout_parse_cell_ctx *cctx;
|
||||
struct window_pane *wp;
|
||||
int i;
|
||||
|
||||
if (pctx->clen == 0)
|
||||
fatalx("layouts must have at least one pane");
|
||||
struct layout_parse_cell_ctx *cctx;
|
||||
struct window_pane *wp, *wpnext;
|
||||
int i;
|
||||
|
||||
/* Apply z-indexes. */
|
||||
while (!TAILQ_EMPTY(&w->z_index)) {
|
||||
wp = TAILQ_FIRST(&w->z_index);
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
wp = TAILQ_FIRST(&w->z_index);
|
||||
while (wp != NULL) {
|
||||
wpnext = TAILQ_NEXT(wp, zentry);
|
||||
if (window_pane_is_floating(wp))
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
wp = wpnext;
|
||||
}
|
||||
|
||||
qsort(pctx->cctxs, pctx->clen, sizeof pctx->cctxs[0],
|
||||
@@ -907,7 +971,8 @@ 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;
|
||||
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
|
||||
if (window_pane_is_floating(wp))
|
||||
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
|
||||
}
|
||||
|
||||
/* Set the active pane. */
|
||||
@@ -931,9 +996,8 @@ 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->last < 0 || cctx->active == 1) {
|
||||
if (cctx->last < 0 || cctx->active == 1)
|
||||
continue;
|
||||
}
|
||||
window_pane_stack_push(&w->last_panes, wp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,18 +23,21 @@
|
||||
# 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;
|
||||
# strings, the number and boolean forms, and one failure for each way json.c
|
||||
# can reject an input;
|
||||
# - 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 and the panes being assigned to its cells
|
||||
# in order;
|
||||
# another layout has been applied in between, and the same for a layout with
|
||||
# two floating panes in it;
|
||||
# - parsing a hand-written v2 layout;
|
||||
# - "i" deciding which pane goes in which cell, checked with a layout whose
|
||||
# cells are written in a different order from their indexes;
|
||||
# - the same layouts with their fields in reversed and scrambled orders,
|
||||
# including "c" before "t" (children parsed before the cell type is known)
|
||||
# and "V" after "L";
|
||||
# including "c" before "t" and "V" after "L", neither of which changes the
|
||||
# order the fields are read in;
|
||||
# - a layout with more cells than the window has panes having the bottom right
|
||||
# cells dropped, in both formats;
|
||||
# - a layout naming no active or last pane leaving both as they were;
|
||||
# - a layout naming no active or last pane leaving both as they were, whether
|
||||
# it leaves "a" out or gives it as false;
|
||||
# - parsing a v1 layout and dumping it back as v1 through a control client,
|
||||
# with the checksum computed here independently of layout_checksum(), and a
|
||||
# v1 layout leaving the active pane and last pane stack untouched;
|
||||
@@ -42,9 +45,10 @@
|
||||
# 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.
|
||||
# duplicated root cell, missing sizes, sizes out of range, bad cell types and
|
||||
# pane ids, a pane cell missing "i" or "I", leaf cells with children and node
|
||||
# cells without, more than one active pane, a string too long for json.c to
|
||||
# return, too few cells for the panes and inconsistent sizes.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
@@ -98,16 +102,16 @@ check_ok()
|
||||
out=$($TMUX "$@" 2>&1) || fail "Command failed (expected success): $* ($out)"
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
# check_fail $cmd...
|
||||
#
|
||||
# Run a command and require that it fails with the given error message.
|
||||
# Run a command and require that it fails. The error text itself is never
|
||||
# checked anywhere in this test: the wording of a message is not part of what
|
||||
# the layout formats promise, so matching on it only makes the test fail when a
|
||||
# message is reworded.
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1) &&
|
||||
$TMUX "$@" >/dev/null 2>&1 &&
|
||||
fail "Command succeeded (expected failure): $*"
|
||||
must_equal "Error for: $*" "$out" "$exp"
|
||||
}
|
||||
|
||||
# layout $target
|
||||
@@ -161,11 +165,15 @@ v1()
|
||||
# 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}'
|
||||
check_ok new-session -d -s L -x 80 -y 24 -n one
|
||||
|
||||
$TMUX new-session -d -s L -x 80 -y 24 -n one || exit 1
|
||||
p0=$($TMUX display-message -p -t L:one.0 '#{pane_id}')
|
||||
|
||||
# A single leaf cell filling the window. Every pane cell carries "i", its pane
|
||||
# index, and "I", its pane id; both are required, so they are here even in the
|
||||
# JSON checks, which care about the syntax around the cell rather than the cell
|
||||
# itself.
|
||||
LEAF='{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$p0"'"}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dumping a single pane.
|
||||
@@ -183,9 +191,10 @@ 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: the cell that is left takes the space of the
|
||||
# one that was closed.
|
||||
# one that was closed. The cell that is closed is the only one whose "I" names
|
||||
# no pane of this window, there being just the one pane to name.
|
||||
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}]}}'
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$p0"'"},{"t":"p","w":80,"h":12,"x":0,"y":12,"i":1,"I":"%999"}]}}'
|
||||
must_equal 'Trimmed layout' "$(layout L:one)" "$ONE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -201,11 +210,10 @@ must_equal 'Trimmed layout' "$(layout L:one)" "$ONE"
|
||||
# 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".
|
||||
# One of json.c's rejections cannot be reached from the shell and so is not
|
||||
# covered: json_parse_tokens() refusing a top level that is not an object,
|
||||
# because layout_construct() only calls json_parse() once the string already
|
||||
# starts with '{'.
|
||||
|
||||
# check_json_ok $what $layout
|
||||
#
|
||||
@@ -216,27 +224,20 @@ check_json_ok()
|
||||
must_equal "Layout after '$1'" "$(layout L:one)" "$ONE"
|
||||
}
|
||||
|
||||
# check_json_fail $what $reason $layout
|
||||
# check_json_fail $what $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.
|
||||
# select-layout must reject $layout.
|
||||
check_json_fail()
|
||||
{
|
||||
out=$($TMUX select-layout -t L:one "$3" 2>&1) &&
|
||||
$TMUX select-layout -t L:one "$2" >/dev/null 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 } }'
|
||||
'{ "V" : 2 , "L" : { "t" : "p" , "w" : 80 , "h" : 24 , "x" : 0 , "y" : 0 , "i" : 0 , "I" : "'"$p0"'" } }'
|
||||
|
||||
check_json_ok 'Newlines and tabs between tokens' "$(printf '{
|
||||
\t"V": 2,
|
||||
@@ -245,9 +246,11 @@ check_json_ok 'Newlines and tabs between tokens' "$(printf '{
|
||||
\t\t"w": 80,
|
||||
\t\t"h": 24,
|
||||
\t\t"x": 0,
|
||||
\t\t"y": 0
|
||||
\t\t"y": 0,
|
||||
\t\t"i": 0,
|
||||
\t\t"I": "%s"
|
||||
\t}
|
||||
}')"
|
||||
}' "$p0")"
|
||||
|
||||
check_json_ok 'Carriage returns between tokens' \
|
||||
"$(printf '{\r"V":2,\r"L":%s\r}' "$LEAF")"
|
||||
@@ -275,48 +278,43 @@ check_json_ok 'Booleans' '{"V":2,"b":true,"d":false,"L":'"$LEAF"'}'
|
||||
# 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\"}}'
|
||||
check_json_fail 'Unterminated number' '{"V":2'
|
||||
check_json_fail 'Unterminated string' '{"V":"x'
|
||||
check_json_fail 'Escaped closing quote' '{"V":2,"L":{"t":"p\"}}'
|
||||
|
||||
# Something that is not a quoted string where a key belongs.
|
||||
check_json_fail 'Missing key' 'invalid key' '{"V":2,,"L":'"$LEAF"'}'
|
||||
check_json_fail 'Missing key' '{"V":2,,"L":'"$LEAF"'}'
|
||||
|
||||
# A key not followed by ':'.
|
||||
check_json_fail 'Missing colon' 'missing colon' '{"V","L":2}'
|
||||
check_json_fail 'Missing colon' '{"V","L":2}'
|
||||
|
||||
# A bare word that is neither "true", "false" nor a number. This is where
|
||||
# "null" ends up.
|
||||
check_json_fail 'Unknown literal' 'invalid value' '{"V":null,"L":'"$LEAF"'}'
|
||||
check_json_fail 'Unknown literal' '{"V":null,"L":'"$LEAF"'}'
|
||||
|
||||
# A ':' with no value after it, so the token where the value belongs is one the
|
||||
# object parser has no case for.
|
||||
check_json_fail 'Missing value' 'unsupported object token' '{"V":}'
|
||||
check_json_fail 'Missing value' '{"V":}'
|
||||
|
||||
# A ',' with nothing after it, and a value with no ',' before the next key.
|
||||
check_json_fail 'Trailing comma in an object' 'invalid object' \
|
||||
'{"V":2,"L":'"$LEAF"',}'
|
||||
check_json_fail 'Missing comma in an object' 'invalid object' \
|
||||
'{"V":2 "L":'"$LEAF"'}'
|
||||
check_json_fail 'Trailing comma in an object' '{"V":2,"L":'"$LEAF"',}'
|
||||
check_json_fail 'Missing comma in an object' '{"V":2 "L":'"$LEAF"'}'
|
||||
|
||||
# Arrays hold objects and nothing else.
|
||||
check_json_fail 'Non-object in an array' 'invalid array member' \
|
||||
check_json_fail 'Non-object in an array' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":["x"]}}'
|
||||
check_json_fail 'Trailing comma in an array' '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},]}}'
|
||||
check_json_fail 'Trailing comma in an array' \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":['"$LEAF"',]}}'
|
||||
|
||||
# An empty string is two adjacent quotes with no value token between them,
|
||||
# which the string parser does not accept.
|
||||
check_json_fail 'Empty string' 'invalid string' '{"V":2,"L":""}'
|
||||
check_json_fail 'Empty string' '{"V":2,"L":""}'
|
||||
|
||||
# A number token that strtoll does not consume all of.
|
||||
check_json_fail 'Number with trailing characters' 'invalid number' \
|
||||
'{"V":8a,"L":'"$LEAF"'}'
|
||||
check_json_fail 'Number with trailing characters' '{"V":8a,"L":'"$LEAF"'}'
|
||||
|
||||
# Anything after the top level object.
|
||||
check_json_fail 'Data after the top level object' 'unexpected trailing data' \
|
||||
'{"V":2,"L":'"$LEAF"'}{}'
|
||||
check_json_fail 'Data after the top level object' '{"V":2,"L":'"$LEAF"'}{}'
|
||||
|
||||
# None of the rejections touched the layout.
|
||||
must_equal 'Layout after rejected parses' "$(layout L:one)" "$ONE"
|
||||
@@ -394,7 +392,7 @@ must_equal 'Round tripped layout' "$(raw_layout L:two)" "$saved"
|
||||
# "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 '{
|
||||
check_ok select-layout -t L:two "$(printf '{
|
||||
"V": 2,
|
||||
"L": {
|
||||
"t": "h",
|
||||
@@ -403,11 +401,11 @@ check_ok select-layout -t L:two '{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"c": [
|
||||
{"t": "p", "w": 30, "h": 24, "x": 0, "y": 0, "a": true},
|
||||
{"t": "p", "w": 49, "h": 24, "x": 31, "y": 0, "l": 0}
|
||||
{"t": "p", "w": 30, "h": 24, "x": 0, "y": 0, "a": true, "i": 0, "I": "%s"},
|
||||
{"t": "p", "w": 49, "h": 24, "x": 31, "y": 0, "l": 0, "i": 1, "I": "%s"}
|
||||
]
|
||||
}
|
||||
}'
|
||||
}' "$q0" "$q1")"
|
||||
must_equal 'Hand-written layout' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"h","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":30,"h":24,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":49,"h":24,"x":31,"y":0,"l":0,"i":1,"I":"%N"}]}}'
|
||||
|
||||
@@ -420,11 +418,13 @@ must_equal 'Second pane width' \
|
||||
# ---------------------------------------------------------------------------
|
||||
# Field order.
|
||||
|
||||
# Fields are looked up by key, so any order must give the same layout. Here
|
||||
# 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":[{"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}'
|
||||
# Fields are looked up by key once the object has been parsed, so the order
|
||||
# they are written in must give the same layout. Here every object has its keys
|
||||
# reversed: "c" comes before "t" and "V" comes after "L", neither of which
|
||||
# changes the order they are read in - the cell type is always read before the
|
||||
# children and the version before the layout.
|
||||
check_ok select-layout -t L:two \
|
||||
'{"L":{"c":[{"I":"'"$q0"'","i":0,"a":true,"y":0,"x":0,"h":8,"w":80,"t":"p"},{"I":"'"$q1"'","i":1,"l":0,"y":9,"x":0,"h":15,"w":80,"t":"p"}],"y":0,"x":0,"h":24,"w":80,"t":"v"},"V":2}'
|
||||
must_equal 'Reversed field order' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":8,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":15,"x":0,"y":9,"l":0,"i":1,"I":"%N"}]}}'
|
||||
|
||||
@@ -433,7 +433,8 @@ must_equal 'Reversed field order' "$(layout L:two)" \
|
||||
# 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}}'
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"h":24,"c":[{"w":40,"t":"p","y":0,"i":0,"h":24,"I":"'"$q0"'","x":0},{"a":true,"h":24,"I":"'"$q1"'","w":39,"y":0,"t":"p","i":1,"x":41}],"w":80,"y":0,"t":"h","x":0}}'
|
||||
must_equal 'Scrambled field order' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"h","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":40,"h":24,"x":0,"y":0,"i":0,"I":"%N"},{"t":"p","w":39,"h":24,"x":41,"y":0,"a":true,"i":1,"I":"%N"}]}}'
|
||||
|
||||
@@ -488,120 +489,182 @@ 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#%}]")"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pane assignment order.
|
||||
|
||||
# "i" is what decides which pane goes into which cell: the cells are ordered by
|
||||
# it and then handed the window's panes in order, so the cell with "i":0 takes
|
||||
# the first pane of the window wherever that cell sits in the layout. Here the
|
||||
# cells are written the other way round from their indexes - the first cell in
|
||||
# the string is "i":1 and the second "i":0 - so the first pane of the window
|
||||
# has to come out in the second cell.
|
||||
#
|
||||
# Every other layout above lists its cells in the same order as their indexes,
|
||||
# which is the order the tree is walked in, so this is the only check that can
|
||||
# tell the two apart.
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":8,"x":0,"y":0,"i":1,"I":"'"$q1"'"},{"t":"p","w":80,"h":15,"x":0,"y":9,"i":0,"I":"'"$q0"'"}]}}'
|
||||
must_equal 'First pane height' \
|
||||
"$($TMUX display-message -p -t "$q0" '#{pane_height}')" '15'
|
||||
must_equal 'Second pane height' \
|
||||
"$($TMUX display-message -p -t "$q1" '#{pane_height}')" '8'
|
||||
|
||||
# So the dump carries the two ids the other way round from every dump above,
|
||||
# and with them their indexes, which are the panes' positions in the window and
|
||||
# have not moved. Neither cell named an active or last pane, so the pane that
|
||||
# was active still is - it is now the one in the second cell.
|
||||
swapped='{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":8,"x":0,"y":0,"i":1,"I":"'"$q1"'"},{"t":"p","w":80,"h":15,"x":0,"y":9,"a":true,"i":0,"I":"'"$q0"'"}]}}'
|
||||
must_equal 'Layout with the panes swapped' "$(raw_layout L:two)" "$swapped"
|
||||
|
||||
# And that dump round trips, indexes out of order and all.
|
||||
check_ok select-layout -t L:two "$swapped"
|
||||
must_equal 'Round tripped swapped layout' "$(raw_layout L:two)" "$swapped"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cells that name no active or last pane.
|
||||
|
||||
# "a" and "l" are the only things that decide which pane is active and what is
|
||||
# on the last pane stack, so a layout naming neither leaves the active pane
|
||||
# where it was and 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".
|
||||
# where it was and puts nothing on the stack. Here the first pane of the window
|
||||
# is active and the second is at index 0 of the stack beforehand; afterwards
|
||||
# the first pane is still active and the second 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}]}}'
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":14,"x":0,"y":10,"i":1,"I":"'"$q1"'"}]}}'
|
||||
must_equal 'Layout naming no active pane' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":14,"x":0,"y":10,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Failures with a message.
|
||||
# "a" may be given as false, which says the same as leaving it out: this pane
|
||||
# is not the active one. A layout where every cell says so names no active pane
|
||||
# at all and so leaves the active pane alone, exactly as the layout above did.
|
||||
check_ok select-layout -t L:two \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":10,"x":0,"y":0,"a":false,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":13,"x":0,"y":11,"a":false,"i":1,"I":"'"$q1"'"}]}}'
|
||||
must_equal 'Layout with only false active panes' "$(layout L:two)" \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":10,"x":0,"y":0,"a":true,"i":0,"I":"%N"},{"t":"p","w":80,"h":13,"x":0,"y":11,"i":1,"I":"%N"}]}}'
|
||||
|
||||
# check_layout_fail $cause $layout
|
||||
# ---------------------------------------------------------------------------
|
||||
# Failures.
|
||||
#
|
||||
# select-layout must reject $layout with "<cause>: <layout>".
|
||||
# Each of these is a different reason for a layout to be rejected, but only the
|
||||
# rejection itself is checked; the message that comes back with it is not.
|
||||
|
||||
# check_layout_fail $layout
|
||||
#
|
||||
# select-layout must reject $layout.
|
||||
check_layout_fail()
|
||||
{
|
||||
check_fail "$1: $2" select-layout -t L:two "$2"
|
||||
check_fail select-layout -t L:two "$1"
|
||||
}
|
||||
|
||||
# A rejected layout must leave the window alone, whatever it was.
|
||||
unchanged=$(raw_layout L:two)
|
||||
|
||||
# Not JSON and not a checksum.
|
||||
check_layout_fail 'malformed layout header' 'garbage'
|
||||
check_layout_fail 'garbage'
|
||||
|
||||
# A v1 body with its checksum left off, and a string of nothing but hex digits.
|
||||
# A v1 header is four hex digits and a comma; neither of these has one, so there
|
||||
# is no header and nothing to check a body against.
|
||||
check_layout_fail '80x24,0,0'
|
||||
check_layout_fail 'ab'
|
||||
|
||||
# A v1 header with the checksum of a different body.
|
||||
good=$(v1 '80x24,0,0')
|
||||
check_layout_fail 'invalid layout checksum' "${good%%,*},80x24,0,1"
|
||||
check_layout_fail "${good%%,*},80x24,0,1"
|
||||
|
||||
# A correct checksum over a body that is not a layout: a cell with no offsets,
|
||||
# and a top to bottom cell closed with '}' instead of ']'. layout_construct_v1
|
||||
# returns NULL for both 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}')"
|
||||
# returns NULL for both.
|
||||
check_layout_fail "$(v1 '80x24')"
|
||||
check_layout_fail "$(v1 '80x24,0,0[80x11,0,0,80x12,0,12}')"
|
||||
|
||||
# Fewer cells than the window has panes; unlike the other way around this
|
||||
# cannot be fixed up.
|
||||
check_layout_fail 'have 2 panes but need 1' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# The children of a top to bottom cell must all be the width of their parent.
|
||||
check_layout_fail '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}]}}'
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":40,"h":12,"x":0,"y":12,"i":1,"I":"'"$q1"'"}]}}'
|
||||
|
||||
# The rest are valid JSON, so it is layout_parse_json() and
|
||||
# layout_parse_json_layout() doing the rejecting rather than json.c, and their
|
||||
# own cause reaches the client.
|
||||
# layout_parse_json_layout() doing the rejecting rather than json.c. Each of
|
||||
# them is a layout that would be applied but for the one thing being checked.
|
||||
|
||||
# Two root cells.
|
||||
check_layout_fail '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}}'
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"},"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# A missing "y". A cell needs all four of "w", "h", "x" and "y".
|
||||
check_layout_fail 'cell geometry must be fully specified' \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0}}'
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"i":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# Cell sizes are bounded below by one column or row and above by 10000 of
|
||||
# either. Both cases are otherwise complete two cell layouts, so the size is
|
||||
# the only thing wrong with them.
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":0,"h":12,"x":0,"y":12,"i":1,"I":"'"$q1"'"}]}}'
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":10001,"x":0,"y":12,"i":1,"I":"'"$q1"'"}]}}'
|
||||
|
||||
# An unknown cell type: only "h", "v" and "p" exist.
|
||||
check_layout_fail 'invalid cell type q' \
|
||||
'{"V":2,"L":{"t":"q","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail '{"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"}}'
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"0"}}'
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"%1x"}}'
|
||||
|
||||
# A pane cell needs both of them.
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0}}'
|
||||
check_layout_fail '{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# A string longer than json.c will hand back: it copies a string out into a
|
||||
# 16384 byte buffer and refuses anything that does not fit, so this has to be
|
||||
# longer than that to be refused at all. It is the cell type, the first string a
|
||||
# cell is read for, and the layout must be rejected rather than the server going
|
||||
# down with it - which the check at the end of this section would see, the
|
||||
# layout being unreadable from a server that is not there.
|
||||
big=$(awk 'BEGIN { while (i++ < 20000) printf "a" }')
|
||||
check_layout_fail '{"V":2,"L":{"t":"'"$big"'","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
|
||||
# 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}]}}'
|
||||
check_layout_fail '{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0}}'
|
||||
check_layout_fail '{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[]}}'
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'","c":[{"t":"p","w":80,"h":24,"x":0,"y":0,"i":1,"I":"'"$q1"'"}]}}'
|
||||
|
||||
# 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}}'
|
||||
# Only one cell may be the active pane.
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"a":true,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":12,"x":0,"y":12,"a":true,"i":1,"I":"'"$q1"'"}]}}'
|
||||
|
||||
# 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}}'
|
||||
# The same rejections apply whatever order the fields are written in: a leaf
|
||||
# with children when "c" comes first, a node with no children when "t" comes
|
||||
# last, a bad cell type when "t" comes last, and a bad pane id when "I" comes
|
||||
# first.
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"c":[{"t":"p","w":80,"h":24,"x":0,"y":0,"i":1,"I":"'"$q1"'"}],"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
check_layout_fail '{"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"v"}}'
|
||||
check_layout_fail '{"V":2,"L":{"w":80,"h":24,"x":0,"y":0,"t":"q"}}'
|
||||
check_layout_fail '{"V":2,"L":{"I":"0","i":0,"t":"p","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# A child that fails after a sibling has already been parsed and added to the
|
||||
# parent. This is the case the cleanup at the end of layout_parse_json_layout
|
||||
# exists for: the children built so far have to be freed along with the parent
|
||||
# that is never returned. The second child has no "y".
|
||||
check_layout_fail \
|
||||
'{"V":2,"L":{"c":[{"t":"p","w":80,"h":11,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":12,"x":0,"i":1,"I":"'"$q1"'"}],"t":"v","w":80,"h":24,"x":0,"y":0}}'
|
||||
|
||||
# No root cell at all. Every other rejection above comes from a cell that
|
||||
# failed to parse; this one is the check after the loop, reached when no "L"
|
||||
# was seen at all.
|
||||
check_layout_fail 'missing layout' '{"V":2}'
|
||||
# failed to parse; this one is the check for "L" itself.
|
||||
check_layout_fail '{"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}'
|
||||
# The wrong version, with "V" before and after "L". Fields are looked up by
|
||||
# key, so the version is read before the layout either way and the position of
|
||||
# "V" in the string makes no difference.
|
||||
check_layout_fail '{"V":1,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"}}'
|
||||
check_layout_fail '{"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"i":0,"I":"'"$q0"'"},"V":1}'
|
||||
|
||||
# None of that touched the layout.
|
||||
must_equal 'Layout after failures' "$(raw_layout L:two)" "$unchanged"
|
||||
@@ -612,12 +675,23 @@ must_equal 'Layout after failures' "$(raw_layout L:two)" "$unchanged"
|
||||
check_ok new-window -d -t L:3 -n float
|
||||
check_ok select-window -t L:float
|
||||
check_ok new-pane -d -x 20 -y 6 -X 8 -Y 3 'sleep 100'
|
||||
check_ok new-pane -d -x 30 -y 8 -X 30 -Y 10 'sleep 100'
|
||||
|
||||
# A floating cell is dumped with its z-index, which is what marks it as
|
||||
# floating when the layout is parsed back.
|
||||
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":'
|
||||
# floating when the layout is parsed back. Two of them, so that there is an
|
||||
# order between them to get wrong: the newer floating pane is in front, and a
|
||||
# cell's "z" is its place in that order counting from the front.
|
||||
floating=$(raw_layout L:float)
|
||||
must_contain 'Floating layout front z-index' "$floating" '"z":0'
|
||||
must_contain 'Floating layout back z-index' "$floating" '"z":1'
|
||||
|
||||
# Each floating cell goes in after the cell of the pane that was current when
|
||||
# it was made, which is the tiled pane both times, so the newer floating cell
|
||||
# is written before the older one while its pane comes after in the window.
|
||||
# The dump therefore has its cells in one order and their indexes in another,
|
||||
# and only comes back the same if the panes go by index.
|
||||
check_ok select-layout -t L:float "$floating"
|
||||
must_equal 'Floating layout after round trip' "$(raw_layout L:float)" "$floating"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Control mode notifications.
|
||||
@@ -633,7 +707,7 @@ must_contain 'Floating layout after round trip' "$(layout L:float)" '"z":'
|
||||
# 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
|
||||
DIR=$(mktemp -d) || fail 'Could not make a temporary directory'
|
||||
OLDIN="$DIR/old-in"
|
||||
OLDOUT="$DIR/old-out"
|
||||
NEWIN="$DIR/new-in"
|
||||
@@ -645,6 +719,7 @@ cleanup()
|
||||
{
|
||||
[ -n "$OLDPID" ] && kill "$OLDPID" 2>/dev/null
|
||||
[ -n "$NEWPID" ] && kill "$NEWPID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
@@ -665,7 +740,7 @@ wait_for()
|
||||
return 1
|
||||
}
|
||||
|
||||
mkfifo "$OLDIN" "$NEWIN" || exit 1
|
||||
mkfifo "$OLDIN" "$NEWIN" || fail 'Could not make the control client fifos'
|
||||
: >"$OLDOUT"
|
||||
: >"$NEWOUT"
|
||||
|
||||
@@ -709,7 +784,7 @@ wait_for "$OLDOUT" "%layout-change $wid $v1now $v1now " ||
|
||||
# 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}]}}'
|
||||
'{"V":2,"L":{"t":"v","w":80,"h":24,"x":0,"y":0,"c":[{"t":"p","w":80,"h":9,"x":0,"y":0,"i":0,"I":"'"$q0"'"},{"t":"p","w":80,"h":14,"x":0,"y":10,"i":1,"I":"'"$q1"'"}]}}'
|
||||
sleep 2
|
||||
n2=$(grep -c "%layout-change $wid " "$OLDOUT")
|
||||
must_equal 'Notifications for a v2 layout' "$((n2 - n1))" '1'
|
||||
|
||||
2
tmux.h
2
tmux.h
@@ -4277,7 +4277,7 @@ void hyperlinks_free(struct hyperlinks *);
|
||||
/* json.c */
|
||||
struct json_node *json_parse(const char *, char **);
|
||||
void json_destroy_node(struct json_node *);
|
||||
struct json_node *json_find(struct json_node *, const char *);
|
||||
struct json_node *json_find(const struct json_node *, const char *);
|
||||
struct json_node *json_array_first(const struct json_node *);
|
||||
struct json_node *json_array_next(const struct json_node *);
|
||||
int json_get_string(struct json_node *, const char **);
|
||||
|
||||
Reference in New Issue
Block a user