diff --git a/arguments.c b/arguments.c index 7852655ab..72e16d511 100644 --- a/arguments.c +++ b/arguments.c @@ -115,7 +115,7 @@ args_value_as_string(struct args_value *value) return (""); case ARGS_COMMANDS: if (value->cached == NULL) - value->cached = cmd_parse_print(value->cmd); + value->cached = cmd_parse_print(value->cmd, 0); return (value->cached); case ARGS_STRING: return (value->string); @@ -469,7 +469,7 @@ args_to_vector(struct args *args, int *argc, char ***argv) cmd_append_argv(argc, argv, args->values[i].string); break; case ARGS_COMMANDS: - s = cmd_parse_print(args->values[i].cmd); + s = cmd_parse_print(args->values[i].cmd, 0); cmd_append_argv(argc, argv, s); free(s); break; @@ -524,7 +524,7 @@ args_print_add_value(char **buf, size_t *len, struct args_value *value) case ARGS_NONE: break; case ARGS_COMMANDS: - expanded = cmd_parse_print(value->cmd); + expanded = cmd_parse_print(value->cmd, 0); args_print_add(buf, len, "{ %s }", expanded); break; case ARGS_STRING: diff --git a/cmd-list-keys.c b/cmd-list-keys.c index fdc98d2f1..53ff31c80 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -44,8 +44,8 @@ const struct cmd_entry cmd_list_keys_entry = { .name = "list-keys", .alias = "lsk", - .args = { "1aF:NO:P:rT:", 0, 1, NULL }, - .usage = "[-1aNr] [-F format] [-O order] [-P prefix-string]" + .args = { "1aF:NO:P:prT:", 0, 1, NULL }, + .usage = "[-1aNpr] [-F format] [-O order] [-P prefix-string]" "[-T key-table] [key]", .flags = CMD_STARTSERVER|CMD_AFTERHOOK, @@ -119,7 +119,7 @@ cmd_list_keys_get_root_and_prefix(u_int *n, struct sort_criteria *sort_crit) } static void -cmd_list_keys_filter_key_list(int filter_notes, int filter_key, key_code only, +cmd_list_keys_do_filter(int filter_notes, int filter_key, key_code only, struct key_binding **l, u_int *n) { key_code key; @@ -137,8 +137,8 @@ cmd_list_keys_filter_key_list(int filter_notes, int filter_key, key_code only, } static void -cmd_list_keys_format_add_key_binding(struct format_tree *ft, - const struct key_binding *bd, const char *prefix) +cmd_list_keys_format_key_binding(struct format_tree *ft, + const struct key_binding *bd, const char *prefix, int flags) { char *s; @@ -157,7 +157,7 @@ cmd_list_keys_format_add_key_binding(struct format_tree *ft, format_add(ft, "key_string", "%s", key_string_lookup_key(bd->key, 0)); - s = cmd_parse_print(bd->cmd); + s = cmd_parse_print(bd->cmd, flags); format_add(ft, "key_command", "%s", s); free(s); } @@ -176,6 +176,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) char *prefix = NULL; u_int i, n; int single, notes_only, filter_notes, filter_key; + int print_flags = 0; struct sort_criteria sort_crit; if ((keystr = args_string(args, 0)) != NULL) { @@ -205,6 +206,8 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) prefix = cmd_list_keys_get_prefix(args); single = args_has(args, '1'); notes_only = args_has(args, 'N'); + if (args_has(args, 'p')) + print_flags |= CMD_PARSE_PRINT_MULTILINE; if ((template = args_get(args, 'F')) == NULL) template = LIST_KEYS_TEMPLATE; @@ -218,10 +221,8 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) filter_notes = notes_only && !args_has(args, 'a'); filter_key = only != KEYC_UNKNOWN; - if (filter_notes || filter_key) { - cmd_list_keys_filter_key_list(filter_notes, filter_key, only, l, - &n); - } + if (filter_notes || filter_key) + cmd_list_keys_do_filter(filter_notes, filter_key, only, l, &n); if (filter_key && n == 0) { cmdq_error(item, "unknown key: %s", keystr); free(prefix); @@ -238,7 +239,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) format_add(ft, "key_table_width", "%u", cmd_list_keys_get_table_width(l, n)); for (i = 0; i < n; i++) { - cmd_list_keys_format_add_key_binding(ft, l[i], prefix); + cmd_list_keys_format_key_binding(ft, l[i], prefix, print_flags); line = format_expand(ft, template); if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL)) diff --git a/cmd-parse.y b/cmd-parse.y index 88f9cb326..e2c250e18 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -100,10 +100,10 @@ static struct cmd_parse_node *cmd_parse_make_assign(enum cmd_parse_node_type, static struct cmd_parse_node *cmd_parse_token_from_string(const char *); static void cmd_parse_onegroup(struct cmd_parse_node *); static void cmd_parse_print_sequence(char **, struct cmd_parse_node *, - u_int); + u_int, int); static void cmd_parse_print_item(char **, struct cmd_parse_node *, u_int, int); -static char *cmd_parse_make_string(struct cmd_parse_node *); +static char *cmd_parse_make_string(struct cmd_parse_node *); %} @@ -1173,21 +1173,42 @@ cmd_parse_print_string(char **buf, struct cmd_parse_node *string) } } +static const char * +cmd_parse_print_separator(u_int depth, int flags) +{ + if ((~flags & CMD_PARSE_PRINT_MULTILINE) && depth == 0) + return (" \\; "); + return (" ; "); +} + static void cmd_parse_print_commands(char **buf, struct cmd_parse_node *commands, - u_int depth) + u_int depth, int flags) { struct cmd_parse_node *child; + int first = 1; if (TAILQ_EMPTY(&commands->children)) { cmd_parse_strcat(buf, "{}"); return; } + if (~flags & CMD_PARSE_PRINT_MULTILINE) { + cmd_parse_strcat(buf, "{ "); + TAILQ_FOREACH(child, &commands->children, entry) { + if (!first) + cmd_parse_strcat(buf, " ; "); + first = 0; + cmd_parse_print_sequence(buf, child, depth + 1, flags); + } + cmd_parse_strcat(buf, " }"); + return; + } + cmd_parse_strcat(buf, "{\n"); TAILQ_FOREACH(child, &commands->children, entry) { cmd_parse_print_indent(buf, depth + 1); - cmd_parse_print_sequence(buf, child, depth + 1); + cmd_parse_print_sequence(buf, child, depth + 1, flags); cmd_parse_strcat(buf, "\n"); } cmd_parse_print_indent(buf, depth); @@ -1195,7 +1216,8 @@ cmd_parse_print_commands(char **buf, struct cmd_parse_node *commands, } static void -cmd_parse_print_command(char **buf, struct cmd_parse_node *cmd, u_int depth) +cmd_parse_print_command(char **buf, struct cmd_parse_node *cmd, u_int depth, + int flags) { struct cmd_parse_node *child; int first = 1; @@ -1205,7 +1227,7 @@ cmd_parse_print_command(char **buf, struct cmd_parse_node *cmd, u_int depth) cmd_parse_strcat(buf, " "); first = 0; if (child->type == CMD_PARSE_COMMANDS) - cmd_parse_print_commands(buf, child, depth); + cmd_parse_print_commands(buf, child, depth, flags); else cmd_parse_print_string(buf, child); } @@ -1224,9 +1246,10 @@ cmd_parse_print_break(char **buf, u_int depth, int oneline) static void cmd_parse_print_if(char **buf, struct cmd_parse_node *node, u_int depth, - int oneline) + int flags) { struct cmd_parse_node *child, *sub; + int oneline = (~flags & CMD_PARSE_PRINT_MULTILINE); child = TAILQ_FIRST(&node->children); cmd_parse_strcat(buf, "%%if "); @@ -1237,7 +1260,7 @@ cmd_parse_print_if(char **buf, struct cmd_parse_node *node, u_int depth, switch (child->type) { case CMD_PARSE_SEQUENCE: cmd_parse_print_break(buf, depth, oneline); - cmd_parse_print_sequence(buf, child, depth); + cmd_parse_print_sequence(buf, child, depth, flags); break; case CMD_PARSE_ELIF: sub = TAILQ_FIRST(&child->children); @@ -1247,7 +1270,7 @@ cmd_parse_print_if(char **buf, struct cmd_parse_node *node, u_int depth, for (sub = TAILQ_NEXT(sub, entry); sub != NULL; sub = TAILQ_NEXT(sub, entry)) { cmd_parse_print_break(buf, depth, oneline); - cmd_parse_print_sequence(buf, sub, depth); + cmd_parse_print_sequence(buf, sub, depth, flags); } break; case CMD_PARSE_ELSE: @@ -1255,7 +1278,7 @@ cmd_parse_print_if(char **buf, struct cmd_parse_node *node, u_int depth, cmd_parse_strcat(buf, "%%else"); TAILQ_FOREACH(sub, &child->children, entry) { cmd_parse_print_break(buf, depth, oneline); - cmd_parse_print_sequence(buf, sub, depth); + cmd_parse_print_sequence(buf, sub, depth, flags); } break; default: @@ -1268,11 +1291,11 @@ cmd_parse_print_if(char **buf, struct cmd_parse_node *node, u_int depth, static void cmd_parse_print_item(char **buf, struct cmd_parse_node *item, u_int depth, - int oneline) + int flags) { switch (item->type) { case CMD_PARSE_COMMAND: - cmd_parse_print_command(buf, item, depth); + cmd_parse_print_command(buf, item, depth, flags); break; case CMD_PARSE_ASSIGN: cmd_parse_strcat(buf, "%s=", item->value); @@ -1283,7 +1306,7 @@ cmd_parse_print_item(char **buf, struct cmd_parse_node *item, u_int depth, cmd_parse_print_string(buf, TAILQ_FIRST(&item->children)); break; case CMD_PARSE_IF: - cmd_parse_print_if(buf, item, depth, oneline); + cmd_parse_print_if(buf, item, depth, flags); break; default: break; @@ -1291,39 +1314,47 @@ cmd_parse_print_item(char **buf, struct cmd_parse_node *item, u_int depth, } static void -cmd_parse_print_sequence(char **buf, struct cmd_parse_node *seq, u_int depth) +cmd_parse_print_sequence(char **buf, struct cmd_parse_node *seq, u_int depth, + int flags) { - struct cmd_parse_node *child; - int first = 1, oneline = 0; + struct cmd_parse_node *child, *first_child; + const char *sep = cmd_parse_print_separator(depth, flags); + int first = 1; - if (TAILQ_NEXT(TAILQ_FIRST(&seq->children), entry) != NULL) { - /* - * If there is more than one item in a sequence, force - * everything on to one line. - */ - oneline = 1; - } + /* + * If there is more than one item in a sequence everything must be on one + * line (a %if or brace body cannot span lines mid-sequence), so clear + * the multiline flag for the items. The separator is chosen above from + * the original flags so a multiline sequence keeps its ";" separator. + */ + first_child = TAILQ_FIRST(&seq->children); + if (first_child != NULL && TAILQ_NEXT(first_child, entry) != NULL) + flags &= ~CMD_PARSE_PRINT_MULTILINE; TAILQ_FOREACH(child, &seq->children, entry) { if (!first) - cmd_parse_strcat(buf, " ; "); + cmd_parse_strcat(buf, "%s", sep); first = 0; - cmd_parse_print_item(buf, child, depth, oneline); + cmd_parse_print_item(buf, child, depth, flags); } } char * -cmd_parse_print(struct cmd_parse_tree *tree) +cmd_parse_print(struct cmd_parse_tree *tree, int flags) { struct cmd_parse_node *root = tree->root, *child; char *buf = NULL; int first = 1; TAILQ_FOREACH(child, &root->children, entry) { - if (!first) - cmd_parse_strcat(&buf, "\n"); + if (!first) { + if (flags & CMD_PARSE_PRINT_MULTILINE) + cmd_parse_strcat(&buf, "\n"); + else + cmd_parse_strcat(&buf, " \\; "); + } first = 0; - cmd_parse_print_sequence(&buf, child, 0); + cmd_parse_print_sequence(&buf, child, 0, flags); } if (buf == NULL) buf = xstrdup(""); diff --git a/key-bindings.c b/key-bindings.c index ae3eac05e..4a9155c22 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -224,7 +224,7 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat, bd->cmd = cmd; k = key_string_lookup_key(bd->key, 1); - s = cmd_parse_print(bd->cmd); + s = cmd_parse_print(bd->cmd, 0); log_debug("%s: %#llx %s = %s", __func__, bd->key, k, s); free(s); } diff --git a/notify.c b/notify.c index 471e2d351..755850525 100644 --- a/notify.c +++ b/notify.c @@ -45,7 +45,7 @@ notify_insert_one_hook(struct cmdq_item *item, struct notify_entry *ne, if (tree == NULL) return (item); if (log_get_level() != 0) { - s = cmd_parse_print(tree); + s = cmd_parse_print(tree, 0); log_debug("%s: hook %s is: %s", __func__, ne->name, s); free(s); } diff --git a/options.c b/options.c index 631b27663..d0d689809 100644 --- a/options.c +++ b/options.c @@ -136,7 +136,7 @@ options_value_to_string(struct options_entry *o, union options_value *ov, char *s; if (OPTIONS_IS_COMMAND(o)) - return (cmd_parse_print(ov->cmd)); + return (cmd_parse_print(ov->cmd, 0)); if (OPTIONS_IS_NUMBER(o)) { switch (o->tableentry->type) { case OPTIONS_TABLE_NUMBER: diff --git a/regress/cmd-parse-print.sh b/regress/cmd-parse-print.sh index af1525e2c..01ad5e37f 100755 --- a/regress/cmd-parse-print.sh +++ b/regress/cmd-parse-print.sh @@ -4,10 +4,12 @@ # # Binds a spread of command syntax forms into a dedicated key table, then prints # them back with list-keys (which calls cmd_parse_print on the stored tree). A -# command-valued option is printed too. The normalized output is compared byte -# for byte against the expected block below, locking in the current parse/print -# behaviour: quoting, separators, braced and nested bodies, and preservation of -# unexpanded ${env}, ~ and #{format} syntax inside stored bodies. +# command-valued option is printed too. Three things are checked: +# - the default (single line) form is byte-for-byte as expected: quoting, +# escaped \; separators, inline braces, and preservation of unexpanded +# ${env}, ~ and #{format} inside stored bodies; +# - the -p (multiline) form is as expected; +# - the default form round-trips: sourcing it again reproduces the same keys. PATH=/bin:/usr/bin TERM=screen @@ -19,7 +21,9 @@ $TMUX kill-server 2>/dev/null TMP=$(mktemp) CONF=$(mktemp) EXP=$(mktemp) -trap "rm -f $TMP $CONF $EXP" 0 1 15 +RT1=$(mktemp) +RT2=$(mktemp) +trap "rm -f $TMP $CONF $EXP $RT1 $RT2" 0 1 15 cat <<'EOF' >$CONF bind -T parsetest a display-message hello @@ -37,14 +41,27 @@ bind -T parsetest m if-shell true { display-message "${HOME}" "~" "~root" "#{pan bind -T parsetest n if-shell true { display-message "a\nb" "x;y" '#literal' } EOF -# Expected normalized output. Lines inside the braced bodies are indented with a -# single tab. +# Expected output. The default form is single line (round-trippable); the -p +# form is multiline, with lines inside braced bodies indented by a single tab. cat <<'EOF' >$EXP bind-key -T parsetest a display-message hello bind-key -T parsetest b display-message 'hello world' bind-key -T parsetest c display-message 'literal $HOME #{p} ~' bind-key -T parsetest d display-message '' bind-key -T parsetest e display-message '#{pane_id}' +bind-key -T parsetest f display-message a \; display-message b +bind-key -T parsetest g display-message one \; display-message two +bind-key -T parsetest h if-shell true { display-message yes } { display-message no } +bind-key -T parsetest m if-shell true { display-message ${HOME} ~ ~root '#{pane_id}' } +bind-key -T parsetest n if-shell true { display-message a\nb 'x;y' '#literal' } +--- options --- +display-message 'hi there' +--- multiline --- +bind-key -T parsetest a display-message hello +bind-key -T parsetest b display-message 'hello world' +bind-key -T parsetest c display-message 'literal $HOME #{p} ~' +bind-key -T parsetest d display-message '' +bind-key -T parsetest e display-message '#{pane_id}' bind-key -T parsetest f display-message a ; display-message b bind-key -T parsetest g display-message one display-message two @@ -59,8 +76,6 @@ bind-key -T parsetest m if-shell true { bind-key -T parsetest n if-shell true { display-message a\nb 'x;y' '#literal' } ---- options --- -display-message 'hi there' EOF $TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1 @@ -71,14 +86,29 @@ $TMUX set -g default-client-command 'display-message "hi there"' || exit 1 $TMUX list-keys -T parsetest echo "--- options ---" $TMUX show -gv default-client-command + echo "--- multiline ---" + $TMUX list-keys -p -T parsetest } >$TMP 2>&1 || exit 1 -$TMUX kill-server 2>/dev/null - cmp -s $TMP $EXP || { echo "cmd-parse-print: output differs from expected" >&2 diff -u $EXP $TMP >&2 exit 1 } +# Round-trip: the default form re-sourced into a fresh server must reproduce the +# identical key list. +$TMUX list-keys -T parsetest >$RT1 || exit 1 +$TMUX kill-server 2>/dev/null +$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1 +$TMUX source-file $RT1 || exit 1 +$TMUX list-keys -T parsetest >$RT2 || exit 1 +$TMUX kill-server 2>/dev/null + +cmp -s $RT1 $RT2 || { + echo "cmd-parse-print: default form does not round-trip" >&2 + diff -u $RT1 $RT2 >&2 + exit 1 +} + exit 0 diff --git a/tmux-parser.h b/tmux-parser.h index eaeafb249..cfd441561 100644 --- a/tmux-parser.h +++ b/tmux-parser.h @@ -71,6 +71,8 @@ enum cmd_parse_node_type { CMD_PARSE_ELSE }; +#define CMD_PARSE_PRINT_MULTILINE 0x1 + struct cmd_parse_tree *cmd_parse_from_file(FILE *, struct cmd_parse_input *, char **); struct cmd_parse_tree *cmd_parse_from_buffer(const void *, size_t, @@ -90,7 +92,7 @@ void cmd_parse_free(struct cmd_parse_tree *); struct cmd_parse_node *cmd_parse_root(struct cmd_parse_tree *); const char *cmd_parse_file(struct cmd_parse_tree *); int cmd_parse_flags(struct cmd_parse_tree *); -char *cmd_parse_print(struct cmd_parse_tree *); +char *cmd_parse_print(struct cmd_parse_tree *, int); void cmd_parse_log(const char *, struct cmd_parse_tree *); void cmd_parse_log_node(const char *, struct cmd_parse_node *); diff --git a/tmux.1 b/tmux.1 index 054464527..f04c09c32 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4287,7 +4287,7 @@ To view the default bindings and possible commands, see the command. .Tg lsk .It Xo Ic list\-keys -.Op Fl 1aNr +.Op Fl 1aNpr .Op Fl F Ar format .Op Fl O Ar sort\-order .Op Fl P Ar prefix\-string @@ -4307,6 +4307,8 @@ specifies a to list from. .Fl 1 lists only the first matching key. +.Fl p +allows each command to use multiple lines. .Fl O specifies the sort order: one of .Ql key , diff --git a/window-customize.c b/window-customize.c index 776b579c0..01f1eeff4 100644 --- a/window-customize.c +++ b/window-customize.c @@ -490,7 +490,7 @@ window_customize_build_keys(struct window_customize_modedata *data, expanded, NULL, 0); free(expanded); - tmp = cmd_parse_print(bd->cmd); + tmp = cmd_parse_print(bd->cmd, 0); xasprintf(&text, "#[ignore]%s", tmp); free(tmp); mti = mode_tree_add(data->data, child, item, @@ -625,7 +625,7 @@ window_customize_draw_key(__unused struct window_customize_modedata *data, if (s->cy >= cy + sy - 1) return; - cmd = cmd_parse_print(bd->cmd); + cmd = cmd_parse_print(bd->cmd, CMD_PARSE_PRINT_MULTILINE); if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, "Command: %s", cmd)) { free(cmd); @@ -633,7 +633,8 @@ window_customize_draw_key(__unused struct window_customize_modedata *data, } default_bd = key_bindings_get_default(kt, bd->key); if (default_bd != NULL) { - default_cmd = cmd_parse_print(default_bd->cmd); + default_cmd = cmd_parse_print(default_bd->cmd, + CMD_PARSE_PRINT_MULTILINE); if (strcmp(cmd, default_cmd) != 0 && !screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, "The default is: %s", default_cmd)) { @@ -1287,7 +1288,7 @@ window_customize_set_key(struct client *c, bd->flags ^= KEY_BINDING_REPEAT; else if (strcmp(s, "Command") == 0) { xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 0)); - value = cmd_parse_print(bd->cmd); + value = cmd_parse_print(bd->cmd, 0); new_item = xcalloc(1, sizeof *new_item); new_item->data = data;