aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/ex_cmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/ex_cmds.c')
-rw-r--r--src/nvim/ex_cmds.c325
1 files changed, 310 insertions, 15 deletions
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);
+}