mirror of
https://github.com/neovim/neovim.git
synced 2025-10-02 07:58:35 +00:00
Incsubsitution feature
Originally implemented by * Clement0 * DesbyP * aym7 * Adrey06 * Robinhola in #4811. Major reworkings and bug fixes by * bfredl Most tests suggested by ZyX-l, suggestions for improvements by oni-link.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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, ®match) == FAIL) {
|
||||
int search_options = eap->is_live ? 0 : SEARCH_HIS;
|
||||
if (search_regcomp(pat, RE_SUBST, which_pat, search_options,
|
||||
®match) == 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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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',
|
||||
|
@@ -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"
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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)) {
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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[]) =
|
||||
|
@@ -1210,6 +1210,14 @@ return {
|
||||
varname='p_is',
|
||||
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'},
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
};
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user