changeset 9971:98b39d2eb895 v7.4.2259

commit https://github.com/vim/vim/commit/4d6f32cbfbaf324ac4a25c0206a5db0e9f7a48f7 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Aug 26 19:13:46 2016 +0200 patch 7.4.2259 Problem: With 'incsearch' can only see the next match. Solution: Make CTRL-N/CTRL-P move to the previous/next match. (Christian Brabandt)
author Christian Brabandt <cb@256bit.org>
date Fri, 26 Aug 2016 19:15:07 +0200
parents c57383365947
children e8db856a5c74
files runtime/doc/cmdline.txt src/Makefile src/ex_getln.c src/testdir/Make_all.mak src/testdir/test_search.vim src/version.c
diffstat 6 files changed, 393 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -409,11 +409,19 @@ CTRL-D		List names that match the patter
 							*c_CTRL-N*
 CTRL-N		After using 'wildchar' which got multiple matches, go to next
 		match.  Otherwise recall more recent command-line from history.
+		                                            */_CTRL-N*
+		When 'incsearch' is set, entering a search pattern for "/" or
+		"?" and the current match is displayed then CTRL-N will move
+		to the next match (does not take |search-offset| into account)
 <S-Tab>							*c_CTRL-P* *c_<S-Tab>*
 CTRL-P		After using 'wildchar' which got multiple matches, go to
 		previous match.  Otherwise recall older command-line from
 		history.  <S-Tab> only works with the GUI, on the Amiga and
 		with MS-DOS.
+		                                            */_CTRL-P*
+		When 'incsearch' is set, entering a search pattern for "/" or
+		"?" and the current match is displayed then CTRL-P will move
+		to the previous match (does not take |search-offset| into account).
 							*c_CTRL-A*
 CTRL-A		All names that match the pattern in front of the cursor are
 		inserted.
@@ -423,6 +431,7 @@ CTRL-L		A match is done on the pattern i
 		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
--- a/src/Makefile
+++ b/src/Makefile
@@ -2110,6 +2110,7 @@ test_arglist \
 	test_regexp_utf8 \
 	test_reltime \
 	test_ruby \
+	test_search \
 	test_searchpos \
 	test_set \
 	test_signs \
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -137,6 +137,9 @@ static int
 #endif
 sort_func_compare(const void *s1, const void *s2);
 #endif
+#ifdef FEAT_SEARCH_EXTRA
+static void set_search_match(pos_T *t);
+#endif
 
 /*
  * getcmdline() - accept a command line starting with firstc.
@@ -178,6 +181,9 @@ getcmdline(
     colnr_T	old_curswant;
     colnr_T	old_leftcol;
     linenr_T	old_topline;
+    pos_T       cursor_start;
+    pos_T       match_start = curwin->w_cursor;
+    pos_T       match_end;
 # ifdef FEAT_DIFF
     int		old_topfill;
 # endif
@@ -223,7 +229,9 @@ getcmdline(
 
     ccline.overstrike = FALSE;		    /* always start in insert mode */
 #ifdef FEAT_SEARCH_EXTRA
+    clearpos(&match_end);
     old_cursor = curwin->w_cursor;	    /* needs to be restored later */
+    cursor_start = old_cursor;
     old_curswant = curwin->w_curswant;
     old_leftcol = curwin->w_leftcol;
     old_topline = curwin->w_topline;
@@ -996,6 +1004,15 @@ getcmdline(
 
 		    /* Truncate at the end, required for multi-byte chars. */
 		    ccline.cmdbuff[ccline.cmdlen] = NUL;
+#ifdef FEAT_SEARCH_EXTRA
+		    if (ccline.cmdlen == 0)
+			old_cursor = cursor_start;
+		    else
+		    {
+			old_cursor = match_start;
+			decl(&old_cursor);
+		    }
+#endif
 		    redrawcmd();
 		}
 		else if (ccline.cmdlen == 0 && c != Ctrl_W
@@ -1021,6 +1038,10 @@ getcmdline(
 			    msg_col = 0;
 			msg_putchar(' ');		/* delete ':' */
 		    }
+#ifdef FEAT_SEARCH_EXTRA
+		    if (ccline.cmdlen == 0)
+			old_cursor = cursor_start;
+#endif
 		    redraw_cmdline = TRUE;
 		    goto returncmd;		/* back to cmd mode */
 		}
