diff options
-rw-r--r-- | src/nvim/buffer.c | 38 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 249 | ||||
-rw-r--r-- | test/functional/ui/inccommand_spec.lua | 832 |
3 files changed, 981 insertions, 138 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 9d42fbc1b3..f874268910 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -5278,6 +5278,44 @@ int bufhl_add_hl(buf_T *buf, return src_id; } +/// Add highlighting to a buffer, bounded by two cursor positions, +/// with an offset. +/// +/// @param buf Buffer to add highlights to +/// @param src_id src_id to use or 0 to use a new src_id group, +/// or -1 for ungrouped highlight. +/// @param hl_id Highlight group id +/// @param pos_start Cursor position to start the hightlighting at +/// @param pos_end Cursor position to end the highlighting at +/// @param offset Move the whole highlighting this many columns to the right +void bufhl_add_hl_pos_offset(buf_T *buf, + int src_id, + int hl_id, + lpos_T pos_start, + lpos_T pos_end, + colnr_T offset) +{ + colnr_T hl_start = 0; + colnr_T hl_end = 0; + + for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { + if (pos_start.lnum < lnum && lnum < pos_end.lnum) { + hl_start = offset; + hl_end = MAXCOL; + } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { + hl_start = pos_start.col + offset + 1; + hl_end = MAXCOL; + } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { + hl_start = offset; + hl_end = pos_end.col + offset; + } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { + hl_start = pos_start.col + offset + 1; + hl_end = pos_end.col + offset; + } + (void)bufhl_add_hl(buf, src_id, hl_id, lnum, hl_start, hl_end); + } +} + /// Clear bufhl highlights from a given source group and range of lines. /// /// @param buf The buffer to remove highlights from diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 918e7a0c91..112f53247c 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -90,14 +90,20 @@ typedef struct { SubIgnoreType do_ic; ///< ignore case flag } subflags_T; -/// Lines matched during :substitute. +/// Partial result of a substitution during :substitute. +/// Numbers refer to the buffer _after_ substitution typedef struct { - linenr_T lnum; - long nmatch; - char_u *line; - kvec_t(colnr_T) cols; ///< columns of in-line matches -} MatchedLine; -typedef kvec_t(MatchedLine) MatchedLineVec; + lpos_T start; // start of the match + lpos_T end; // end of the match + linenr_T pre_match; // where to begin showing lines before the match +} SubResult; + +// Collected results of a substitution for showing them in +// the preview window +typedef struct { + kvec_t(SubResult) subresults; + linenr_T lines_needed; // lines neede in the preview window +} PreviewLines; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds.c.generated.h" @@ -3168,7 +3174,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) linenr_T old_line_count = curbuf->b_ml.ml_line_count; char_u *sub_firstline; // allocated copy of first sub line bool endcolumn = false; // cursor in last column when done - MatchedLineVec matched_lines = KV_INITIAL_VALUE; + PreviewLines preview_lines = { KV_INITIAL_VALUE, 0 }; + static int pre_src_id = 0; // Source id for the preview highlight + static int pre_hl_id = 0; + buf_T *orig_buf = curbuf; // save to reset highlighting pos_T old_cursor = curwin->w_cursor; int start_nsubs; int save_ma = 0; @@ -3336,7 +3345,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) linenr_T line2 = eap->line2; for (linenr_T lnum = eap->line1; lnum <= line2 && !got_quit && !aborting() - && (!preview || matched_lines.size < (size_t)p_cwh + && (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh || lnum <= curwin->w_botline); lnum++) { long nmatch = vim_regexec_multi(®match, curwin, curbuf, lnum, @@ -3401,8 +3410,6 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) sub_firstlnum = lnum; copycol = 0; matchcol = 0; - // the current match - MatchedLine matched_line = { 0, 0, NULL, KV_INITIAL_VALUE }; /* At first match, remember current cursor position. */ if (!got_match) { @@ -3419,10 +3426,19 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) * 5. break if there isn't another match in this line */ for (;; ) { - /* Advance "lnum" to the line where the match starts. The - * match does not start in the first line when there is a line - * break before \zs. */ + SubResult current_match = { + .start = { 0, 0 }, + .end = { 0, 0 }, + .pre_match = 0, + }; + // lnum is where the match start, but maybe not the pattern match, + // since we can have \n before \zs in the pattern + + // Advance "lnum" to the line where the match starts. The + // match does not start in the first line when there is a line + // break before \zs. if (regmatch.startpos[0].lnum > 0) { + current_match.pre_match = lnum; lnum += regmatch.startpos[0].lnum; sub_firstlnum += regmatch.startpos[0].lnum; nmatch -= regmatch.startpos[0].lnum; @@ -3430,6 +3446,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) sub_firstline = NULL; } + // Now we're at the line where the pattern match starts + // Note: If not first match on a line, column can't be known here + current_match.start.lnum = sub_firstlnum; + if (sub_firstline == NULL) { sub_firstline = vim_strsave(ml_get(sub_firstlnum)); } @@ -3439,12 +3459,6 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) curwin->w_cursor.lnum = lnum; do_again = FALSE; - if (preview) { - // Increment the in-line match count and store the column. - matched_line.nmatch++; - kv_push(matched_line.cols, regmatch.startpos[0].col); - } - /* * 1. Match empty string does not count, except for first * match. This reproduces the strange vi behaviour. @@ -3669,6 +3683,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) // go beyond the last line of the buffer. if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1) { nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1; + current_match.end.lnum = sub_firstlnum + nmatch; skip_match = true; } @@ -3696,8 +3711,19 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) } \ } while (0) + // Save the line numbers for the preview buffer + // NOTE: If the pattern matches a final newline, the next line will + // be shown also, but should not be highlighted. Intentional for now. if (preview && !has_second_delim) { + current_match.start.col = regmatch.startpos[0].col; + if (current_match.end.lnum == 0) { + current_match.end.lnum = sub_firstlnum + nmatch - 1; + } + current_match.end.col = regmatch.endpos[0].col; + ADJUST_SUB_FIRSTLNUM(); + lnum += nmatch - 1; + goto skip; } @@ -3747,6 +3773,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) memmove(new_end, sub_firstline + copycol, (size_t)copy_len); new_end += copy_len; + // Finally, at this point we can know where the match actually will + // start in the new text + current_match.start.col = new_end - new_start; + (void)vim_regsub_multi(®match, sub_firstlnum - regmatch.startpos[0].lnum, sub, new_end, true, p_magic, true); @@ -3799,6 +3829,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) p1 += (*mb_ptr2len)(p1) - 1; } } + current_match.end.col = STRLEN(new_start); + current_match.end.lnum = lnum; } // 4. If subflags.do_all is set, find next match. @@ -3907,9 +3939,30 @@ skip: * found the match. */ if (nmatch == -1) lnum -= regmatch.startpos[0].lnum; + +#define PUSH_PREVIEW_LINES() \ + do { \ + linenr_T match_lines = current_match.end.lnum \ + - current_match.start.lnum +1; \ + if (preview_lines.subresults.size > 0) { \ + linenr_T last = kv_last(preview_lines.subresults).end.lnum; \ + if (last == current_match.start.lnum) { \ + preview_lines.lines_needed += match_lines - 1; \ + } \ + } else { \ + preview_lines.lines_needed += match_lines; \ + } \ + kv_push(preview_lines.subresults, current_match); \ + } while (0) + + // Push the match to preview_lines. + PUSH_PREVIEW_LINES(); + break; } } + // Push the match to preview_lines. + PUSH_PREVIEW_LINES(); line_breakcheck(); } @@ -3919,12 +3972,6 @@ skip: xfree(new_start); /* for when substitute was cancelled */ xfree(sub_firstline); /* free the copy of the original line */ sub_firstline = NULL; - - if (preview) { - matched_line.lnum = lnum; - matched_line.line = vim_strsave(ml_get(lnum)); - kv_push(matched_lines, matched_line); - } } line_breakcheck(); @@ -3999,25 +4046,34 @@ skip: // Show 'inccommand' preview if there are matched lines. buf_T *preview_buf = NULL; + size_t subsize = preview_lines.subresults.size; if (preview && !aborting()) { if (got_quit) { // Substitution is too slow, disable 'inccommand'. set_string_option_direct((char_u *)"icm", -1, (char_u *)"", OPT_FREE, SID_NONE); - } else if (*p_icm != NUL && matched_lines.size != 0 && pat != NULL) { + } else if (*p_icm != NUL && pat != NULL) { + if (pre_src_id == 0) { + // Get a unique new src_id, saved in a static + pre_src_id = bufhl_add_hl(NULL, 0, -1, 0, 0, 0); + } + if (pre_hl_id == 0) { + pre_hl_id = syn_check_group((char_u *)S_LEN("Substitute")); + } curbuf->b_changed = save_b_changed; // preserve 'modified' during preview - preview_buf = show_sub(eap, old_cursor, pat, sub, &matched_lines); + preview_buf = show_sub(eap, old_cursor, &preview_lines, + pre_hl_id, pre_src_id); + if (subsize > 0) { + bufhl_clear_line_range(orig_buf, pre_src_id, eap->line1, + kv_last(preview_lines.subresults).end.lnum); + } } } - for (MatchedLine m; kv_size(matched_lines);) { - m = kv_pop(matched_lines); - xfree(m.line); - kv_destroy(m.cols); - } - kv_destroy(matched_lines); + kv_destroy(preview_lines.subresults); return preview_buf; #undef ADJUST_SUB_FIRSTLNUM +#undef PUSH_PREVIEW_LINES } // NOLINT(readability/fn_size) /* @@ -6017,8 +6073,8 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) /// Shows the effects of the :substitute command being typed ('inccommand'). /// If inccommand=split, shows a preview window and later restores the layout. -static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, char_u *pat, char_u *sub, - MatchedLineVec *matched_lines) +static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, + PreviewLines *preview_lines, int hl_id, int src_id) FUNC_ATTR_NONNULL_ALL { static handle_T bufnr = 0; // special buffer, re-used on each visit @@ -6026,8 +6082,8 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, char_u *pat, char_u *sub, win_T *save_curwin = curwin; cmdmod_T save_cmdmod = cmdmod; char_u *save_shm_p = vim_strsave(p_shm); - size_t sub_size = mb_string2cells(sub); - size_t pat_size = mb_string2cells(pat); + PreviewLines lines = *preview_lines; + buf_T *orig_buf = curbuf; // We keep a special-purpose buffer around, but don't assume it exists. buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0; @@ -6039,22 +6095,26 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, char_u *pat, char_u *sub, bool outside_curline = (eap->line1 != old_cusr.lnum || eap->line2 != old_cusr.lnum); - bool split = outside_curline && (*p_icm != 'n') && (sub_size || pat_size); + bool split = outside_curline && (*p_icm != 'n'); if (preview_buf == curbuf) { // Preview buffer cannot preview itself! split = false; preview_buf = NULL; } // Place cursor on nearest matching line, to undo do_sub() cursor placement. - for (size_t i = 0; i < matched_lines->size; i++) { - MatchedLine curmatch = matched_lines->items[i]; - if (curmatch.lnum >= old_cusr.lnum) { - curwin->w_cursor.lnum = curmatch.lnum; - curwin->w_cursor.col = curmatch.cols.items[0]; + for (size_t i = 0; i < lines.subresults.size; i++) { + SubResult curres = lines.subresults.items[i]; + if (curres.start.lnum >= old_cusr.lnum) { + curwin->w_cursor.lnum = curres.start.lnum; + curwin->w_cursor.col = curres.start.col; break; } // Else: All matches are above, do_sub() already placed cursor. } + // Width of the "| lnum|..." column which displays the line numbers. + linenr_T highest_num_line = 0; + int col_width = 0; + if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) { buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]"); buf_clear(); @@ -6069,44 +6129,77 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, char_u *pat, char_u *sub, curwin->w_p_spell = false; curwin->w_p_fen = false; - // Width of the "| lnum|..." column which displays the line numbers. - linenr_T highest_num_line = kv_last(*matched_lines).lnum; - int col_width = log10(highest_num_line) + 1 + 3; - - char *str = NULL; - size_t old_line_size = 0; - size_t line_size; - int src_id_highlight = 0; - int hl_id = syn_check_group((char_u *)"Substitute", 13); - - // Dump the lines into the preview buffer. - for (size_t line = 0; line < matched_lines->size; line++) { - MatchedLine mat = matched_lines->items[line]; - line_size = mb_string2cells(mat.line) + col_width + 1; - - // Reallocate if str not long enough - if (line_size > old_line_size) { - str = xrealloc(str, line_size * sizeof(char)); - old_line_size = line_size; + if (lines.subresults.size > 0) { + highest_num_line = kv_last(lines.subresults).end.lnum; + col_width = log10(highest_num_line) + 1 + 3; + } + } + + char *str = NULL; // construct the line to show in here + size_t old_line_size = 0; + size_t line_size = 0; + linenr_T linenr_preview = 0; // last line added to preview buffer + linenr_T linenr_origbuf = 0; // last line added to original buffer + linenr_T next_linenr = 0; // next line to show for the match + + for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) { + SubResult match = lines.subresults.items[matchidx]; + + if (split && preview_buf) { + lpos_T p_start = { 0, match.start.col }; // match starts here in preview + lpos_T p_end = { 0, match.end.col }; // ... and ends here + + if (match.pre_match == 0) { + next_linenr = match.start.lnum; + } else { + next_linenr = match.pre_match; + } + // Don't add a line twice + if (next_linenr == linenr_origbuf) { + next_linenr++; + p_start.lnum = linenr_preview; // might be redefined below + p_end.lnum = linenr_preview; // might be redefined below } - // Put "|lnum| line" into `str` and append it to the preview buffer. - snprintf(str, line_size, "|%*ld| %s", col_width - 3, mat.lnum, mat.line); - ml_append(line, (char_u *)str, (colnr_T)line_size, false); - - // highlight the replaced part - if (sub_size > 0) { - for (size_t i = 0; i < mat.cols.size; i++) { - colnr_T col_start = mat.cols.items[i] + col_width - + i * (sub_size - pat_size) + 1; - colnr_T col_end = col_start - 1 + sub_size; - src_id_highlight = bufhl_add_hl(curbuf, src_id_highlight, hl_id, - line + 1, col_start, col_end); + for (; next_linenr <= match.end.lnum; next_linenr++) { + if (next_linenr == match.start.lnum) { + p_start.lnum = linenr_preview + 1; } + if (next_linenr == match.end.lnum) { + p_end.lnum = linenr_preview + 1; + } + char *line; + if (next_linenr == orig_buf->b_ml.ml_line_count + 1) { + line = ""; + } else { + line = (char *)ml_get_buf(orig_buf, next_linenr, false); + line_size = strlen(line) + col_width + 1; + + // Reallocate if line not long enough + if (line_size > old_line_size) { + str = xrealloc(str, line_size * sizeof(char)); + old_line_size = line_size; + } + } + // Put "|lnum| line" into `str` and append it to the preview buffer. + snprintf(str, line_size, "|%*ld| %s", col_width - 3, + next_linenr, line); + if (linenr_preview == 0) { + ml_replace(1, (char_u *)str, true); + } else { + ml_append(linenr_preview, (char_u *)str, (colnr_T)line_size, false); + } + linenr_preview += 1; } + linenr_origbuf = match.end.lnum; + + bufhl_add_hl_pos_offset(preview_buf, src_id, hl_id, p_start, + p_end, col_width); } - xfree(str); + bufhl_add_hl_pos_offset(orig_buf, src_id, hl_id, match.start, + match.end, 0); } + xfree(str); redraw_later(SOME_VALID); win_enter(save_curwin, false); // Return to original window @@ -6156,7 +6249,11 @@ void ex_substitute(exarg_T *eap) curwin->w_p_cul = false; // Disable 'cursorline' curwin->w_p_cuc = false; // Disable 'cursorcolumn' + // Don't show search highlighting during live substitution + bool save_hls = p_hls; + p_hls = false; buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt)); + p_hls = save_hls; if (save_changedtick != curbuf->b_changedtick) { // Undo invisibly. This also moves the cursor! diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index cc023ef10d..6646e65bad 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -21,6 +21,42 @@ local default_text = [[ two lines ]] +local multiline_text = [[ + 1 2 3 + A B C + 4 5 6 + X Y Z + 7 8 9 +]] + +local multimatch_text = [[ + a bdc eae a fgl lzia r + x +]] + +local multibyte_text = [[ + £ ¥ ѫѫ PEPPERS +£ ¥ ѫfѫ + a£ ѫ¥KOL +£ ¥ libm +£ ¥ +]] + +local long_multiline_text = [[ + 1 2 3 + A B C + 4 5 6 + X Y Z + 7 8 9 + K L M + a b c + d e f + q r s + x y z + £ m n + t œ ¥ +]] + local function common_setup(screen, inccommand, text) if screen then command("syntax on") @@ -682,14 +718,14 @@ describe(":substitute, inccommand=split", function() feed(":%s/tw") screen:expect([[ Inc substitution on | - two lines | + {12:tw}o lines | | {15:~ }| {15:~ }| {11:[No Name] }| - |2| two lines | - |4| two lines | - | + |2| {12:tw}o lines | + |4| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -704,13 +740,13 @@ describe(":substitute, inccommand=split", function() it("shows preview when cmd modifiers are present", function() -- one modifier feed(':keeppatterns %s/tw/to') - screen:expect([[too lines]], nil, nil, nil, true) + screen:expect([[{12:to}o lines]], nil, nil, nil, true) feed('<Esc>') screen:expect([[two lines]], nil, nil, nil, true) -- multiple modifiers feed(':keeppatterns silent %s/tw/to') - screen:expect([[too lines]], nil, nil, nil, true) + screen:expect([[{12:to}o lines]], nil, nil, nil, true) feed('<Esc>') screen:expect([[two lines]], nil, nil, nil, true) @@ -724,14 +760,14 @@ describe(":substitute, inccommand=split", function() feed(":%s/tw") screen:expect([[ Inc substitution on | - two lines | + {12:tw}o lines | | {15:~ }| {15:~ }| {11:[No Name] [+] }| - |2| two lines | - |4| two lines | - | + |2| {12:tw}o lines | + |4| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -752,7 +788,7 @@ describe(":substitute, inccommand=split", function() {11:[No Name] [+] }| |2| o lines | |4| o lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -764,14 +800,14 @@ describe(":substitute, inccommand=split", function() feed("x") screen:expect([[ Inc substitution on | - xo lines | + {12:x}o lines | | {15:~ }| {15:~ }| {11:[No Name] [+] }| |2| {12:x}o lines | |4| {12:x}o lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -790,7 +826,7 @@ describe(":substitute, inccommand=split", function() {11:[No Name] [+] }| |2| o lines | |4| o lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -805,14 +841,14 @@ describe(":substitute, inccommand=split", function() feed(":%s/tw/XX") screen:expect([[ Inc substitution on | - XXo lines | + {12:XX}o lines | | {15:~ }| {15:~ }| {11:[No Name] [+] }| |2| {12:XX}o lines | |4| {12:XX}o lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -828,7 +864,7 @@ describe(":substitute, inccommand=split", function() screen:sleep(1) screen:expect([[ Inc substitution on | - two lines | + {12:tw}o lines | Inc substitution on | two lines | | @@ -872,14 +908,14 @@ describe(":substitute, inccommand=split", function() -- 'cursorline' is NOT active during preview. screen:expect([[ Inc substitution on | - {9:tw}o lines | + {12:tw}o lines | Inc substitution on | - {9:tw}o lines | + {12:tw}o lines | | {11:[No Name] [+] }| - |2| {9:tw}o lines | - |4| {9:tw}o lines | - | + |2| {12:tw}o lines | + |4| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -894,14 +930,14 @@ describe(":substitute, inccommand=split", function() feed('M M M<esc>') feed(':%s/M/123/g') screen:expect([[ - 123 123 123 | + {12:123} {12:123} {12:123} | Inc substitution on | two lines | Inc substitution on | two lines | {11:[No Name] [+] }| |1| {12:123} {12:123} {12:123} | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -912,6 +948,28 @@ describe(":substitute, inccommand=split", function() ]]) end) + it("highlights nothing when there's no match", function() + feed('gg') + feed(':%s/Inx') + screen:expect([[ + Inc substitution on | + two lines | + Inc substitution on | + two lines | + | + {11:[No Name] [+] }| + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/Inx^ | + ]]) + end) + it('previews correctly when previewhight is small', function() feed_command('set cwh=3') feed_command('set hls') @@ -919,15 +977,15 @@ describe(":substitute, inccommand=split", function() insert(string.rep('abc abc abc\n', 20)) feed(':%s/abc/MMM/g') screen:expect([[ - MMM MMM MMM | - MMM MMM MMM | - MMM MMM MMM | - MMM MMM MMM | - MMM MMM MMM | - MMM MMM MMM | - MMM MMM MMM | - MMM MMM MMM | - MMM MMM MMM | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | {11:[No Name] [+] }| | 1| {12:MMM} {12:MMM} {12:MMM} | | 2| {12:MMM} {12:MMM} {12:MMM} | @@ -969,9 +1027,9 @@ describe(":substitute, inccommand=split", function() screen:expect([[ BBo lines | Inc substitution on | - Xo lines | + {12:X}o lines | Inc substitution on | - Xo lines | + {12:X}o lines | {11:[No Name] [+] }| |1001| {12:X}o lines | |1003| {12:X}o lines | @@ -1069,15 +1127,15 @@ describe(":substitute, inccommand=split", function() feed(":1,2s/t/X") screen:expect([[ - Inc subsXitution on | - Xwo lines | + Inc subs{12:X}itution on | + {12:X}wo lines | Inc substitution on | two lines | | {11:[No Name] [+] }| |1| Inc subs{12:X}itution on | |2| {12:X}wo lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -1134,7 +1192,7 @@ describe("inccommand=nosplit", function() two lines | Inc substitution on | two lines | - Line *.X | + Line *.{12:X} | {15:~ }| {15:~ }| {15:~ }| @@ -1142,7 +1200,6 @@ describe("inccommand=nosplit", function() :%smagic/3.*/X^ | ]]) - feed([[<C-\><C-N>]]) -- cancel feed(":%snomagic/3.*/X") -- start :snomagic command screen:expect([[ @@ -1150,7 +1207,7 @@ describe("inccommand=nosplit", function() two lines | Inc substitution on | two lines | - Line *.X here | + Line *.{12:X} here | {15:~ }| {15:~ }| {15:~ }| @@ -1162,13 +1219,13 @@ describe("inccommand=nosplit", function() it("shows preview when cmd modifiers are present", function() -- one modifier feed(':keeppatterns %s/tw/to') - screen:expect([[too lines]], nil, nil, nil, true) + screen:expect([[{12:to}o lines]], nil, nil, nil, true) feed('<Esc>') screen:expect([[two lines]], nil, nil, nil, true) -- multiple modifiers feed(':keeppatterns silent %s/tw/to') - screen:expect([[too lines]], nil, nil, nil, true) + screen:expect([[{12:to}o lines]], nil, nil, nil, true) feed('<Esc>') screen:expect([[two lines]], nil, nil, nil, true) @@ -1178,15 +1235,38 @@ describe("inccommand=nosplit", function() feed('<Esc>') end) + it("does not show window after toggling :set inccommand", function() + feed(":%s/tw/OKOK") + feed("<Esc>") + command("set icm=split") + feed(":%s/tw/OKOK") + feed("<Esc>") + command("set icm=nosplit") + feed(":%s/tw/OKOK") + wait() + screen:expect([[ + Inc substitution on | + {12:OKOK}o lines | + Inc substitution on | + {12:OKOK}o lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/tw/OKOK^ | + ]]) + end) + it('never shows preview buffer', function() feed_command("set hlsearch") feed(":%s/tw") screen:expect([[ Inc substitution on | - {9:tw}o lines | + {12:tw}o lines | Inc substitution on | - {9:tw}o lines | + {12:tw}o lines | | {15:~ }| {15:~ }| @@ -1198,9 +1278,9 @@ describe("inccommand=nosplit", function() feed("/BM") screen:expect([[ Inc substitution on | - BMo lines | + {12:BM}o lines | Inc substitution on | - BMo lines | + {12:BM}o lines | | {15:~ }| {15:~ }| @@ -1212,9 +1292,9 @@ describe("inccommand=nosplit", function() feed("/") screen:expect([[ Inc substitution on | - BMo lines | + {12:BM}o lines | Inc substitution on | - BMo lines | + {12:BM}o lines | | {15:~ }| {15:~ }| @@ -1245,8 +1325,8 @@ describe("inccommand=nosplit", function() feed(":1,2s/t/X") screen:expect([[ - Inc subsXitution on | - Xwo lines | + Inc subs{12:X}itution on | + {12:X}wo lines | Inc substitution on | two lines | | @@ -1563,7 +1643,7 @@ describe("'inccommand' split windows", function() feed(":%s/tw") screen:expect([[ Inc substitution on {10:|}Inc substitution on| - two lines {10:|}two lines | + {12:tw}o lines {10:|}{12:tw}o lines | {10:|} | {15:~ }{10:|}{15:~ }| {15:~ }{10:|}{15:~ }| @@ -1578,13 +1658,13 @@ describe("'inccommand' split windows", function() {15:~ }{10:|}{15:~ }| {11:[No Name] [+] }{10:|}{15:~ }| Inc substitution on {10:|}{15:~ }| - two lines {10:|}{15:~ }| + {12:tw}o lines {10:|}{15:~ }| {10:|}{15:~ }| {15:~ }{10:|}{15:~ }| {15:~ }{10:|}{15:~ }| {10:[No Name] [+] [No Name] [+] }| - |2| two lines | - | + |2| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -1602,7 +1682,7 @@ describe("'inccommand' split windows", function() feed(":%s/tw") screen:expect([[ Inc substitution on {10:|}Inc substitution on| - two lines {10:|}two lines | + {12:tw}o lines {10:|}{12:tw}o lines | {10:|} | {15:~ }{10:|}{15:~ }| {15:~ }{10:|}{15:~ }| @@ -1617,13 +1697,13 @@ describe("'inccommand' split windows", function() {15:~ }{10:|}{15:~ }| {11:[No Name] [+] }{10:[No Name] [+] }| Inc substitution on | - two lines | + {12:tw}o lines | | {15:~ }| {15:~ }| {10:[No Name] [+] }| - |2| two lines | - | + |2| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -1652,7 +1732,7 @@ describe("'inccommand' split windows", function() screen:expect([[ Inc substitution on | - two lines | + {12:tw}o lines | | {15:~ }| {15:~ }| @@ -1672,8 +1752,8 @@ describe("'inccommand' split windows", function() {15:~ }| {15:~ }| {11:[No Name] [+] }| - |2| two lines | - | + |2| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -1712,3 +1792,631 @@ describe("'inccommand' with 'gdefault'", function() eq({mode='n', blocking=false}, nvim("get_mode")) end) end) + +describe(":substitute", function() + local screen = Screen.new(30,15) + + before_each(function() + clear() + end) + + it(", inccommand=split, highlights multiline substitutions", function() + common_setup(screen, "split", multiline_text) + feed("gg") + + feed(":%s/2\\_.*X") + screen:expect([[ + 1 {12:2 3} | + {12:A B C} | + {12:4 5 6} | + {12:X} Y Z | + 7 8 9 | + {11:[No Name] [+] }| + |1| 1 {12:2 3} | + |2|{12: A B C} | + |3|{12: 4 5 6} | + |4|{12: X} Y Z | + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/2\_.*X^ | + ]]) + + feed("/MMM") + screen:expect([[ + 1 {12:MMM} Y Z | + 7 8 9 | + | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| 1 {12:MMM} Y Z | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/2\_.*X/MMM^ | + ]]) + + feed("\\rK\\rLLL") + screen:expect([[ + 1 {12:MMM} | + {12:K} | + {12:LLL} Y Z | + 7 8 9 | + | + {11:[No Name] [+] }| + |1| 1 {12:MMM} | + |2|{12: K} | + |3|{12: LLL} Y Z | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/2\_.*X/MMM\rK\rLLL^ | + ]]) + end) + + it(", inccommand=nosplit, highlights multiline substitutions", function() + common_setup(screen, "nosplit", multiline_text) + feed("gg") + + feed(":%s/2\\_.*X/MMM") + screen:expect([[ + 1 {12:MMM} Y Z | + 7 8 9 | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/2\_.*X/MMM^ | + ]]) + + feed("\\rK\\rLLL") + screen:expect([[ + 1 {12:MMM} | + {12:K} | + {12:LLL} Y Z | + 7 8 9 | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/2\_.*X/MMM\rK\rLLL^ | + ]]) + end) + + it(", inccommand=split, highlights multiple matches on a line", function() + common_setup(screen, "split", multimatch_text) + command("set gdefault") + feed("gg") + + feed(":%s/a/XLK") + screen:expect([[ + {12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:XLK} r| + x | + | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| {12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:X}| + {12:LK} r | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/a/XLK^ | + ]]) + end) + + it(", inccommand=nosplit, highlights multiple matches on a line", function() + common_setup(screen, "nosplit", multimatch_text) + command("set gdefault") + feed("gg") + + feed(":%s/a/XLK") + screen:expect([[ + {12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:XLK} r| + x | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/a/XLK^ | + ]]) + end) + + it(", inccommand=split, with \\zs", function() + common_setup(screen, "split", multiline_text) + feed("gg") + + feed(":%s/[0-9]\\n\\zs[A-Z]/OKO") + screen:expect([[ + 1 2 3 | + {12:OKO} B C | + 4 5 6 | + {12:OKO} Y Z | + 7 8 9 | + {11:[No Name] [+] }| + |1| 1 2 3 | + |2| {12:OKO} B C | + |3| 4 5 6 | + |4| {12:OKO} Y Z | + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/[0-9]\n\zs[A-Z]/OKO^ | + ]]) + end) + + it(", inccommand=nosplit, with \\zs", function() + common_setup(screen, "nosplit", multiline_text) + feed("gg") + + feed(":%s/[0-9]\\n\\zs[A-Z]/OKO") + screen:expect([[ + 1 2 3 | + {12:OKO} B C | + 4 5 6 | + {12:OKO} Y Z | + 7 8 9 | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/[0-9]\n\zs[A-Z]/OKO^ | + ]]) + end) + + it(", inccommand=split, substitutions of different length", + function() + common_setup(screen, "split", "T T123 T2T TTT T090804\nx") + + feed(":%s/T\\([0-9]\\+\\)/\\1\\1/g") + screen:expect([[ + T {12:123123} {12:22}T TTT {12:090804090804} | + x | + {15:~ }| + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| T {12:123123} {12:22}T TTT {12:090804090}| + {12:804} | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/T\([0-9]\+\)/\1\1/g^ | + ]]) + end) + + it(", inccommand=nosplit, substitutions of different length", function() + common_setup(screen, "nosplit", "T T123 T2T TTT T090804\nx") + + feed(":%s/T\\([0-9]\\+\\)/\\1\\1/g") + screen:expect([[ + T {12:123123} {12:22}T TTT {12:090804090804} | + x | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/T\([0-9]\+\)/\1\1/g^ | + ]]) + end) + + it(", inccommand=split, contraction of lines", function() + local text = [[ + T T123 T T123 T2T TT T23423424 + x + afa Q + adf la;lkd R + alx + ]] + + common_setup(screen, "split", text) + feed(":%s/[QR]\\n") + screen:expect([[ + afa {12:Q} | + adf la;lkd {12:R} | + alx | + | + {15:~ }| + {11:[No Name] [+] }| + |3| afa {12:Q} | + |4|{12: }adf la;lkd {12:R} | + |5|{12: }alx | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/[QR]\n^ | + ]]) + + feed("/KKK") + screen:expect([[ + x | + afa {12:KKK}adf la;lkd {12:KKK}alx | + | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |3| afa {12:KKK}adf la;lkd {12:KKK}alx | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/[QR]\n/KKK^ | + ]]) + end) + + it(", inccommand=nosplit, contraction of lines", function() + local text = [[ + T T123 T T123 T2T TT T23423424 + x + afa Q + adf la;lkd R + alx + ]] + + common_setup(screen, "nosplit", text) + feed(":%s/[QR]\\n/KKK") + screen:expect([[ + T T123 T T123 T2T TT T23423424| + x | + afa {12:KKK}adf la;lkd {12:KKK}alx | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/[QR]\n/KKK^ | + ]]) + end) + + it(", inccommand=split, multibyte text", function() + common_setup(screen, "split", multibyte_text) + feed(":%s/£.*ѫ/X¥¥") + screen:expect([[ + {12:X¥¥} | + a{12:X¥¥}¥KOL | + £ ¥ libm | + £ ¥ | + | + {11:[No Name] [+] }| + |1| {12:X¥¥} PEPPERS | + |2| {12:X¥¥} | + |3| a{12:X¥¥}¥KOL | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/£.*ѫ/X¥¥^ | + ]]) + + feed("\\ra££ ¥") + screen:expect([[ + {12:a££ ¥} | + a{12:X¥¥} | + {12:a££ ¥}¥KOL | + £ ¥ libm | + £ ¥ | + {11:[No Name] [+] }| + |1| {12:X¥¥} | + |2|{12: a££ ¥} PEPPERS | + |3| {12:X¥¥} | + |4|{12: a££ ¥} | + |5| a{12:X¥¥} | + |6|{12: a££ ¥}¥KOL | + {15:~ }| + {10:[Preview] }| + :%s/£.*ѫ/X¥¥\ra££ ¥^ | + ]]) + end) + + it(", inccommand=nosplit, multibyte text", function() + common_setup(screen, "nosplit", multibyte_text) + feed(":%s/£.*ѫ/X¥¥") + screen:expect([[ + {12:X¥¥} PEPPERS | + {12:X¥¥} | + a{12:X¥¥}¥KOL | + £ ¥ libm | + £ ¥ | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/£.*ѫ/X¥¥^ | + ]]) + + feed("\\ra££ ¥") + screen:expect([[ + {12:X¥¥} | + {12:a££ ¥} PEPPERS | + {12:X¥¥} | + {12:a££ ¥} | + a{12:X¥¥} | + {12:a££ ¥}¥KOL | + £ ¥ libm | + £ ¥ | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/£.*ѫ/X¥¥\ra££ ¥^ | + ]]) + end) + + it(", inccommand=split, small cmdwinheight", function() + common_setup(screen, "split", long_multiline_text) + command("set cmdwinheight=2") + + feed(":%s/[a-z]") + screen:expect([[ + X Y Z | + 7 8 9 | + K L M | + {12:a} b c | + {12:d} e f | + {12:q} r s | + {12:x} y z | + £ {12:m} n | + {12:t} œ ¥ | + | + {11:[No Name] [+] }| + | 7| {12:a} b c | + | 8| {12:d} e f | + {10:[Preview] }| + :%s/[a-z]^ | + ]]) + + feed("/JLKR £") + screen:expect([[ + X Y Z | + 7 8 9 | + K L M | + {12:JLKR £} b c | + {12:JLKR £} e f | + {12:JLKR £} r s | + {12:JLKR £} y z | + £ {12:JLKR £} n | + {12:JLKR £} œ ¥ | + | + {11:[No Name] [+] }| + | 7| {12:JLKR £} b c | + | 8| {12:JLKR £} e f | + {10:[Preview] }| + :%s/[a-z]/JLKR £^ | + ]]) + + feed("\\rѫ ab \\rXXXX") + screen:expect([[ + 7 8 9 | + K L M | + {12:JLKR £} | + {12:ѫ ab } | + {12:XXXX} b c | + {12:JLKR £} | + {12:ѫ ab } | + {12:XXXX} e f | + {12:JLKR £} | + {11:[No Name] [+] }| + | 7| {12:JLKR £} | + | 8|{12: ѫ ab } | + {10:[Preview] }| + :%s/[a-z]/JLKR £\rѫ ab \rXXX| + X^ | + ]]) + end) + + it(", inccommand=split, large cmdwinheight", function() + common_setup(screen, "split", long_multiline_text) + command("set cmdwinheight=11") + + feed(":%s/. .$") + screen:expect([[ + t {12:œ ¥} | + {11:[No Name] [+] }| + | 1| 1 {12:2 3} | + | 2| A {12:B C} | + | 3| 4 {12:5 6} | + | 4| X {12:Y Z} | + | 5| 7 {12:8 9} | + | 6| K {12:L M} | + | 7| a {12:b c} | + | 8| d {12:e f} | + | 9| q {12:r s} | + |10| x {12:y z} | + |11| £ {12:m n} | + {10:[Preview] }| + :%s/. .$^ | + ]]) + + feed("/ YYY") + screen:expect([[ + t {12: YYY} | + {11:[No Name] [+] }| + | 1| 1 {12: YYY} | + | 2| A {12: YYY} | + | 3| 4 {12: YYY} | + | 4| X {12: YYY} | + | 5| 7 {12: YYY} | + | 6| K {12: YYY} | + | 7| a {12: YYY} | + | 8| d {12: YYY} | + | 9| q {12: YYY} | + |10| x {12: YYY} | + |11| £ {12: YYY} | + {10:[Preview] }| + :%s/. .$/ YYY^ | + ]]) + + feed("\\r KKK") + screen:expect([[ + a {12: YYY} | + {11:[No Name] [+] }| + | 1| 1 {12: YYY} | + | 2|{12: KKK} | + | 3| A {12: YYY} | + | 4|{12: KKK} | + | 5| 4 {12: YYY} | + | 6|{12: KKK} | + | 7| X {12: YYY} | + | 8|{12: KKK} | + | 9| 7 {12: YYY} | + |10|{12: KKK} | + |11| K {12: YYY} | + {10:[Preview] }| + :%s/. .$/ YYY\r KKK^ | + ]]) + end) + + it(", inccommand=split, lookaround", function() + common_setup(screen, "split", "something\neverything\nsomeone") + feed([[:%s/\(some\)\@<lt>=thing/one/]]) + screen:expect([[ + some{12:one} | + everything | + someone | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| some{12:one} | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/\(some\)\@<=thing/one/^ | + ]]) + + feed("<C-c>") + wait() + feed([[:%s/\(some\)\@<lt>!thing/one/]]) + screen:expect([[ + something | + every{12:one} | + someone | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |2| every{12:one} | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/\(some\)\@<!thing/one/^ | + ]]) + + feed([[<C-c>]]) + wait() + feed([[:%s/some\(thing\)\@=/every/]]) + screen:expect([[ + {12:every}thing | + everything | + someone | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| {12:every}thing | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/some\(thing\)\@=/every/^ | + ]]) + + feed([[<C-c>]]) + wait() + feed([[:%s/some\(thing\)\@!/every/]]) + screen:expect([[ + everything | + {12:every}one | + {15:~ }| + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |3| {12:every}one | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/some\(thing\)\@!/every/^ | + ]]) + end) +end) |