diff options
-rw-r--r-- | src/nvim/ex_getln.c | 63 | ||||
-rw-r--r-- | src/nvim/normal.c | 6 | ||||
-rw-r--r-- | src/nvim/testdir/test_search.vim | 32 | ||||
-rw-r--r-- | src/nvim/version.c | 2 | ||||
-rw-r--r-- | test/functional/legacy/search_spec.lua | 110 |
5 files changed, 174 insertions, 39 deletions
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 45fff4f9d7..e8f9d9bf47 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -100,13 +100,18 @@ 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; - pos_T cursor_start; + linenr_T init_botline; pos_T match_start; pos_T match_end; int did_incsearch; @@ -171,6 +176,11 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) 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; @@ -184,8 +194,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) ccline.overstrike = false; // always start in insert mode clearpos(&s->match_end); - s->old_cursor = curwin->w_cursor; // needs to be restored later - s->cursor_start = s->old_cursor; + 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; @@ -288,9 +298,15 @@ 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->cursor_start; + 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; @@ -939,10 +955,14 @@ 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->old_cursor = s->cursor_start; - } else { - s->old_cursor = s->match_start; - decl(&s->old_cursor); + 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 @@ -962,6 +982,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 } @@ -1017,7 +1038,7 @@ 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->old_cursor = s->cursor_start; + s->search_start = s->save_cursor; } redrawcmd(); return command_line_changed(s); @@ -1250,7 +1271,7 @@ static int command_line_handle_key(CommandLineState *s) // Add a character from under the cursor for 'incsearch' if (s->did_incsearch) { curwin->w_cursor = s->match_end; - if (!equalpos(curwin->w_cursor, s->old_cursor)) { + 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 @@ -1453,23 +1474,23 @@ static int command_line_handle_key(CommandLineState *s) emsg_off--; ui_busy_stop(); if (s->i) { - s->old_cursor = s->match_start; + s->search_start = s->match_start; s->match_end = t; s->match_start = t; if (s->c == Ctrl_T && s->firstc == '/') { // move just before the current match, so that // when nv_search finishes the cursor will be // put back on the match - s->old_cursor = t; - (void)decl(&s->old_cursor); + s->search_start = t; + (void)decl(&s->search_start); } - if (lt(t, s->old_cursor) && s->c == Ctrl_G) { + if (lt(t, s->search_start) && s->c == Ctrl_G) { // wrap around - s->old_cursor = t; + s->search_start = t; if (s->firstc == '?') { - (void)incl(&s->old_cursor); + (void)incl(&s->search_start); } else { - (void)decl(&s->old_cursor); + (void)decl(&s->search_start); } } @@ -1607,7 +1628,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) { @@ -1698,7 +1719,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; 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 28e504a883..2106fc2dec 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -33,6 +33,7 @@ func Test_search_cmdline() " 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') @@ -61,6 +62,7 @@ func Test_search_cmdline() " 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 @@ -182,11 +184,11 @@ func Test_search_cmdline() 1 " delete one char, add another call feedkeys("/thei\<bs>s\<cr>", 'tx') - call assert_equal(' 9 these', getline('.')) + 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(' 8 them', getline('.')) + call assert_equal(' 9 these', getline('.')) 1 " delete all chars, start from the beginning again call feedkeys("/them". repeat("\<bs>",4).'the\>'."\<cr>", 'tx') @@ -240,7 +242,33 @@ func Test_search_cmdline2() 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 diff --git a/src/nvim/version.c b/src/nvim/version.c index 5a107f0023..094ca29b01 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -124,7 +124,7 @@ static const int included_patches[] = { 2323, 2322, 2321, - // 2320, + 2320, // 2319 NA 2318, 2317, diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index 36c91accd6..5f71861821 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -54,6 +54,7 @@ describe('search cmdline', function() 3 {inc:the} | /the^ | ]]) + eq({0, 0, 0, 0}, funcs.getpos('"')) feed('<C-G>') screen:expect([[ 3 the | @@ -103,6 +104,8 @@ describe('search cmdline', function() 9 {inc:the}se | /the^ | ]]) + feed('<CR>') + eq({0, 0, 0, 0}, funcs.getpos('"')) end end @@ -276,40 +279,40 @@ describe('search cmdline', function() 5 there | /thei^ | ]]) - -- Stay on this match when deleting a character + -- Match from initial cursor position when modifying search feed('<BS>') screen:expect([[ - 4 {inc:the}ir | - 5 there | + 1 | + 2 {inc:the}se | /the^ | ]]) -- New text advances to next match feed('s') screen:expect([[ - 9 {inc:thes}e | - 10 foobar | + 1 | + 2 {inc:thes}e | /thes^ | ]]) -- Stay on this match when deleting a character feed('<BS>') screen:expect([[ - 9 {inc:the}se | - 10 foobar | + 1 | + 2 {inc:the}se | /the^ | ]]) -- Advance to previous match feed('<C-T>') screen:expect([[ - 8 {inc:the}m | - 9 these | + 9 {inc:the}se | + 10 foobar | /the^ | ]]) -- Extend search to include next character feed('<C-L>') screen:expect([[ - 8 {inc:them} | - 9 these | - /them^ | + 9 {inc:thes}e | + 10 foobar | + /thes^ | ]]) -- Deleting all characters resets the cursor position feed('<BS><BS><BS><BS>') @@ -320,14 +323,14 @@ describe('search cmdline', function() ]]) feed('the') screen:expect([[ + 1 | 2 {inc:the}se | - 3 the | /the^ | ]]) feed('\\>') screen:expect([[ + 2 these | 3 {inc:the} | - 4 their | /the\>^ | ]]) end) @@ -389,4 +392,83 @@ describe('search cmdline', function() /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) |