Add an additional {} syntax for defining strings in the configuration

file, making it much tidier to define commands that contain other tmux
or shell commands (like if-shell). Also tweak bind-key to expect a
string if it is only given one argument, so {} can be used with it as
well. From Avi Halachmi.
This commit is contained in:
nicm 2019-05-27 12:16:27 +00:00
parent 65e5e14561
commit 6b332127ca
5 changed files with 157 additions and 21 deletions

View File

@ -207,7 +207,7 @@ args_print(struct args *args)
char *
args_escape(const char *s)
{
static const char quoted[] = " #\"';$";
static const char quoted[] = " #\"';${}";
char *escaped, *result;
int flags;

View File

@ -44,14 +44,16 @@ const struct cmd_entry cmd_bind_key_entry = {
static enum cmd_retval
cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = self->args;
key_code key;
const char *tablename;
struct cmd_parse_result *pr;
struct args *args = self->args;
key_code key;
const char *tablename;
struct cmd_parse_result *pr;
char **argv = args->argv;
int argc = args->argc;
key = key_string_lookup_string(args->argv[0]);
key = key_string_lookup_string(argv[0]);
if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
cmdq_error(item, "unknown key: %s", args->argv[0]);
cmdq_error(item, "unknown key: %s", argv[0]);
return (CMD_RETURN_ERROR);
}
@ -62,7 +64,10 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
else
tablename = "prefix";
pr = cmd_parse_from_arguments(args->argc - 1, args->argv + 1, NULL);
if (argc == 2)
pr = cmd_parse_from_string(argv[1], NULL);
else
pr = cmd_parse_from_arguments(argc - 1, argv + 1, NULL);
switch (pr->status) {
case CMD_PARSE_EMPTY:
cmdq_error(item, "empty command");

View File

@ -1236,6 +1236,99 @@ yylex_token_tilde(char **buf, size_t *len)
return (1);
}
static int
yylex_token_brace(char **buf, size_t *len)
{
struct cmd_parse_state *ps = &parse_state;
int ch, nesting = 1, escape = 0, quote = '\0';
int lines = 0;
/*
* Extract a string up to the matching unquoted '}', including newlines
* and handling nested braces.
*
* To detect the final and intermediate braces which affect the nesting
* depth, we scan the input as if it was a tmux config file, and ignore
* braces which would be considered quoted, escaped, or in a comment.
*
* The result is verbatim copy of the input excluding the final brace.
*/
for (ch = yylex_getc1(); ch != EOF; ch = yylex_getc1()) {
yylex_append1(buf, len, ch);
if (ch == '\n')
lines++;
/*
* If the previous character was a backslash (escape is set),
* escape anything if unquoted or in double quotes, otherwise
* escape only '\n' and '\\'.
*/
if (escape &&
(quote == '\0' ||
quote == '"' ||
ch == '\n' ||
ch == '\\')) {
escape = 0;
continue;
}
/*
* The character is not escaped. If it is a backslash, set the
* escape flag.
*/
if (ch == '\\') {
escape = 1;
continue;
}
escape = 0;
/* A newline always resets to unquoted. */
if (ch == '\n') {
quote = 0;
continue;
}
if (quote) {
/*
* Inside quotes or comment. Check if this is the
* closing quote.
*/
if (ch == quote && quote != '#')
quote = 0;
} else {
/* Not inside quotes or comment. */
switch (ch) {
case '"':
case '\'':
case '#':
/* Beginning of quote or comment. */
quote = ch;
break;
case '{':
nesting++;
break;
case '}':
nesting--;
if (nesting == 0) {
(*len)--; /* remove closing } */
ps->input->line += lines;
return (1);
}
break;
}
}
}
/*
* Update line count after error as reporting the opening line
* is more useful than EOF.
*/
yyerror("unterminated brace string");
ps->input->line += lines;
return (0);
}
static char *
yylex_token(int ch)
{
@ -1282,6 +1375,13 @@ yylex_token(int ch)
goto error;
goto skip;
}
if (ch == '{' && state == NONE) {
if (!yylex_token_brace(&buf, &len))
goto error;
goto skip;
}
if (ch == '}' && state == NONE)
goto error; /* unmatched (matched ones were handled) */
/*
* ' and " starts or end quotes (and is consumed).

View File

@ -242,8 +242,8 @@ key_bindings_init(void)
"bind w choose-tree -Zw",
"bind x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane",
"bind z resize-pane -Z",
"bind { swap-pane -U",
"bind } swap-pane -D",
"bind '{' swap-pane -U",
"bind '}' swap-pane -D",
"bind '~' show-messages",
"bind PPage copy-mode -u",
"bind -r Up select-pane -U",
@ -347,8 +347,8 @@ key_bindings_init(void)
"bind -Tcopy-mode M-r send -X middle-line",
"bind -Tcopy-mode M-v send -X page-up",
"bind -Tcopy-mode M-w send -X copy-selection-and-cancel",
"bind -Tcopy-mode M-{ send -X previous-paragraph",
"bind -Tcopy-mode M-} send -X next-paragraph",
"bind -Tcopy-mode 'M-{' send -X previous-paragraph",
"bind -Tcopy-mode 'M-}' send -X next-paragraph",
"bind -Tcopy-mode M-Up send -X halfpage-up",
"bind -Tcopy-mode M-Down send -X halfpage-down",
"bind -Tcopy-mode C-Up send -X scroll-up",
@ -413,8 +413,8 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi t command-prompt -1p'(jump to forward)' 'send -X jump-to-forward \"%%%\"'",
"bind -Tcopy-mode-vi v send -X rectangle-toggle",
"bind -Tcopy-mode-vi w send -X next-word",
"bind -Tcopy-mode-vi { send -X previous-paragraph",
"bind -Tcopy-mode-vi } send -X next-paragraph",
"bind -Tcopy-mode-vi '{' send -X previous-paragraph",
"bind -Tcopy-mode-vi '}' send -X next-paragraph",
"bind -Tcopy-mode-vi % send -X next-matching-bracket",
"bind -Tcopy-mode-vi MouseDown1Pane select-pane",
"bind -Tcopy-mode-vi MouseDrag1Pane select-pane\\; send -X begin-selection",

45
tmux.1
View File

@ -490,11 +490,13 @@ line (the \e and the newline are completely removed).
This is called line continuation and applies both inside and outside quoted
strings and in comments.
.Pp
Command arguments may be specified as strings surrounded by either single (')
or double quotes (").
Command arguments may be specified as strings surrounded by single (') quotes,
double quotes (") or braces ({}).
.\" "
This is required when the argument contains any special character.
Strings cannot span multiple lines except with line continuation.
Single and double quoted strings cannot span multiple lines except with line
continuation.
Braces can span multiple lines.
.Pp
Outside of quotes and inside double quotes, these replacements are performed:
.Bl -dash -offset indent
@ -520,6 +522,34 @@ is removed) and are not treated as having any special meaning - so for example
variable.
.El
.Pp
Braces are similar to single quotes in that the text inside is taken literally
without replacements, but they can span multiple lines.
They are designed to avoid the need for additional escaping when passing a group
of
.Nm
or shell commands as an argument (for example to
.Ic if-shell
or
.Ic pipe-pane ) .
These two examples produce an identical command - note that no escaping is
needed when using {}:
.Bd -literal -offset indent
if-shell true {
display -p 'brace-dollar-foo: }$foo'
}
if-shell true "\en display -p 'brace-dollar-foo: }\e$foo'\en"
.Ed
.Pp
Braces may be enclosed inside braces, for example:
.Bd -literal -offset indent
bind x if-shell "true" {
if-shell "true" {
display "true!"
}
}
.Ed
.Pp
Environment variables may be set by using the syntax
.Ql name=value ,
for example
@ -820,15 +850,16 @@ directly without invoking the shell.
.Op Ar arguments
refers to a
.Nm
command, passed with the command and arguments separately, for example:
command, either passed with the command and arguments separately, for example:
.Bd -literal -offset indent
bind-key F1 set-option status off
.Ed
.Pp
Or if using
.Xr sh 1 :
Or passed as a single string argument in
.Pa .tmux.conf ,
for example:
.Bd -literal -offset indent
$ tmux bind-key F1 set-option status off
bind-key F1 { set-option status off }
.Ed
.Pp
Example