aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/cmdline.txt14
-rw-r--r--src/nvim/ex_getln.c286
-rw-r--r--src/nvim/normal.c6
-rw-r--r--src/nvim/testdir/test_search.vim273
-rw-r--r--src/nvim/version.c8
-rw-r--r--test/functional/legacy/search_spec.lua474
6 files changed, 976 insertions, 85 deletions
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index 948c921431..d870a72600 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -389,12 +389,26 @@ CTRL-L A match is done on the pattern in front of the cursor. If
If there are multiple matches the longest common part is
inserted in place of the pattern. If the result is shorter
than the pattern, no completion is done.
+ */_CTRL-L*
When 'incsearch' is set, entering a search pattern for "/" or
"?" and the current match is displayed then CTRL-L will add
one character from the end of the current match. If
'ignorecase' and 'smartcase' are set and the command line has
no uppercase characters, the added character is converted to
lowercase.
+ *c_CTRL-G* */_CTRL-G*
+CTRL-G When 'incsearch' is set, entering a search pattern for "/" or
+ "?" and the current match is displayed then CTRL-G will move
+ to the next match (does not take |search-offset| into account)
+ Use CTRL-T to move to the previous match. Hint: on a regular
+ keyboard T is above G.
+ *c_CTRL-T* */_CTRL-T*
+CTRL-T When 'incsearch' is set, entering a search pattern for "/" or
+ "?" and the current match is displayed then CTRL-T will move
+ to the previous match (does not take |search-offset| into
+ account).
+ Use CTRL-G to move to the next match. Hint: on a regular
+ keyboard T is above G.
The 'wildchar' option defaults to <Tab> (CTRL-E when in Vi compatible mode; in
a previous version <Esc> was used). In the pattern standard wildcards '*' and
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 858374c68e..7793081957 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -100,12 +100,20 @@ typedef struct command_line_state {
char_u *lookfor; // string to match
int hiscnt; // current history line in use
int histype; // history type to be used
- pos_T old_cursor;
+ pos_T search_start; // where 'incsearch' starts searching
+ pos_T save_cursor;
colnr_T old_curswant;
+ colnr_T init_curswant;
colnr_T old_leftcol;
+ colnr_T init_leftcol;
linenr_T old_topline;
+ linenr_T init_topline;
int old_topfill;
+ int init_topfill;
linenr_T old_botline;
+ linenr_T init_botline;
+ pos_T match_start;
+ pos_T match_end;
int did_incsearch;
int incsearch_postponed;
int did_wild_list; // did wild_list() recently
@@ -167,6 +175,12 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
s->save_State = State;
s->save_p_icm = vim_strsave(p_icm);
s->ignore_drag_release = true;
+ s->match_start = curwin->w_cursor;
+ s->init_curswant = curwin->w_curswant;
+ s->init_leftcol = curwin->w_leftcol;
+ s->init_topline = curwin->w_topline;
+ s->init_topfill = curwin->w_topfill;
+ s->init_botline = curwin->w_botline;
if (s->firstc == -1) {
s->firstc = NUL;
@@ -179,7 +193,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
}
ccline.overstrike = false; // always start in insert mode
- s->old_cursor = curwin->w_cursor; // needs to be restored later
+ clearpos(&s->match_end);
+ s->save_cursor = curwin->w_cursor; // may be restored later
+ s->search_start = curwin->w_cursor;
s->old_curswant = curwin->w_curswant;
s->old_leftcol = curwin->w_leftcol;
s->old_topline = curwin->w_topline;
@@ -282,7 +298,16 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ccline.xpc = NULL;
if (s->did_incsearch) {
- curwin->w_cursor = s->old_cursor;
+ if (s->gotesc) {
+ curwin->w_cursor = s->save_cursor;
+ } else {
+ if (!equalpos(s->save_cursor, s->search_start)) {
+ // put the '" mark at the original position
+ curwin->w_cursor = s->save_cursor;
+ setpcmark();
+ }
+ curwin->w_cursor = s->search_start;
+ }
curwin->w_curswant = s->old_curswant;
curwin->w_leftcol = s->old_leftcol;
curwin->w_topline = s->old_topline;
@@ -857,6 +882,118 @@ static int command_line_execute(VimState *state, int key)
return command_line_handle_key(s);
}
+static void command_line_next_incsearch(CommandLineState *s, bool next_match)
+{
+ ui_busy_start();
+ ui_flush();
+
+ pos_T t;
+ int search_flags = SEARCH_KEEP + SEARCH_NOOF + SEARCH_PEEK;
+ if (next_match) {
+ t = s->match_end;
+ search_flags += SEARCH_COL;
+ } else {
+ t = s->match_start;
+ }
+ emsg_off++;
+ s->i = searchit(curwin, curbuf, &t,
+ next_match ? FORWARD : BACKWARD,
+ ccline.cmdbuff, s->count, search_flags,
+ RE_SEARCH, 0, NULL);
+ emsg_off--;
+ ui_busy_stop();
+ if (s->i) {
+ s->search_start = s->match_start;
+ s->match_end = t;
+ s->match_start = t;
+ if (!next_match && s->firstc == '/') {
+ // move just before the current match, so that
+ // when nv_search finishes the cursor will be
+ // put back on the match
+ s->search_start = t;
+ (void)decl(&s->search_start);
+ }
+ if (lt(t, s->search_start) && next_match) {
+ // wrap around
+ s->search_start = t;
+ if (s->firstc == '?') {
+ (void)incl(&s->search_start);
+ } else {
+ (void)decl(&s->search_start);
+ }
+ }
+
+ set_search_match(&s->match_end);
+ curwin->w_cursor = s->match_start;
+ changed_cline_bef_curs();
+ update_topline();
+ validate_cursor();
+ highlight_match = true;
+ s->old_curswant = curwin->w_curswant;
+ s->old_leftcol = curwin->w_leftcol;
+ s->old_topline = curwin->w_topline;
+ s->old_topfill = curwin->w_topfill;
+ s->old_botline = curwin->w_botline;
+ update_screen(NOT_VALID);
+ redrawcmdline();
+ } else {
+ vim_beep(BO_ERROR);
+ }
+ return;
+}
+
+static void command_line_next_histidx(CommandLineState *s, bool next_match)
+{
+ s->j = (int)STRLEN(s->lookfor);
+ for (;; ) {
+ // one step backwards
+ if (!next_match) {
+ if (s->hiscnt == hislen) {
+ // first time
+ s->hiscnt = hisidx[s->histype];
+ } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) {
+ s->hiscnt = hislen - 1;
+ } else if (s->hiscnt != hisidx[s->histype] + 1) {
+ s->hiscnt--;
+ } else {
+ // at top of list
+ s->hiscnt = s->i;
+ break;
+ }
+ } else { // one step forwards
+ // on last entry, clear the line
+ if (s->hiscnt == hisidx[s->histype]) {
+ s->hiscnt = hislen;
+ break;
+ }
+
+ // not on a history line, nothing to do
+ if (s->hiscnt == hislen) {
+ break;
+ }
+
+ if (s->hiscnt == hislen - 1) {
+ // wrap around
+ s->hiscnt = 0;
+ } else {
+ s->hiscnt++;
+ }
+ }
+
+ if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) {
+ s->hiscnt = s->i;
+ break;
+ }
+
+ if ((s->c != K_UP && s->c != K_DOWN)
+ || s->hiscnt == s->i
+ || STRNCMP(history[s->histype][s->hiscnt].hisstr,
+ s->lookfor, (size_t)s->j) == 0) {
+ break;
+ }
+ }
+}
+
static int command_line_handle_key(CommandLineState *s)
{
// Big switch for a typed command line character.
@@ -929,6 +1066,16 @@ static int command_line_handle_key(CommandLineState *s)
// Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
+ if (ccline.cmdlen == 0) {
+ s->search_start = s->save_cursor;
+ // save view settings, so that the screen won't be restored at the
+ // wrong position
+ s->old_curswant = s->init_curswant;
+ s->old_leftcol = s->init_leftcol;
+ s->old_topline = s->init_topline;
+ s->old_topfill = s->init_topfill;
+ s->old_botline = s->init_botline;
+ }
redrawcmd();
} else if (ccline.cmdlen == 0 && s->c != Ctrl_W
&& ccline.cmdprompt == NULL && s->indent == 0) {
@@ -947,6 +1094,7 @@ static int command_line_handle_key(CommandLineState *s)
}
msg_putchar(' '); // delete ':'
}
+ s->search_start = s->save_cursor;
redraw_cmdline = true;
return 0; // back to cmd mode
}
@@ -1001,6 +1149,9 @@ static int command_line_handle_key(CommandLineState *s)
// Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
+ if (ccline.cmdlen == 0) {
+ s->search_start = s->save_cursor;
+ }
redrawcmd();
return command_line_changed(s);
@@ -1230,24 +1381,27 @@ static int command_line_handle_key(CommandLineState *s)
case Ctrl_L:
if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
// Add a character from under the cursor for 'incsearch'
- if (s->did_incsearch && !equalpos(curwin->w_cursor, s->old_cursor)) {
- s->c = gchar_cursor();
- // If 'ignorecase' and 'smartcase' are set and the
- // command line has no uppercase characters, convert
- // the character to lowercase
- if (p_ic && p_scs && !pat_has_uppercase(ccline.cmdbuff)) {
- s->c = mb_tolower(s->c);
- }
-
- if (s->c != NUL) {
- if (s->c == s->firstc
- || vim_strchr((char_u *)(p_magic ? "\\^$.*[" : "\\^$"), s->c)
- != NULL) {
- // put a backslash before special characters
- stuffcharReadbuff(s->c);
- s->c = '\\';
+ if (s->did_incsearch) {
+ curwin->w_cursor = s->match_end;
+ if (!equalpos(curwin->w_cursor, s->search_start)) {
+ s->c = gchar_cursor();
+ // If 'ignorecase' and 'smartcase' are set and the
+ // command line has no uppercase characters, convert
+ // the character to lowercase
+ if (p_ic && p_scs
+ && !pat_has_uppercase(ccline.cmdbuff)) {
+ s->c = mb_tolower(s->c);
+ }
+ if (s->c != NUL) {
+ if (s->c == s->firstc
+ || vim_strchr((char_u *)(p_magic ? "\\^$.*[" : "\\^$"), s->c)
+ != NULL) {
+ // put a backslash before special characters
+ stuffcharReadbuff(s->c);
+ s->c = '\\';
+ }
+ break;
}
- break;
}
}
return command_line_not_changed(s);
@@ -1266,7 +1420,7 @@ static int command_line_handle_key(CommandLineState *s)
0, s->firstc != '@') == FAIL) {
break;
}
- return command_line_changed(s);
+ return command_line_not_changed(s);
}
// fallthrough
@@ -1291,55 +1445,9 @@ static int command_line_handle_key(CommandLineState *s)
s->lookfor[ccline.cmdpos] = NUL;
}
- s->j = (int)STRLEN(s->lookfor);
- for (;; ) {
- // one step backwards
- if (s->c == K_UP|| s->c == K_S_UP || s->c == Ctrl_P
- || s->c == K_PAGEUP || s->c == K_KPAGEUP) {
- if (s->hiscnt == hislen) {
- // first time
- s->hiscnt = hisidx[s->histype];
- } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) {
- s->hiscnt = hislen - 1;
- } else if (s->hiscnt != hisidx[s->histype] + 1) {
- --s->hiscnt;
- } else {
- // at top of list
- s->hiscnt = s->i;
- break;
- }
- } else { // one step forwards
- // on last entry, clear the line
- if (s->hiscnt == hisidx[s->histype]) {
- s->hiscnt = hislen;
- break;
- }
-
- // not on a history line, nothing to do
- if (s->hiscnt == hislen) {
- break;
- }
-
- if (s->hiscnt == hislen - 1) {
- // wrap around
- s->hiscnt = 0;
- } else {
- ++s->hiscnt;
- }
- }
-
- if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) {
- s->hiscnt = s->i;
- break;
- }
-
- if ((s->c != K_UP && s->c != K_DOWN)
- || s->hiscnt == s->i
- || STRNCMP(history[s->histype][s->hiscnt].hisstr,
- s->lookfor, (size_t)s->j) == 0) {
- break;
- }
- }
+ bool next_match = (s->c == K_DOWN || s->c == K_S_DOWN || s->c == Ctrl_N
+ || s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN);
+ command_line_next_histidx(s, next_match);
if (s->hiscnt != s->i) {
// jumped to other entry
@@ -1407,6 +1515,17 @@ static int command_line_handle_key(CommandLineState *s)
beep_flush();
return command_line_not_changed(s);
+ case Ctrl_G: // next match
+ case Ctrl_T: // previous match
+ if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
+ if (char_avail()) {
+ return 1;
+ }
+ command_line_next_incsearch(s, s->c == Ctrl_G);
+ return command_line_not_changed(s);
+ }
+ break;
+
case Ctrl_V:
case Ctrl_Q:
s->ignore_drag_release = true;
@@ -1521,7 +1640,7 @@ static int command_line_changed(CommandLineState *s)
return 1;
}
s->incsearch_postponed = false;
- curwin->w_cursor = s->old_cursor; // start at old position
+ curwin->w_cursor = s->search_start; // start at old position
// If there is no command line, don't do anything
if (ccline.cmdlen == 0) {
@@ -1566,16 +1685,11 @@ static int command_line_changed(CommandLineState *s)
if (s->i != 0) {
pos_T save_pos = curwin->w_cursor;
- // First move cursor to end of match, then to the start. This
- // moves the whole match onto the screen when 'nowrap' is set.
- curwin->w_cursor.lnum += search_match_lines;
- curwin->w_cursor.col = search_match_endcol;
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- coladvance((colnr_T)MAXCOL);
- }
+ s->match_start = curwin->w_cursor;
+ set_search_match(&curwin->w_cursor);
validate_cursor();
end_pos = curwin->w_cursor;
+ s->match_end = end_pos;
curwin->w_cursor = save_pos;
} else {
end_pos = curwin->w_cursor; // shutup gcc 4
@@ -1617,7 +1731,7 @@ static int command_line_changed(CommandLineState *s)
emsg_silent--; // Unblock error reporting
// Restore the window "view".
- curwin->w_cursor = s->old_cursor;
+ curwin->w_cursor = s->save_cursor;
curwin->w_curswant = s->old_curswant;
curwin->w_leftcol = s->old_leftcol;
curwin->w_topline = s->old_topline;
@@ -5519,3 +5633,15 @@ histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
*new_hisnum = &(hisnum[history_type]);
return history[history_type];
}
+
+static void set_search_match(pos_T *t)
+{
+ // First move cursor to end of match, then to the start. This
+ // moves the whole match onto the screen when 'nowrap' is set.
+ t->lnum += search_match_lines;
+ t->col = search_match_endcol;
+ if (t->lnum > curbuf->b_ml.ml_line_count) {
+ t->lnum = curbuf->b_ml.ml_line_count;
+ coladvance((colnr_T)MAXCOL);
+ }
+}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 050020d79d..39cd2c6631 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -5231,6 +5231,7 @@ static void nv_dollar(cmdarg_T *cap)
static void nv_search(cmdarg_T *cap)
{
oparg_T *oap = cap->oap;
+ pos_T save_cursor = curwin->w_cursor;
if (cap->cmdchar == '?' && cap->oap->op_type == OP_ROT13) {
/* Translate "g??" to "g?g?" */
@@ -5240,6 +5241,8 @@ static void nv_search(cmdarg_T *cap)
return;
}
+ // When using 'incsearch' the cursor may be moved to set a different search
+ // start position.
cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0);
if (cap->searchbuf == NULL) {
@@ -5248,7 +5251,8 @@ static void nv_search(cmdarg_T *cap)
}
(void)normal_search(cap, cap->cmdchar, cap->searchbuf,
- (cap->arg ? 0 : SEARCH_MARK));
+ (cap->arg || !equalpos(save_cursor, curwin->w_cursor))
+ ? 0 : SEARCH_MARK);
}
/*
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index e85525e663..2106fc2dec 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -1,5 +1,278 @@
" Test for the search command
+func Test_search_cmdline()
+ " See test/functional/legacy/search_spec.lua
+ throw 'skipped: Nvim does not support test_disable_char_avail()'
+ if !exists('+incsearch')
+ return
+ endif
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_disable_char_avail(1)
+ new
+ call setline(1, [' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there', ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar'])
+ " Test 1
+ " CTRL-N / CTRL-P skips through the previous search history
+ set noincsearch
+ :1
+ call feedkeys("/foobar\<cr>", 'tx')
+ call feedkeys("/the\<cr>",'tx')
+ call assert_equal('the', @/)
+ call feedkeys("/thes\<C-P>\<C-P>\<cr>",'tx')
+ call assert_equal('foobar', @/)
+
+ " Test 2
+ " Ctrl-G goes from one match to the next
+ " until the end of the buffer
+ set incsearch nowrapscan
+ :1
+ " first match
+ call feedkeys("/the\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ :1
+ " second match
+ call feedkeys("/the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the', getline('.'))
+ call assert_equal([0, 0, 0, 0], getpos('"'))
+ :1
+ " third match
+ call feedkeys("/the".repeat("\<C-G>", 2)."\<cr>", 'tx')
+ call assert_equal(' 4 their', getline('.'))
+ :1
+ " fourth match
+ call feedkeys("/the".repeat("\<C-G>", 3)."\<cr>", 'tx')
+ call assert_equal(' 5 there', getline('.'))
+ :1
+ " fifth match
+ call feedkeys("/the".repeat("\<C-G>", 4)."\<cr>", 'tx')
+ call assert_equal(' 6 their', getline('.'))
+ :1
+ " sixth match
+ call feedkeys("/the".repeat("\<C-G>", 5)."\<cr>", 'tx')
+ call assert_equal(' 7 the', getline('.'))
+ :1
+ " seventh match
+ call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
+ call assert_equal(' 8 them', getline('.'))
+ :1
+ " eigth match
+ call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ :1
+ " no further match
+ call feedkeys("/the".repeat("\<C-G>", 8)."\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ call assert_equal([0, 0, 0, 0], getpos('"'))
+
+ " Test 3
+ " Ctrl-G goes from one match to the next
+ " and continues back at the top
+ set incsearch wrapscan
+ :1
+ " first match
+ call feedkeys("/the\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ :1
+ " second match
+ call feedkeys("/the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the', getline('.'))
+ :1
+ " third match
+ call feedkeys("/the".repeat("\<C-G>", 2)."\<cr>", 'tx')
+ call assert_equal(' 4 their', getline('.'))
+ :1
+ " fourth match
+ call feedkeys("/the".repeat("\<C-G>", 3)."\<cr>", 'tx')
+ call assert_equal(' 5 there', getline('.'))
+ :1
+ " fifth match
+ call feedkeys("/the".repeat("\<C-G>", 4)."\<cr>", 'tx')
+ call assert_equal(' 6 their', getline('.'))
+ :1
+ " sixth match
+ call feedkeys("/the".repeat("\<C-G>", 5)."\<cr>", 'tx')
+ call assert_equal(' 7 the', getline('.'))
+ :1
+ " seventh match
+ call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
+ call assert_equal(' 8 them', getline('.'))
+ :1
+ " eigth match
+ call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ :1
+ " back at first match
+ call feedkeys("/the".repeat("\<C-G>", 8)."\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+
+ " Test 4
+ " CTRL-T goes to the previous match
+ set incsearch nowrapscan
+ $
+ " first match
+ call feedkeys("?the\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ $
+ " first match
+ call feedkeys("?the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ $
+ " second match
+ call feedkeys("?the".repeat("\<C-T>", 1)."\<cr>", 'tx')
+ call assert_equal(' 8 them', getline('.'))
+ $
+ " last match
+ call feedkeys("?the".repeat("\<C-T>", 7)."\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ $
+ " last match
+ call feedkeys("?the".repeat("\<C-T>", 8)."\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+
+ " Test 5
+ " CTRL-T goes to the previous match
+ set incsearch wrapscan
+ $
+ " first match
+ call feedkeys("?the\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ $
+ " first match at the top
+ call feedkeys("?the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ $
+ " second match
+ call feedkeys("?the".repeat("\<C-T>", 1)."\<cr>", 'tx')
+ call assert_equal(' 8 them', getline('.'))
+ $
+ " last match
+ call feedkeys("?the".repeat("\<C-T>", 7)."\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ $
+ " back at the bottom of the buffer
+ call feedkeys("?the".repeat("\<C-T>", 8)."\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+
+ " Test 6
+ " CTRL-L adds to the search pattern
+ set incsearch wrapscan
+ 1
+ " first match
+ call feedkeys("/the\<c-l>\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " go to next match of 'thes'
+ call feedkeys("/the\<c-l>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ 1
+ " wrap around
+ call feedkeys("/the\<c-l>\<C-G>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " wrap around
+ set nowrapscan
+ call feedkeys("/the\<c-l>\<C-G>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+
+ " Test 7
+ " <bs> remove from match, but stay at current match
+ set incsearch wrapscan
+ 1
+ " first match
+ call feedkeys("/thei\<cr>", 'tx')
+ call assert_equal(' 4 their', getline('.'))
+ 1
+ " delete one char, add another
+ call feedkeys("/thei\<bs>s\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " delete one char, add another, go to previous match, add one char
+ call feedkeys("/thei\<bs>s\<bs>\<C-T>\<c-l>\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ 1
+ " delete all chars, start from the beginning again
+ call feedkeys("/them". repeat("\<bs>",4).'the\>'."\<cr>", 'tx')
+ call assert_equal(' 3 the', getline('.'))
+
+ " clean up
+ call test_disable_char_avail(0)
+ bw!
+endfunc
+
+func Test_search_cmdline2()
+ " See test/functional/legacy/search_spec.lua
+ throw 'skipped: Nvim does not support test_disable_char_avail()'
+ if !exists('+incsearch')
+ return
+ endif
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_disable_char_avail(1)
+ new
+ call setline(1, [' 1', ' 2 these', ' 3 the theother'])
+ " Test 1
+ " Ctrl-T goes correctly back and forth
+ set incsearch
+ 1
+ " first match
+ call feedkeys("/the\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " go to next match (on next line)
+ call feedkeys("/the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to next match (still on line 3)
+ call feedkeys("/the\<C-G>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to next match (still on line 3)
+ call feedkeys("/the\<C-G>\<C-G>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to previous match (on line 3)
+ call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to previous match (on line 3)
+ call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to previous match (on line 2)
+ call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<C-T>\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+
+ " Test 2: keep the view,
+ " after deleting a character from the search cmd
+ call setline(1, [' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there', ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar'])
+ resize 5
+ 1
+ call feedkeys("/foo\<bs>\<cr>", 'tx')
+ redraw
+ call assert_equal({'lnum': 10, 'leftcol': 0, 'col': 4, 'topfill': 0, 'topline': 6, 'coladd': 0, 'skipcol': 0, 'curswant': 4}, winsaveview())
+
+ " remove all history entries
+ for i in range(10)
+ call histdel('/')
+ endfor
+
+ " Test 3: reset the view,
+ " after deleting all characters from the search cmd
+ norm! 1gg0
+ " unfortunately, neither "/foo\<c-w>\<cr>", nor "/foo\<bs>\<bs>\<bs>\<cr>",
+ " nor "/foo\<c-u>\<cr>" works to delete the commandline.
+ " In that case Vim should return "E35 no previous regular expression",
+ " but it looks like Vim still sees /foo and therefore the test fails.
+ " Therefore, disableing this test
+ "call assert_fails(feedkeys("/foo\<c-w>\<cr>", 'tx'), 'E35')
+ "call assert_equal({'lnum': 1, 'leftcol': 0, 'col': 0, 'topfill': 0, 'topline': 1, 'coladd': 0, 'skipcol': 0, 'curswant': 0}, winsaveview())
+
+ " clean up
+ set noincsearch
+ call test_disable_char_avail(0)
+ bw!
+endfunc
+
func Test_use_sub_pat()
split
let @/ = ''
diff --git a/src/nvim/version.c b/src/nvim/version.c
index a28dbcf061..094ca29b01 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -124,9 +124,9 @@ static const int included_patches[] = {
2323,
2322,
2321,
- // 2320,
+ 2320,
// 2319 NA
- // 2318,
+ 2318,
2317,
// 2316 NA
2315,
@@ -176,7 +176,7 @@ static const int included_patches[] = {
// 2271 NA
// 2270 NA
2269,
- // 2268,
+ 2268,
// 2267 NA
2266,
2265,
@@ -185,7 +185,7 @@ static const int included_patches[] = {
// 2262 NA
// 2261 NA
// 2260 NA
- // 2259,
+ 2259,
// 2258 NA
// 2257 NA
2256,
diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua
new file mode 100644
index 0000000000..5f71861821
--- /dev/null
+++ b/test/functional/legacy/search_spec.lua
@@ -0,0 +1,474 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local eval = helpers.eval
+local feed = helpers.feed
+local funcs = helpers.funcs
+
+describe('search cmdline', function()
+ local screen
+
+ before_each(function()
+ clear()
+ command('set nohlsearch')
+ screen = Screen.new(20, 3)
+ screen:attach()
+ screen:set_default_attr_ids({
+ inc = {reverse = true}
+ })
+ end)
+
+ local function tenlines()
+ funcs.setline(1, {
+ ' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there',
+ ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar'
+ })
+ command('1')
+ end
+
+ it('history can be navigated with <C-N>/<C-P>', function()
+ tenlines()
+ command('set noincsearch')
+ feed('/foobar<CR>')
+ feed('/the<CR>')
+ eq('the', eval('@/'))
+ feed('/thes<C-P><C-P><CR>')
+ eq('foobar', eval('@/'))
+ end)
+
+ describe('can traverse matches', function()
+ before_each(tenlines)
+ local function forwarditer(wrapscan)
+ command('set incsearch '..wrapscan)
+ feed('/the')
+ screen:expect([[
+ 1 |
+ 2 {inc:the}se |
+ /the^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 2 these |
+ 3 {inc:the} |
+ /the^ |
+ ]])
+ eq({0, 0, 0, 0}, funcs.getpos('"'))
+ feed('<C-G>')
+ screen:expect([[
+ 3 the |
+ 4 {inc:the}ir |
+ /the^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 4 their |
+ 5 {inc:the}re |
+ /the^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 5 there |
+ 6 {inc:the}ir |
+ /the^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 6 their |
+ 7 {inc:the} |
+ /the^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 7 the |
+ 8 {inc:the}m |
+ /the^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 8 them |
+ 9 {inc:the}se |
+ /the^ |
+ ]])
+ feed('<C-G>')
+ if wrapscan == 'wrapscan' then
+ screen:expect([[
+ 2 {inc:the}se |
+ 3 the |
+ /the^ |
+ ]])
+ else
+ screen:expect([[
+ 8 them |
+ 9 {inc:the}se |
+ /the^ |
+ ]])
+ feed('<CR>')
+ eq({0, 0, 0, 0}, funcs.getpos('"'))
+ end
+ end
+
+ local function backiter(wrapscan)
+ command('set incsearch '..wrapscan)
+ command('$')
+
+ feed('?the')
+ screen:expect([[
+ 9 {inc:the}se |
+ 10 foobar |
+ ?the^ |
+ ]])
+ if wrapscan == 'wrapscan' then
+ feed('<C-G>')
+ screen:expect([[
+ 2 {inc:the}se |
+ 3 the |
+ ?the^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ 2 ^these |
+ 3 the |
+ ?the |
+ ]])
+ else
+ feed('<C-G>')
+ screen:expect([[
+ 9 {inc:the}se |
+ 10 foobar |
+ ?the^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ 9 ^these |
+ 10 foobar |
+ ?the |
+ ]])
+ end
+ command('$')
+ feed('?the')
+ screen:expect([[
+ 9 {inc:the}se |
+ 10 foobar |
+ ?the^ |
+ ]])
+ feed('<C-T>')
+ screen:expect([[
+ 8 {inc:the}m |
+ 9 these |
+ ?the^ |
+ ]])
+ for i = 1, 6 do
+ feed('<C-T>')
+ -- Avoid sleep just before expect, otherwise expect will take the full
+ -- timeout
+ if i ~= 6 then
+ screen:sleep(1)
+ end
+ end
+ screen:expect([[
+ 2 {inc:the}se |
+ 3 the |
+ ?the^ |
+ ]])
+ feed('<C-T>')
+ if wrapscan == 'wrapscan' then
+ screen:expect([[
+ 9 {inc:the}se |
+ 10 foobar |
+ ?the^ |
+ ]])
+ else
+ screen:expect([[
+ 2 {inc:the}se |
+ 3 the |
+ ?the^ |
+ ]])
+ end
+ end
+
+ it("using <C-G> and 'nowrapscan'", function()
+ forwarditer('nowrapscan')
+ end)
+
+ it("using <C-G> and 'wrapscan'", function()
+ forwarditer('wrapscan')
+ end)
+
+ it("using <C-T> and 'nowrapscan'", function()
+ backiter('nowrapscan')
+ end)
+
+ it("using <C-T> and 'wrapscan'", function()
+ backiter('wrapscan')
+ end)
+ end)
+
+ it('expands pattern with <C-L>', function()
+ tenlines()
+ command('set incsearch wrapscan')
+
+ feed('/the')
+ screen:expect([[
+ 1 |
+ 2 {inc:the}se |
+ /the^ |
+ ]])
+ feed('<C-L>')
+ screen:expect([[
+ 1 |
+ 2 {inc:thes}e |
+ /thes^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 9 {inc:thes}e |
+ 10 foobar |
+ /thes^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 2 {inc:thes}e |
+ 3 the |
+ /thes^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ 2 ^these |
+ 3 the |
+ /thes |
+ ]])
+
+ command('1')
+ command('set nowrapscan')
+ feed('/the')
+ screen:expect([[
+ 1 |
+ 2 {inc:the}se |
+ /the^ |
+ ]])
+ feed('<C-L>')
+ screen:expect([[
+ 1 |
+ 2 {inc:thes}e |
+ /thes^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 9 {inc:thes}e |
+ 10 foobar |
+ /thes^ |
+ ]])
+ feed('<C-G><CR>')
+ screen:expect([[
+ 9 ^these |
+ 10 foobar |
+ /thes |
+ ]])
+ end)
+
+ it('reduces pattern with <BS> and keeps cursor position', function()
+ tenlines()
+ command('set incsearch wrapscan')
+
+ -- First match
+ feed('/thei')
+ screen:expect([[
+ 4 {inc:thei}r |
+ 5 there |
+ /thei^ |
+ ]])
+ -- Match from initial cursor position when modifying search
+ feed('<BS>')
+ screen:expect([[
+ 1 |
+ 2 {inc:the}se |
+ /the^ |
+ ]])
+ -- New text advances to next match
+ feed('s')
+ screen:expect([[
+ 1 |
+ 2 {inc:thes}e |
+ /thes^ |
+ ]])
+ -- Stay on this match when deleting a character
+ feed('<BS>')
+ screen:expect([[
+ 1 |
+ 2 {inc:the}se |
+ /the^ |
+ ]])
+ -- Advance to previous match
+ feed('<C-T>')
+ screen:expect([[
+ 9 {inc:the}se |
+ 10 foobar |
+ /the^ |
+ ]])
+ -- Extend search to include next character
+ feed('<C-L>')
+ screen:expect([[
+ 9 {inc:thes}e |
+ 10 foobar |
+ /thes^ |
+ ]])
+ -- Deleting all characters resets the cursor position
+ feed('<BS><BS><BS><BS>')
+ screen:expect([[
+ 1 |
+ 2 these |
+ /^ |
+ ]])
+ feed('the')
+ screen:expect([[
+ 1 |
+ 2 {inc:the}se |
+ /the^ |
+ ]])
+ feed('\\>')
+ screen:expect([[
+ 2 these |
+ 3 {inc:the} |
+ /the\>^ |
+ ]])
+ end)
+
+ it('can traverse matches in the same line with <C-G>/<C-T>', function()
+ funcs.setline(1, { ' 1', ' 2 these', ' 3 the theother' })
+ command('1')
+ command('set incsearch')
+
+ -- First match
+ feed('/the')
+ screen:expect([[
+ 1 |
+ 2 {inc:the}se |
+ /the^ |
+ ]])
+
+ -- Next match, different line
+ feed('<C-G>')
+ screen:expect([[
+ 2 these |
+ 3 {inc:the} theother |
+ /the^ |
+ ]])
+
+ -- Next match, same line
+ feed('<C-G>')
+ screen:expect([[
+ 2 these |
+ 3 the {inc:the}other |
+ /the^ |
+ ]])
+ feed('<C-G>')
+ screen:expect([[
+ 2 these |
+ 3 the theo{inc:the}r |
+ /the^ |
+ ]])
+
+ -- Previous match, same line
+ feed('<C-T>')
+ screen:expect([[
+ 2 these |
+ 3 the {inc:the}other |
+ /the^ |
+ ]])
+ feed('<C-T>')
+ screen:expect([[
+ 2 these |
+ 3 {inc:the} theother |
+ /the^ |
+ ]])
+
+ -- Previous match, different line
+ feed('<C-T>')
+ screen:expect([[
+ 2 {inc:the}se |
+ 3 the theother |
+ /the^ |
+ ]])
+ end)
+
+ it('keeps the view after deleting a char from the search', function()
+ screen:detach()
+ screen = Screen.new(20, 6)
+ screen:attach()
+ screen:set_default_attr_ids({
+ inc = {reverse = true}
+ })
+ screen:set_default_attr_ignore({
+ {bold=true, reverse=true}, {bold=true, foreground=Screen.colors.Blue1}
+ })
+ tenlines()
+
+ feed('/foo')
+ screen:expect([[
+ 6 their |
+ 7 the |
+ 8 them |
+ 9 these |
+ 10 {inc:foo}bar |
+ /foo^ |
+ ]])
+ feed('<BS>')
+ screen:expect([[
+ 6 their |
+ 7 the |
+ 8 them |
+ 9 these |
+ 10 {inc:fo}obar |
+ /fo^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ 6 their |
+ 7 the |
+ 8 them |
+ 9 these |
+ 10 ^foobar |
+ /fo |
+ ]])
+ eq({lnum = 10, leftcol = 0, col = 4, topfill = 0, topline = 6,
+ coladd = 0, skipcol = 0, curswant = 4},
+ funcs.winsaveview())
+ end)
+
+ it('restores original view after failed search', function()
+ screen:detach()
+ screen = Screen.new(40, 3)
+ screen:attach()
+ screen:set_default_attr_ids({
+ inc = {reverse = true},
+ err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
+ more = { bold = true, foreground = Screen.colors.SeaGreen4 },
+ })
+ tenlines()
+ feed('0')
+ feed('/foo')
+ screen:expect([[
+ 9 these |
+ 10 {inc:foo}bar |
+ /foo^ |
+ ]])
+ feed('<C-W>')
+ screen:expect([[
+ 1 |
+ 2 these |
+ /^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ / |
+ {err:E35: No previous regular expression} |
+ {more:Press ENTER or type command to continue}^ |
+ ]])
+ feed('<CR>')
+ eq({lnum = 1, leftcol = 0, col = 0, topfill = 0, topline = 1,
+ coladd = 0, skipcol = 0, curswant = 0},
+ funcs.winsaveview())
+ end)
+end)