diff options
| -rw-r--r-- | runtime/doc/cmdline.txt | 6 | ||||
| -rw-r--r-- | src/nvim/ex_getln.c | 64 | ||||
| -rw-r--r-- | src/nvim/search.c | 143 | ||||
| -rw-r--r-- | test/functional/ui/searchhl_spec.lua | 10 | ||||
| -rw-r--r-- | test/old/testdir/test_search.vim | 21 |
5 files changed, 159 insertions, 85 deletions
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index bc250e7825..2dc7ac0a0f 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -449,7 +449,8 @@ CTRL-L A match is done on the pattern in front of the cursor. If *c_CTRL-G* */_CTRL-G* CTRL-G While the "/" or "?" 'incsearch' prompt is active, CTRL-G moves to the next match (without changing the |jumplist|). - Does not take |search-offset| into account. + The |search-offset| is applied when <CR> is pressed, but does + not affect the match highlighting. Use CTRL-T to move to the previous match. Mnemonic: on a regular keyboard G is below T. @@ -457,7 +458,8 @@ CTRL-G While the "/" or "?" 'incsearch' prompt is active, CTRL-G *c_CTRL-T* */_CTRL-T* CTRL-T While the "/" or "?" 'incsearch' prompt is active, CTRL-T moves to the previous match (without changing the |jumplist|). - Does not take |search-offset| into account. + The |search-offset| is applied when <CR> is pressed, but does + not affect the match highlighting. Use CTRL-G to move to the next match. Mnemonic: on a regular keyboard T is above G. diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 2278ba2494..6cc697cc6e 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1595,31 +1595,19 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s ui_flush(); pos_T t; - char *pat; + char *pat = ccline.cmdbuff + skiplen; + char *dircp = NULL; + char *searchstr = pat; + char *strcopy = NULL; + size_t searchstrlen = (size_t)patlen; + SearchOffset offset; int search_flags = SEARCH_NOOF; + size_t patlen_s = (size_t)(ccline.cmdlen - skiplen); - if (search_delim == ccline.cmdbuff[skiplen]) { - pat = last_search_pattern(); - if (pat == NULL) { - restore_last_search_pattern(); - return FAIL; - } - skiplen = 0; - patlen = (int)last_search_pattern_len(); - } else { - pat = ccline.cmdbuff + skiplen; - } - - bool bslsh = false; // do not search for the search end delimiter, // unless it is part of the pattern - if (patlen > 2 && firstc == pat[patlen - 1]) { - patlen--; - if (pat[patlen - 1] == '\\') { - pat[patlen - 1] = (char)(uint8_t)firstc; - bslsh = true; - } - } + parse_search_pattern_offset(&pat, &patlen_s, search_delim, SEARCH_OPT, &strcopy, &searchstr, + &searchstrlen, &dircp, &offset); if (next_match) { t = s->match_end; @@ -1636,20 +1624,39 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s search_flags += SEARCH_KEEP; } emsg_off++; - char save = pat[patlen]; - pat[patlen] = NUL; int found = searchit(curwin, curbuf, &t, NULL, next_match ? FORWARD : BACKWARD, - pat, (size_t)patlen, count, search_flags, + searchstr, searchstrlen, count, search_flags, RE_SEARCH, NULL); emsg_off--; - pat[patlen] = save; - if (bslsh) { - pat[patlen - 1] = '\\'; + if (dircp != NULL) { + *dircp = (char)search_delim; } ui_busy_stop(); if (found) { - s->search_start = s->match_start; + pos_T match_start = s->match_start; + pos_T match_end = s->match_end; + int64_t off = offset.off; + + s->search_start = match_start; + if (!offset.line && (offset.end || off != 0)) { + if (offset.end) { + s->search_start = match_end; + decl(&s->search_start); + } + while (off > 0) { + if (incl(&s->search_start) == -1) { + break; + } + off--; + } + while (off < 0) { + if (decl(&s->search_start) == -1) { + break; + } + off++; + } + } s->match_end = t; s->match_start = t; if (!next_match && firstc != '?') { @@ -1690,6 +1697,7 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s } else { vim_beep(kOptBoFlagError); } + xfree(strcopy); restore_last_search_pattern(); return FAIL; } diff --git a/src/nvim/search.c b/src/nvim/search.c index 555f6e6d94..89555afb41 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1024,6 +1024,91 @@ static int first_submatch(regmmatch_T *rp) return submatch; } +/// Parse a search pattern followed by an optional offset (e.g. "pat/e+1"). +/// On entry "*pat" points at the start of the pattern and "*patlen" is its +/// length. Updates the in/out parameters: +/// *pat / *patlen - moved past the pattern and offset +/// *strcopy - allocated copy if "\?" or "\/" was unescaped +/// (caller must vim_free() it) +/// *searchstr and *searchstrlen - pointer/length of the search pattern only +/// *dircp - location of the trailing delimiter that was +/// replaced with NUL (or NULL); caller may restore +/// it +/// *offset - parsed offset (line/end/off) +/// +/// Returns the length of the parsed pattern + offset (used by get_address() +/// to know how much of the command line was consumed). +int parse_search_pattern_offset(char **pat, size_t *patlen, int search_delim, int options, + char **strcopy, char **searchstr, size_t *searchstrlen, + char **dircp, SearchOffset *offset) +{ + if (*pat == NULL || **pat == NUL) { + return 0; + } + + int cmdlen = 0; + char *p; + char *ps = *strcopy; + + *searchstr = *pat; + *searchstrlen = *patlen; + *dircp = NULL; + + // Find end of regular expression. + // If there is a matching '/' or '?', toss it. + p = skip_regexp_ex(*pat, search_delim, magic_isset(), strcopy, NULL, NULL); + if (*strcopy != ps) { + size_t len = strlen(*strcopy); + // made a copy of "pat" to change "\?" to "?" + cmdlen += (int)(*patlen - len); + *pat = *strcopy; + *patlen = len; + *searchstr = *strcopy; + *searchstrlen = len; + } + if (*p == search_delim) { + *searchstrlen = (size_t)(p - *pat); + *dircp = p; // remember where we put the NUL + *p++ = NUL; + } + + offset->line = false; + offset->end = false; + offset->off = 0; + // Check for a line offset or a character offset. + // For get_address (echo off) we don't check for a character + // offset, because it is meaningless and the 's' could be a + // substitute command. + if (*p == '+' || *p == '-' || ascii_isdigit(*p)) { + offset->line = true; + } else if ((options & SEARCH_OPT) && (*p == 'e' || *p == 's' || *p == 'b')) { + if (*p == 'e') { // end + offset->end = true; + } + p++; + } + if (ascii_isdigit(*p) || *p == '+' || *p == '-') { // got an offset + if (ascii_isdigit(*p) || ascii_isdigit(*(p + 1))) { + offset->off = atol(p); + } else if (*p == '-') { // single '-' + offset->off = -1; + } else { // single '+' + offset->off = 1; + } + p++; + while (ascii_isdigit(*p)) { // skip number + p++; + } + } + + // compute length of search command for get_address() + cmdlen += (int)(p - *pat); + *patlen -= (size_t)(p - *pat); + *pat = p; // put pat after search command + + return cmdlen; +} + /// Highest level string search function. /// Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc' /// @@ -1059,7 +1144,6 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen int64_t c; char *dircp; char *strcopy = NULL; - char *ps; char *msgbuf = NULL; size_t msgbuflen = 0; bool has_offset = false; @@ -1134,60 +1218,9 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen } if (pat != NULL && *pat != NUL) { // look for (new) offset - // Find end of regular expression. - // If there is a matching '/' or '?', toss it. - ps = strcopy; - p = skip_regexp_ex(pat, search_delim, magic_isset(), &strcopy, NULL, NULL); - if (strcopy != ps) { - size_t len = strlen(strcopy); - // made a copy of "pat" to change "\?" to "?" - searchcmdlen += (int)(patlen - len); - pat = strcopy; - patlen = len; - searchstr = strcopy; - searchstrlen = len; - } - if (*p == search_delim) { - searchstrlen = (size_t)(p - pat); - dircp = p; // remember where we put the NUL - *p++ = NUL; - } - spats[0].off.line = false; - spats[0].off.end = false; - spats[0].off.off = 0; - // Check for a line offset or a character offset. - // For get_address (echo off) we don't check for a character - // offset, because it is meaningless and the 's' could be a - // substitute command. - if (*p == '+' || *p == '-' || ascii_isdigit(*p)) { - spats[0].off.line = true; - } else if ((options & SEARCH_OPT) - && (*p == 'e' || *p == 's' || *p == 'b')) { - if (*p == 'e') { // end - spats[0].off.end = true; - } - p++; - } - if (ascii_isdigit(*p) || *p == '+' || *p == '-') { // got an offset - // 'nr' or '+nr' or '-nr' - if (ascii_isdigit(*p) || ascii_isdigit(*(p + 1))) { - spats[0].off.off = atol(p); - } else if (*p == '-') { // single '-' - spats[0].off.off = -1; - } else { // single '+' - spats[0].off.off = 1; - } - p++; - while (ascii_isdigit(*p)) { // skip number - p++; - } - } - - // compute length of search command for get_address() - searchcmdlen += (int)(p - pat); - - patlen -= (size_t)(p - pat); - pat = p; // put pat after search command + searchcmdlen += parse_search_pattern_offset(&pat, &patlen, search_delim, options, + &strcopy, &searchstr, &searchstrlen, &dircp, + &spats[0].off); } bool show_search_stats = false; diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 2819090294..4e5e7e41d9 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -574,6 +574,16 @@ describe('search highlighting', function() {1:~ }|*4 /mat/e | ]]) + + command([[call setline(1, ['blah blah blah']) | 2,$delete_]]) + feed('gg0/blah/e<C-g><cr>') + eq({ 0, 1, 9, 0 }, fn.getpos('.')) + + feed('gg0/blah/e<C-g><C-g><cr>') + eq({ 0, 1, 14, 0 }, fn.getpos('.')) + + feed('gg0/blah/e<C-g><C-g><C-t><cr>') + eq({ 0, 1, 9, 0 }, fn.getpos('.')) end) it('works with multiline regexps', function() diff --git a/test/old/testdir/test_search.vim b/test/old/testdir/test_search.vim index 8071eef014..f60ac4d333 100644 --- a/test/old/testdir/test_search.vim +++ b/test/old/testdir/test_search.vim @@ -642,6 +642,27 @@ func Test_search_cmdline7() call feedkeys("//e\<c-g>\<cr>", 'tx') call assert_equal('1 bbvimb', getline('.')) call assert_equal(4, col('.')) + call cursor(1, 1) + call feedkeys("//+1\<c-g>\<cr>", 'tx') + call assert_equal(' 2 bbvimb', getline('.')) + call assert_equal([0, 2, 1, 0], getpos('.')) + call setline(1, ['blah blah blah']) + call feedkeys("gg0/blah/e\<c-g>\<cr>", 'tx') + call assert_equal([0, 1, 9, 0], getpos('.')) + call feedkeys("gg0/blah/e\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0, 1, 14, 0], getpos('.')) + call feedkeys("gg0/blah/e\<c-g>\<c-g>\<c-t>\<cr>", 'tx') + call assert_equal([0, 1, 9, 0], getpos('.')) + call cursor(1, col('$')) + call feedkeys("?blah?e\<c-g>\<cr>", 'tx') + call assert_equal([0, 1, 9, 0], getpos('.')) + call feedkeys("gg0/blah/e+1\<c-g>\<cr>", 'tx') + call assert_equal([0, 1, 10, 0], getpos('.')) + call feedkeys("gg0/blah/e-2\<c-g>\<cr>", 'tx') + call assert_equal([0, 1, 7, 0], getpos('.')) + call setline(1, ['a/b a/b a/b']) + call feedkeys("gg0/a\\/b/e\<c-g>\<cr>", 'tx') + call assert_equal([0, 1, 7, 0], getpos('.')) set noincsearch call Ntest_override("char_avail", 0) |
