From a7843a2a7e3d030d12d98cec65a49d390d29fd7d Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Fri, 24 Jul 2026 22:35:22 -0700 Subject: [PATCH] Bugfixes and documentation updates. --- cmd-select-layout.c | 2 +- control-notify.c | 16 +++-- json.c | 143 +++++++++++++++++++++++++------------------- layout-custom.c | 14 +++-- tmux.1 | 7 ++- 5 files changed, 107 insertions(+), 75 deletions(-) diff --git a/cmd-select-layout.c b/cmd-select-layout.c index 93cdb413b..56d4fc8aa 100644 --- a/cmd-select-layout.c +++ b/cmd-select-layout.c @@ -78,7 +78,7 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) struct window *w = wl->window; struct window_pane *wp = target->wp; const char *layoutname; - char *oldlayout, *cause; + char *oldlayout, *cause = NULL; int next, previous, layout; int flags = 0; diff --git a/control-notify.c b/control-notify.c index 5ea914f3d..58d025a1a 100644 --- a/control-notify.c +++ b/control-notify.c @@ -68,6 +68,7 @@ control_window_layout_changed_cb(__unused const char *name, struct session *s; struct winlink *wl; struct window *w = event_payload_get_window(ep, "window"); + struct format_tree *ft; const char *template; char *cp; @@ -90,11 +91,16 @@ control_window_layout_changed_cb(__unused const char *name, continue; s = c->session; wl = winlink_find_by_window_id(&s->windows, w->id); - if (wl != NULL) { - cp = format_single(NULL, template, c, s, wl, NULL); - control_write(c, "%s", cp); - free(cp); - } + if (wl == NULL) + continue; + + ft = format_create(c, NULL, FORMAT_NONE, 0); + format_defaults(ft, c, s, wl, NULL); + cp = format_expand(ft, template); + format_free(ft); + + control_write(c, "%s", cp); + free(cp); } } diff --git a/json.c b/json.c index f4772c667..ab94a1ba3 100644 --- a/json.c +++ b/json.c @@ -18,15 +18,14 @@ #include #include -#include -#include -#include #include #include +#include #include "tmux.h" #define TOKENS_MAX 4096 +#define ERROR_CTX_LEN 8 /* JSON Token types. */ enum json_token_type { @@ -67,7 +66,7 @@ static struct json_node *json_create_node(struct json_node *, 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 **, +static int json_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 **); @@ -80,10 +79,26 @@ static struct json_node *json_parse_number(struct json_token **, static struct json_node *json_parse_boolean(struct json_token **, struct json_node *, struct json_string *, char **); -static int +/* Fill an error cause. */ +static void json_error(char **cause, const char *reason, const char *input) { - return (xasprintf(cause, "%s: %.*s...", reason, 8, input)); + const char *ellipsis = "..."; + int i; + + if (input == NULL || *input == '\0') { + xasprintf(cause, "%s", reason); + return; + } + + for (i = 0; i < ERROR_CTX_LEN; i++) { + if (input[i] == '\0') { + ellipsis = ""; + break; + } + } + + xasprintf(cause, "%s: %.*s%s", reason, ERROR_CTX_LEN, input, ellipsis); } /* Wrapper for string views with strcmp semantics. */ @@ -107,12 +122,16 @@ 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 }; + struct json_string jstr; int scan; while (*input != '\0') { if (tokens->size >= TOKENS_MAX) goto fail; + + jstr.ptr = input; + jstr.len = 1; + switch (*input) { case ' ': case '\t': @@ -151,8 +170,6 @@ json_tokenize_input(const char *input, char **cause) 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) @@ -167,7 +184,7 @@ fail: /* * Tokenize a value from the input string. Strings are terminated by a '"', and - * numbers/booleans are terminated by a ',', ']', or '}'. + * numbers/booleans are terminated by a ',', ']', '}', or whitespace. */ static int json_tokenize_value(struct json_tokens *tokens, const char *input, @@ -187,14 +204,14 @@ json_tokenize_value(struct json_tokens *tokens, const char *input, else if (escaping) escaping = 0; scan++; - } while (input[scan] != '"' && !escaping); + } 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])); + input[scan] != ',' && !isspace((u_char) input[scan])); } else return (-1); @@ -241,6 +258,9 @@ json_add_token(struct json_tokens *tokens, enum json_token_type type, if (jstr != NULL) { tok->val.ptr = jstr->ptr; tok->val.len = jstr->len; + } else { + tok->val.ptr = NULL; + tok->val.len = 0; } return (0); @@ -332,6 +352,7 @@ json_destroy_node(struct json_node *node) free(node); } +/* Parse an input string into JSON. */ struct json_node * json_parse(const char *input, char **cause) { @@ -348,27 +369,29 @@ static struct json_node * json_parse_tokens(struct json_tokens **tokens, char **cause) { struct json_token *toks = (*tokens)->toks; - struct json_node *layout; + struct json_node *json = NULL; if (toks->type == TOK_OPENOBJECT) - layout = json_parse_object(&toks, NULL, NULL, cause); + json = json_parse_object(&toks, NULL, NULL, cause); else { - json_error(cause, "must start with object", toks->val.ptr); + json_error(cause, "expected object", toks->val.ptr); goto fail; } - if (layout == NULL) + if (json == NULL) goto fail; if (toks->type != TOK_EOF) { - json_destroy_node(layout); - layout = NULL; + json_error(cause, "unexpected trailing data", toks->val.ptr); + goto fail; } json_free_tokens(*tokens); *tokens = NULL; - return (layout); + return (json); fail: + if (json != NULL) + json_destroy_node(json); json_free_tokens(*tokens); *tokens = NULL; return (NULL); @@ -376,7 +399,7 @@ fail: /* Parse and return a key string, and advance the token pointer. */ static int -layout_parse_key(struct json_token **toks, struct json_string *jstr, +json_parse_key(struct json_token **toks, struct json_string *jstr, char **cause) { const char *start = (*toks)->val.ptr; @@ -398,7 +421,7 @@ layout_parse_key(struct json_token **toks, struct json_string *jstr, return (0); fail: - json_error(cause, "invalid key string", start); + json_error(cause, "invalid key", start); return (-1); } @@ -408,7 +431,8 @@ 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; + struct json_string fkey, *val; + u_char *valstr; if ((*toks)->type != TOK_OPENOBJECT) return (NULL); @@ -416,7 +440,7 @@ json_parse_object(struct json_token **toks, struct json_node *parent, object = json_create_node(parent, NODE_OBJECT, key, NULL); while ((*toks)->type != TOK_CLOSEOBJECT) { - if (layout_parse_key(toks, &fkey, cause) != 0) + if (json_parse_key(toks, &fkey, cause) != 0) goto fail; if ((*toks)->type != TOK_COLON) { json_error(cause, "missing colon", (*toks)->val.ptr); @@ -429,13 +453,19 @@ json_parse_object(struct json_token **toks, struct json_node *parent, 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); + val = &(*toks)->val; + valstr = (u_char *)val->ptr; + if (jstrcmp(val, "true") == 0 || + jstrcmp(val, "false") == 0) { + field = json_parse_boolean(toks, object, &fkey, + cause); + } else if ((*valstr == '-' && isdigit(valstr[1])) || + isdigit(*valstr)) { + field = json_parse_number(toks, object, &fkey, + cause); } else { - field = json_parse_number(toks, object, - &fkey, cause); + json_error(cause, "invalid value", val->ptr); + goto fail; } break; case TOK_OPENOBJECT: @@ -445,27 +475,23 @@ json_parse_object(struct json_token **toks, struct json_node *parent, field = json_parse_array(toks, object, &fkey, cause); break; default: - json_error(cause, "unsupported token for object", + json_error(cause, "unsupported object token", (*toks)->val.ptr); goto fail; } - if (field == NULL) { - json_error(cause, "could not parse object value", - (*toks)->val.ptr); + if (field == NULL) goto fail; - } - json_assign_value(object, field); + json_assign_value(object, field); if ((*toks)->type == TOK_COMMA) { if ((*toks)[1].type == TOK_CLOSEOBJECT) { - json_error(cause, "invalid json object", + json_error(cause, "invalid object", (*toks)->val.ptr); goto fail; } (*toks)++; } else if ((*toks)->type != TOK_CLOSEOBJECT) { - json_error(cause, "invalid json object", - (*toks)->val.ptr); + json_error(cause, "invalid object", (*toks)->val.ptr); goto fail; } } @@ -494,28 +520,25 @@ json_parse_array(struct json_token **toks, struct json_node *parent, 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", + default: + json_error(cause, "invalid array member", + (*toks)->val.ptr); + goto fail; + } + if (member == NULL) + goto fail; + + json_assign_value(array, member); + + if ((*toks)->type == TOK_COMMA) { + if ((*toks)[1].type == TOK_CLOSEARRAY) { + json_error(cause, "invalid array", (*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); + } else if ((*toks)->type != TOK_CLOSEARRAY) { + json_error(cause, "invalid array", (*toks)->val.ptr); goto fail; } } @@ -549,7 +572,7 @@ json_parse_string(struct json_token **toks, struct json_node *parent, return (json_create_node(parent, NODE_STRING, key, val)); fail: - json_error(cause, "invalid json string", start); + json_error(cause, "invalid string", start); return (NULL); } @@ -570,7 +593,7 @@ json_parse_number(struct json_token **toks, struct json_node *parent, return (json_create_node(parent, NODE_NUMBER, key, &val)); fail: - json_error(cause, "invalid json number", numstr); + json_error(cause, "invalid number", numstr); return (NULL); } @@ -593,7 +616,7 @@ json_parse_boolean(struct json_token **toks, struct json_node *parent, return (json_create_node(parent, NODE_BOOLEAN, key, &val)); fail: - json_error(cause, "invalid json boolean", start); + json_error(cause, "invalid boolean", start); return (NULL); } @@ -617,7 +640,7 @@ json_val_is_eq(const struct json_node *field, const void *val) return (field->val.boolean == *(int *)val); case NODE_OBJECT: case NODE_ARRAY: - fatalx("unknown value comparison"); + fatalx("cannot compare object or array"); } return (0); } diff --git a/layout-custom.c b/layout-custom.c index 030494ac1..d309c7f05 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -21,10 +21,9 @@ #include #include #include -#include #include -#include #include +#include #include "tmux.h" @@ -700,7 +699,8 @@ fail: static int layout_parse_json(struct json_node *json, struct layout_parse_ctx *pctx) { - struct json_node *field; + struct json_node *field; + char **cause = pctx->cause; TAILQ_FOREACH(field, &json->val.fields, entry) { switch (field->type) { @@ -710,8 +710,10 @@ layout_parse_json(struct json_node *json, struct layout_parse_ctx *pctx) break; case NODE_OBJECT: if (json_key_is_eq(field, "L")) { - if (pctx->root != NULL) + if (pctx->root != NULL) { + *cause = xstrdup("duplicate layout"); goto fail; + } pctx->root = layout_parse_json_layout(field, NULL, pctx); if (pctx->root== NULL) @@ -733,8 +735,8 @@ fail: layout_free_cell(pctx->root, 0); return (-1); } - -/* Evaluate nodes into layout cells. */ +// TODO: add causes for failures. +/* Parse nodes into layout cells. */ static struct layout_cell * layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent, struct layout_parse_ctx *pctx) diff --git a/tmux.1 b/tmux.1 index 936b5027a..77c54f58f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2695,8 +2695,8 @@ For example: .Bd -literal -offset indent $ tmux list\-windows 0: ksh [159x48] - layout: {"v":2,"L":{"t":"h","w":159,"h":48,"x":0,"y":0,"c":[{"t":"p","w":79,"h":48,"x":0,"y":0,"l":0,"i":"%0"},{"t":"p","w":79,"h":48,"x":80,"y":0,"a":true,"i":"%2"}]}} -$ tmux select\-layout \[aq]{"v":2,"L":{"t":"h","w":159,"h":48,"x":0,"y":0,"c":[{"t":"p","w":79,"h":48,"x":0,"y":0,"l":0,"i":"%0"},{"t":"p","w":79,"h":48,"x":80,"y":0,"a":true,"i":"%2"}]}}\[aq] + layout: {"V":2,"L":{"t":"h","w":159,"h":48,"x":0,"y":0,"c":[{"t":"p","w":79,"h":48,"x":0,"y":0,"l":0,"i":0,"I":"%0"},{"t":"p","w":79,"h":48,"x":80,"y":0,"a":true,"i":1,"I":"%2"}]}} +$ tmux select\-layout \[aq]{"V":2,"L":{"t":"h","w":159,"h":48,"x":0,"y":0,"c":[{"t":"p","w":79,"h":48,"x":0,"y":0,"l":0,"i":0,"I":"%0"},{"t":"p","w":79,"h":48,"x":80,"y":0,"a":true,"i":1","I":"%2"}]}}\[aq] .Ed .Pp .Nm @@ -9007,7 +9007,8 @@ and flags (currently not used). For example: .Bd -literal -offset indent %begin 1363006971 2 1 -0: ksh* (1 panes) [80x24] [layout {"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"a":true,"i":"%2"}}] @2 (active) +0: ksh* (1 panes) [80x24] [layout +{"V":2,"L":{"t":"p","w":80,"h":24,"x":0,"y":0,"a":true,"i":0,"I":"%2"}}] @2 (active) %end 1363006971 2 1 .Ed .Pp