aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/options.txt11
-rw-r--r--runtime/doc/syntax.txt2
-rw-r--r--runtime/syntax/vim.vim2
-rw-r--r--src/nvim/buffer.c15
-rw-r--r--src/nvim/eval.c2
-rw-r--r--src/nvim/ex_cmds.c325
-rw-r--r--src/nvim/ex_cmds.h22
-rw-r--r--src/nvim/ex_cmds.lua2
-rw-r--r--src/nvim/ex_cmds_defs.h1
-rw-r--r--src/nvim/ex_docmd.c46
-rw-r--r--src/nvim/ex_docmd.h1
-rw-r--r--src/nvim/ex_getln.c4
-rw-r--r--src/nvim/fileio.c2
-rw-r--r--src/nvim/globals.h7
-rw-r--r--src/nvim/mark.c2
-rw-r--r--src/nvim/option.c11
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/options.lua8
-rw-r--r--src/nvim/quickfix.c2
-rw-r--r--src/nvim/shada.c2
-rw-r--r--src/nvim/syntax.c1
-rw-r--r--src/nvim/undo.c74
-rw-r--r--src/nvim/window.c2
23 files changed, 486 insertions, 59 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 5a5999e64c..0db1053d5e 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3946,6 +3946,7 @@ A jump table for the options with a short description can be found at |Q_op|.
global
Strings to use in 'list' mode and for the |:list| command. It is a
comma separated list of string settings.
+
*lcs-eol*
eol:c Character to show at the end of each line. When
omitted, there is no extra character at the end of the
@@ -3991,6 +3992,16 @@ A jump table for the options with a short description can be found at |Q_op|.
"precedes". "SpecialKey" for "nbsp", "space", "tab" and "trail".
|hl-NonText| |hl-SpecialKey|
+ *'incsubstitute'* *'ics'*
+'incsubstitute' 'ics' string (default "")
+ global
+
+ If set to "split" or "nosplit", substitutions (|:s|) are updated live
+ while the user types the command. If set to "split", a split window
+ is open which displays the lines where the search matches. The
+ replacement text in the split is hightlighted using
+ |hl-IncSubstitute|. Note: Only '/' is supported as a delimiter.
+
*'lpl'* *'nolpl'* *'loadplugins'* *'noloadplugins'*
'loadplugins' 'lpl' boolean (default on)
global
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index 308fa90ab3..4bad9d7ff7 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -4855,6 +4855,8 @@ IncSearch 'incsearch' highlighting; also used for the text replaced with
*hl-LineNr*
LineNr Line number for ":number" and ":#" commands, and when 'number'
or 'relativenumber' option is set.
+ *hl-IncSubstitute*
+IncSubstitute The replacement text when using the |incsubstitute| functionality
*hl-CursorLineNr*
CursorLineNr Like LineNr when 'cursorline' or 'relativenumber' is set for
the cursor line.
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index 32e871ea79..dc5649f2b1 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -58,7 +58,7 @@ syn case ignore
syn keyword vimGroup contained Comment Constant String Character Number Boolean Float Identifier Function Statement Conditional Repeat Label Operator Keyword Exception PreProc Include Define Macro PreCondit Type StorageClass Structure Typedef Special SpecialChar Tag Delimiter SpecialComment Debug Underlined Ignore Error Todo
" Default highlighting groups {{{2
-syn keyword vimHLGroup contained ColorColumn Cursor CursorColumn CursorIM CursorLine CursorLineNr DiffAdd DiffChange DiffDelete DiffText Directory ErrorMsg FoldColumn Folded IncSearch LineNr MatchParen Menu ModeMsg MoreMsg NonText Normal Pmenu PmenuSbar PmenuSel PmenuThumb Question Scrollbar Search SignColumn SpecialKey SpellBad SpellCap SpellLocal SpellRare StatusLine StatusLineNC TabLine TabLineFill TabLineSel Title Tooltip VertSplit Visual WarningMsg WildMenu
+syn keyword vimHLGroup contained ColorColumn Cursor CursorColumn CursorIM CursorLine CursorLineNr DiffAdd DiffChange DiffDelete DiffText Directory ErrorMsg FoldColumn Folded IncSearch IncSubstitute LineNr MatchParen Menu ModeMsg MoreMsg NonText Normal Pmenu PmenuSbar PmenuSel PmenuThumb Question Scrollbar Search SignColumn SpecialKey SpellBad SpellCap SpellLocal SpellRare StatusLine StatusLineNC TabLine TabLineFill TabLineSel Title Tooltip VertSplit Visual WarningMsg WildMenu
syn match vimHLGroup contained "Conceal"
syn keyword vimOnlyHLGroup contained VisualNOS
syn keyword nvimHLGroup contained EndOfBuffer TermCursor TermCursorNC QuickFixLine
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index a66fdc1304..17300fbdfe 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -678,7 +678,7 @@ void handle_swap_exists(buf_T *old_curbuf)
swap_exists_did_quit = TRUE;
close_buffer(curwin, curbuf, DOBUF_UNLOAD, FALSE);
if (!buf_valid(old_curbuf) || old_curbuf == curbuf)
- old_curbuf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED);
+ old_curbuf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED, 0);
if (old_curbuf != NULL) {
enter_buffer(old_curbuf);
if (old_tw != curbuf->b_p_tw)
@@ -1339,7 +1339,8 @@ buflist_new (
char_u *ffname, /* full path of fname or relative */
char_u *sfname, /* short fname or NULL */
linenr_T lnum, /* preferred cursor line */
- int flags /* BLN_ defines */
+ int flags, /* BLN_ defines */
+ handle_T bufnr
)
{
buf_T *buf;
@@ -1458,7 +1459,9 @@ buflist_new (
}
lastbuf = buf;
- buf->b_fnum = top_file_num++;
+ // If bufnr is nonzero it is assumed to be a previously
+ // reserved buffer number (handle)
+ buf->handle = bufnr != 0 ? bufnr : top_file_num++;
handle_register_buffer(buf);
if (top_file_num < 0) { // wrap around (may cause duplicates)
EMSG(_("W14: Warning: List of file names overflow"));
@@ -2375,7 +2378,7 @@ buf_T *setaltfname(char_u *ffname, char_u *sfname, linenr_T lnum)
buf_T *buf;
/* Create a buffer. 'buflisted' is not set if it's a new buffer */
- buf = buflist_new(ffname, sfname, lnum, 0);
+ buf = buflist_new(ffname, sfname, lnum, 0, 0);
if (buf != NULL && !cmdmod.keepalt)
curwin->w_alt_fnum = buf->b_fnum;
return buf;
@@ -2411,7 +2414,7 @@ int buflist_add(char_u *fname, int flags)
{
buf_T *buf;
- buf = buflist_new(fname, NULL, (linenr_T)0, flags);
+ buf = buflist_new(fname, NULL, (linenr_T)0, flags, 0);
if (buf != NULL)
return buf->b_fnum;
return 0;
@@ -5193,7 +5196,7 @@ bool buf_contents_changed(buf_T *buf)
bool differ = true;
// Allocate a buffer without putting it in the buffer list.
- buf_T *newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ buf_T *newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY, 0);
if (newbuf == NULL) {
return true;
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 512555eac1..508bdac46d 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -7703,7 +7703,7 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
&& !error
&& (name = get_tv_string_chk(&argvars[0])) != NULL
&& !error)
- buf = buflist_new(name, NULL, (linenr_T)1, 0);
+ buf = buflist_new(name, NULL, (linenr_T)1, 0, 0);
if (buf != NULL)
rettv->vval.v_number = buf->b_fnum;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 17780c58e4..767f4b50a2 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -8,6 +8,7 @@
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
+#include <math.h>
#include "nvim/vim.h"
#include "nvim/ascii.h"
@@ -64,6 +65,12 @@
*/
typedef struct sign sign_T;
+// boolean to know if inc_sub needs to undo
+static bool inc_sub_did_changes = false;
+
+// reuse the same bufnr for inc_sub
+static handle_T inc_sub_bufnr = 0;
+
/// Case matching style to use for :substitute
typedef enum {
kSubHonorOptions = 0, ///< Honor the user's 'ignorecase'/'smartcase' options
@@ -1523,7 +1530,7 @@ int rename_buffer(char_u *new_fname)
}
curbuf->b_flags |= BF_NOTEDITED;
if (xfname != NULL && *xfname != NUL) {
- buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, 0);
+ buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, 0, 0);
if (buf != NULL && !cmdmod.keepalt)
curwin->w_alt_fnum = buf->b_fnum;
}
@@ -2174,7 +2181,7 @@ do_ecmd (
buflist_altfpos(oldwin);
}
- if (fnum)
+ if (fnum && !(flags & ECMD_RESERVED_BUFNR))
buf = buflist_findnr(fnum);
else {
if (flags & ECMD_ADDBUF) {
@@ -2185,11 +2192,11 @@ do_ecmd (
if (tlnum <= 0)
tlnum = 1L;
}
- (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED);
+ (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED, fnum);
goto theend;
}
buf = buflist_new(ffname, sfname, 0L,
- BLN_CURBUF | ((flags & ECMD_SET_HELP) ? 0 : BLN_LISTED));
+ BLN_CURBUF | ((flags & ECMD_SET_HELP) ? 0 : BLN_LISTED), fnum);
// Autocmds may change curwin and curbuf.
if (oldwin != NULL) {
oldwin = curwin;
@@ -2978,10 +2985,12 @@ static bool sub_joining_lines(exarg_T *eap, char_u *pat,
ex_may_print(eap);
}
- if (!cmdmod.keeppatterns) {
- save_re_pat(RE_SUBST, pat, p_magic);
+ if (!eap->is_live){
+ if (!cmdmod.keeppatterns) {
+ save_re_pat(RE_SUBST, pat, p_magic);
+ }
+ add_to_history(HIST_SEARCH, pat, TRUE, NUL);
}
- add_to_history(HIST_SEARCH, pat, TRUE, NUL);
return true;
}
@@ -3127,6 +3136,9 @@ void do_sub(exarg_T *eap)
int start_nsubs;
int save_ma = 0;
+ inc_sub_did_changes = false;
+ bool has_second_delim = false;
+
if (!global_busy) {
sub_nsubs = 0;
sub_nlines = 0;
@@ -3161,6 +3173,7 @@ void do_sub(exarg_T *eap)
which_pat = RE_SEARCH; /* use last '/' pattern */
pat = (char_u *)""; /* empty search pattern */
delimiter = *cmd++; /* remember delimiter character */
+ has_second_delim = true;
} else { /* find the end of the regexp */
if (p_altkeymap && curwin->w_p_rl)
lrF_sub(cmd);
@@ -3168,8 +3181,10 @@ void do_sub(exarg_T *eap)
delimiter = *cmd++; /* remember delimiter character */
pat = cmd; /* remember start of search pat */
cmd = skip_regexp(cmd, delimiter, p_magic, &eap->arg);
- if (cmd[0] == delimiter) /* end delimiter found */
+ if (cmd[0] == delimiter) { /* end delimiter found */
*cmd++ = NUL; /* replace it with a NUL */
+ has_second_delim = true;
+ }
}
/*
@@ -3188,7 +3203,7 @@ void do_sub(exarg_T *eap)
mb_ptr_adv(cmd);
}
- if (!eap->skip) {
+ if (!eap->skip && !eap->is_live) {
sub_set_replacement((SubReplacementString) {
.sub = xstrdup((char *) sub),
.timestamp = os_time(),
@@ -3252,7 +3267,9 @@ void do_sub(exarg_T *eap)
return;
}
- if (search_regcomp(pat, RE_SUBST, which_pat, SEARCH_HIS, &regmatch) == FAIL) {
+ int search_options = eap->is_live ? 0 : SEARCH_HIS;
+ if (search_regcomp(pat, RE_SUBST, which_pat, search_options,
+ &regmatch) == FAIL) {
if (subflags.do_error) {
EMSG(_(e_invcmd));
}
@@ -3276,6 +3293,9 @@ void do_sub(exarg_T *eap)
if (!(sub[0] == '\\' && sub[1] == '='))
sub = regtilde(sub, p_magic);
+ // list to save matched lines
+ MatchedLineVec lmatch = KV_INITIAL_VALUE;
+
// Check for a match on each line.
linenr_T line2 = eap->line2;
for (linenr_T lnum = eap->line1;
@@ -3343,6 +3363,8 @@ void do_sub(exarg_T *eap)
sub_firstlnum = lnum;
copycol = 0;
matchcol = 0;
+ // the current match
+ MatchedLine cmatch = { 0, 0, NULL, KV_INITIAL_VALUE };
/* At first match, remember current cursor position. */
if (!got_match) {
@@ -3379,6 +3401,10 @@ void do_sub(exarg_T *eap)
curwin->w_cursor.lnum = lnum;
do_again = FALSE;
+ // increment number of match on the line and store the column
+ cmatch.nmatch++;
+ kv_push(cmatch.start_col, regmatch.startpos[0].col);
+
/*
* 1. Match empty string does not count, except for first
* match. This reproduces the strange vi behaviour.
@@ -3426,7 +3452,7 @@ void do_sub(exarg_T *eap)
goto skip;
}
- if (subflags.do_ask) {
+ if (subflags.do_ask && !eap->is_live) {
int typed = 0;
/* change State to CONFIRM, so that the mouse works
@@ -3597,9 +3623,9 @@ void do_sub(exarg_T *eap)
* use "\=col("."). */
curwin->w_cursor.col = regmatch.startpos[0].col;
- /*
- * 3. substitute the string.
- */
+ // 3. Substitute the string. Don't do this while incsubstitution and
+ // there's no word to replace by eg : ":%s/pattern"
+ if (!eap->is_live || has_second_delim) {
if (subflags.do_count) {
// prevent accidentally changing the buffer by a function
save_ma = curbuf->b_p_ma;
@@ -3726,6 +3752,7 @@ void do_sub(exarg_T *eap)
} else if (has_mbyte)
p1 += (*mb_ptr2len)(p1) - 1;
}
+ }
// 4. If subflags.do_all is set, find next match.
// Prevent endless loop with patterns that match empty
@@ -3845,6 +3872,12 @@ skip:
xfree(new_start); /* for when substitute was cancelled */
xfree(sub_firstline); /* free the copy of the original line */
sub_firstline = NULL;
+
+ // saving info about the matched line
+ cmatch.lnum = lnum;
+ cmatch.line = vim_strsave(ml_get(lnum));
+
+ kv_push(lmatch, cmatch);
}
line_breakcheck();
@@ -3880,7 +3913,7 @@ skip:
beginline(BL_WHITE | BL_FIX);
}
}
- if (!do_sub_msg(subflags.do_count) && subflags.do_ask) {
+ if (!eap->is_live && !do_sub_msg(subflags.do_count) && subflags.do_ask) {
MSG("");
}
} else {
@@ -3912,6 +3945,79 @@ skip:
// Restore the flag values, they can be used for ":&&".
subflags.do_all = save_do_all;
subflags.do_ask = save_do_ask;
+
+ // inc_sub if sub on the whole file and there are results to display
+ if (lmatch.size != 0) {
+ // we did incsubstitute only if we had no word to replace by
+ // by and no ending slash
+ if (!subflags.do_count && (!eap->is_live || has_second_delim)) {
+ inc_sub_did_changes = true;
+ }
+ if (pat != NULL && *p_ics != NUL && eap->is_live) {
+ bool split = true;
+
+ // p_ics is "", "nosplit" or "split"
+ if (*p_ics == 'n' || eap[0].cmdlinep[0][0] == 's') {
+ split = false;
+ }
+
+ // Place cursor on the first match after the cursor
+ // If all matches are before the cursor, then do_sub did
+ // already place the cursor on the last match
+
+ linenr_T cur_lnum = 0;
+ colnr_T cur_col = -1;
+ MatchedLine current;
+
+ for (size_t j = 0; j < lmatch.size; j++) {
+ current = lmatch.items[j];
+ cur_lnum = current.lnum;
+
+ // 1. Match on line of the cursor, need to iterate over the
+ // matches on this line to see if there is one on a later
+ // column
+ if (cur_lnum == old_cursor.lnum) {
+ for (size_t i = 0; i < current.start_col.size; i++) {
+ if (current.start_col.items[i] >= old_cursor.col) {
+ cur_col = current.start_col.items[i];
+ break;
+ }
+ }
+ // match on cursor's line, after the cursor
+ if (cur_col != -1) {
+ curwin->w_cursor.lnum = cur_lnum;
+ curwin->w_cursor.col = cur_col;
+ break;
+ }
+ // 2. Match on line after cursor, just put cursor on column
+ // of first match there
+ } else if (cur_lnum > old_cursor.lnum) {
+ cur_col = current.start_col.items[0];
+ curwin->w_cursor.lnum = cur_lnum;
+ curwin->w_cursor.col = cur_col;
+ break;
+ }
+ }
+
+ inc_sub_display(pat, sub, &lmatch, split);
+ } else if (*p_ics != NUL && eap->is_live) {
+ curwin->w_cursor = old_cursor;
+ }
+ } else {
+ curwin->w_cursor = old_cursor;
+ }
+
+ MatchedLine current;
+ for (size_t j = 0; j < lmatch.size; j++) {
+ current = lmatch.items[j];
+
+ if (current.line) { xfree(current.line); }
+
+ kv_destroy(current.start_col);
+ }
+
+
+ kv_destroy(lmatch);
} // NOLINT(readability/fn_size)
/*
@@ -5951,3 +6057,192 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
}
}
}
+
+/// Open a window for displaying of the inc_sub mode.
+///
+/// Does not allow editing in the window. Closes the window and restores
+/// the window layout before returning.
+///
+/// @param pat The pattern word
+/// @param sub The replacement word
+/// @param lmatch The list containing our data
+static void inc_sub_display(char_u * pat,
+ char_u * sub,
+ MatchedLineVec *lmatch,
+ bool split)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
+{
+ garray_T winsizes;
+ int save_restart_edit = restart_edit;
+ int save_State = State;
+ int save_exmode = exmode_active;
+ int save_cmdmsg_rl = cmdmsg_rl;
+
+ // Can't do this recursively. Can't do it when typing a password.
+ if (cmdline_star > 0) {
+ beep_flush();
+ return;
+ }
+
+ // Save current window sizes.
+ win_size_save(&winsizes);
+
+ // Save the current window to restore it later
+ win_T *oldwin = curwin;
+
+ if (split) {
+ // don't use a new tab page
+ cmdmod.tab = 0;
+
+ // Create a window for the command-line buffer.
+ if (win_split((int)p_cwh, WSP_BOT) == FAIL) {
+ beep_flush();
+ return;
+ }
+ cmdwin_type = get_cmdline_type();
+
+ // Create the command-line buffer empty.
+ (void)do_ecmd(inc_sub_bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE | ECMD_RESERVED_BUFNR, NULL);
+ inc_sub_bufnr = curbuf->handle;
+ (void)setfname(curbuf, (char_u *) "[inc_sub]", NULL, true);
+ set_option_value((char_u *) "bt", 0L, (char_u *) "incsub", OPT_LOCAL);
+ set_option_value((char_u *) "swf", 0L, NULL, OPT_LOCAL);
+ curbuf->b_p_ma = false; // Not Modifiable
+ curwin->w_p_fen = false;
+ curwin->w_p_rl = cmdmsg_rl;
+ cmdmsg_rl = false;
+ RESET_BINDING(curwin);
+
+ // Showing the prompt may have set need_wait_return, reset it.
+ need_wait_return = false;
+
+ // Reset 'textwidth' after setting 'filetype'
+ // (the Vim filetype plugin sets 'textwidth' to 78).
+ curbuf->b_p_tw = 0;
+
+ // Save the buffer used in the split
+ livebuf = curbuf;
+
+ // Initialize line and highlight variables
+ int line = 0;
+ int src_id_highlight = 0;
+ long sub_size = STRLEN(sub);
+ long pat_size = STRLEN(pat);
+
+ // Get the width of the column which display the number of the line
+ linenr_T highest_num_line = kv_last(*lmatch).lnum;
+
+ // computing the length of the column that will display line number
+ int col_width = log10(highest_num_line) + 1 + 3;
+
+ // will be allocated in the loop
+ char *str = NULL;
+
+ size_t old_line_size = 0;
+ size_t line_size;
+
+ // Append the lines to our buffer
+ for (size_t i = 0; i < (*lmatch).size; i++) {
+ MatchedLine mat = (*lmatch).items[i];
+ line_size = STRLEN(mat.line) + col_width + 1;
+
+ // Reallocation if str 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 incsubstitute 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) {
+ int hlgroup_ls = syn_check_group((char_u *)"IncSubstitute", 13);
+
+ for (size_t j = 0; j < mat.start_col.size; j++) {
+ src_id_highlight =
+ bufhl_add_hl(curbuf,
+ src_id_highlight,
+ hlgroup_ls, // id of our highlight
+ line,
+ mat.start_col.items[j] + col_width
+ + j * (sub_size - pat_size) + 1,
+ mat.start_col.items[j] + col_width
+ + j * (sub_size - pat_size) + sub_size);
+ }
+ }
+ }
+ xfree(str);
+ redraw_later(SOME_VALID);
+ }
+
+ // Restore the old window
+ win_enter(oldwin, false);
+ win_size_restore(&winsizes);
+ ga_clear(&winsizes);
+ exmode_active = save_exmode;
+ restart_edit = save_restart_edit;
+ cmdmsg_rl = save_cmdmsg_rl;
+ State = save_State;
+
+ cmdwin_type = 0;
+ int save_rd = RedrawingDisabled;
+ RedrawingDisabled = 0;
+ update_screen(0);
+ RedrawingDisabled = save_rd;
+
+ setmouse();
+}
+
+
+/// :substitute command implementation
+///
+/// Uses do_sub() to do the actual substitution. Undoes the substitution and
+/// removes it from the undo history unless finishing the command. If
+/// ics is set to "", it just calls do_sub().
+void do_inc_sub(exarg_T *eap)
+{
+ // if incsubstitute disabled, do it the classical way
+ if (*p_ics == NUL || !eap->is_live) {
+ do_sub(eap);
+ return;
+ }
+
+ // Save the state of eap
+ char_u *tmp = eap->arg;
+
+ save_search_patterns();
+
+ // save the value of undolevels to the maximum value to avoid losing
+ // history when it is set to a low value
+ long b_p_ul_save = curbuf->b_p_ul;
+ curbuf->b_p_ul = LONG_MAX;
+
+ // Incsub window/buffer is opened in do_sub, so to suppress autocmd
+ // we need to start it before the call
+ block_autocmds();
+
+ emsg_off++; // No error messages for live commands
+ do_sub(eap);
+ emsg_off--;
+ if (inc_sub_did_changes) {
+ if (!u_undo_and_forget(1)) {
+ abort();
+ }
+ inc_sub_did_changes = false;
+ }
+
+ // Put back eap in first state
+ eap->arg = tmp;
+ restore_search_patterns();
+ curbuf->b_p_ul = b_p_ul_save;
+
+ update_screen(0);
+ if (livebuf != NULL && buf_valid(livebuf)) {
+ close_windows(livebuf, false);
+ wipe_buffer(livebuf, false);
+ }
+ unblock_autocmds();
+ redraw_later(SOME_VALID);
+}
diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h
index 721145efd8..991695deca 100644
--- a/src/nvim/ex_cmds.h
+++ b/src/nvim/ex_cmds.h
@@ -5,6 +5,9 @@
#include "nvim/os/time.h"
#include "nvim/eval_defs.h"
+#include "nvim/pos.h"
+#include "nvim/lib/klist.h"
+#include "nvim/lib/kvec.h"
/* flags for do_ecmd() */
#define ECMD_HIDE 0x01 /* don't free the current buffer */
@@ -13,6 +16,7 @@
#define ECMD_OLDBUF 0x04 /* use existing buffer if it exists */
#define ECMD_FORCEIT 0x08 /* ! used in Ex command */
#define ECMD_ADDBUF 0x10 /* don't edit, just add to buffer list */
+#define ECMD_RESERVED_BUFNR 0x20 /* bufnr argument is reserved bufnr */
/* for lnum argument in do_ecmd() */
#define ECMD_LASTL (linenr_T)0 /* use last position in loaded file */
@@ -26,6 +30,24 @@ typedef struct {
list_T *additional_elements; ///< Additional data left from ShaDa file.
} SubReplacementString;
+
+// Defs for inc_sub functionality
+
+/// Structure to backup and display matched lines in incsubstitution
+typedef struct {
+ linenr_T lnum;
+ long nmatch;
+ char_u *line;
+ // list of column numbers of matches on this line
+ kvec_t(colnr_T) start_col;
+} MatchedLine;
+
+// List of matched lines
+typedef kvec_t(MatchedLine) MatchedLineVec;
+
+// End defs for inc_sub functionality
+
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.h.generated.h"
#endif
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 3f5d9b3244..04fc163222 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -2194,7 +2194,7 @@ return {
command='substitute',
flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN),
addr_type=ADDR_LINES,
- func='do_sub',
+ func='do_inc_sub',
},
{
command='sNext',
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 8148eb5cee..5b647ff69a 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -124,6 +124,7 @@ struct exarg {
LineGetter getline; ///< Function used to get the next line
void *cookie; ///< argument for getline()
struct condstack *cstack; ///< condition stack for ":if" etc.
+ bool is_live; ///< live preview
};
#define FORCE_BIN 1 // ":edit ++bin file"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 5e418bf099..a7b1ee2f54 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -271,7 +271,7 @@ do_exmode (
int do_cmdline_cmd(char *cmd)
{
return do_cmdline((char_u *)cmd, NULL, NULL,
- DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
+ DOCMD_NOWAIT|DOCMD_KEYTYPED);
}
/*
@@ -597,11 +597,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
* do_one_cmd() will return NULL if there is no trailing '|'.
* "cmdline_copy" can change, e.g. for '%' and '#' expansion.
*/
- ++recursive;
- next_cmdline = do_one_cmd(&cmdline_copy, flags & DOCMD_VERBOSE,
- &cstack,
- cmd_getline, cmd_cookie);
- --recursive;
+ recursive++;
+ next_cmdline = do_one_cmd(&cmdline_copy, flags,
+ &cstack,
+ cmd_getline, cmd_cookie);
+ recursive--;
if (cmd_cookie == (void *)&cmd_loop_cookie)
/* Use "current_line" from "cmd_loop_cookie", it may have been
@@ -1225,7 +1225,7 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap)
* This function may be called recursively!
*/
static char_u * do_one_cmd(char_u **cmdlinep,
- int sourcing,
+ int flags,
struct condstack *cstack,
LineGetter fgetline,
void *cookie /* argument for fgetline() */
@@ -1248,6 +1248,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
memset(&ea, 0, sizeof(ea));
ea.line1 = 1;
ea.line2 = 1;
+ ea.is_live = flags & DOCMD_LIVE_PREVIEW;
++ex_nesting_level;
/* When the last file has not been edited :q has to be typed twice. */
@@ -1726,7 +1727,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (ea.cmdidx == CMD_SIZE) {
if (!ea.skip) {
STRCPY(IObuff, _("E492: Not an editor command"));
- if (!sourcing)
+ if (!(flags & DOCMD_VERBOSE))
append_command(*cmdlinep);
errormsg = IObuff;
did_emsg_syntax = TRUE;
@@ -1809,7 +1810,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
*/
if (!global_busy && ea.line1 > ea.line2) {
if (msg_silent == 0) {
- if (sourcing || exmode_active) {
+ if ((flags & DOCMD_VERBOSE) || exmode_active) {
errormsg = (char_u *)_("E493: Backwards range given");
goto doend;
}
@@ -2221,7 +2222,7 @@ doend:
curwin->w_cursor.lnum = 1;
if (errormsg != NULL && *errormsg != NUL && !did_emsg) {
- if (sourcing) {
+ if (flags & DOCMD_VERBOSE) {
if (errormsg != IObuff) {
STRCPY(IObuff, errormsg);
errormsg = IObuff;
@@ -9647,3 +9648,28 @@ static void ex_terminal(exarg_T *eap)
xfree(name);
}
}
+
+/// Check whether commandline starts with a live command
+///
+/// @param[in] cmd_live Commandline to check. May start with a range.
+///
+/// @return True if first command is a live command
+/// Currently :s is the only one
+bool is_live(char_u *cmd_live)
+{
+ exarg_T ea;
+ ea.cmd = cmd_live;
+
+ // parse the command line
+ if (ea.cmd != NULL) {
+ ea.cmd = skip_range(ea.cmd, NULL);
+ if (*ea.cmd == '*') {
+ ea.cmd = skipwhite(ea.cmd + 1);
+ }
+ find_command(&ea, NULL);
+ } else {
+ return false;
+ }
+
+ return (ea.cmdidx == CMD_substitute);
+}
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index bafad20169..d2106c545f 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -10,6 +10,7 @@
#define DOCMD_KEYTYPED 0x08 /* don't reset KeyTyped */
#define DOCMD_EXCRESET 0x10 /* reset exception environment (for debugging)*/
#define DOCMD_KEEPLINE 0x20 /* keep typed line for repeating with "." */
+#define DOCMD_LIVE_PREVIEW 0x40 // Command is a live preview like incsubstitute
/* defines for eval_vars() */
#define VALID_PATH 1
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index e525c949bd..d1a2ab8bcf 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1591,6 +1591,10 @@ static int command_line_changed(CommandLineState *s)
msg_starthere();
redrawcmdline();
s->did_incsearch = true;
+ } else if (*p_ics != NUL && s->firstc == ':' && is_live(ccline.cmdbuff)) {
+ // compute a live action
+ do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_LIVE_PREVIEW);
+ redrawcmdline();
}
if (cmdmsg_rl || (p_arshape && !p_tbidi && enc_utf8)) {
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index c0d4a71b35..3871a3ab78 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -5067,7 +5067,7 @@ void buf_reload(buf_T *buf, int orig_mode)
savebuf = NULL;
else {
/* Allocate a buffer without putting it in the buffer list. */
- savebuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ savebuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY, 0);
if (savebuf != NULL && buf == curbuf) {
/* Open the memline. */
curbuf = savebuf;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 87fb928b30..a5188e98d7 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -600,9 +600,10 @@ EXTERN int redraw_tabline INIT(= FALSE); /* need to redraw tabline */
* All buffers are linked in a list. 'firstbuf' points to the first entry,
* 'lastbuf' to the last entry and 'curbuf' to the currently active buffer.
*/
-EXTERN buf_T *firstbuf INIT(= NULL); /* first buffer */
-EXTERN buf_T *lastbuf INIT(= NULL); /* last buffer */
-EXTERN buf_T *curbuf INIT(= NULL); /* currently active buffer */
+EXTERN buf_T *firstbuf INIT(= NULL); // first buffer
+EXTERN buf_T *lastbuf INIT(= NULL); // last buffer
+EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
+EXTERN buf_T *livebuf INIT(= NULL); // buffer used for live actions
// Iterates over all buffers in the buffer list.
# define FOR_ALL_BUFFERS(buf) for (buf_T *buf = firstbuf; buf != NULL; buf = buf->b_next)
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 2a65cf396b..489e5a2dde 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -474,7 +474,7 @@ static void fname2fnum(xfmark_T *fm)
p = path_shorten_fname(NameBuff, IObuff);
/* buflist_new() will call fmarks_check_names() */
- (void)buflist_new(NameBuff, p, (linenr_T)1, 0);
+ (void)buflist_new(NameBuff, p, (linenr_T)1, 0, 0);
}
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 81919c00d2..761e4451b9 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -288,6 +288,7 @@ static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent",
static char *(p_fcl_values[]) = { "all", NULL };
static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview",
"noinsert", "noselect", NULL };
+static char *(p_ics_values[]) = { "nosplit", "split", NULL };
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "option.c.generated.h"
@@ -3110,9 +3111,13 @@ did_set_string_option (
else if (gvarp == &p_cino) {
/* TODO: recognize errors */
parse_cino(curbuf);
- }
- /* Options that are a list of flags. */
- else {
+ // incsubstitute
+ } else if (varp == &p_ics) {
+ if (check_opt_strings(p_ics, p_ics_values, false) != OK) {
+ errmsg = e_invarg;
+ }
+ // Options that are a list of flags.
+ } else {
p = NULL;
if (varp == &p_ww)
p = (char_u *)WW_ALL;
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index fdfcd1f428..e0711c7c8f 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -589,6 +589,7 @@ EXTERN int p_spr; // 'splitright'
EXTERN int p_sol; // 'startofline'
EXTERN char_u *p_su; // 'suffixes'
EXTERN char_u *p_swb; // 'switchbuf'
+EXTERN char_u *p_ics; // 'incsubstitute'
EXTERN unsigned swb_flags;
#ifdef IN_OPTION_C
static char *(p_swb_values[]) =
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 583c63614a..359bf3fcee 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1211,6 +1211,14 @@ return {
defaults={if_true={vi=false, vim=true}}
},
{
+ full_name='incsubstitute', abbreviation='ics',
+ type='string', scope={'global'},
+ vi_def=true,
+ redraw={'everything'},
+ varname='p_ics',
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='indentexpr', abbreviation='inde',
type='string', scope={'buffer'},
vi_def=true,
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index a7aff15121..08345a323c 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -3327,7 +3327,7 @@ load_dummy_buffer (
aco_save_T aco;
/* Allocate a buffer without putting it in the buffer list. */
- newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY, 0);
if (newbuf == NULL)
return NULL;
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 01c0807d82..1cd8f44ea8 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1405,7 +1405,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
cur_entry.data.buffer_list.buffers[i].fname);
buf_T *const buf = buflist_new(
cur_entry.data.buffer_list.buffers[i].fname, sfname, 0,
- BLN_LISTED);
+ BLN_LISTED, 0);
if (buf != NULL) {
RESET_FMARK(&buf->b_last_cursor,
cur_entry.data.buffer_list.buffers[i].pos, 0);
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index b49ae9da21..a1c6718b06 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -5902,6 +5902,7 @@ static char *highlight_init_both[] =
"WildMenu ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black",
"default link EndOfBuffer NonText",
"default link QuickFixLine Search",
+ "default link IncSubstitute Search",
NULL
};
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index d80aaf443a..a4af45d441 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -1669,7 +1669,7 @@ void u_undo(int count)
undo_undoes = TRUE;
else
undo_undoes = !undo_undoes;
- u_doit(count);
+ u_doit(count, false);
}
/*
@@ -1678,15 +1678,58 @@ void u_undo(int count)
*/
void u_redo(int count)
{
- if (vim_strchr(p_cpo, CPO_UNDO) == NULL)
- undo_undoes = FALSE;
- u_doit(count);
+ if (vim_strchr(p_cpo, CPO_UNDO) == NULL) {
+ undo_undoes = false;
+ }
+
+ u_doit(count, false);
+}
+
+/// undo and forget.
+bool u_undo_and_forget(int count)
+{
+ if (curbuf->b_u_synced == false) {
+ u_sync(true);
+ count = 1;
+ }
+ undo_undoes = true;
+ u_doit(count, true);
+
+ if (curbuf->b_u_curhead == NULL) {
+ // nothing was undone.
+ return false;
+ }
+
+ // Delete the current redo header
+ // set the redo header to the next alternative branch (if any)
+ // otherwise we will be in the leaf state
+ u_header_T *to_forget = curbuf->b_u_curhead;
+ curbuf->b_u_newhead = to_forget->uh_next.ptr;
+ curbuf->b_u_curhead = to_forget->uh_alt_next.ptr;
+ if (curbuf->b_u_curhead) {
+ to_forget->uh_alt_next.ptr = NULL;
+ curbuf->b_u_curhead->uh_alt_prev.ptr = to_forget->uh_alt_prev.ptr;
+ curbuf->b_u_seq_cur = curbuf->b_u_curhead->uh_seq-1;
+ } else if (curbuf->b_u_newhead) {
+ curbuf->b_u_seq_cur = curbuf->b_u_newhead->uh_seq;
+ }
+ if (to_forget->uh_alt_prev.ptr) {
+ to_forget->uh_alt_prev.ptr->uh_alt_next.ptr = curbuf->b_u_curhead;
+ }
+ if (curbuf->b_u_newhead) {
+ curbuf->b_u_newhead->uh_prev.ptr = curbuf->b_u_curhead;
+ }
+ if (curbuf->b_u_seq_last == to_forget->uh_seq) {
+ curbuf->b_u_seq_last--;
+ }
+ u_freebranch(curbuf, to_forget, NULL);
+ return true;
}
/*
* Undo or redo, depending on 'undo_undoes', 'count' times.
*/
-static void u_doit(int startcount)
+static void u_doit(int startcount, bool quiet)
{
int count = startcount;
@@ -1722,7 +1765,7 @@ static void u_doit(int startcount)
break;
}
- u_undoredo(TRUE);
+ u_undoredo(true);
} else {
if (curbuf->b_u_curhead == NULL || get_undolevel() <= 0) {
beep_flush(); /* nothing to redo */
@@ -1742,7 +1785,7 @@ static void u_doit(int startcount)
curbuf->b_u_curhead = curbuf->b_u_curhead->uh_prev.ptr;
}
}
- u_undo_end(undo_undoes, FALSE);
+ u_undo_end(undo_undoes, false, quiet);
}
/*
@@ -2055,7 +2098,7 @@ void undo_time(long step, int sec, int file, int absolute)
}
}
}
- u_undo_end(did_undo, absolute);
+ u_undo_end(did_undo, absolute, false);
}
/*
@@ -2304,10 +2347,11 @@ static void u_undoredo(int undo)
* Otherwise, report the number of changes (this may be incorrect
* in some cases, but it's better than nothing).
*/
-static void
-u_undo_end (
- int did_undo, /* just did an undo */
- int absolute /* used ":undo N" */
+static void
+u_undo_end(
+ int did_undo, // just did an undo
+ int absolute, // used ":undo N"
+ bool quiet
)
{
char *msgstr;
@@ -2317,9 +2361,11 @@ u_undo_end (
if ((fdo_flags & FDO_UNDO) && KeyTyped)
foldOpenCursor();
- if (global_busy /* no messages now, wait until global is finished */
- || !messaging()) /* 'lazyredraw' set, don't do messages now */
+ if (global_busy // no messages now, wait until global is finished
+ || !messaging() // 'lazyredraw' set, don't do messages now
+ || quiet) { // livemode doesn't show messages
return;
+ }
if (curbuf->b_ml.ml_flags & ML_EMPTY)
--u_newcount;
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 9c6a2e26a6..53bc484b48 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -2903,7 +2903,7 @@ static int win_alloc_firstwin(win_T *oldwin)
if (oldwin == NULL) {
/* Very first window, need to create an empty buffer for it and
* initialize from scratch. */
- curbuf = buflist_new(NULL, NULL, 1L, BLN_LISTED);
+ curbuf = buflist_new(NULL, NULL, 1L, BLN_LISTED, 0);
if (curbuf == NULL)
return FAIL;
curwin->w_buffer = curbuf;