@@ -1104,6 +1125,10 @@ getcmdline(
 		    ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
 		/* Truncate at the end, required for multi-byte chars. */
 		ccline.cmdbuff[ccline.cmdlen] = NUL;
+#ifdef FEAT_SEARCH_EXTRA
+		if (ccline.cmdlen == 0)
+		    old_cursor = cursor_start;
+#endif
 		redrawcmd();
 		goto cmdline_changed;
 
@@ -1440,26 +1465,31 @@ getcmdline(
 		if (p_is && !cmd_silent && (firstc == '/' || firstc == '?'))
 		{
 		    /* Add a character from under the cursor for 'incsearch' */
-		    if (did_incsearch
-				   && !equalpos(curwin->w_cursor, old_cursor))
+		    if (did_incsearch)
 		    {
-			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))
-			    c = MB_TOLOWER(c);
-			if (c != NUL)
+			curwin->w_cursor = match_end;
+			if (!equalpos(curwin->w_cursor, old_cursor))
 			{
-			    if (c == firstc || vim_strchr((char_u *)(
-					    p_magic ? "\\^$.*[" : "\\^$"), c)
-								      != NULL)
+			    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))
+				c = MB_TOLOWER(c);
+			    if (c != NUL)
 			    {
-				/* put a backslash before special characters */
-				stuffcharReadbuff(c);
-				c = '\\';
+				if (c == firstc || vim_strchr((char_u *)(
+					      p_magic ? "\\^$.*[" : "\\^$"), c)
+								       != NULL)
+				{
+				    /* put a backslash before special
+				     * characters */
+				    stuffcharReadbuff(c);
+				    c = '\\';
+				}
+				break;
 			    }
-			    break;
 			}
 		    }
 		    goto cmdline_not_changed;
@@ -1473,7 +1503,75 @@ getcmdline(
 
 	case Ctrl_N:	    /* next match */
 	case Ctrl_P:	    /* previous match */
-		if (xpc.xp_numfiles > 0)
+#ifdef FEAT_SEARCH_EXTRA
+		    if (p_is && !cmd_silent && (firstc == '/' || firstc == '?'))
+		    {
+			pos_T  t;
+			int    search_flags = SEARCH_KEEP + SEARCH_NOOF
+								 + SEARCH_PEEK;
+
+			if (char_avail())
+			    continue;
+			cursor_off();
+			out_flush();
+			if (c == Ctrl_N)
+			{
+			    t = match_end;
+			    search_flags += SEARCH_COL;
+			}
+			else
+			    t = match_start;
+			++emsg_off;
+			i = searchit(curwin, curbuf, &t,
+				     c == Ctrl_N ? FORWARD : BACKWARD,
+				     ccline.cmdbuff, count, search_flags,
+				     RE_SEARCH, 0, NULL);
+			--emsg_off;
+			if (i)
+			{
+			    old_cursor = match_start;
+			    match_end = t;
+			    match_start = t;
+			    if (c == Ctrl_P && firstc == '/')
+			    {
+				/* move just before the current match, so that
+				 * when nv_search finishes the cursor will be
+				 * put back on the match */
+				old_cursor = t;
+				(void)decl(&old_cursor);
+			    }
+			    if (lt(t, old_cursor) && c == Ctrl_N)
+			    {
+				/* wrap around */
+				old_cursor = t;
+				if (firstc == '?')
+				    (void)incl(&old_cursor);
+				else
+				    (void)decl(&old_cursor);
+			    }
+
+			    set_search_match(&match_end);
+			    curwin->w_cursor = match_start;
+			    changed_cline_bef_curs();
+			    update_topline();
+			    validate_cursor();
+			    highlight_match = TRUE;
+			    old_curswant = curwin->w_curswant;
+			    old_leftcol = curwin->w_leftcol;
+			    old_topline = curwin->w_topline;
+# ifdef FEAT_DIFF
+			    old_topfill = curwin->w_topfill;
+# endif
+			    old_botline = curwin->w_botline;
+			    update_screen(NOT_VALID);
+			    redrawcmdline();
+			}
+			else
+			    vim_beep(BO_ERROR);
+			goto cmdline_not_changed;
+		}
+#endif
+		else if (xpc.xp_numfiles > 0)
 		{
 		    if (nextwild(&xpc, (c == Ctrl_P) ? WILD_PREV : WILD_NEXT,
 						    0, firstc != '@') == FAIL)
@@ -1821,19 +1919,11 @@ cmdline_changed:
 	    {
 		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);
-		}
+		match_start = curwin->w_cursor;
+		set_search_match(&curwin->w_cursor);
 		validate_cursor();
 		end_pos = curwin->w_cursor;
+		match_end = end_pos;
 		curwin->w_cursor = save_pos;
 	    }
 	    else
