diff --git a/Makefile.am b/Makefile.am index cbdb4e863..60770fd1f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -174,6 +174,7 @@ dist_tmux_SOURCES = \ input-keys.c \ input.c \ job.c \ + json.c \ key-bindings.c \ key-string.c \ layout-custom.c \ diff --git a/json.c b/json.c new file mode 100644 index 000000000..f4772c667 --- /dev/null +++ b/json.c @@ -0,0 +1,623 @@ +/* + * Copyright (c) 2026 Dane Jensen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "tmux.h" + +#define TOKENS_MAX 4096 + +/* JSON Token types. */ +enum json_token_type { + TOK_OPENOBJECT, + TOK_CLOSEOBJECT, + TOK_OPENARRAY, + TOK_CLOSEARRAY, + TOK_COMMA, + TOK_COLON, + TOK_QUOTE, + TOK_VALUE, + TOK_EOF +}; + +/* JSON token. */ +struct json_token { + enum json_token_type type; + struct json_string val; +}; + +/* JSON tokens. */ +struct json_tokens { + struct json_token *toks; + int size; +}; + +static struct json_tokens *json_tokenize_input(const char *, char **); +static int json_tokenize_value(struct json_tokens *, const char *, + struct json_string *); +static struct json_tokens *json_create_tokens(void); +static void json_free_tokens(struct json_tokens *); +static int json_add_token(struct json_tokens *, + enum json_token_type, struct json_string *); +static const struct json_token *json_tokens_tail(struct json_tokens *); + +static struct json_node *json_create_node(struct json_node *, + enum json_node_type, struct json_string *, + 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 layout_parse_key(struct json_token **, + struct json_string *, char **); +static struct json_node *json_parse_object(struct json_token **, + struct json_node *, struct json_string *, char **); +static struct json_node *json_parse_array(struct json_token **, + struct json_node *, struct json_string *, char **); +static struct json_node *json_parse_string(struct json_token **, + struct json_node *, struct json_string *, char **); +static struct json_node *json_parse_number(struct json_token **, + struct json_node *, struct json_string *, char **); +static struct json_node *json_parse_boolean(struct json_token **, + struct json_node *, struct json_string *, char **); + +static int +json_error(char **cause, const char *reason, const char *input) +{ + return (xasprintf(cause, "%s: %.*s...", reason, 8, input)); +} + +/* Wrapper for string views with strcmp semantics. */ +static int +jstrcmp(const struct json_string *jstr, const char *s) +{ + size_t slen; + + slen = strlen(s); + if ((size_t)jstr->len < slen) + return (-1); + if ((size_t)jstr->len > slen) + return (1); + + return (memcmp(jstr->ptr, s, jstr->len)); +} + +/* Tokenize the json string. */ +static struct json_tokens * +json_tokenize_input(const char *input, char **cause) +{ + struct json_tokens *tokens = json_create_tokens(); + enum json_token_type type; + struct json_string jstr = { 0 }; + int scan; + + while (*input != '\0') { + if (tokens->size >= TOKENS_MAX) + goto fail; + switch (*input) { + case ' ': + case '\t': + case '\n': + case '\r': + input++; + continue; + case '{': + type = TOK_OPENOBJECT; + break; + case '}': + type = TOK_CLOSEOBJECT; + break; + case '[': + type = TOK_OPENARRAY; + break; + case ']': + type = TOK_CLOSEARRAY; + break; + case '"': + type = TOK_QUOTE; + break; + case ':': + type = TOK_COLON; + break; + case ',': + type = TOK_COMMA; + break; + default: + type = TOK_VALUE; + scan = json_tokenize_value(tokens, input, &jstr); + if (scan == -1) + goto fail; + input += scan - 1; + } + if (json_add_token(tokens, type, &jstr) != 0) + goto fail; + + jstr.ptr = NULL; + jstr.len = 0; + input++; + } + if (json_add_token(tokens, TOK_EOF, NULL) != 0) + goto fail; + + return (tokens); +fail: + json_error(cause, "tokenization error", input); + json_free_tokens(tokens); + return (NULL); +} + +/* + * Tokenize a value from the input string. Strings are terminated by a '"', and + * numbers/booleans are terminated by a ',', ']', or '}'. + */ +static int +json_tokenize_value(struct json_tokens *tokens, const char *input, + struct json_string *jstr) +{ + const struct json_token *prev = json_tokens_tail(tokens); + int scan = 0, escaping = 0; + + if (prev == NULL) + return (-1); + if (prev->type == TOK_QUOTE) { + do { + if (input[scan] == '\0') + return (-1); + if (input[scan] == '\\' && !escaping) + escaping = 1; + else if (escaping) + escaping = 0; + scan++; + } while (input[scan] != '"' && !escaping); + } else if (prev->type == TOK_COLON) { + do { + if (input[scan] == '\0') + return (-1); + scan++; + } while (input[scan] != ']' && input[scan] != '}' && + input[scan] != ',' && !isspace(input[scan])); + } else + return (-1); + + jstr->ptr = input; + jstr->len = scan; + + return (scan); +} + +/* Create a new token container. */ +static struct json_tokens * +json_create_tokens(void) +{ + struct json_tokens *tokens; + + tokens = xmalloc(sizeof *tokens); + tokens->size = 0; + tokens->toks = xmalloc(sizeof *tokens->toks * TOKENS_MAX); + + return (tokens); +} + +/* Free a token container. */ +static void +json_free_tokens(struct json_tokens *tokens) +{ + free(tokens->toks); + tokens->toks = NULL; + free(tokens); +} + +/* Add a token to tokens. */ +static int +json_add_token(struct json_tokens *tokens, enum json_token_type type, + struct json_string *jstr) +{ + struct json_token *tok; + + if (tokens->size >= TOKENS_MAX) + return (-1); + + tok = &tokens->toks[tokens->size++]; + tok->type = type; + if (jstr != NULL) { + tok->val.ptr = jstr->ptr; + tok->val.len = jstr->len; + } + + return (0); +} + +/* Returns a reference to the last token. */ +static const struct json_token * +json_tokens_tail(struct json_tokens *tokens) +{ + if (tokens->size == 0) + return (NULL); + return (&tokens->toks[tokens->size - 1]); +} + +/* Create a node and assign given values. */ +static struct json_node * +json_create_node(struct json_node *parent, enum json_node_type type, + struct json_string *key, const void *val) +{ + struct json_node *node; + + node = xcalloc(1, sizeof *node); + node->parent = parent; + if (key != NULL) { + node->key.ptr = key->ptr; + node->key.len = key->len; + } + node->type = type; + TAILQ_INIT(&node->val.fields); + if (val != NULL) + json_assign_value(node, val); + + return (node); +} + +/* Assign a value to a node. */ +static void +json_assign_value(struct json_node *node, const void *val) +{ + struct json_node *child; + struct json_string *jstr; + + switch (node->type) { + case NODE_STRING: + jstr = (struct json_string *)val; + node->val.jstr.ptr = jstr->ptr; + node->val.jstr.len = jstr->len; + break; + case NODE_NUMBER: + node->val.num = *(int64_t *)val; + break; + case NODE_BOOLEAN: + node->val.boolean = *(int *)val; + break; + case NODE_OBJECT: + case NODE_ARRAY: + child = (struct json_node *)val; + TAILQ_INSERT_TAIL(&node->val.fields, child, entry); + break; + default: + fatalx("unknown node type"); + } +} + +/* Destroy a node and all of the node's fields. */ +void +json_destroy_node(struct json_node *node) +{ + struct json_node *field; + + if (node == NULL) + return; + + switch (node->type) { + case NODE_STRING: + case NODE_NUMBER: + case NODE_BOOLEAN: + break; + case NODE_OBJECT: + case NODE_ARRAY: + while (!TAILQ_EMPTY(&node->val.fields)) { + field = TAILQ_FIRST(&node->val.fields); + TAILQ_REMOVE(&node->val.fields, field, entry); + json_destroy_node(field); + } + break; + } + + free(node); +} + +struct json_node * +json_parse(const char *input, char **cause) +{ + struct json_tokens *tokens; + + if ((tokens = json_tokenize_input(input, cause)) == NULL) + return (NULL); + + return (json_parse_tokens(&tokens, cause)); +} + +/* Parse a stream of tokens into nodes. Consumes the tokens. */ +static struct json_node * +json_parse_tokens(struct json_tokens **tokens, char **cause) +{ + struct json_token *toks = (*tokens)->toks; + struct json_node *layout; + + if (toks->type == TOK_OPENOBJECT) + layout = json_parse_object(&toks, NULL, NULL, cause); + else { + json_error(cause, "must start with object", toks->val.ptr); + goto fail; + } + + if (layout == NULL) + goto fail; + + if (toks->type != TOK_EOF) { + json_destroy_node(layout); + layout = NULL; + } + json_free_tokens(*tokens); + *tokens = NULL; + + return (layout); +fail: + json_free_tokens(*tokens); + *tokens = NULL; + return (NULL); +} + +/* Parse and return a key string, and advance the token pointer. */ +static int +layout_parse_key(struct json_token **toks, struct json_string *jstr, + char **cause) +{ + const char *start = (*toks)->val.ptr; + + if ((*toks)->type != TOK_QUOTE) + goto fail; + (*toks)++; + if ((*toks)->type != TOK_VALUE) + goto fail; + + jstr->ptr = (*toks)->val.ptr; + jstr->len = (*toks)->val.len; + + (*toks)++; + if ((*toks)->type != TOK_QUOTE) + goto fail; + + (*toks)++; + + return (0); +fail: + json_error(cause, "invalid key string", start); + return (-1); +} + +/* Parse an object value, return the node, and advance the token pointer. */ +static struct json_node * +json_parse_object(struct json_token **toks, struct json_node *parent, + struct json_string *key, char **cause) +{ + struct json_node *object, *field; + struct json_string fkey; + + if ((*toks)->type != TOK_OPENOBJECT) + return (NULL); + (*toks)++; + + object = json_create_node(parent, NODE_OBJECT, key, NULL); + while ((*toks)->type != TOK_CLOSEOBJECT) { + if (layout_parse_key(toks, &fkey, cause) != 0) + goto fail; + if ((*toks)->type != TOK_COLON) { + json_error(cause, "missing colon", (*toks)->val.ptr); + goto fail; + } + (*toks)++; + + switch ((*toks)->type) { + case TOK_QUOTE: + field = json_parse_string(toks, object, &fkey, cause); + break; + case TOK_VALUE: + if (jstrcmp(&(*toks)->val, "true") == 0 || + jstrcmp(&(*toks)->val, "false") == 0) { + field = json_parse_boolean(toks, object, + &fkey, cause); + } else { + field = json_parse_number(toks, object, + &fkey, cause); + } + break; + case TOK_OPENOBJECT: + field = json_parse_object(toks, object, &fkey, cause); + break; + case TOK_OPENARRAY: + field = json_parse_array(toks, object, &fkey, cause); + break; + default: + json_error(cause, "unsupported token for object", + (*toks)->val.ptr); + goto fail; + } + if (field == NULL) { + json_error(cause, "could not parse object value", + (*toks)->val.ptr); + goto fail; + } + json_assign_value(object, field); + + if ((*toks)->type == TOK_COMMA) { + if ((*toks)[1].type == TOK_CLOSEOBJECT) { + json_error(cause, "invalid json object", + (*toks)->val.ptr); + goto fail; + } + (*toks)++; + } else if ((*toks)->type != TOK_CLOSEOBJECT) { + json_error(cause, "invalid json object", + (*toks)->val.ptr); + goto fail; + } + } + (*toks)++; + return (object); +fail: + json_destroy_node(object); + return (NULL); +} + +/* Parse an array value, return the node, and advance the token pointer. */ +static struct json_node * +json_parse_array(struct json_token **toks, struct json_node *parent, + struct json_string *key, char **cause) +{ + struct json_node *array; + struct json_node *member; + + if ((*toks)->type != TOK_OPENARRAY) + return (NULL); + (*toks)++; + + array = json_create_node(parent, NODE_ARRAY, key, NULL); + while ((*toks)->type != TOK_CLOSEARRAY) { + switch ((*toks)->type) { + case TOK_OPENOBJECT: + member = json_parse_object(toks, array, NULL, cause); + break; + case TOK_COMMA: /* only objects */ + if ((*toks)[1].type != TOK_OPENOBJECT) { + json_error(cause, "only objects in arrays", + (*toks)->val.ptr); + goto fail; + } + (*toks)++; + continue; + default: + goto fail; + } + if (member == NULL) { + json_error(cause, "invalid json array", + (*toks)->val.ptr); + goto fail; + } + json_assign_value(array, member); + + if ((*toks)->type != TOK_COMMA && + (*toks)->type != TOK_CLOSEARRAY) { + json_error(cause, "invalid json array", + (*toks)->val.ptr); + goto fail; + } + } + (*toks)++; + return (array); +fail: + json_destroy_node(array); + return (NULL); +} + +/* Parse a string value, return the node, and advance the token pointer. */ +static struct json_node * +json_parse_string(struct json_token **toks, struct json_node *parent, + struct json_string *key, char **cause) +{ + struct json_string *val; + const char *start = (*toks)->val.ptr; + + if ((*toks)->type != TOK_QUOTE) + goto fail; + (*toks)++; + if ((*toks)->type != TOK_VALUE) + goto fail; + + val = &(*toks)->val; + (*toks)++; + + if ((*toks)->type != TOK_QUOTE) + goto fail; + (*toks)++; + + return (json_create_node(parent, NODE_STRING, key, val)); +fail: + json_error(cause, "invalid json string", start); + return (NULL); +} + +/* Parse a number value, return the node, and advance the token pointer. */ +static struct json_node * +json_parse_number(struct json_token **toks, struct json_node *parent, + struct json_string *key, char **cause) +{ + const char *numstr = (*toks)->val.ptr; + char *endptr; + int64_t val; + + errno = 0; + val = strtoll(numstr, &endptr, 10); + if (errno != 0 || endptr != numstr + (*toks)->val.len) + goto fail; + (*toks)++; + + return (json_create_node(parent, NODE_NUMBER, key, &val)); +fail: + json_error(cause, "invalid json number", numstr); + return (NULL); +} + +/* Parse a boolean value, return the node, and advance the token pointer. */ +static struct json_node * +json_parse_boolean(struct json_token **toks, struct json_node *parent, + struct json_string *key, char **cause) +{ + const char *start = (*toks)->val.ptr; + int val; + + if (jstrcmp(&(*toks)->val, "true") == 0) + val = 1; + else if (jstrcmp(&(*toks)->val, "false") == 0) + val = 0; + else + goto fail; + + (*toks)++; + + return (json_create_node(parent, NODE_BOOLEAN, key, &val)); +fail: + json_error(cause, "invalid json boolean", start); + return (NULL); +} + +/* Return 1 when the node's key is equal to the parameter. */ +int +json_key_is_eq(const struct json_node *field, const char *key) +{ + return (jstrcmp(&field->key, key) == 0); +} + +/* Return 1 when the node's value is equal to the parameter. */ +int +json_val_is_eq(const struct json_node *field, const void *val) +{ + switch (field->type) { + case NODE_STRING: + return (jstrcmp(&field->val.jstr, val) == 0); + case NODE_NUMBER: + return (field->val.num == *(int64_t *)val); + case NODE_BOOLEAN: + return (field->val.boolean == *(int *)val); + case NODE_OBJECT: + case NODE_ARRAY: + fatalx("unknown value comparison"); + } + return (0); +} diff --git a/layout-custom.c b/layout-custom.c index f66c07708..030494ac1 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -48,745 +48,83 @@ * "c": array of child cells * If the cell is a leaf cell (that is, containing a pane and no child cells), * it additionally has: - * "i": pane ID as %n + * "I": pane ID as %n * "l": index into last panes list, if not the active pane * "a": true if the active pane + * "i": pane index * "z": z-index, if a floating pane */ -/* Maximums */ -#define LAYOUT_STRING_MAX 8192 -#define TOKENS_MAX (LAYOUT_STRING_MAX) - /* Layout string. */ struct layout_string { char *write; - char dat[LAYOUT_STRING_MAX]; + char dat[8192]; }; -/* Layout string view. */ -struct layout_string_view { - const char *ptr; - int len; +struct layout_parse_cell_ctx { + struct layout_cell *lc; + int active; + int last; + int id; + int index; + int zindex; }; -/* Token types. */ -enum layout_token_type { - TOK_OPENOBJECT, - TOK_CLOSEOBJECT, - TOK_OPENARRAY, - TOK_CLOSEARRAY, - TOK_COMMA, - TOK_COLON, - TOK_QUOTE, - TOK_VALUE, - TOK_EOF +struct layout_parse_ctx { + int version; + struct layout_cell *root; + char **cause; + +#define CCTX_MAX 512 + int clen; + struct layout_parse_cell_ctx cctxs[CCTX_MAX]; }; -/* Layout token. */ -struct layout_token { - enum layout_token_type type; - struct layout_string_view val; -}; +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_parse_apply_ctx(struct window *, + struct layout_parse_ctx *); +static struct layout_cell *layout_parse_json_layout(struct json_node *, + struct layout_cell *, + struct layout_parse_ctx *); -/* Layout tokens. */ -struct layout_tokens { - struct layout_token *toks; - int size; -}; - -/* Node type. */ -enum layout_node_type { - NODE_STRING, - NODE_NUMBER, - NODE_BOOLEAN, - NODE_OBJECT, - NODE_ARRAY -}; - -/* Node queue. */ -TAILQ_HEAD(layout_nodes, layout_node); - -/* Node. */ -struct layout_node { - struct layout_node *parent; - struct layout_string_view key; - enum layout_node_type type; - union { - struct layout_string_view lsv; - int64_t num; - int bool; - struct layout_nodes fields; - } val; - TAILQ_ENTRY(layout_node) entry; -}; - -static struct layout_tokens *layout_tokenize_input(const char *); -static int layout_tokenize_value(struct layout_tokens *, - const char *, struct layout_string_view *); -static struct layout_tokens *layout_tokens_create(void); -static void layout_tokens_destroy(struct layout_tokens *); -static int layout_tokens_push(struct layout_tokens *, - enum layout_token_type, - struct layout_string_view *); -static const struct layout_token *layout_tokens_last(struct layout_tokens *); -static struct layout_node *layout_node_create(struct layout_node *, - enum layout_node_type, - struct layout_string_view *, const void *); -static void layout_node_destroy(struct layout_node *); -static void layout_node_assign(struct layout_node *, - const void *); -static struct layout_node *layout_parse_layout_tokens(struct layout_tokens **); -static int layout_parse_key(struct layout_token **, - struct layout_string_view *); -static struct layout_node *layout_parse_object(struct layout_token **, - struct layout_node *, - struct layout_string_view *); -static struct layout_node *layout_parse_array(struct layout_token **, - struct layout_node *, - struct layout_string_view *); -static struct layout_node *layout_parse_string(struct layout_token **, - struct layout_node *, - struct layout_string_view *); -static struct layout_node *layout_parse_number(struct layout_token **, - struct layout_node *, - struct layout_string_view *); -static struct layout_node *layout_parse_boolean(struct layout_token **, - struct layout_node *, - struct layout_string_view *); -static int layout_key_is_eq(const struct layout_node *, - const char *); -static int layout_val_is_eq(const struct layout_node *, - const void *); -static struct layout_cell *layout_evaluate_nodes(struct layout_node *, - int); -static struct layout_cell *layout_evaluate_layout(struct layout_node *, - struct layout_cell *); - -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_construct(const char *, char **); -static void layout_assign(struct window_pane **, - struct layout_cell *); - -/* Wrapper for string views with strcmp semantics. */ +/* Compare cell contexts in descending order of index. */ static int -lsvcmp(const struct layout_string_view *lsv, const char *s) +layout_parse_index_cmp(const void *a, const void *b) { - int len; + const struct layout_parse_cell_ctx *cca = a; + const struct layout_parse_cell_ctx *ccb = b; - len = lsv->len - (int)strlen(s); - if (len < 0) - return (-1); - if (len > 0) - return (1); - - return (memcmp(lsv->ptr, s, lsv->len)); + return (ccb->index - cca->index); } -/* Tokenize the layout string. */ -static struct layout_tokens * -layout_tokenize_input(const char *input) +/* Compare cell contexts in descending order of z-index. */ +static int +layout_parse_zindex_cmp(const void *a, const void *b) { - struct layout_tokens *tokens = layout_tokens_create(); - enum layout_token_type type; - struct layout_string_view lsv = { 0 }; - int scan; + const struct layout_parse_cell_ctx *cca = a; + const struct layout_parse_cell_ctx *ccb = b; - while (*input != '\0') { - if (tokens->size >= TOKENS_MAX) - goto fail; - switch (*input) { - case ' ': - case '\t': - case '\n': - case '\r': - input++; - continue; - case '{': - type = TOK_OPENOBJECT; - break; - case '}': - type = TOK_CLOSEOBJECT; - break; - case '[': - type = TOK_OPENARRAY; - break; - case ']': - type = TOK_CLOSEARRAY; - break; - case '"': - type = TOK_QUOTE; - break; - case ':': - type = TOK_COLON; - break; - case ',': - type = TOK_COMMA; - break; - default: - type = TOK_VALUE; - scan = layout_tokenize_value(tokens, input, &lsv); - if (scan == -1) - goto fail; - input += scan - 1; - } - if (layout_tokens_push(tokens, type, &lsv) != 0) - goto fail; - - lsv.ptr = NULL; - lsv.len = 0; - input++; - } - if (layout_tokens_push(tokens, TOK_EOF, NULL) != 0) - goto fail; - - return (tokens); -fail: - layout_tokens_destroy(tokens); - return (NULL); + return (ccb->zindex - cca->zindex); } /* - * Tokenize a value from the input string. Strings are terminated by a '"', and - * numbers/booleans are terminated by a ',', ']', or '}'. + * Compare cell contexts in descending order of z-index. The active pane has + * index of -1. */ static int -layout_tokenize_value(struct layout_tokens *tokens, const char *input, - struct layout_string_view *lsv) +layout_parse_last_cmp(const void *a, const void *b) { - const struct layout_token *prev = layout_tokens_last(tokens); - int scan = 0; + const struct layout_parse_cell_ctx *cca = a; + const struct layout_parse_cell_ctx *ccb = b; - if (prev == NULL) - return (-1); - if (prev->type == TOK_QUOTE) { - do { - if (input[scan] == '\0') - return (-1); - scan++; - } while (input[scan] != '"' || input[scan - 1] == '\\'); - } else if (prev->type == TOK_COLON) { - do { - if (input[scan] == '\0') - return (-1); - scan++; - } while (input[scan] != ']' && input[scan] != '}' && - input[scan] != ',' && !isspace(input[scan])); - } else - return (-1); - - lsv->ptr = input; - lsv->len = scan; - - return (scan); -} - -/* Create a new token container. */ -static struct layout_tokens * -layout_tokens_create(void) -{ - struct layout_tokens *tokens; - - tokens = xmalloc(sizeof *tokens); - tokens->size = 0; - tokens->toks = xmalloc(sizeof *tokens->toks * TOKENS_MAX); - - return (tokens); -} - -/* - * Destroy a token container, freeing remaining memory in the case of a parsing - * failure. - */ -static void -layout_tokens_destroy(struct layout_tokens *tokens) -{ - free(tokens->toks); - tokens->toks = NULL; - free(tokens); -} - -/* Add a token to the token container. */ -static int -layout_tokens_push(struct layout_tokens *tokens, enum layout_token_type type, - struct layout_string_view *lsv) -{ - struct layout_token *tok; - - if (tokens->size >= TOKENS_MAX) - return (-1); - - tok = &tokens->toks[tokens->size++]; - tok->type = type; - if (lsv != NULL) { - tok->val.ptr = lsv->ptr; - tok->val.len = lsv->len; - } - - return (0); -} - -/* Returns a reference to the last token. */ -static const struct layout_token * -layout_tokens_last(struct layout_tokens *tokens) -{ - if (tokens->size == 0) - return (NULL); - return (&tokens->toks[tokens->size - 1]); -} - -/* Create a node and assign given values. */ -static struct layout_node * -layout_node_create(struct layout_node *parent, enum layout_node_type type, - struct layout_string_view *key, const void *val) -{ - struct layout_node *node; - - node = xcalloc(1, sizeof *node); - node->parent = parent; - if (key != NULL) { - node->key.ptr = key->ptr; - node->key.len = key->len; - } - node->type = type; - TAILQ_INIT(&node->val.fields); - if (val != NULL) - layout_node_assign(node, val); - - return (node); -} - -/* Assign a value to a node. */ -static void -layout_node_assign(struct layout_node *node, const void *val) -{ - struct layout_node *child; - struct layout_string_view *lsv; - - switch (node->type) { - case NODE_STRING: - lsv = (struct layout_string_view *)val; - node->val.lsv.ptr = lsv->ptr; - node->val.lsv.len = lsv->len; - break; - case NODE_NUMBER: - node->val.num = *(int64_t *)val; - break; - case NODE_BOOLEAN: - node->val.bool = *(int *)val; - break; - case NODE_OBJECT: - case NODE_ARRAY: - child = (struct layout_node *)val; - TAILQ_INSERT_TAIL(&node->val.fields, child, entry); - break; - default: - fatalx("unknown node type"); - } -} - -/* Destroy a node and all of the node's fields. */ -static void -layout_node_destroy(struct layout_node *node) -{ - struct layout_node *field; - - if (node == NULL) - return; - - switch (node->type) { - case NODE_STRING: - case NODE_NUMBER: - case NODE_BOOLEAN: - break; - case NODE_OBJECT: - case NODE_ARRAY: - while (!TAILQ_EMPTY(&node->val.fields)) { - field = TAILQ_FIRST(&node->val.fields); - TAILQ_REMOVE(&node->val.fields, field, entry); - layout_node_destroy(field); - } - break; - } - - free(node); -} - -/* Parse a stream of tokens into nodes. Consumes the tokens. */ -static struct layout_node * -layout_parse_layout_tokens(struct layout_tokens **tokens) -{ - struct layout_token *toks = (*tokens)->toks; - struct layout_node *layout; - - if (toks->type == TOK_OPENOBJECT) - layout = layout_parse_object(&toks, NULL, NULL); - else - goto fail; - - if (layout == NULL) - goto fail; - - if (toks->type != TOK_EOF) { - layout_node_destroy(layout); - layout = NULL; - } - layout_tokens_destroy(*tokens); - *tokens = NULL; - - return (layout); -fail: - layout_tokens_destroy(*tokens); - *tokens = NULL; - return (NULL); -} - -/* Parse and return a key string, and advance the token pointer. */ -static int -layout_parse_key(struct layout_token **toks, struct layout_string_view *lsv) -{ - if ((*toks)->type != TOK_QUOTE) - return (-1); - (*toks)++; - if ((*toks)->type != TOK_VALUE) - return (-1); - - lsv->ptr = (*toks)->val.ptr; - lsv->len = (*toks)->val.len; - - (*toks)++; - if ((*toks)->type != TOK_QUOTE) - return (-1); - - (*toks)++; - - return (0); -} - -/* Parse an object value, return the node, and advance the token pointer. */ -static struct layout_node * -layout_parse_object(struct layout_token **toks, struct layout_node *parent, - struct layout_string_view *key) -{ - struct layout_node *object, *field; - struct layout_string_view fkey; - - if ((*toks)->type != TOK_OPENOBJECT) - return (NULL); - (*toks)++; - - object = layout_node_create(parent, NODE_OBJECT, key, NULL); - while ((*toks)->type != TOK_CLOSEOBJECT) { - if (layout_parse_key(toks, &fkey) != 0) - goto fail; - if ((*toks)->type != TOK_COLON) - goto fail; - (*toks)++; - - switch ((*toks)->type) { - case TOK_QUOTE: - field = layout_parse_string(toks, object, &fkey); - break; - case TOK_VALUE: - if (lsvcmp(&(*toks)->val, "true") == 0 || - lsvcmp(&(*toks)->val, "false") == 0) { - field = layout_parse_boolean(toks, object, - &fkey); - } else { - field = layout_parse_number(toks, object, - &fkey); - } - break; - case TOK_OPENOBJECT: - field = layout_parse_object(toks, object, &fkey); - break; - case TOK_OPENARRAY: - field = layout_parse_array(toks, object, &fkey); - break; - default: - goto fail; - } - if (field == NULL) - goto fail; - layout_node_assign(object, field); - - if ((*toks)->type == TOK_COMMA) { - if ((*toks)[1].type == TOK_CLOSEOBJECT) - goto fail; - (*toks)++; - } else if ((*toks)->type != TOK_CLOSEOBJECT) - goto fail; - } - (*toks)++; - return (object); -fail: - layout_node_destroy(object); - return (NULL); -} - -/* Parse an array value, return the node, and advance the token pointer. */ -static struct layout_node * -layout_parse_array(struct layout_token **toks, struct layout_node *parent, - struct layout_string_view *key) -{ - struct layout_node *array; - struct layout_node *member; - - if ((*toks)->type != TOK_OPENARRAY) - return (NULL); - (*toks)++; - - array = layout_node_create(parent, NODE_ARRAY, key, NULL); - while ((*toks)->type != TOK_CLOSEARRAY) { - switch ((*toks)->type) { - case TOK_OPENOBJECT: - member = layout_parse_object(toks, array, NULL); - break; - case TOK_COMMA: - if ((*toks)[1].type != TOK_OPENOBJECT || - (*toks)[-1].type != TOK_CLOSEOBJECT) - goto fail; - (*toks)++; - continue; - default: - goto fail; - } - if (member == NULL) - goto fail; - layout_node_assign(array, member); - - if ((*toks)->type != TOK_COMMA && - (*toks)->type != TOK_CLOSEARRAY) - goto fail; - } - (*toks)++; - return (array); -fail: - layout_node_destroy(array); - return (NULL); -} - -/* Parse a string value, return the node, and advance the token pointer. */ -static struct layout_node * -layout_parse_string(struct layout_token **toks, struct layout_node *parent, - struct layout_string_view *key) -{ - struct layout_string_view *val; - - if ((*toks)->type != TOK_QUOTE) - return (NULL); - (*toks)++; - if ((*toks)->type != TOK_VALUE) - return (NULL); - - val = &(*toks)->val; - (*toks)++; - - if ((*toks)->type != TOK_QUOTE) - return (NULL); - (*toks)++; - - return (layout_node_create(parent, NODE_STRING, key, val)); -} - -/* Parse a number value, return the node, and advance the token pointer. */ -static struct layout_node * -layout_parse_number(struct layout_token **toks, struct layout_node *parent, - struct layout_string_view *key) -{ - const char *numstr = (*toks)->val.ptr; - char *endptr; - int64_t val; - - errno = 0; - val = strtoll(numstr, &endptr, 10); - if (errno != 0 || endptr != numstr + (*toks)->val.len) - return (NULL); - (*toks)++; - - return (layout_node_create(parent, NODE_NUMBER, key, &val)); -} - -/* Parse a boolean value, return the node, and advance the token pointer. */ -static struct layout_node * -layout_parse_boolean(struct layout_token **toks, struct layout_node *parent, - struct layout_string_view *key) -{ - int val; - - if (lsvcmp(&(*toks)->val, "true") == 0) - val = 1; - else if (lsvcmp(&(*toks)->val, "false") == 0) - val = 0; - else - return (NULL); - - (*toks)++; - - return (layout_node_create(parent, NODE_BOOLEAN, key, &val)); -} - -/* Return 1 when the node's key is equal to the parameter. */ -static int -layout_key_is_eq(const struct layout_node *field, const char *key) -{ - return (lsvcmp(&field->key, key) == 0); -} - -/* Return 1 when the node's value is equal to the parameter. */ -static int -layout_val_is_eq(const struct layout_node *field, const void *val) -{ - switch (field->type) { - case NODE_STRING: - return (lsvcmp(&field->val.lsv, val) == 0); - case NODE_NUMBER: - return (field->val.num == *(int64_t *)val); - case NODE_BOOLEAN: - return (field->val.bool == *(int *)val); - case NODE_OBJECT: - case NODE_ARRAY: - fatalx("unknown value comparison"); - } - return (0); -} - -/* - * Evaluate parsed nodes. Check metadata at the top level and return the new - * layout root. Consumes the layout_node tree. - */ -static struct layout_cell * -layout_evaluate_nodes(struct layout_node *root, int version) -{ - struct layout_node *field; - struct layout_cell *lcroot = NULL; - int ver = -1; - - TAILQ_FOREACH(field, &root->val.fields, entry) { - switch (field->type) { - case NODE_NUMBER: - if (layout_key_is_eq(field, "V")) - ver = field->val.num; - break; - case NODE_OBJECT: - if (layout_key_is_eq(field, "L")) { - if (lcroot != NULL) - goto fail; - lcroot = layout_evaluate_layout(field, NULL); - if (lcroot == NULL) - goto fail; - } - break; - default: - break; - } - } - if (ver != version) - goto fail; - if (lcroot == NULL) - goto fail; - layout_node_destroy(root); - - return (lcroot); -fail: - layout_node_destroy(root); - if (lcroot != NULL) - layout_free_cell(lcroot, 0); - return (NULL); -} - -/* Evaluate nodes into layout cells. */ -static struct layout_cell * -layout_evaluate_layout(struct layout_node *node, struct layout_cell *lcparent) -{ - struct layout_node *field, *fieldc; - struct layout_cell *lc = layout_create_cell(lcparent), *lcchild; - enum layout_type type = LAYOUT_WINDOWPANE; - const char *numstr; - u_int sx = UINT_MAX, sy = UINT_MAX; - int xoff = INT_MAX, yoff = INT_MAX; - __unused int id, last, active, zindex; - - TAILQ_FOREACH(field, &node->val.fields, entry) { - switch (field->type) { - case NODE_NUMBER: - if (layout_key_is_eq(field, "w")) - sx = field->val.num; - else if (layout_key_is_eq(field, "h")) - sy = field->val.num; - else if (layout_key_is_eq(field, "x")) - xoff = field->val.num; - else if (layout_key_is_eq(field, "y")) - yoff = field->val.num; - else if (layout_key_is_eq(field, "l")) - last = field->val.num; /* unused */ - else if (layout_key_is_eq(field, "z")) { - zindex = field->val.num; /* unused */ - lc->flags |= LAYOUT_CELL_FLOATING; - } - break; - case NODE_BOOLEAN: - if (layout_key_is_eq(field, "a")) - active = field->val.bool; /* unused */ - break; - case NODE_STRING: - if (layout_key_is_eq(field, "t")) { - if (layout_val_is_eq(field, "h")) - type = LAYOUT_LEFTRIGHT; - else if (layout_val_is_eq(field, "v")) - type = LAYOUT_TOPBOTTOM; - else if (layout_val_is_eq(field, "p")) - type = LAYOUT_WINDOWPANE; - else - goto fail; - lc->type = type; - } else if (layout_key_is_eq(field, "i")) { - errno = 0; - numstr = field->val.lsv.ptr; - if (*numstr != '%') - goto fail; - id = strtol(numstr + 1, NULL, 10); - if (errno != 0) - goto fail; - } - break; - case NODE_ARRAY: - if (!layout_key_is_eq(field, "c")) - break; - TAILQ_FOREACH(fieldc, &field->val.fields, entry) { - lcchild = layout_evaluate_layout(fieldc, lc); - if (lcchild == NULL) - goto fail; - TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry); - } - break; - default: - break; - } - } - if (type == LAYOUT_WINDOWPANE && !TAILQ_EMPTY(&lc->cells)) - goto fail; - if (type != LAYOUT_WINDOWPANE && TAILQ_EMPTY(&lc->cells)) - goto fail; - - if (sx == UINT_MAX || sy == UINT_MAX || xoff == INT_MAX || - yoff == INT_MAX) - goto fail; - layout_set_size(lc, sx, sy, xoff, yoff); - return (lc); -fail: - /* - * Ensure the children are freed if the child field is parsed and fails - * before the layout type is set. - */ - if (type == LAYOUT_WINDOWPANE && !TAILQ_EMPTY(&lc->cells)) - lc->type = LAYOUT_TOPBOTTOM; - - layout_free_cell(lc, 0); - return (NULL); + return (ccb->last - cca->last); } /* Initialize a layout string. */ @@ -816,6 +154,56 @@ layout_string_write(struct layout_string *ls, const char *fmt, ...) return (0); } +static void +layout_parse_init_ctx(struct layout_parse_ctx *pctx, char **cause) +{ + pctx->version = -1; + pctx->root = NULL; + pctx->cause = cause; + pctx->clen = 0; +} + +/* Add a cell context to the parse context. */ +static int +layout_parse_add_cctx(struct layout_parse_ctx *pctx, struct layout_cell *lc, + int active, int last, int id, int index, int zindex) +{ + struct layout_parse_cell_ctx *cctx; + + if (pctx->clen == CCTX_MAX) + return (-1); + cctx = &pctx->cctxs[pctx->clen++]; + + cctx->lc = lc; + cctx->active = active; + cctx->last = last; + cctx->id = id; + cctx->index = index; + cctx->zindex = zindex; + + return (0); +} + +/* Remove a cell context from the parse context. Does not preserve ordering. */ +static int +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]; + memcpy(&pctx->cctxs[i], cctx, sizeof *cctx); + return (0); + } + } + return (-1); +} + /* Find the bottom-right cell. */ static struct layout_cell * layout_find_bottomright(struct layout_cell *lc) @@ -910,12 +298,16 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls) if (layout_string_write(ls, ",\"a\":true") != 0) return (-1); } else { - if (window_pane_last_index(wp, &i) != -1) { + if (window_pane_last_index(wp, &i) == 0) { if (layout_string_write(ls, ",\"l\":%u", i) != 0) return (-1); } } + if (window_pane_index(wp, &i) != -1) { + 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 (layout_string_write(ls, ",\"z\":%u", i) @@ -923,7 +315,7 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls) return (-1); } } - if (layout_string_write(ls, ",\"i\":\"%%%u\"", wp->id) != 0) + if (layout_string_write(ls, ",\"I\":\"%%%u\"", wp->id) != 0) return (-1); } @@ -1041,13 +433,17 @@ int layout_parse(struct window *w, const char *layout, char **cause) { struct layout_cell *lcchild, *lc = NULL; - struct window_pane *wp; + struct layout_parse_ctx pctx; u_int npanes, ncells, sx = 0, sy = 0; /* Build the layout. */ - lc = layout_construct(layout, cause); - if (lc == NULL) + layout_parse_init_ctx(&pctx, cause); + if (layout_construct(layout, &pctx) != 0) { + if (pctx.root != NULL) + 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); @@ -1066,6 +462,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) + goto fail; layout_destroy_cell(w, lcchild, &lc); } @@ -1118,11 +516,10 @@ layout_parse(struct window *w, const char *layout, char **cause) w->layout_root = lc; /* Assign the panes into the cells. */ - wp = TAILQ_FIRST(&w->panes); - layout_assign(&wp, lc); + layout_assign(w, &pctx); + layout_parse_apply_ctx(w, &pctx); /* Update pane offsets and sizes. */ - layout_fix_zindexes(w); layout_fix_offsets(w); layout_fix_panes(w, NULL); recalculate_sizes(); @@ -1137,9 +534,31 @@ fail: return (-1); } -/* Assign panes into cells. */ +/* Assign panes into cells from the cell contexts. */ static void -layout_assign(struct window_pane **wp, struct layout_cell *lc) +layout_assign_from_ctx(struct window *w, struct layout_parse_ctx *pctx) +{ + struct layout_cell *lc; + struct window_pane *wp; + int i; + + qsort(pctx->cctxs, pctx->clen, sizeof pctx->cctxs[0], + layout_parse_index_cmp); + + wp = TAILQ_FIRST(&w->panes); + for (i = 0; i < pctx->clen; i++) { + lc = pctx->cctxs[i].lc; + layout_make_leaf(lc, wp); + wp = TAILQ_NEXT(wp, entry); + } +} + +/* + * Assign panes into cells when there are no cell contexts. This will be removed + * when the non-JSON format is deprecated. + */ +static void +layout_assign_fallback(struct window_pane **wp, struct layout_cell *lc) { struct layout_cell *lcchild; @@ -1154,11 +573,22 @@ layout_assign(struct window_pane **wp, struct layout_cell *lc) case LAYOUT_LEFTRIGHT: case LAYOUT_TOPBOTTOM: TAILQ_FOREACH(lcchild, &lc->cells, entry) - layout_assign(wp, lcchild); + layout_assign_fallback(wp, lcchild); return; } } +/* Assign panes into cells. Number of cells must match the number of panes. */ +static void +layout_assign(struct window *w, struct layout_parse_ctx *pctx) +{ + struct window_pane *wp = NULL; + + if (pctx->clen > 0) + return (layout_assign_from_ctx(w, pctx)); + return (layout_assign_fallback(&wp, pctx->root)); +} + /* Construct a cell from the legacy (v1) format. */ static struct layout_cell * layout_construct_cell(struct layout_cell *lcparent, const char **layout) @@ -1263,13 +693,160 @@ fail: return (NULL); } -/* Construct a layout root from a formatted string. */ -static struct layout_cell * -layout_construct(const char *layout, char **cause) +/* + * Evaluate parsed JSON. Check metadata at the top level and return the new + * layout root. Consumes json input. + */ +static int +layout_parse_json(struct json_node *json, struct layout_parse_ctx *pctx) { - struct layout_cell *lc; - struct layout_tokens *tokens = NULL; - struct layout_node *node = NULL; + struct json_node *field; + + TAILQ_FOREACH(field, &json->val.fields, entry) { + switch (field->type) { + case NODE_NUMBER: + if (json_key_is_eq(field, "V")) + pctx->version = field->val.num; + break; + case NODE_OBJECT: + if (json_key_is_eq(field, "L")) { + if (pctx->root != NULL) + goto fail; + pctx->root = layout_parse_json_layout(field, + NULL, pctx); + if (pctx->root== NULL) + goto fail; + } + break; + default: + break; + } + } + if (pctx->root == NULL) + goto fail; + json_destroy_node(json); + + return (0); +fail: + json_destroy_node(json); + if (pctx->root != NULL) + layout_free_cell(pctx->root, 0); + return (-1); +} + +/* Evaluate nodes into layout cells. */ +static struct layout_cell * +layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent, + struct layout_parse_ctx *pctx) +{ + struct json_node *field, *fieldc; + struct layout_cell *lc = layout_create_cell(lcparent), *lcchild; + enum layout_type type = LAYOUT_WINDOWPANE; + const char *numstr; + char *endptr; + u_int sx = UINT_MAX, sy = UINT_MAX; + int xoff = INT_MAX, yoff = INT_MAX; + int active = -1, last = -1, id = -1, index = -1; + int zindex = INT_MAX, numlen; + + TAILQ_FOREACH(field, &node->val.fields, entry) { + switch (field->type) { + case NODE_NUMBER: + if (json_key_is_eq(field, "w")) + sx = field->val.num; + else if (json_key_is_eq(field, "h")) + sy = field->val.num; + else if (json_key_is_eq(field, "x")) + xoff = field->val.num; + else if (json_key_is_eq(field, "y")) + yoff = field->val.num; + else if (json_key_is_eq(field, "l")) + last = field->val.num; + else if (json_key_is_eq(field, "z")) { + zindex = field->val.num; + lc->flags |= LAYOUT_CELL_FLOATING; + } + break; + case NODE_BOOLEAN: + if (json_key_is_eq(field, "a")) + active = field->val.boolean; + break; + case NODE_STRING: + if (json_key_is_eq(field, "t")) { + if (json_val_is_eq(field, "h")) + type = LAYOUT_LEFTRIGHT; + else if (json_val_is_eq(field, "v")) + type = LAYOUT_TOPBOTTOM; + else if (json_val_is_eq(field, "p")) + type = LAYOUT_WINDOWPANE; + else + 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 != '%') + 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) + goto fail; + } + break; + case NODE_ARRAY: + if (!json_key_is_eq(field, "c")) + break; + TAILQ_FOREACH(fieldc, &field->val.fields, entry) { + lcchild = layout_parse_json_layout(fieldc, lc, + pctx); + if (lcchild == NULL) + goto fail; + TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry); + } + break; + default: + break; + } + } + if (type == LAYOUT_WINDOWPANE && !TAILQ_EMPTY(&lc->cells)) + goto fail; + if (type != LAYOUT_WINDOWPANE && TAILQ_EMPTY(&lc->cells)) + goto fail; + + if (sx == UINT_MAX || sy == UINT_MAX || xoff == INT_MAX || + yoff == INT_MAX) + goto fail; + + layout_set_size(lc, sx, sy, xoff, yoff); + if (lc->type == LAYOUT_WINDOWPANE) + layout_parse_add_cctx(pctx, lc, active, last, id, index, + zindex); + return (lc); +fail: + /* + * Ensure the children are freed if the child field is parsed and fails + * before the layout type is set. + */ + if (type == LAYOUT_WINDOWPANE && !TAILQ_EMPTY(&lc->cells)) + lc->type = LAYOUT_TOPBOTTOM; + + layout_free_cell(lc, 0); + return (NULL); +} + + +/* Construct a layout root from a formatted string. */ +static int +layout_construct(const char *layout, struct layout_parse_ctx *pctx) +{ + struct json_node *json; u_short csum; int n; @@ -1278,28 +855,66 @@ layout_construct(const char *layout, char **cause) if (*layout != '{') { /* sniffing version */ if (sscanf(layout, "%hx,%n", &csum, &n) != 1 || n != 5) { - *cause = xstrdup("malformed layout header"); - return (NULL); + *pctx->cause = xstrdup("malformed layout header"); + return (-1); } layout += n; if (csum != layout_checksum(layout)) { - *cause = xstrdup("invalid layout checksum"); - return (NULL); + *pctx->cause = xstrdup("invalid layout checksum"); + return (-1); } - lc = layout_construct_v1(NULL, &layout); + pctx->root = layout_construct_v1(NULL, &layout); } else { - if ((tokens = layout_tokenize_input(layout)) == NULL) { - *cause = xstrdup("invalid layout characters"); - return (NULL); - } - if ((node = layout_parse_layout_tokens(&tokens)) == NULL) { - *cause = xstrdup("invalid layout json"); - return (NULL); - } - lc = layout_evaluate_nodes(node, 2); + if ((json = json_parse(layout, pctx->cause)) == NULL) + return (-1); + if (layout_parse_json(json, pctx) != 0) + return (-1); + if (pctx->version != 2) + return (-1); } - if (lc == NULL) - *cause = xstrdup("invalid layout"); - return (lc); + return (0); +} + +/* Apply the remaining context to the layout. */ +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; + + /* Apply z-indexes. */ + while (!TAILQ_EMPTY(&w->z_index)) { + wp = TAILQ_FIRST(&w->z_index); + TAILQ_REMOVE(&w->z_index, wp, zentry); + } + + qsort(pctx->cctxs, pctx->clen, sizeof pctx->cctxs[0], + layout_parse_zindex_cmp); + + for (i = 0; i < pctx->clen; i++) { + cctx = &pctx->cctxs[i]; + wp = cctx->lc->wp; + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); + } + + /* Apply active/last. */ + while (!TAILQ_EMPTY(&w->last_panes)) { + wp = TAILQ_FIRST(&w->last_panes); + TAILQ_REMOVE(&w->last_panes, wp, sentry); + } + + qsort(pctx->cctxs, pctx->clen, sizeof pctx->cctxs[0], + layout_parse_last_cmp); + + 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); + continue; + } + TAILQ_INSERT_HEAD(&w->last_panes, wp, sentry); + } } diff --git a/tmux.h b/tmux.h index 8aed11c2d..b319976ee 100644 --- a/tmux.h +++ b/tmux.h @@ -1561,6 +1561,38 @@ struct layout_cell { TAILQ_ENTRY(layout_cell) entry; }; +/* JSON string view. */ +struct json_string { + const char *ptr; + int len; +}; + +/* JSON node type. */ +enum json_node_type { + NODE_STRING, + NODE_NUMBER, + NODE_BOOLEAN, + NODE_OBJECT, + NODE_ARRAY +}; + +/* JSON node queue. */ +TAILQ_HEAD(json_nodes, json_node); + +/* JSON node. */ +struct json_node { + struct json_node *parent; + struct json_string key; + enum json_node_type type; + union { + struct json_string jstr; + int64_t num; + int boolean; + struct json_nodes fields; + } val; + TAILQ_ENTRY(json_node) entry; +}; + /* Environment variable. */ struct environ_entry { char *name; @@ -4220,4 +4252,10 @@ struct hyperlinks *hyperlinks_copy(struct hyperlinks *); void hyperlinks_reset(struct hyperlinks *); void hyperlinks_free(struct hyperlinks *); +/* json.c */ +struct json_node *json_parse(const char *, char **); +void json_destroy_node(struct json_node *); +int json_key_is_eq(const struct json_node *, const char *); +int json_val_is_eq(const struct json_node *, const void *); + #endif /* TMUX_H */ diff --git a/window.c b/window.c index 9c184dc00..d7be4b840 100644 --- a/window.c +++ b/window.c @@ -1197,7 +1197,7 @@ window_pane_last_index(struct window_pane *wp, u_int *i) struct window_pane *wq; *i = 0; - TAILQ_FOREACH(wq, &w->last_panes, zentry) { + TAILQ_FOREACH(wq, &w->last_panes, sentry) { if (wq == wp) { return (0); }