diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2016-10-31 03:50:19 +0100 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2016-11-08 21:20:08 +0100 |
commit | c04ffe866d276d6a6bd9e9c6a8b0dbb71504db7c (patch) | |
tree | 0fca258fbfb83cb871a493916d9dd6e0ef1195c3 /src | |
parent | e8c0f909626094350be7ee7b524697804da38dc1 (diff) | |
download | rneovim-c04ffe866d276d6a6bd9e9c6a8b0dbb71504db7c.tar.gz rneovim-c04ffe866d276d6a6bd9e9c6a8b0dbb71504db7c.tar.bz2 rneovim-c04ffe866d276d6a6bd9e9c6a8b0dbb71504db7c.zip |
'inccommand': rework
- Eliminate/isolate static/global variables
- Remove special-case parameter from buflist_new()
- Remove special-case ECMD_RESERVED_BUFNR
- To determine when u_undo_and_forget() should be done, check
b_changedtick instead of a heuristic.
- use mb_string2cells() instead of strlen() to measure the :sub patterns
- call ml_close() before buf_clear_file(). Avoids leaks caught by ASan.
Original patch by:
Robin Elrharbi-Fleury (Robinhola)
Audrey Rayé (Adrey06)
Philémon Hullot (DesbyP)
Aymeric Collange (aym7)
Clément Guyomard (Clement0)
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/buffer.c | 79 | ||||
-rw-r--r-- | src/nvim/eval.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 513 | ||||
-rw-r--r-- | src/nvim/ex_cmds.h | 21 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 6 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 25 | ||||
-rw-r--r-- | src/nvim/ex_docmd.h | 3 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 23 | ||||
-rw-r--r-- | src/nvim/fileio.c | 5 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | src/nvim/mark.c | 2 | ||||
-rw-r--r-- | src/nvim/memline.c | 15 | ||||
-rw-r--r-- | src/nvim/quickfix.c | 2 | ||||
-rw-r--r-- | src/nvim/shada.c | 2 | ||||
-rw-r--r-- | src/nvim/syntax.c | 13 | ||||
-rw-r--r-- | src/nvim/undo.c | 30 | ||||
-rw-r--r-- | src/nvim/window.c | 2 |
17 files changed, 335 insertions, 409 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 65a42f063a..190c281c76 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -268,6 +268,9 @@ open_buffer ( bool buf_valid(buf_T *buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + if (buf == NULL) { + return false; + } FOR_ALL_BUFFERS(bp) { if (bp == buf) { return true; @@ -479,6 +482,18 @@ void buf_clear_file(buf_T *buf) buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */ } +/// Clears the current buffer contents. +void buf_clear(void) +{ + linenr_T line_count = curbuf->b_ml.ml_line_count; + while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) { + ml_delete((linenr_T)1, false); + } + deleted_lines_mark(1, line_count); // prepare for display + ml_close(curbuf, true); // free memline_T + buf_clear_file(curbuf); +} + /// buf_freeall() - free all things allocated for a buffer that are related to /// the file. Careful: get here with "curwin" NULL when exiting. /// @@ -678,7 +693,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, 0); + old_curbuf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED); } if (old_curbuf != NULL) { enter_buffer(old_curbuf); @@ -1320,29 +1335,29 @@ void do_autochdir(void) } } -/* - * functions for dealing with the buffer list - */ +// +// functions for dealing with the buffer list +// -/* - * Add a file name to the buffer list. Return a pointer to the buffer. - * If the same file name already exists return a pointer to that buffer. - * If it does not exist, or if fname == NULL, a new entry is created. - * If (flags & BLN_CURBUF) is TRUE, may use current buffer. - * If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list. - * If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer. - * This is the ONLY way to create a new buffer. - */ static int top_file_num = 1; /* highest file number */ -buf_T * -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 - handle_T bufnr -) +/// Add a file name to the buffer list. +/// If the same file name already exists return a pointer to that buffer. +/// If it does not exist, or if fname == NULL, a new entry is created. +/// If (flags & BLN_CURBUF) is TRUE, may use current buffer. +/// If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list. +/// If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer. +/// If (flags & BLN_NEW) is TRUE, don't use an existing buffer. +/// This is the ONLY way to create a new buffer. +/// +/// @param ffname full path of fname or relative +/// @param sfname short fname or NULL +/// @param lnum preferred cursor line +/// @param flags BLN_ defines +/// @param bufnr +/// +/// @return pointer to the buffer +buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) { buf_T *buf; @@ -1460,9 +1475,7 @@ buflist_new( } lastbuf = buf; - // If bufnr is nonzero it is assumed to be a previously - // reserved buffer number (handle) - buf->handle = bufnr != 0 ? bufnr : top_file_num++; + buf->b_fnum = 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")); @@ -2379,7 +2392,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, 0); + buf = buflist_new(ffname, sfname, lnum, 0); if (buf != NULL && !cmdmod.keepalt) { curwin->w_alt_fnum = buf->b_fnum; } @@ -2416,7 +2429,7 @@ int buflist_add(char_u *fname, int flags) { buf_T *buf; - buf = buflist_new(fname, NULL, (linenr_T)0, flags, 0); + buf = buflist_new(fname, NULL, (linenr_T)0, flags); if (buf != NULL) { return buf->b_fnum; } @@ -5199,7 +5212,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, 0); + buf_T *newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); if (newbuf == NULL) { return true; } @@ -5259,3 +5272,15 @@ wipe_buffer ( unblock_autocmds(); } } + +/// Creates or switches to a special-purpose buffer. +/// +/// @param bufnr Buffer to switch to, or 0 to create a new buffer. +void buf_open_special(handle_T bufnr, char *bufname, char *buftype) +{ + (void)do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); + (void)setfname(curbuf, (char_u *)bufname, NULL, true); + set_option_value((char_u *)"bt", 0L, (char_u *)buftype, OPT_LOCAL); + set_option_value((char_u *)"swf", 0L, NULL, OPT_LOCAL); + RESET_BINDING(curwin); +} diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 508bdac46d..512555eac1 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, 0); + buf = buflist_new(name, NULL, (linenr_T)1, 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 97aac76860..9f781c0f8e 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -10,6 +10,9 @@ #include <inttypes.h> #include <math.h> +#include "nvim/api/private/defs.h" +#include "nvim/api/buffer.h" +#include "nvim/log.h" #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/ex_cmds.h" @@ -65,12 +68,6 @@ */ 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 @@ -90,6 +87,15 @@ typedef struct { SubIgnoreType do_ic; ///< ignore case flag } subflags_T; +/// Lines matched during 'incsubstitute'. +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; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds.c.generated.h" #endif @@ -1530,7 +1536,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, 0); + buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, 0); if (buf != NULL && !cmdmod.keepalt) { curwin->w_alt_fnum = buf->b_fnum; } @@ -2027,37 +2033,34 @@ theend: return retval; } -/* - * start editing a new file - * - * fnum: file number; if zero use ffname/sfname - * ffname: the file name - * - full path if sfname used, - * - any file name if sfname is NULL - * - empty string to re-edit with the same file name (but may be - * in a different directory) - * - NULL to start an empty buffer - * sfname: the short file name (or NULL) - * eap: contains the command to be executed after loading the file and - * forced 'ff' and 'fenc' - * newlnum: if > 0: put cursor on this line number (if possible) - * if ECMD_LASTL: use last position in loaded file - * if ECMD_LAST: use last position in all files - * if ECMD_ONE: use first line - * flags: - * ECMD_HIDE: if TRUE don't free the current buffer - * ECMD_SET_HELP: set b_help flag of (new) buffer before opening file - * ECMD_OLDBUF: use existing buffer if it exists - * ECMD_FORCEIT: ! used for Ex command - * ECMD_ADDBUF: don't edit, just add to buffer list - * oldwin: Should be "curwin" when editing a new buffer in the current - * window, NULL when splitting the window first. When not NULL info - * of the previous buffer for "oldwin" is stored. - * - * return FAIL for failure, OK otherwise - */ -int -do_ecmd ( +/// start editing a new file +/// +/// @param fnum file number; if zero use ffname/sfname +/// @param ffname the file name +/// - full path if sfname used, +/// - any file name if sfname is NULL +/// - empty string to re-edit with the same file name (but may +/// be in a different directory) +/// - NULL to start an empty buffer +/// @param sfname the short file name (or NULL) +/// @param eap contains the command to be executed after loading the file +/// and forced 'ff' and 'fenc' +/// @param newlnum if > 0: put cursor on this line number (if possible) +/// ECMD_LASTL: use last position in loaded file +/// ECMD_LAST: use last position in all files +/// ECMD_ONE: use first line +/// @param flags ECMD_HIDE: if TRUE don't free the current buffer +/// ECMD_SET_HELP: set b_help flag of (new) buffer before +/// opening file +/// ECMD_OLDBUF: use existing buffer if it exists +/// ECMD_FORCEIT: ! used for Ex command +/// ECMD_ADDBUF: don't edit, just add to buffer list +/// @param oldwin Should be "curwin" when editing a new buffer in the current +/// window, NULL when splitting the window first. When not NULL +/// info of the previous buffer for "oldwin" is stored. +/// +/// @return FAIL for failure, OK otherwise +int do_ecmd ( int fnum, char_u *ffname, char_u *sfname, @@ -2182,9 +2185,10 @@ do_ecmd ( buflist_altfpos(oldwin); } - if (fnum && !(flags & ECMD_RESERVED_BUFNR)) { + if (fnum) { buf = buflist_findnr(fnum); } else { + ILOG("here"); if (flags & ECMD_ADDBUF) { linenr_T tlnum = 1L; @@ -2193,12 +2197,11 @@ do_ecmd ( if (tlnum <= 0) tlnum = 1L; } - (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED, fnum); + (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED); goto theend; } buf = buflist_new(ffname, sfname, 0L, - BLN_CURBUF | ((flags & ECMD_SET_HELP) ? 0 : BLN_LISTED), - fnum); + BLN_CURBUF | ((flags & ECMD_SET_HELP) ? 0 : BLN_LISTED)); // Autocmds may change curwin and curbuf. if (oldwin != NULL) { oldwin = curwin; @@ -3098,16 +3101,17 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, return cmd; } -/* do_sub() - * - * Perform a substitution from line eap->line1 to line eap->line2 using the - * command pointed to by eap->arg which should be of the form: - * - * /pattern/substitution/{flags} - * - * The usual escapes are supported as described in the regexp docs. - */ -void do_sub(exarg_T *eap) +/// do_sub() +/// +/// Perform a substitution from line eap->line1 to line eap->line2 using the +/// command pointed to by eap->arg which should be of the form: +/// +/// /pattern/substitution/{flags} +/// +/// The usual escapes are supported as described in the regexp docs. +/// +/// @return buffer used for 'incsubstitute' +buf_T *do_sub(exarg_T *eap) { long i = 0; regmmatch_T regmatch; @@ -3123,24 +3127,22 @@ void do_sub(exarg_T *eap) }; char_u *pat = NULL, *sub = NULL; // init for GCC int delimiter; + bool has_second_delim = false; int sublen; int got_quit = false; int got_match = false; int which_pat; char_u *cmd = eap->arg; linenr_T first_line = 0; // first changed line - linenr_T last_line= 0; // below last changed line AFTER the - // change + linenr_T last_line= 0; // below last changed line AFTER the change 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 + 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; pos_T old_cursor = curwin->w_cursor; 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; @@ -3158,7 +3160,7 @@ void do_sub(exarg_T *eap) /* don't accept alphanumeric for separator */ if (isalpha(*cmd)) { EMSG(_("E146: Regular expressions can't be delimited by letters")); - return; + return NULL; } /* * undocumented vi feature: @@ -3169,7 +3171,7 @@ void do_sub(exarg_T *eap) ++cmd; if (vim_strchr((char_u *)"/?&", *cmd) == NULL) { EMSG(_(e_backslash)); - return; + return NULL; } if (*cmd != '&') { which_pat = RE_SEARCH; // use last '/' pattern @@ -3217,7 +3219,7 @@ void do_sub(exarg_T *eap) } else if (!eap->skip) { /* use previous pattern and substitution */ if (old_sub.sub == NULL) { /* there is no previous command */ EMSG(_(e_nopresub)); - return; + return NULL; } pat = NULL; /* search_regcomp() will use previous pattern */ sub = (char_u *) old_sub.sub; @@ -3228,7 +3230,7 @@ void do_sub(exarg_T *eap) } if (sub_joining_lines(eap, pat, sub, cmd)) { - return; + return NULL; } cmd = sub_parse_flags(cmd, &subflags, &which_pat); @@ -3242,7 +3244,7 @@ void do_sub(exarg_T *eap) i = getdigits_long(&cmd); if (i <= 0 && !eap->skip && subflags.do_error) { EMSG(_(e_zerocount)); - return; + return NULL; } eap->line1 = eap->line2; eap->line2 += i - 1; @@ -3258,17 +3260,17 @@ void do_sub(exarg_T *eap) eap->nextcmd = check_nextcmd(cmd); if (eap->nextcmd == NULL) { EMSG(_(e_trailing)); - return; + return NULL; } } if (eap->skip) /* not executing commands, only parsing */ - return; + return NULL; if (!subflags.do_count && !MODIFIABLE(curbuf)) { // Substitution is not allowed in non-'modifiable' buffer EMSG(_(e_modifiable)); - return; + return NULL; } int search_options = eap->is_live ? 0 : SEARCH_HIS; @@ -3277,7 +3279,7 @@ void do_sub(exarg_T *eap) if (subflags.do_error) { EMSG(_(e_invcmd)); } - return; + return NULL; } // the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase' @@ -3297,9 +3299,6 @@ 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; @@ -3368,7 +3367,7 @@ void do_sub(exarg_T *eap) copycol = 0; matchcol = 0; // the current match - MatchedLine cmatch = { 0, 0, NULL, KV_INITIAL_VALUE }; + MatchedLine matched_line = { 0, 0, NULL, KV_INITIAL_VALUE }; /* At first match, remember current cursor position. */ if (!got_match) { @@ -3405,9 +3404,9 @@ 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); + // 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 @@ -3627,8 +3626,8 @@ void do_sub(exarg_T *eap) * use "\=col("."). */ curwin->w_cursor.col = regmatch.startpos[0].col; - // 3. Substitute the string. Don't do this while incsubstitution and - // there's no word to replace by eg : ":%s/pattern" + // 3. Substitute the string. During 'incsubstitute' only do this if + // there is a replace pattern. if (!eap->is_live || has_second_delim) { if (subflags.do_count) { // prevent accidentally changing the buffer by a function @@ -3873,11 +3872,9 @@ skip: 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); + matched_line.lnum = lnum; + matched_line.line = vim_strsave(ml_get(lnum)); + kv_push(matched_lines, matched_line); } line_breakcheck(); @@ -3946,78 +3943,65 @@ skip: 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; + // Show 'incsubstitute' preview if there are matched lines. + buf_T *incsub_buf = NULL; + if (matched_lines.size != 0 && pat != NULL && *p_ics != NUL && eap->is_live) { + // Place cursor on the first match after the cursor. + // If all matches are before the cursor, then do_sub already placed the + // cursor on the last match. + + linenr_T cur_lnum = 0; + colnr_T cur_col = -1; + MatchedLine current; + + for (size_t j = 0; j < matched_lines.size; j++) { + current = matched_lines.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.cols.size; i++) { + if (current.cols.items[i] >= old_cursor.col) { + cur_col = current.cols.items[i]; 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; } + // 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.cols.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); } + incsub_buf = incsub_display(pat, sub, eap->line1, eap->line2, + &matched_lines); - kv_destroy(current.start_col); + } else if (*p_ics != NUL && eap->is_live) { + curwin->w_cursor = old_cursor; // don't move the cursor } + MatchedLine current; + for (size_t j = 0; j < matched_lines.size; j++) { + current = matched_lines.items[j]; + if (current.line) { + xfree(current.line); + } + kv_destroy(current.cols); + } - kv_destroy(lmatch); + kv_destroy(matched_lines); + return incsub_buf; } // NOLINT(readability/fn_size) /* @@ -6058,192 +6042,143 @@ 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) +/// Shows a preview of :substitute (for 'incsubstitute'). +/// With incsubstitute=split, shows a special buffer in a window, draws the +/// screen, then restores the layout. +static buf_T *incsub_display(char_u *pat, char_u *sub, + linenr_T line1, linenr_T line2, + MatchedLineVec *matched_lines) + FUNC_ATTR_NONNULL_ALL { - 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 + static handle_T bufnr = 0; // special buffer, re-used on each visit + + garray_T save_winsizes; + win_T *save_curwin = curwin; + cmdmod_T save_cmdmod = cmdmod; + char_u *save_shm_p = vim_strsave(p_shm); + size_t sub_size = mb_string2cells(sub); + size_t pat_size = mb_string2cells(pat); + + // We keep a special-purpose buffer around, but don't assume it exists. + buf_T *incsub_buf = bufnr ? buflist_findnr(bufnr) : 0; + win_size_save(&save_winsizes); // Save current window sizes. + cmdmod.tab = 0; // disable :tab modifier + cmdmod.noswapfile = true; // disable swap for 'incsubstitute' buffer + // disable file info message + set_option_value((char_u *)"shm", 0L, (char_u *)"F", 0); + + bool outside_curline = (line1 != curwin->w_cursor.lnum + || line2 != curwin->w_cursor.lnum); + bool split = outside_curline && (*p_ics != 'n') && (sub_size || pat_size); + if (incsub_buf == curbuf) { // Preview buffer cannot preview itself! + split = false; + incsub_buf = NULL; + } + + if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) { + buf_open_special(incsub_buf ? bufnr : 0, "[inc_sub]", "incsub"); + buf_clear(); + incsub_buf = curbuf; + set_option_value((char_u *)"bl", 0L, NULL, OPT_LOCAL); + set_option_value((char_u *)"bh", 0L, (char_u *)"hide", OPT_LOCAL); + bufnr = incsub_buf->handle; + curbuf->b_p_ma = true; + curbuf->b_p_ul = -1; curwin->w_p_fen = false; - curwin->w_p_rl = cmdmsg_rl; - cmdmsg_rl = false; - RESET_BINDING(curwin); + curbuf->b_p_tw = 0; // Reset 'textwidth' (was set by ftplugin) - // 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 + // 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; - // will be allocated in the loop 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 *)"IncSubstitute", 13); - // 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; + // Dump the lines into the incsub 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; - // Reallocation if str not long enough + // Reallocate 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 + // 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); + 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); + 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); } } } 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; + redraw_later(SOME_VALID); + + win_enter(save_curwin, false); // Return to original window + win_size_restore(&save_winsizes); + ga_clear(&save_winsizes); + + set_option_value((char_u *)"shm", 0L, save_shm_p, 0); + xfree(save_shm_p); - cmdwin_type = 0; + // Update screen now. Must do this _before_ close_windows(). int save_rd = RedrawingDisabled; RedrawingDisabled = 0; - update_screen(0); + update_screen(NOT_VALID); RedrawingDisabled = save_rd; - setmouse(); -} + cmdmod = save_cmdmod; + return incsub_buf; +} -/// :substitute command implementation +/// :substitute command /// -/// 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' is empty, this just calls do_sub(). +/// If 'incsubstitute' is set, substitutes as-you-type ("live"). +/// If the command is cancelled the changes are removed from undo history. +void ex_substitute(exarg_T *eap) { - // if incsubstitute disabled, do it the classical way - if (*p_ics == NUL || !eap->is_live) { - do_sub(eap); + if (*p_ics == NUL || !eap->is_live) { // 'incsubstitute' is disabled + (void)do_sub(eap); return; } - // Save the state of eap - char_u *tmp = eap->arg; - + char_u *save_eap = eap->arg; save_search_patterns(); + int save_changedtick = curbuf->b_changedtick; + long save_b_p_ul = curbuf->b_p_ul; + curbuf->b_p_ul = LONG_MAX; // make sure we can undo all changes + block_autocmds(); // disable events before incsub opening window/buffer + emsg_off++; // No error messages for live commands - // 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(); + buf_T *incsub_buf = do_sub(eap); - 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; + if (save_changedtick != curbuf->b_changedtick + && !u_undo_and_forget(1)) { + abort(); } - - // 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); + if (buf_valid(incsub_buf)) { + // XXX: Must do this *after* u_undo_and_forget(), why? + close_windows(incsub_buf, false); } + curbuf->b_p_ul = save_b_p_ul; + eap->arg = save_eap; + restore_search_patterns(); + emsg_off--; unblock_autocmds(); - redraw_later(SOME_VALID); } diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index 82848528ce..ccb2202edb 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -6,8 +6,6 @@ #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 @@ -18,6 +16,7 @@ #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 */ #define ECMD_LAST (linenr_T)-1 /* use last position in all files */ @@ -30,24 +29,6 @@ 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 04fc163222..95ede4bdc5 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_inc_sub', + func='ex_substitute', }, { command='sNext', @@ -3181,7 +3181,7 @@ return { enum='CMD_and', flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), addr_type=ADDR_LINES, - func='do_sub', + func='ex_substitute', }, { command='<', @@ -3222,6 +3222,6 @@ return { enum='CMD_tilde', flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), addr_type=ADDR_LINES, - func='do_sub', + func='ex_substitute', }, } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d8b92e9ec0..30347cbe85 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1248,7 +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; + ea.is_live = flags & DOCMD_LIVE; ex_nesting_level++; /* When the last file has not been edited :q has to be typed twice. */ @@ -9652,25 +9652,22 @@ static void ex_terminal(exarg_T *eap) /// Check whether commandline starts with a live command /// -/// @param[in] cmd_live Commandline to check. May start with a range. +/// @param[in] cmd 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) +bool cmd_is_live(char_u *cmd) { - exarg_T ea; - ea.cmd = cmd_live; + if (cmd == NULL) { + return false; + } + exarg_T ea; // 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; + ea.cmd = skip_range(cmd, NULL); + if (*ea.cmd == '*') { + ea.cmd = skipwhite(ea.cmd + 1); } + find_command(&ea, NULL); return (ea.cmdidx == CMD_substitute); } diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index e30ece3e1b..84f1a13a15 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -10,8 +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 - // incsubstitution +#define DOCMD_LIVE 0x40 // show updates as-you-type ("live" command) /* defines for eval_vars() */ #define VALID_PATH 1 diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index d1a2ab8bcf..dba7a107e2 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1591,9 +1591,12 @@ 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); + } else if (s->firstc == ':' + && *p_ics != NUL // 'incsubstitute' is set + && cmdline_star == 0 // not typing a password + && cmd_is_live(ccline.cmdbuff)) { + // process a "live" command + do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_LIVE); redrawcmdline(); } @@ -5141,16 +5144,12 @@ static int ex_window(void) } cmdwin_type = get_cmdline_type(); - /* Create the command-line buffer empty. */ - (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); - (void)setfname(curbuf, (char_u *)"[Command Line]", NULL, TRUE); - set_option_value((char_u *)"bt", 0L, (char_u *)"nofile", OPT_LOCAL); - set_option_value((char_u *)"swf", 0L, NULL, OPT_LOCAL); - curbuf->b_p_ma = TRUE; - curwin->w_p_fen = FALSE; + // Create empty command-line buffer. + buf_open_special(0, "[Command Line]", "nofile"); curwin->w_p_rl = cmdmsg_rl; - cmdmsg_rl = FALSE; - RESET_BINDING(curwin); + cmdmsg_rl = false; + curbuf->b_p_ma = true; + curwin->w_p_fen = false; /* Do execute autocommands for setting the filetype (load syntax). */ unblock_autocmds(); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 7ce2c57420..e83e9f6b01 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, 0); + savebuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); if (savebuf != NULL && buf == curbuf) { /* Open the memline. */ curbuf = savebuf; @@ -6409,8 +6409,9 @@ win_found: && curbuf != aco->new_curbuf && buf_valid(aco->new_curbuf) && aco->new_curbuf->b_ml.ml_mfp != NULL) { - if (curwin->w_s == &curbuf->b_s) + if (curwin->w_s == &curbuf->b_s) { curwin->w_s = &aco->new_curbuf->b_s; + } --curbuf->b_nwindows; curbuf = aco->new_curbuf; curwin->w_buffer = curbuf; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index a5188e98d7..690be70c4d 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -603,7 +603,6 @@ EXTERN int redraw_tabline INIT(= FALSE); /* need to redraw tabline */ 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 96983a6c3b..6453c41415 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, 0); + (void)buflist_new(NameBuff, p, (linenr_T)1, 0); } } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 5505335769..a9a53ebca7 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -2340,14 +2340,13 @@ int ml_replace(linenr_T lnum, char_u *line, int copy) return OK; } -/* - * Delete line 'lnum' in the current buffer. - * - * Check: The caller of this function should probably also call - * deleted_lines() after this. - * - * return FAIL for failure, OK otherwise - */ +/// Delete line `lnum` in the current buffer. +/// +/// @note The caller of this function should probably also call +/// deleted_lines() after this. +/// +/// @param message Show "--No lines in buffer--" message. +/// @return FAIL for failure, OK otherwise int ml_delete(linenr_T lnum, int message) { ml_flush_line(curbuf); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 97172a15ff..f23037613b 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, 0); + newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); if (newbuf == NULL) { return NULL; } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 1cd8f44ea8..01c0807d82 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, 0); + BLN_LISTED); 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 a1c6718b06..68d0e3da0f 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -7160,16 +7160,13 @@ int syn_namen2id(char_u *linep, int len) */ int syn_check_group(char_u *pp, int len) { - int id; - char_u *name; - - name = vim_strnsave(pp, len); - - id = syn_name2id(name); - if (id == 0) /* doesn't exist yet */ + char_u *name = vim_strnsave(pp, len); + int id = syn_name2id(name); + if (id == 0) { // doesn't exist yet id = syn_add_group(name); - else + } else { xfree(name); + } return id; } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index a4af45d441..20adf40012 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -1685,7 +1685,7 @@ void u_redo(int count) u_doit(count, false); } -/// undo and forget. +/// undo, and remove the undo branch from the undo tree. bool u_undo_and_forget(int count) { if (curbuf->b_u_synced == false) { @@ -1726,9 +1726,7 @@ bool u_undo_and_forget(int count) return true; } -/* - * Undo or redo, depending on 'undo_undoes', 'count' times. - */ +/// Undo or redo, depending on `undo_undoes`, `count` times. static void u_doit(int startcount, bool quiet) { int count = startcount; @@ -2342,17 +2340,13 @@ static void u_undoredo(int undo) #endif } -/* - * If we deleted or added lines, report the number of less/more lines. - * 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" - bool quiet -) +/// If we deleted or added lines, report the number of less/more lines. +/// 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" + bool quiet) { char *msgstr; u_header_T *uhp; @@ -2361,9 +2355,9 @@ 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 - || quiet) { // livemode doesn't show messages + if (quiet + || global_busy // no messages until global is finished + || !messaging()) { // 'lazyredraw' set, don't do messages now return; } diff --git a/src/nvim/window.c b/src/nvim/window.c index 45b52e9454..8512556c0a 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, 0); + curbuf = buflist_new(NULL, NULL, 1L, BLN_LISTED); if (curbuf == NULL) { return FAIL; } |