diff --git a/Makefile.am b/Makefile.am index ceaae6ad..94b1564b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -88,7 +88,6 @@ dist_tmux_SOURCES = \ cmd-list-panes.c \ cmd-list-sessions.c \ cmd-list-windows.c \ - cmd-list.c \ cmd-load-buffer.c \ cmd-lock-server.c \ cmd-move-window.c \ @@ -152,6 +151,7 @@ dist_tmux_SOURCES = \ options.c \ paste.c \ proc.c \ + regsub.c \ resize.c \ screen-redraw.c \ screen-write.c \ diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index 47cb0ca2..3e669093 100644 --- a/cmd-load-buffer.c +++ b/cmd-load-buffer.c @@ -93,7 +93,7 @@ cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_WAIT); } - file = server_client_get_path(c, path); + file = server_client_get_path(item->client, path); free(path); f = fopen(file, "rb"); diff --git a/cmd-parse.y b/cmd-parse.y index a7c12f62..1dbc27a7 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -59,6 +59,7 @@ struct cmd_parse_state { size_t len; size_t off; + int condition; int eol; int eof; struct cmd_parse_input *input; @@ -104,7 +105,7 @@ static void cmd_parse_print_commands(struct cmd_parse_input *, u_int, %token ENDIF %token <token> FORMAT TOKEN EQUALS -%type <token> argument expanded +%type <token> argument expanded format %type <arguments> arguments %type <flag> if_open if_elif %type <elif> elif elif1 @@ -160,7 +161,16 @@ statement : condition } } -expanded : FORMAT +format : FORMAT + { + $$ = $1; + } + | TOKEN + { + $$ = $1; + } + +expanded : format { struct cmd_parse_state *ps = &parse_state; struct cmd_parse_input *pi = ps->input; @@ -507,7 +517,10 @@ cmd_parse_print_commands(struct cmd_parse_input *pi, u_int line, if (pi->item != NULL && (pi->flags & CMD_PARSE_VERBOSE)) { s = cmd_list_print(cmdlist, 0); - cmdq_print(pi->item, "%u: %s", line, s); + if (pi->file != NULL) + cmdq_print(pi->item, "%s:%u: %s", pi->file, line, s); + else + cmdq_print(pi->item, "%u: %s", line, s); free(s); } } @@ -967,12 +980,15 @@ yylex(void) { struct cmd_parse_state *ps = &parse_state; char *token, *cp; - int ch, next; + int ch, next, condition; if (ps->eol) ps->input->line++; ps->eol = 0; + condition = ps->condition; + ps->condition = 0; + for (;;) { ch = yylex_getc(); @@ -1012,11 +1028,11 @@ yylex(void) if (ch == '#') { /* - * #{ opens a format; anything else is a comment, - * ignore up to the end of the line. + * #{ after a condition opens a format; anything else + * is a comment, ignore up to the end of the line. */ next = yylex_getc(); - if (next == '{') { + if (condition && next == '{') { yylval.token = yylex_format(); if (yylval.token == NULL) return (ERROR); @@ -1043,6 +1059,7 @@ yylex(void) } if (*cp == '\0') return (TOKEN); + ps->condition = 1; if (strcmp(yylval.token, "%if") == 0) { free(yylval.token); return (IF); diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 938bb3d6..3395612f 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -104,7 +104,7 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) if (args_has(self->args, 'a')) flags = "ab"; - file = server_client_get_path(c, path); + file = server_client_get_path(item->client, path); free(path); f = fopen(file, flags); diff --git a/format.c b/format.c index 5ca970b7..8126bfb2 100644 --- a/format.c +++ b/format.c @@ -23,6 +23,7 @@ #include <errno.h> #include <fnmatch.h> #include <libgen.h> +#include <regex.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> @@ -1112,7 +1113,7 @@ format_find(struct format_tree *ft, const char *key, int modifiers) envent = environ_find(ft->s->environ, key); if (envent == NULL) envent = environ_find(global_environ, key); - if (envent != NULL) { + if (envent != NULL && envent->value != NULL) { found = xstrdup(envent->value); goto found; } @@ -1263,7 +1264,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) cp++; /* Check single character modifiers with no arguments. */ - if (strchr("lmCbdtqETSWP<>", cp[0]) != NULL && + if (strchr("lbdtqETSWP<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -1284,7 +1285,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) } /* Now try single character with arguments. */ - if (strchr("s=", cp[0]) == NULL) + if (strchr("mCs=", cp[0]) == NULL) break; c = cp[0]; @@ -1345,39 +1346,67 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) return list; } +/* Match against an fnmatch(3) pattern or regular expression. */ +static char * +format_match(struct format_modifier *fm, const char *pattern, const char *text) +{ + const char *s = ""; + regex_t r; + int flags = 0; + + if (fm->argc >= 1) + s = fm->argv[0]; + if (strchr(s, 'r') == NULL) { + if (strchr(s, 'i') != NULL) + flags |= FNM_CASEFOLD; + if (fnmatch(pattern, text, flags) != 0) + return (xstrdup("0")); + } else { + flags = REG_EXTENDED|REG_NOSUB; + if (strchr(s, 'i') != NULL) + flags |= REG_ICASE; + if (regcomp(&r, pattern, flags) != 0) + return (xstrdup("0")); + if (regexec(&r, text, 0, NULL, 0) != 0) { + regfree(&r); + return (xstrdup("0")); + } + regfree(&r); + } + return (xstrdup("1")); +} + /* Perform substitution in string. */ static char * -format_substitute(const char *source, const char *from, const char *to) +format_sub(struct format_modifier *fm, const char *text, const char *pattern, + const char *with) { - char *copy, *new; - const char *cp; - size_t fromlen, tolen, newlen, used; + char *value; + int flags = REG_EXTENDED; - fromlen = strlen(from); - tolen = strlen(to); + if (fm->argc >= 3 && strchr(fm->argv[2], 'i') != NULL) + flags |= REG_ICASE; + value = regsub(pattern, with, text, flags); + if (value == NULL) + return (xstrdup(text)); + return (value); +} - newlen = strlen(source) + 1; - copy = new = xmalloc(newlen); +/* Search inside pane. */ +static char * +format_search(struct format_modifier *fm, struct window_pane *wp, const char *s) +{ + int ignore = 0, regex = 0; + char *value; - for (cp = source; *cp != '\0'; /* nothing */) { - if (strncmp(cp, from, fromlen) != 0) { - *new++ = *cp++; - continue; - } - used = new - copy; - - newlen += tolen; - copy = xrealloc(copy, newlen); - - new = copy + used; - memcpy(new, to, tolen); - - new += tolen; - cp += fromlen; + if (fm->argc >= 1) { + if (strchr(fm->argv[0], 'i') != NULL) + ignore = 1; + if (strchr(fm->argv[0], 'r') != NULL) + regex = 1; } - - *new = '\0'; - return (copy); + xasprintf(&value, "%u", window_pane_search(wp, s, regex, ignore)); + return (value); } /* Loop over sessions. */ @@ -1522,11 +1551,10 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, char *copy0, *condition, *found, *new; char *value, *left, *right; size_t valuelen; - int modifiers = 0, limit = 0; + int modifiers = 0, limit = 0, j; struct format_modifier *list, *fm, *cmp = NULL, *search = NULL; struct format_modifier *sub = NULL; u_int i, count; - int j; /* Make a copy of the key. */ copy = copy0 = xstrndup(key, keylen); @@ -1553,18 +1581,18 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, search = fm; break; case 's': - if (fm->argc != 2) + if (fm->argc < 2) break; sub = fm; break; case '=': - if (fm->argc != 1 && fm->argc != 2) + if (fm->argc < 1) break; limit = strtonum(fm->argv[0], INT_MIN, INT_MAX, &errptr); if (errptr != NULL) limit = 0; - if (fm->argc == 2 && fm->argv[1] != NULL) + if (fm->argc >= 2 && fm->argv[1] != NULL) marker = fm->argv[1]; break; case 'l': @@ -1635,7 +1663,7 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, value = xstrdup("0"); } else { format_log(ft, "search '%s' pane %%%u", copy, wp->id); - xasprintf(&value, "%u", window_pane_search(wp, copy)); + value = format_search(fm, wp, copy); } } else if (cmp != NULL) { /* Comparison of left and right. */ @@ -1687,12 +1715,8 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, value = xstrdup("1"); else value = xstrdup("0"); - } else if (strcmp(cmp->modifier, "m") == 0) { - if (fnmatch(left, right, 0) == 0) - value = xstrdup("1"); - else - value = xstrdup("0"); - } + } else if (strcmp(cmp->modifier, "m") == 0) + value = format_match(fm, left, right); free(right); free(left); @@ -1770,8 +1794,8 @@ done: /* Perform substitution if any. */ if (sub != NULL) { - new = format_substitute(value, sub->argv[0], sub->argv[1]); - format_log(ft, "substituted '%s' to '%s: %s", sub->argv[0], + new = format_sub(sub, value, sub->argv[0], sub->argv[1]); + format_log(ft, "substituted '%s' to '%s': %s", sub->argv[0], sub->argv[1], new); free(value); value = new; diff --git a/regsub.c b/regsub.c new file mode 100644 index 00000000..53c83a8f --- /dev/null +++ b/regsub.c @@ -0,0 +1,98 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * 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 <sys/types.h> + +#include <regex.h> +#include <string.h> + +#include "tmux.h" + +static void +regsub_copy(char **buf, size_t *len, const char *text, size_t start, + size_t end) +{ + size_t add = end - start; + + *buf = xrealloc(*buf, (*len) + add + 1); + memcpy((*buf) + *len, text + start, add); + (*len) += add; +} + +static void +regsub_expand(char **buf, size_t *len, const char *with, const char *text, + regmatch_t *m, u_int n) +{ + const char *cp; + u_int i; + + for (cp = with; *cp != '\0'; cp++) { + if (*cp == '\\') { + cp++; + if (*cp >= '0' && *cp <= '9') { + i = *cp - '0'; + if (i < n && m[i].rm_so != m[i].rm_eo) { + regsub_copy(buf, len, text, m[i].rm_so, + m[i].rm_eo); + continue; + } + } + } + *buf = xrealloc(*buf, (*len) + 2); + (*buf)[(*len)++] = *cp; + } +} + +char * +regsub(const char *pattern, const char *with, const char *text, int flags) +{ + regex_t r; + regmatch_t m[10]; + size_t start, end, len = 0; + char *buf = NULL; + + if (*text == '\0') + return (xstrdup("")); + if (regcomp(&r, pattern, flags) != 0) + return (NULL); + + start = 0; + end = strlen(text); + + while (start != end) { + m[0].rm_so = start; + m[0].rm_eo = end; + + if (regexec(&r, text, nitems(m), m, REG_STARTEND) != 0) { + regsub_copy(&buf, &len, text, start, end); + break; + } + if (m[0].rm_so == m[0].rm_eo) { + regsub_copy(&buf, &len, text, start, end); + break; + } + + regsub_copy(&buf, &len, text, start, m[0].rm_so); + regsub_expand(&buf, &len, with, text, m, nitems(m)); + start = m[0].rm_eo; + } + buf[len] = '\0'; + + regfree(&r); + return (buf); +} diff --git a/tmux.1 b/tmux.1 index fe27105d..9188ba47 100644 --- a/tmux.1 +++ b/tmux.1 @@ -587,9 +587,9 @@ or .Ql %endif . For example: .Bd -literal -offset indent -%if #{==:#{host},myhost} +%if "#{==:#{host},myhost}" set -g status-style bg=red -%elif #{==:#{host},myotherhost} +%elif "#{==:#{host},myotherhost}" set -g status-style bg=green %else set -g status-style bg=blue @@ -4017,25 +4017,45 @@ if running on .Ql myhost , otherwise by .Ql 0 . -An -.Ql m -specifies an -.Xr fnmatch 3 -comparison where the first argument is the pattern and the second the string to -compare, for example -.Ql #{m:*foo*,#{host}} . .Ql || and .Ql && evaluate to true if either or both of two comma-separated alternatives are true, for example .Ql #{||:#{pane_in_mode},#{alternate_on}} . +.Pp +An +.Ql m +specifies an +.Xr fnmatch 3 +or regular expression comparison. +The first argument is the pattern and the second the string to compare. +An optional third argument specifies flags: +.Ql r +means the pattern is a regular expression instead of the default +.Xr fnmatch 3 +pattern, and +.Ql i +means to ignore case. +For example: +.Ql #{m:*foo*,#{host}} +or +.Ql #{m/ri:^A,MYVAR} . A .Ql C performs a search for an .Xr fnmatch 3 -pattern in the pane content and evaluates to zero if not found, or a line -number if found. +pattern or regular expression in the pane content and evaluates to zero if not +found, or a line number if found. +Like +.Ql m , +an +.Ql r +flag means search for a regular expression and +.Ql i +ignores case. +For example: +.Ql #{C/r:^Start} .Pp A limit may be placed on the length of the resultant string by prefixing it by an @@ -4108,6 +4128,14 @@ will substitute with .Ql bar throughout. +The first argument may be an extended regular expression and a final argument may be +.Ql i +to ignore case, for example +.Ql s/a(.)/\e1x/i: +would change +.Ql abABab +into +.Ql bxBxbx . .Pp In addition, the first line of a shell command's output may be inserted using .Ql #() . diff --git a/tmux.h b/tmux.h index 41a98c92..1b784104 100644 --- a/tmux.h +++ b/tmux.h @@ -2398,7 +2398,8 @@ void window_pane_key(struct window_pane *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); int window_pane_visible(struct window_pane *); -u_int window_pane_search(struct window_pane *, const char *); +u_int window_pane_search(struct window_pane *, const char *, int, + int); const char *window_printable_flags(struct winlink *); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); @@ -2635,4 +2636,7 @@ int style_is_default(struct style *); struct winlink *spawn_window(struct spawn_context *, char **); struct window_pane *spawn_pane(struct spawn_context *, char **); +/* regsub.c */ +char *regsub(const char *, const char *, const char *, int); + #endif /* TMUX_H */ diff --git a/window-copy.c b/window-copy.c index d81073bf..a7cb5dfa 100644 --- a/window-copy.c +++ b/window-copy.c @@ -3027,8 +3027,8 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely) window_copy_other_end(wme); - data->cx = data->lastcx; if (scroll_only || data->cy == 0) { + data->cx = data->lastcx; window_copy_scroll_down(wme, 1); if (scroll_only) { if (data->cy == screen_size_y(s) - 1) @@ -3037,7 +3037,7 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) window_copy_redraw_lines(wme, data->cy, 2); } } else { - window_copy_update_cursor(wme, data->cx, data->cy - 1); + window_copy_update_cursor(wme, data->lastcx, data->cy - 1); if (window_copy_update_selection(wme, 1)) { if (data->cy == screen_size_y(s) - 1) window_copy_redraw_lines(wme, data->cy, 1); @@ -3077,13 +3077,13 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely) window_copy_other_end(wme); - data->cx = data->lastcx; if (scroll_only || data->cy == screen_size_y(s) - 1) { + data->cx = data->lastcx; window_copy_scroll_up(wme, 1); if (scroll_only && data->cy > 0) window_copy_redraw_lines(wme, data->cy - 1, 2); } else { - window_copy_update_cursor(wme, data->cx, data->cy + 1); + window_copy_update_cursor(wme, data->lastcx, data->cy + 1); if (window_copy_update_selection(wme, 1)) window_copy_redraw_lines(wme, data->cy - 1, 2); } diff --git a/window.c b/window.c index a6e15a34..4c5f7817 100644 --- a/window.c +++ b/window.c @@ -22,6 +22,7 @@ #include <errno.h> #include <fcntl.h> #include <fnmatch.h> +#include <regex.h> #include <signal.h> #include <stdint.h> #include <stdlib.h> @@ -1207,24 +1208,41 @@ window_pane_visible(struct window_pane *wp) } u_int -window_pane_search(struct window_pane *wp, const char *searchstr) +window_pane_search(struct window_pane *wp, const char *term, int regex, + int ignore) { struct screen *s = &wp->base; - char *newsearchstr, *line; + regex_t r; + char *new = NULL, *line; u_int i; + int flags = 0, found; - xasprintf(&newsearchstr, "*%s*", searchstr); + if (!regex) { + if (ignore) + flags |= FNM_CASEFOLD; + xasprintf(&new, "*%s*", term); + } else { + if (ignore) + flags |= REG_ICASE; + if (regcomp(&r, term, flags|REG_EXTENDED) != 0) + return (0); + } for (i = 0; i < screen_size_y(s); i++) { line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s)); - if (fnmatch(newsearchstr, line, 0) == 0) { - free(line); - break; - } + if (!regex) + found = (fnmatch(new, line, 0) == 0); + else + found = (regexec(&r, line, 0, NULL, 0) == 0); free(line); + if (found) + break; } + if (!regex) + free(new); + else + regfree(&r); - free(newsearchstr); if (i == screen_size_y(s)) return (0); return (i + 1);