mirror of
https://github.com/neovim/neovim.git
synced 2024-12-29 14:41:06 -07:00
Inccommand: Multiline substitutions, highlighting, multibyte.
Make inccomand work with multiline patterns and substitutions. Also care for proper highlighting and multibyte characters.
This commit is contained in:
parent
45296b331f
commit
8d929f558c
@ -5278,6 +5278,43 @@ 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 The 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 Id of the highlight group to use
|
||||
/// @param lpos_start Cursor position to start the hightlighting at
|
||||
/// @param lpos_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
|
||||
|
@ -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 (;; ) {
|
||||
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.
|
||||
@ -3696,8 +3710,17 @@ 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;
|
||||
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 +3770,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 +3826,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 +3936,35 @@ skip:
|
||||
* found the match. */
|
||||
if (nmatch == -1)
|
||||
lnum -= regmatch.startpos[0].lnum;
|
||||
|
||||
// Push the match to preview_lines
|
||||
// TODO(KillTheMule): Code duplication at line 3961
|
||||
linenr_T match_lines = current_match.end.lnum
|
||||
- current_match.start.lnum +1;
|
||||
if (preview_lines.subresults.size > 0) {
|
||||
linenr_T last_lnum = kv_last(preview_lines.subresults).end.lnum;
|
||||
if (last_lnum == 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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Push the match to preview_lines
|
||||
linenr_T match_lines = current_match.end.lnum
|
||||
- current_match.start.lnum +1;
|
||||
if (preview_lines.subresults.size > 0) {
|
||||
linenr_T last_lnum = kv_last(preview_lines.subresults).end.lnum;
|
||||
if (last_lnum == 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);
|
||||
|
||||
line_breakcheck();
|
||||
}
|
||||
@ -3919,12 +3974,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();
|
||||
@ -4003,18 +4052,23 @@ skip:
|
||||
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 && preview_lines.subresults.size != 0 && 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 *)"Substitute", 13);
|
||||
}
|
||||
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, pat, sub, &preview_lines,
|
||||
has_second_delim, pre_hl_id, pre_src_id);
|
||||
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
|
||||
@ -6018,7 +6072,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)
|
||||
PreviewLines *preview_lines, bool show_hl, int hl_id,
|
||||
int src_id)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
static handle_T bufnr = 0; // special buffer, re-used on each visit
|
||||
@ -6028,6 +6083,8 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, char_u *pat, char_u *sub,
|
||||
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;
|
||||
@ -6046,15 +6103,19 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, char_u *pat, char_u *sub,
|
||||
}
|
||||
|
||||
// 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();
|
||||
@ -6070,43 +6131,70 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, char_u *pat, char_u *sub,
|
||||
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;
|
||||
highest_num_line = kv_last(lines.subresults).end.lnum;
|
||||
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);
|
||||
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; // # of last line added to preview buffer
|
||||
linenr_T linenr_origbuf = 0; // # of last line added to original number
|
||||
linenr_T next_linenr = 0; // # of the next line to show for the match
|
||||
|
||||
// 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;
|
||||
for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) {
|
||||
SubResult match = lines.subresults.items[matchidx];
|
||||
|
||||
// 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 (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_u *line = 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);
|
||||
ml_append(linenr_preview, (char_u *)str, (colnr_T)line_size, false);
|
||||
linenr_preview += 1;
|
||||
}
|
||||
linenr_origbuf = match.end.lnum;
|
||||
|
||||
if (show_hl) {
|
||||
bufhl_add_hl_pos_offset(preview_buf, src_id, hl_id, p_start,
|
||||
p_end, col_width);
|
||||
}
|
||||
}
|
||||
xfree(str);
|
||||
if (show_hl) {
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user