@@ -1894,6 +1984,8 @@ returncmd:
     if (did_incsearch)
     {
 	curwin->w_cursor = old_cursor;
+	if (gotesc)
+	    curwin->w_cursor = cursor_start;
 	curwin->w_curswant = old_curswant;
 	curwin->w_leftcol = old_leftcol;
 	curwin->w_topline = old_topline;
@@ -6983,3 +7075,21 @@ script_get(exarg_T *eap, char_u *cmd)
 
     return (char_u *)ga.ga_data;
 }
+
+#ifdef FEAT_SEARCH_EXTRA
+    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);
+    }
+}
+#endif
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -181,6 +181,7 @@ NEW_TESTS = test_arglist.res \
 	    test_perl.res \
 	    test_quickfix.res \
 	    test_ruby.res \
+	    test_search.res \
 	    test_signs.res \
 	    test_startup.res \
 	    test_startup_utf8.res \
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_search.vim
@@ -0,0 +1,242 @@
+" Test for the search command
+
+func Test_search_cmdline()
+  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-N 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-n>\<cr>", 'tx')
+  call assert_equal('  3 the', getline('.'))
+  :1
+  " third match
+  call feedkeys("/the".repeat("\<c-n>", 2)."\<cr>", 'tx')
+  call assert_equal('  4 their', getline('.'))
+  :1
+  " fourth match
+  call feedkeys("/the".repeat("\<c-n>", 3)."\<cr>", 'tx')
+  call assert_equal('  5 there', getline('.'))
+  :1
+  " fifth match
+  call feedkeys("/the".repeat("\<c-n>", 4)."\<cr>", 'tx')
+  call assert_equal('  6 their', getline('.'))
+  :1
+  " sixth match
+  call feedkeys("/the".repeat("\<c-n>", 5)."\<cr>", 'tx')
+  call assert_equal('  7 the', getline('.'))
+  :1
+  " seventh match
+  call feedkeys("/the".repeat("\<c-n>", 6)."\<cr>", 'tx')
+  call assert_equal('  8 them', getline('.'))
+  :1
+  " eigth match
+  call feedkeys("/the".repeat("\<c-n>", 7)."\<cr>", 'tx')
+  call assert_equal('  9 these', getline('.'))
+  :1
+  " no further match
+  call feedkeys("/the".repeat("\<c-n>", 8)."\<cr>", 'tx')
+  call assert_equal('  9 these', getline('.'))
+
+  " Test 3
+  " Ctrl-N 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-n>\<cr>", 'tx')
+  call assert_equal('  3 the', getline('.'))
+  :1
+  " third match
+  call feedkeys("/the".repeat("\<c-n>", 2)."\<cr>", 'tx')
+  call assert_equal('  4 their', getline('.'))
+  :1
+  " fourth match
+  call feedkeys("/the".repeat("\<c-n>", 3)."\<cr>", 'tx')
+  call assert_equal('  5 there', getline('.'))
+  :1
+  " fifth match
+  call feedkeys("/the".repeat("\<c-n>", 4)."\<cr>", 'tx')
+  call assert_equal('  6 their', getline('.'))
+  :1
+  " sixth match
+  call feedkeys("/the".repeat("\<c-n>", 5)."\<cr>", 'tx')
+  call assert_equal('  7 the', getline('.'))
+  :1
+  " seventh match
+  call feedkeys("/the".repeat("\<c-n>", 6)."\<cr>", 'tx')
+  call assert_equal('  8 them', getline('.'))
+  :1
+  " eigth match
+  call feedkeys("/the".repeat("\<c-n>", 7)."\<cr>", 'tx')
+  call assert_equal('  9 these', getline('.'))
+  :1
+  " back at first match
+  call feedkeys("/the".repeat("\<c-n>", 8)."\<cr>", 'tx')
+  call assert_equal('  2 these', getline('.'))
+
+  " Test 4
+  " CTRL-P 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-n>\<cr>", 'tx')
+  call assert_equal('  9 these', getline('.'))
+  $
+  " second match
+  call feedkeys("?the".repeat("\<c-p>", 1)."\<cr>", 'tx')
+  call assert_equal('  8 them', getline('.'))
+  $
+  " last match
+  call feedkeys("?the".repeat("\<c-p>", 7)."\<cr>", 'tx')
+  call assert_equal('  2 these', getline('.'))
+  $
+  " last match
+  call feedkeys("?the".repeat("\<c-p>", 8)."\<cr>", 'tx')
+  call assert_equal('  2 these', getline('.'))
+
+  " Test 5
+  " CTRL-P 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-n>\<cr>", 'tx')
+  call assert_equal('  2 these', getline('.'))
+  $
+  " second match
+  call feedkeys("?the".repeat("\<c-p>", 1)."\<cr>", 'tx')
+  call assert_equal('  8 them', getline('.'))
+  $
+  " last match
+  call feedkeys("?the".repeat("\<c-p>", 7)."\<cr>", 'tx')
+  call assert_equal('  2 these', getline('.'))
+  $
+  " back at the bottom of the buffer
+  call feedkeys("?the".repeat("\<c-p>", 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-n>\<cr>", 'tx')
+  call assert_equal('  9 these', getline('.'))
+  1
+  " wrap around
+  call feedkeys("/the\<c-l>\<c-n>\<c-n>\<cr>", 'tx')
+  call assert_equal('  2 these', getline('.'))
+  1
+  " wrap around
+  set nowrapscan
+  call feedkeys("/the\<c-l>\<c-n>\<c-n>\<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('  9 these', getline('.'))
+  1
+  " delete one char, add another,  go to previous match, add one char
+  call feedkeys("/thei\<bs>s\<bs>\<c-p>\<c-l>\<cr>", 'tx')
+  call assert_equal('  8 them', 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()
+  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-P 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-n>\<cr>", 'tx')
+  call assert_equal('  3 the theother', getline('.'))
+  1
+  " go to next match (still on line 3)
+  call feedkeys("/the\<c-n>\<c-n>\<cr>", 'tx')
+  call assert_equal('  3 the theother', getline('.'))
+  1
+  " go to next match (still on line 3)
+  call feedkeys("/the\<c-n>\<c-n>\<c-n>\<cr>", 'tx')
+  call assert_equal('  3 the theother', getline('.'))
+  1
+  " go to previous match (on line 3)
+  call feedkeys("/the\<c-n>\<c-n>\<c-n>\<c-p>\<cr>", 'tx')
+  call assert_equal('  3 the theother', getline('.'))
+  1
+  " go to previous match (on line 3)
+  call feedkeys("/the\<c-n>\<c-n>\<c-n>\<c-p>\<c-p>\<cr>", 'tx')
+  call assert_equal('  3 the theother', getline('.'))
+  1
+  " go to previous match (on line 2)
+  call feedkeys("/the\<c-n>\<c-n>\<c-n>\<c-p>\<c-p>\<c-p>\<cr>", 'tx')
+  call assert_equal('  2 these', getline('.'))
+
+  " clean up
+  call test_disable_char_avail(0)
+  bw!
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -764,6 +764,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2259,
+/**/
     2258,
 /**/
     2257,