summaryrefslogtreecommitdiffstats
path: root/cmd-parse.y
diff options
context:
space:
mode:
authornicm <nicm>2019-05-27 12:16:27 +0000
committernicm <nicm>2019-05-27 12:16:27 +0000
commit6b332127cae97914d34c39575881fbc87205f4e0 (patch)
treee451d78a0ec378c4b58b3a401a88532c27a25ae0 /cmd-parse.y
parent65e5e1456179d68f36602a5976184b38cc4b636c (diff)
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.
Diffstat (limited to 'cmd-parse.y')
-rw-r--r--cmd-parse.y100
1 files changed, 100 insertions, 0 deletions
diff --git a/cmd-parse.y b/cmd-parse.y
index 43ec8a94..f347280b 100644
--- a/cmd-parse.y
+++ b/cmd-parse.y
@@ -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).