From b50ac07cba558c370be66226ae0ad762157ee59a Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Wed, 7 Jul 2021 09:23:30 +0100 Subject: vi: allow 'gg' to specify a range Commit 7b93e317c (vi: enable 'dG' command. Closes 11801) allowed 'G' to be used as a range specifier for change/yank/delete operations. Add similar support for 'gg'. This requires setting the 'cmd_error' flag if 'g' is followed by any character other than another 'g'. function old new delta do_cmd 4852 4860 +8 .rodata 108179 108180 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 9/0) Total: 9 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- editors/vi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'editors/vi.c') diff --git a/editors/vi.c b/editors/vi.c index f0bbc9518..22b8f7cf1 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -3437,7 +3437,7 @@ static int find_range(char **start, char **stop, int cmd) // for non-change operations WS after NL is not part of word if (cmd != 'c' && dot != t && *dot != '\n') dot = t; - } else if (strchr("GHL+-jk'\r\n", c)) { + } else if (strchr("GHL+-gjk'\r\n", c)) { // these operate on whole lines buftype = WHOLE; do_cmd(c); // execute movement cmd @@ -4027,6 +4027,7 @@ static void do_cmd(int c) buf[1] = (c1 >= 0 ? c1 : '*'); buf[2] = '\0'; not_implemented(buf); + cmd_error = TRUE; break; } if (cmdcnt == 0) -- cgit v1.2.3-55-g6feb From 2916443ab650b10fd401ceb221d26fa58e2e99b3 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Fri, 2 Jul 2021 08:23:06 +0100 Subject: vi: use basic regular expressions for search Both traditional vi and vim use basic regular expressions for search. Also, they don't allow matches to extend across line endings. Thus with the file: 123 234 the search '/2.*4$' should find the second '2', not the first. Make BusyBox vi do the same. Whether or not VI_REGEX_SEARCH is enabled: function old new delta ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/0 up/down: 0/0) Total: 0 bytes Signed-off-by: Andrey Dobrovolsky Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- editors/vi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'editors/vi.c') diff --git a/editors/vi.c b/editors/vi.c index 22b8f7cf1..34d577e90 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -2378,9 +2378,9 @@ static char *char_search(char *p, const char *pat, int dir_and_range) char *q; int i, size, range, start; - re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; + re_syntax_options = RE_SYNTAX_POSIX_BASIC & (~RE_DOT_NEWLINE); if (ignorecase) - re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE; + re_syntax_options |= RE_ICASE; memset(&preg, 0, sizeof(preg)); err = re_compile_pattern(pat, strlen(pat), &preg); -- cgit v1.2.3-55-g6feb From c76c78740a19ed3b1f9c5910313460221096536a Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Tue, 6 Jul 2021 07:43:57 +0100 Subject: vi: improve handling of anchored searches Suppose we search for a git conflict marker '<<<<<<< HEAD' using the command '/^<<<'. Using 'n' to go to the next match finds '<<<' on the current line, apparently ignoring the '^' anchor. Set a flag in the compiled regular expression to indicate that the start of the string should not be considered a beginning-of-line anchor. An exception has to be made when the search starts from the beginning of the file. Make a similar change for end-of-line anchors. This doesn't affect a default build with VI_REGEX_SEARCH disabled. When it's enabled: function old new delta char_search 247 285 +38 Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- editors/vi.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'editors/vi.c') diff --git a/editors/vi.c b/editors/vi.c index 34d577e90..2941b8ae4 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -2384,6 +2384,8 @@ static char *char_search(char *p, const char *pat, int dir_and_range) memset(&preg, 0, sizeof(preg)); err = re_compile_pattern(pat, strlen(pat), &preg); + preg.not_bol = p != text; + preg.not_eol = p != end - 1; if (err != NULL) { status_line_bold("bad search pattern '%s': %s", pat, err); return p; -- cgit v1.2.3-55-g6feb From 95ac4a48f17c2fdd2a10524c0b399e3be72d8f42 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 13 Jul 2021 14:38:20 +0200 Subject: vi: allow regular expressions in ':s' commands BusyBox vi has never supported the use of regular expressions in search/replace (':s') commands. Implement this using GNU regex when VI_REGEX_SEARCH is enabled. The implementation: - uses basic regular expressions, to match those used in the search command; - only supports substitution of back references ('\0' - '\9') in the replacement string. Any other character following a backslash is treated as that literal character. VI_REGEX_SEARCH isn't enabled in the default build. In that case: function old new delta colon 4036 4033 -3 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-3) Total: -3 bytes When VI_REGEX_SEARCH is enabled: function old new delta colon 4036 4378 +342 .rodata 108207 108229 +22 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 364/0) Total: 364 bytes v2: Rebase. Code shrink. Ensure empty replacement string is null terminated. Signed-off-by: Andrey Dobrovolsky Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- editors/vi.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 116 insertions(+), 15 deletions(-) (limited to 'editors/vi.c') diff --git a/editors/vi.c b/editors/vi.c index 2941b8ae4..070e0f55a 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -2677,6 +2677,59 @@ static char *expand_args(char *args) # endif #endif /* FEATURE_VI_COLON */ +#if ENABLE_FEATURE_VI_REGEX_SEARCH +# define MAX_SUBPATTERN 10 // subpatterns \0 .. \9 + +// If the return value is not NULL the caller should free R +static char *regex_search(char *q, regex_t *preg, const char *Rorig, + size_t *len_F, size_t *len_R, char **R) +{ + regmatch_t regmatch[MAX_SUBPATTERN], *cur_match; + char *found = NULL; + const char *t; + char *r; + + regmatch[0].rm_so = 0; + regmatch[0].rm_eo = end_line(q) - q; + if (regexec(preg, q, MAX_SUBPATTERN, regmatch, REG_STARTEND) != 0) + return found; + + found = q + regmatch[0].rm_so; + *len_F = regmatch[0].rm_eo - regmatch[0].rm_so; + *R = NULL; + + fill_result: + // first pass calculates len_R, second fills R + *len_R = 0; + for (t = Rorig, r = *R; *t; t++) { + size_t len = 1; // default is to copy one char from replace pattern + const char *from = t; + if (*t == '\\') { + from = ++t; // skip backslash + if (*t >= '0' && *t < '0' + MAX_SUBPATTERN) { + cur_match = regmatch + (*t - '0'); + if (cur_match->rm_so >= 0) { + len = cur_match->rm_eo - cur_match->rm_so; + from = q + cur_match->rm_so; + } + } + } + *len_R += len; + if (*R) { + memcpy(r, from, len); + r += len; + /* *r = '\0'; - xzalloc did it */ + } + } + if (*R == NULL) { + *R = xzalloc(*len_R + 1); + goto fill_result; + } + + return found; +} +#endif /* ENABLE_FEATURE_VI_REGEX_SEARCH */ + // buf must be no longer than MAX_INPUT_LEN! static void colon(char *buf) { @@ -3083,6 +3136,14 @@ static void colon(char *buf) int subs = 0; // number of substitutions # if ENABLE_FEATURE_VI_VERBOSE_STATUS int last_line = 0, lines = 0; +# endif +# if ENABLE_FEATURE_VI_REGEX_SEARCH + regex_t preg; + int cflags; + char *Rorig; +# if ENABLE_FEATURE_VI_UNDO + int undo = 0; +# endif # endif // F points to the "find" pattern @@ -3100,7 +3161,6 @@ static void colon(char *buf) *flags++ = '\0'; // terminate "replace" gflag = *flags; } - len_R = strlen(R); if (len_F) { // save "find" as last search pattern free(last_search_pattern); @@ -3122,31 +3182,68 @@ static void colon(char *buf) b = e; } +# if ENABLE_FEATURE_VI_REGEX_SEARCH + Rorig = R; + cflags = 0; + if (ignorecase) + cflags = REG_ICASE; + memset(&preg, 0, sizeof(preg)); + if (regcomp(&preg, F, cflags) != 0) { + status_line(":s bad search pattern"); + goto regex_search_end; + } +# else + len_R = strlen(R); +# endif + for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 char *ls = q; // orig line start char *found; vc4: +# if ENABLE_FEATURE_VI_REGEX_SEARCH + found = regex_search(q, &preg, Rorig, &len_F, &len_R, &R); +# else found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find" +# endif if (found) { uintptr_t bias; // we found the "find" pattern - delete it // For undo support, the first item should not be chained - text_hole_delete(found, found + len_F - 1, - subs ? ALLOW_UNDO_CHAIN: ALLOW_UNDO); - // can't do this above, no undo => no third argument - subs++; -# if ENABLE_FEATURE_VI_VERBOSE_STATUS - if (last_line != i) { - last_line = i; - ++lines; + // This needs to be handled differently depending on + // whether or not regex support is enabled. +# if ENABLE_FEATURE_VI_REGEX_SEARCH +# define TEST_LEN_F len_F // len_F may be zero +# define TEST_UNDO1 undo++ +# define TEST_UNDO2 undo++ +# else +# define TEST_LEN_F 1 // len_F is never zero +# define TEST_UNDO1 subs +# define TEST_UNDO2 1 +# endif + if (TEST_LEN_F) // match can be empty, no delete needed + text_hole_delete(found, found + len_F - 1, + TEST_UNDO1 ? ALLOW_UNDO_CHAIN: ALLOW_UNDO); + if (len_R) { // insert the "replace" pattern, if required + bias = string_insert(found, R, + TEST_UNDO2 ? ALLOW_UNDO_CHAIN: ALLOW_UNDO); + found += bias; + ls += bias; + dot = ls; + //q += bias; - recalculated anyway } +# if ENABLE_FEATURE_VI_REGEX_SEARCH + free(R); # endif - // insert the "replace" patern - bias = string_insert(found, R, ALLOW_UNDO_CHAIN); - found += bias; - ls += bias; - dot = ls; - //q += bias; - recalculated anyway + if (TEST_LEN_F || len_R) { + dot = ls; + subs++; +# if ENABLE_FEATURE_VI_VERBOSE_STATUS + if (last_line != i) { + last_line = i; + ++lines; + } +# endif + } // check for "global" :s/foo/bar/g if (gflag == 'g') { if ((found + len_R) < end_line(ls)) { @@ -3166,6 +3263,10 @@ static void colon(char *buf) status_line("%d substitutions on %d lines", subs, lines); # endif } +# if ENABLE_FEATURE_VI_REGEX_SEARCH + regex_search_end: + regfree(&preg); +# endif # endif /* FEATURE_VI_SEARCH */ } else if (strncmp(cmd, "version", i) == 0) { // show software version status_line(BB_VER); -- cgit v1.2.3-55-g6feb From 2759201401cf87a9a8621edd9e5f44dfe838407c Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Sat, 10 Jul 2021 11:00:04 +0100 Subject: vi: allow delimiter in ':s' to be escaped When regular expressions are allowed in search commands it becomes possible to escape the delimiter in search/replace commands. For example, this command will replace '/abc' with '/abc/': :s/\/abc/\/abc\//g The code to split the command into 'find' and 'replace' strings should allow for this possibility. VI_REGEX_SEARCH isn't enabled by default. When it is: function old new delta strchr_backslash - 38 +38 colon 4378 4373 -5 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/1 up/down: 38/-5) Total: 33 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- editors/vi.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'editors/vi.c') diff --git a/editors/vi.c b/editors/vi.c index 070e0f55a..c6bb74cfb 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -2680,6 +2680,19 @@ static char *expand_args(char *args) #if ENABLE_FEATURE_VI_REGEX_SEARCH # define MAX_SUBPATTERN 10 // subpatterns \0 .. \9 +// Like strchr() but skipping backslash-escaped characters +static char *strchr_backslash(const char *s, int c) +{ + for (; *s; ++s) { + if (*s == c) { + return (char *)s; + } else if (*s == '\\' && *++s == '\0') { + break; + } + } + return NULL; +} + // If the return value is not NULL the caller should free R static char *regex_search(char *q, regex_t *preg, const char *Rorig, size_t *len_F, size_t *len_R, char **R) @@ -2728,6 +2741,8 @@ static char *regex_search(char *q, regex_t *preg, const char *Rorig, return found; } +#else /* !ENABLE_FEATURE_VI_REGEX_SEARCH */ +# define strchr_backslash(s, c) strchr(s, c) #endif /* ENABLE_FEATURE_VI_REGEX_SEARCH */ // buf must be no longer than MAX_INPUT_LEN! @@ -3151,12 +3166,12 @@ static void colon(char *buf) // replace the cmd line delimiters "/" with NULs c = buf[1]; // what is the delimiter F = buf + 2; // start of "find" - R = strchr(F, c); // middle delimiter + R = strchr_backslash(F, c); // middle delimiter if (!R) goto colon_s_fail; len_F = R - F; *R++ = '\0'; // terminate "find" - flags = strchr(R, c); + flags = strchr_backslash(R, c); if (flags) { *flags++ = '\0'; // terminate "replace" gflag = *flags; -- cgit v1.2.3-55-g6feb From 36feb2682481d55ae49df899c38e16130227ba4a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 13 Jul 2021 16:16:21 +0200 Subject: vi: somewhat more readable code, no logic changes Signed-off-by: Denys Vlasenko --- editors/vi.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'editors/vi.c') diff --git a/editors/vi.c b/editors/vi.c index c6bb74cfb..5c601c759 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -2683,12 +2683,13 @@ static char *expand_args(char *args) // Like strchr() but skipping backslash-escaped characters static char *strchr_backslash(const char *s, int c) { - for (; *s; ++s) { + while (*s) { if (*s == c) { return (char *)s; - } else if (*s == '\\' && *++s == '\0') { - break; - } + if (*s == '\\') + if (*++s == '\0') + break; + s++; } return NULL; } @@ -3237,10 +3238,10 @@ static void colon(char *buf) # endif if (TEST_LEN_F) // match can be empty, no delete needed text_hole_delete(found, found + len_F - 1, - TEST_UNDO1 ? ALLOW_UNDO_CHAIN: ALLOW_UNDO); - if (len_R) { // insert the "replace" pattern, if required + TEST_UNDO1 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO); + if (len_R != 0) { // insert the "replace" pattern, if required bias = string_insert(found, R, - TEST_UNDO2 ? ALLOW_UNDO_CHAIN: ALLOW_UNDO); + TEST_UNDO2 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO); found += bias; ls += bias; dot = ls; @@ -3249,7 +3250,7 @@ static void colon(char *buf) # if ENABLE_FEATURE_VI_REGEX_SEARCH free(R); # endif - if (TEST_LEN_F || len_R) { + if (TEST_LEN_F || len_R != 0) { dot = ls; subs++; # if ENABLE_FEATURE_VI_VERBOSE_STATUS -- cgit v1.2.3-55-g6feb From e6f4145f2961bfd500214ef1fcf07543ffacb603 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Tue, 13 Jul 2021 16:35:43 +0100 Subject: vi: fix regex search compilation error Building with FEATURE_VI_REGEX_SEARCH enabled fails. Signed-off-by: Ron Yorston Signed-off-by: Bernhard Reutner-Fischer --- editors/vi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/vi.c') diff --git a/editors/vi.c b/editors/vi.c index 5c601c759..a4b958734 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -2684,7 +2684,7 @@ static char *expand_args(char *args) static char *strchr_backslash(const char *s, int c) { while (*s) { - if (*s == c) { + if (*s == c) return (char *)s; if (*s == '\\') if (*++s == '\0') -- cgit v1.2.3-55-g6feb From 95fffd8a7fd49637d7b6d9f25b69ac6d8c9a2fff Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 14 Jul 2021 16:28:43 +0200 Subject: vi: remove redundant assignment Signed-off-by: Denys Vlasenko --- editors/vi.c | 1 - 1 file changed, 1 deletion(-) (limited to 'editors/vi.c') diff --git a/editors/vi.c b/editors/vi.c index a4b958734..23a44d597 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -3244,7 +3244,6 @@ static void colon(char *buf) TEST_UNDO2 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO); found += bias; ls += bias; - dot = ls; //q += bias; - recalculated anyway } # if ENABLE_FEATURE_VI_REGEX_SEARCH -- cgit v1.2.3-55-g6feb