'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)
This commit is contained in:
Justin M. Keyes
2016-10-31 03:50:19 +01:00
parent e8c0f90962
commit c04ffe866d
24 changed files with 386 additions and 472 deletions

View File

@@ -450,7 +450,7 @@ notation meaning equivalent decimal value(s) ~
<k0> - <k9> keypad 0 to 9 *keypad-0* *keypad-9* <k0> - <k9> keypad 0 to 9 *keypad-0* *keypad-9*
<S-...> shift-key *shift* *<S-* <S-...> shift-key *shift* *<S-*
<C-...> control-key *control* *ctrl* *<C-* <C-...> control-key *control* *ctrl* *<C-*
<M-...> alt-key or meta-key *meta* *alt* *<M-* <M-...> alt-key or meta-key *META* *meta* *alt* *<M-*
<A-...> same as <M-...> *<A-* <A-...> same as <M-...> *<A-*
<D-...> command-key or "super" key *<D-* <D-...> command-key or "super" key *<D-*
<t_xx> key with "xx" entry in termcap <t_xx> key with "xx" entry in termcap

View File

@@ -3526,6 +3526,14 @@ A jump table for the options with a short description can be found at |Q_op|.
CTRL-R CTRL-W can be used to add the word at the end of the current CTRL-R CTRL-W can be used to add the word at the end of the current
match, excluding the characters that were already typed. match, excluding the characters that were already typed.
*'incsubstitute'* *'ics'*
'incsubstitute' 'ics' string (default "")
global
If "split" or "nosplit" then |:substitute| updates the buffer
as-you-type. If "split", also show partial off-screen results in
a window. Replacement text is hightlighted with |hl-IncSubstitute|.
*'indentexpr'* *'inde'* *'indentexpr'* *'inde'*
'indentexpr' 'inde' string (default "") 'indentexpr' 'inde' string (default "")
local to buffer local to buffer
@@ -3992,16 +4000,6 @@ A jump table for the options with a short description can be found at |Q_op|.
"precedes". "SpecialKey" for "nbsp", "space", "tab" and "trail". "precedes". "SpecialKey" for "nbsp", "space", "tab" and "trail".
|hl-NonText| |hl-SpecialKey| |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'* *'lpl'* *'nolpl'* *'loadplugins'* *'noloadplugins'*
'loadplugins' 'lpl' boolean (default on) 'loadplugins' 'lpl' boolean (default on)
global global

View File

@@ -4852,11 +4852,11 @@ SignColumn column where |signs| are displayed
*hl-IncSearch* *hl-IncSearch*
IncSearch 'incsearch' highlighting; also used for the text replaced with IncSearch 'incsearch' highlighting; also used for the text replaced with
":s///c" ":s///c"
*hl-IncSubstitute*
IncSubstitute 'incsubstitute' replacement text
*hl-LineNr* *hl-LineNr*
LineNr Line number for ":number" and ":#" commands, and when 'number' LineNr Line number for ":number" and ":#" commands, and when 'number'
or 'relativenumber' option is set. or 'relativenumber' option is set.
*hl-IncSubstitute*
IncSubstitute The replacement text when using the |incsubstitute| functionality
*hl-CursorLineNr* *hl-CursorLineNr*
CursorLineNr Like LineNr when 'cursorline' or 'relativenumber' is set for CursorLineNr Like LineNr when 'cursorline' or 'relativenumber' is set for
the cursor line. the cursor line.

View File

@@ -64,7 +64,7 @@ these differences.
3. New Features *nvim-features* 3. New Features *nvim-features*
MAJOR FEATURES ~ MAJOR COMPONENTS ~
Embedded terminal emulator |terminal-emulator| Embedded terminal emulator |terminal-emulator|
RPC API |RPC| RPC API |RPC|
@@ -85,25 +85,29 @@ avoids features that cannot be provided on all platforms--instead that is
delegated to external plugins/extensions. delegated to external plugins/extensions.
OTHER FEATURES ~ ARCHITECTURE ~
External plugins run in separate processes. |remote-plugin| This improves
stability and allows those plugins to perform tasks without blocking the
editor. Even "legacy" Python and Ruby plugins which use the old Vim interfaces
(|if_py| and |if_ruby|) run out-of-process.
FEATURES ~
|bracketed-paste-mode| is built-in and enabled by default. |bracketed-paste-mode| is built-in and enabled by default.
Meta (alt) chords are recognized (even in the terminal). |META| (ALT) chords are recognized, even in the terminal. Any |<M-| mapping
<M-1>, <M-2>, ... will work. Some examples: <M-1>, <M-2>, <M-BS>, <M-Del>, <M-Ins>, <M-/>,
<M-BS>, <M-Del>, <M-Ins>, ... <M-\>, <M-Space>, <M-Enter>, <M-=>, <M-->, <M-?>, <M-$>, ...
<M-/>, <M-\>, ... META chords are case-sensitive: <M-a> and <M-A> are two different keycodes.
<M-Space>, <M-Enter>, <M-=>, <M-->, <M-?>, <M-$>, ...
Note: Meta chords are case-sensitive (<M-a> is distinguished from <M-A>).
Some `CTRL-SHIFT-...` key chords are distinguished from `CTRL-...` variants Some `CTRL-SHIFT-...` key chords are distinguished from `CTRL-...` variants
(even in the terminal). Specifically, the following are known to work: (even in the terminal). Specifically, the following are known to work:
<C-Tab>, <C-S-Tab> <C-Tab>, <C-S-Tab>, <C-BS>, <C-S-BS>, <C-Enter>, <C-S-Enter>
<C-BS>, <C-S-BS>
<C-Enter>, <C-S-Enter>
Options: Options:
'incsubstitute' shows results while typing a |:substitute| command
'statusline' supports unlimited alignment sections 'statusline' supports unlimited alignment sections
'tabline' %@Func@foo%X can call any function on mouse-click 'tabline' %@Func@foo%X can call any function on mouse-click
@@ -125,14 +129,14 @@ Functions:
Events: Events:
|TabNew| |TabNew|
|TabNewEntered| |TabNewEntered|
|TabClosed|
|TermOpen|
|TermClose| |TermClose|
|TermOpen|
|TextYankPost| |TextYankPost|
Highlight groups: Highlight groups:
|hl-EndOfBuffer| |hl-Substitute|
|hl-QuickFixLine| |hl-QuickFixLine|
|hl-Substitute|
|hl-TermCursor| |hl-TermCursor|
|hl-TermCursorNC| |hl-TermCursorNC|
@@ -298,6 +302,7 @@ Other commands:
:mode (no longer accepts an argument) :mode (no longer accepts an argument)
:open :open
:shell :shell
:smile
:tearoff :tearoff
Other compile-time features: Other compile-time features:

View File

@@ -1,9 +1,4 @@
" Vim syntax file " Vim syntax file
" Language: Vim 7.4 script
" Maintainer: Charles E. Campbell <NdrOchipS@PcampbellAfamily.Mbiz>
" Last Change: March 29, 2016
" Version: 7.4-45
" Automatically generated keyword lists: {{{1
" ############################################################################# " #############################################################################
" ############################################################################# " #############################################################################
@@ -58,10 +53,10 @@ 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 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 " Default highlighting groups {{{2
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 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 match vimHLGroup contained "Conceal" syn match vimHLGroup contained "Conceal"
syn keyword vimOnlyHLGroup contained VisualNOS syn keyword vimOnlyHLGroup contained VisualNOS
syn keyword nvimHLGroup contained EndOfBuffer TermCursor TermCursorNC QuickFixLine syn keyword nvimHLGroup contained EndOfBuffer IncSubstitute TermCursor TermCursorNC QuickFixLine
"}}}2 "}}}2
syn case match syn case match
" Special Vim Highlighting (not automatic) {{{1 " Special Vim Highlighting (not automatic) {{{1

View File

@@ -268,6 +268,9 @@ open_buffer (
bool buf_valid(buf_T *buf) bool buf_valid(buf_T *buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{ {
if (buf == NULL) {
return false;
}
FOR_ALL_BUFFERS(bp) { FOR_ALL_BUFFERS(bp) {
if (bp == buf) { if (bp == buf) {
return true; return true;
@@ -479,6 +482,18 @@ void buf_clear_file(buf_T *buf)
buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */ 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 /// buf_freeall() - free all things allocated for a buffer that are related to
/// the file. Careful: get here with "curwin" NULL when exiting. /// 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; swap_exists_did_quit = true;
close_buffer(curwin, curbuf, DOBUF_UNLOAD, false); close_buffer(curwin, curbuf, DOBUF_UNLOAD, false);
if (!buf_valid(old_curbuf) || old_curbuf == curbuf) { 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) { if (old_curbuf != NULL) {
enter_buffer(old_curbuf); 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 */ static int top_file_num = 1; /* highest file number */
buf_T * /// Add a file name to the buffer list.
buflist_new( /// If the same file name already exists return a pointer to that buffer.
char_u *ffname, // full path of fname or relative /// If it does not exist, or if fname == NULL, a new entry is created.
char_u *sfname, // short fname or NULL /// If (flags & BLN_CURBUF) is TRUE, may use current buffer.
linenr_T lnum, // preferred cursor line /// If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list.
int flags, // BLN_ defines /// If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer.
handle_T bufnr /// 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; buf_T *buf;
@@ -1460,9 +1475,7 @@ buflist_new(
} }
lastbuf = buf; lastbuf = buf;
// If bufnr is nonzero it is assumed to be a previously buf->b_fnum = top_file_num++;
// reserved buffer number (handle)
buf->handle = bufnr != 0 ? bufnr : top_file_num++;
handle_register_buffer(buf); handle_register_buffer(buf);
if (top_file_num < 0) { // wrap around (may cause duplicates) if (top_file_num < 0) { // wrap around (may cause duplicates)
EMSG(_("W14: Warning: List of file names overflow")); 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; buf_T *buf;
// Create a buffer. 'buflisted' is not set if it's a new buffer // 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) { if (buf != NULL && !cmdmod.keepalt) {
curwin->w_alt_fnum = buf->b_fnum; curwin->w_alt_fnum = buf->b_fnum;
} }
@@ -2416,7 +2429,7 @@ int buflist_add(char_u *fname, int flags)
{ {
buf_T *buf; 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) { if (buf != NULL) {
return buf->b_fnum; return buf->b_fnum;
} }
@@ -5199,7 +5212,7 @@ bool buf_contents_changed(buf_T *buf)
bool differ = true; bool differ = true;
// Allocate a buffer without putting it in the buffer list. // 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) { if (newbuf == NULL) {
return true; return true;
} }
@@ -5259,3 +5272,15 @@ wipe_buffer (
unblock_autocmds(); 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);
}

View File

@@ -7703,7 +7703,7 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
&& !error && !error
&& (name = get_tv_string_chk(&argvars[0])) != NULL && (name = get_tv_string_chk(&argvars[0])) != NULL
&& !error) && !error)
buf = buflist_new(name, NULL, (linenr_T)1, 0, 0); buf = buflist_new(name, NULL, (linenr_T)1, 0);
if (buf != NULL) if (buf != NULL)
rettv->vval.v_number = buf->b_fnum; rettv->vval.v_number = buf->b_fnum;

View File

@@ -10,6 +10,9 @@
#include <inttypes.h> #include <inttypes.h>
#include <math.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/vim.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/ex_cmds.h" #include "nvim/ex_cmds.h"
@@ -65,12 +68,6 @@
*/ */
typedef struct sign sign_T; 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 /// Case matching style to use for :substitute
typedef enum { typedef enum {
kSubHonorOptions = 0, ///< Honor the user's 'ignorecase'/'smartcase' options kSubHonorOptions = 0, ///< Honor the user's 'ignorecase'/'smartcase' options
@@ -90,6 +87,15 @@ typedef struct {
SubIgnoreType do_ic; ///< ignore case flag SubIgnoreType do_ic; ///< ignore case flag
} subflags_T; } 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 #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.c.generated.h" # include "ex_cmds.c.generated.h"
#endif #endif
@@ -1530,7 +1536,7 @@ int rename_buffer(char_u *new_fname)
} }
curbuf->b_flags |= BF_NOTEDITED; curbuf->b_flags |= BF_NOTEDITED;
if (xfname != NULL && *xfname != NUL) { 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) { if (buf != NULL && !cmdmod.keepalt) {
curwin->w_alt_fnum = buf->b_fnum; curwin->w_alt_fnum = buf->b_fnum;
} }
@@ -2027,37 +2033,34 @@ theend:
return retval; return retval;
} }
/* /// start editing a new file
* start editing a new file ///
* /// @param fnum file number; if zero use ffname/sfname
* fnum: file number; if zero use ffname/sfname /// @param ffname the file name
* ffname: the file name /// - full path if sfname used,
* - full path if sfname used, /// - any file name if sfname is NULL
* - any file name if sfname is NULL /// - empty string to re-edit with the same file name (but may
* - empty string to re-edit with the same file name (but may be /// be in a different directory)
* in a different directory) /// - NULL to start an empty buffer
* - NULL to start an empty buffer /// @param sfname the short file name (or NULL)
* sfname: the short file name (or NULL) /// @param eap contains the command to be executed after loading the file
* eap: contains the command to be executed after loading the file and /// and forced 'ff' and 'fenc'
* forced 'ff' and 'fenc' /// @param newlnum if > 0: put cursor on this line number (if possible)
* newlnum: if > 0: put cursor on this line number (if possible) /// ECMD_LASTL: use last position in loaded file
* if ECMD_LASTL: use last position in loaded file /// ECMD_LAST: use last position in all files
* if ECMD_LAST: use last position in all files /// ECMD_ONE: use first line
* if ECMD_ONE: use first line /// @param flags ECMD_HIDE: if TRUE don't free the current buffer
* flags: /// ECMD_SET_HELP: set b_help flag of (new) buffer before
* ECMD_HIDE: if TRUE don't free the current buffer /// opening file
* ECMD_SET_HELP: set b_help flag of (new) buffer before opening file /// ECMD_OLDBUF: use existing buffer if it exists
* ECMD_OLDBUF: use existing buffer if it exists /// ECMD_FORCEIT: ! used for Ex command
* ECMD_FORCEIT: ! used for Ex command /// ECMD_ADDBUF: don't edit, just add to buffer list
* ECMD_ADDBUF: don't edit, just add to buffer list /// @param oldwin Should be "curwin" when editing a new buffer in the current
* oldwin: Should be "curwin" when editing a new buffer in the current /// window, NULL when splitting the window first. When not NULL
* window, NULL when splitting the window first. When not NULL info /// info of the previous buffer for "oldwin" is stored.
* of the previous buffer for "oldwin" is stored. ///
* /// @return FAIL for failure, OK otherwise
* return FAIL for failure, OK otherwise int do_ecmd (
*/
int
do_ecmd (
int fnum, int fnum,
char_u *ffname, char_u *ffname,
char_u *sfname, char_u *sfname,
@@ -2182,9 +2185,10 @@ do_ecmd (
buflist_altfpos(oldwin); buflist_altfpos(oldwin);
} }
if (fnum && !(flags & ECMD_RESERVED_BUFNR)) { if (fnum) {
buf = buflist_findnr(fnum); buf = buflist_findnr(fnum);
} else { } else {
ILOG("here");
if (flags & ECMD_ADDBUF) { if (flags & ECMD_ADDBUF) {
linenr_T tlnum = 1L; linenr_T tlnum = 1L;
@@ -2193,12 +2197,11 @@ do_ecmd (
if (tlnum <= 0) if (tlnum <= 0)
tlnum = 1L; tlnum = 1L;
} }
(void)buflist_new(ffname, sfname, tlnum, BLN_LISTED, fnum); (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED);
goto theend; goto theend;
} }
buf = buflist_new(ffname, sfname, 0L, 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. // Autocmds may change curwin and curbuf.
if (oldwin != NULL) { if (oldwin != NULL) {
oldwin = curwin; oldwin = curwin;
@@ -3098,16 +3101,17 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
return cmd; return cmd;
} }
/* do_sub() /// do_sub()
* ///
* Perform a substitution from line eap->line1 to line eap->line2 using the /// 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: /// command pointed to by eap->arg which should be of the form:
* ///
* /pattern/substitution/{flags} /// /pattern/substitution/{flags}
* ///
* The usual escapes are supported as described in the regexp docs. /// The usual escapes are supported as described in the regexp docs.
*/ ///
void do_sub(exarg_T *eap) /// @return buffer used for 'incsubstitute'
buf_T *do_sub(exarg_T *eap)
{ {
long i = 0; long i = 0;
regmmatch_T regmatch; regmmatch_T regmatch;
@@ -3123,24 +3127,22 @@ void do_sub(exarg_T *eap)
}; };
char_u *pat = NULL, *sub = NULL; // init for GCC char_u *pat = NULL, *sub = NULL; // init for GCC
int delimiter; int delimiter;
bool has_second_delim = false;
int sublen; int sublen;
int got_quit = false; int got_quit = false;
int got_match = false; int got_match = false;
int which_pat; int which_pat;
char_u *cmd = eap->arg; char_u *cmd = eap->arg;
linenr_T first_line = 0; // first changed line linenr_T first_line = 0; // first changed line
linenr_T last_line= 0; // below last changed line AFTER the linenr_T last_line= 0; // below last changed line AFTER the change
// change
linenr_T old_line_count = curbuf->b_ml.ml_line_count; linenr_T old_line_count = curbuf->b_ml.ml_line_count;
char_u *sub_firstline; // allocated copy of first sub line char_u *sub_firstline; // allocated copy of first sub line
bool endcolumn = false; // cursor in last column when done bool endcolumn = false; // cursor in last column when done
MatchedLineVec matched_lines = KV_INITIAL_VALUE;
pos_T old_cursor = curwin->w_cursor; pos_T old_cursor = curwin->w_cursor;
int start_nsubs; int start_nsubs;
int save_ma = 0; int save_ma = 0;
inc_sub_did_changes = false;
bool has_second_delim = false;
if (!global_busy) { if (!global_busy) {
sub_nsubs = 0; sub_nsubs = 0;
sub_nlines = 0; sub_nlines = 0;
@@ -3158,7 +3160,7 @@ void do_sub(exarg_T *eap)
/* don't accept alphanumeric for separator */ /* don't accept alphanumeric for separator */
if (isalpha(*cmd)) { if (isalpha(*cmd)) {
EMSG(_("E146: Regular expressions can't be delimited by letters")); EMSG(_("E146: Regular expressions can't be delimited by letters"));
return; return NULL;
} }
/* /*
* undocumented vi feature: * undocumented vi feature:
@@ -3169,7 +3171,7 @@ void do_sub(exarg_T *eap)
++cmd; ++cmd;
if (vim_strchr((char_u *)"/?&", *cmd) == NULL) { if (vim_strchr((char_u *)"/?&", *cmd) == NULL) {
EMSG(_(e_backslash)); EMSG(_(e_backslash));
return; return NULL;
} }
if (*cmd != '&') { if (*cmd != '&') {
which_pat = RE_SEARCH; // use last '/' pattern 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 */ } else if (!eap->skip) { /* use previous pattern and substitution */
if (old_sub.sub == NULL) { /* there is no previous command */ if (old_sub.sub == NULL) { /* there is no previous command */
EMSG(_(e_nopresub)); EMSG(_(e_nopresub));
return; return NULL;
} }
pat = NULL; /* search_regcomp() will use previous pattern */ pat = NULL; /* search_regcomp() will use previous pattern */
sub = (char_u *) old_sub.sub; sub = (char_u *) old_sub.sub;
@@ -3228,7 +3230,7 @@ void do_sub(exarg_T *eap)
} }
if (sub_joining_lines(eap, pat, sub, cmd)) { if (sub_joining_lines(eap, pat, sub, cmd)) {
return; return NULL;
} }
cmd = sub_parse_flags(cmd, &subflags, &which_pat); cmd = sub_parse_flags(cmd, &subflags, &which_pat);
@@ -3242,7 +3244,7 @@ void do_sub(exarg_T *eap)
i = getdigits_long(&cmd); i = getdigits_long(&cmd);
if (i <= 0 && !eap->skip && subflags.do_error) { if (i <= 0 && !eap->skip && subflags.do_error) {
EMSG(_(e_zerocount)); EMSG(_(e_zerocount));
return; return NULL;
} }
eap->line1 = eap->line2; eap->line1 = eap->line2;
eap->line2 += i - 1; eap->line2 += i - 1;
@@ -3258,17 +3260,17 @@ void do_sub(exarg_T *eap)
eap->nextcmd = check_nextcmd(cmd); eap->nextcmd = check_nextcmd(cmd);
if (eap->nextcmd == NULL) { if (eap->nextcmd == NULL) {
EMSG(_(e_trailing)); EMSG(_(e_trailing));
return; return NULL;
} }
} }
if (eap->skip) /* not executing commands, only parsing */ if (eap->skip) /* not executing commands, only parsing */
return; return NULL;
if (!subflags.do_count && !MODIFIABLE(curbuf)) { if (!subflags.do_count && !MODIFIABLE(curbuf)) {
// Substitution is not allowed in non-'modifiable' buffer // Substitution is not allowed in non-'modifiable' buffer
EMSG(_(e_modifiable)); EMSG(_(e_modifiable));
return; return NULL;
} }
int search_options = eap->is_live ? 0 : SEARCH_HIS; int search_options = eap->is_live ? 0 : SEARCH_HIS;
@@ -3277,7 +3279,7 @@ void do_sub(exarg_T *eap)
if (subflags.do_error) { if (subflags.do_error) {
EMSG(_(e_invcmd)); EMSG(_(e_invcmd));
} }
return; return NULL;
} }
// the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase' // the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase'
@@ -3297,9 +3299,6 @@ void do_sub(exarg_T *eap)
if (!(sub[0] == '\\' && sub[1] == '=')) if (!(sub[0] == '\\' && sub[1] == '='))
sub = regtilde(sub, p_magic); sub = regtilde(sub, p_magic);
// list to save matched lines
MatchedLineVec lmatch = KV_INITIAL_VALUE;
// Check for a match on each line. // Check for a match on each line.
linenr_T line2 = eap->line2; linenr_T line2 = eap->line2;
for (linenr_T lnum = eap->line1; for (linenr_T lnum = eap->line1;
@@ -3368,7 +3367,7 @@ void do_sub(exarg_T *eap)
copycol = 0; copycol = 0;
matchcol = 0; matchcol = 0;
// the current match // 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. */ /* At first match, remember current cursor position. */
if (!got_match) { if (!got_match) {
@@ -3405,9 +3404,9 @@ void do_sub(exarg_T *eap)
curwin->w_cursor.lnum = lnum; curwin->w_cursor.lnum = lnum;
do_again = FALSE; do_again = FALSE;
// increment number of match on the line and store the column // Increment the in-line match count and store the column.
cmatch.nmatch++; matched_line.nmatch++;
kv_push(cmatch.start_col, regmatch.startpos[0].col); kv_push(matched_line.cols, regmatch.startpos[0].col);
/* /*
* 1. Match empty string does not count, except for first * 1. Match empty string does not count, except for first
@@ -3627,8 +3626,8 @@ void do_sub(exarg_T *eap)
* use "\=col("."). */ * use "\=col("."). */
curwin->w_cursor.col = regmatch.startpos[0].col; curwin->w_cursor.col = regmatch.startpos[0].col;
// 3. Substitute the string. Don't do this while incsubstitution and // 3. Substitute the string. During 'incsubstitute' only do this if
// there's no word to replace by eg : ":%s/pattern" // there is a replace pattern.
if (!eap->is_live || has_second_delim) { if (!eap->is_live || has_second_delim) {
if (subflags.do_count) { if (subflags.do_count) {
// prevent accidentally changing the buffer by a function // prevent accidentally changing the buffer by a function
@@ -3873,11 +3872,9 @@ skip:
xfree(sub_firstline); /* free the copy of the original line */ xfree(sub_firstline); /* free the copy of the original line */
sub_firstline = NULL; sub_firstline = NULL;
// saving info about the matched line matched_line.lnum = lnum;
cmatch.lnum = lnum; matched_line.line = vim_strsave(ml_get(lnum));
cmatch.line = vim_strsave(ml_get(lnum)); kv_push(matched_lines, matched_line);
kv_push(lmatch, cmatch);
} }
line_breakcheck(); line_breakcheck();
@@ -3946,78 +3943,65 @@ skip:
subflags.do_all = save_do_all; subflags.do_all = save_do_all;
subflags.do_ask = save_do_ask; subflags.do_ask = save_do_ask;
// inc_sub if sub on the whole file and there are results to display // Show 'incsubstitute' preview if there are matched lines.
if (lmatch.size != 0) { buf_T *incsub_buf = NULL;
// we did incsubstitute only if we had no word to replace by if (matched_lines.size != 0 && pat != NULL && *p_ics != NUL && eap->is_live) {
// by and no ending slash // Place cursor on the first match after the cursor.
if (!subflags.do_count && (!eap->is_live || has_second_delim)) { // If all matches are before the cursor, then do_sub already placed the
inc_sub_did_changes = true; // cursor on the last match.
}
if (pat != NULL && *p_ics != NUL && eap->is_live) {
bool split = true;
// p_ics is "", "nosplit" or "split" linenr_T cur_lnum = 0;
if (*p_ics == 'n' || eap[0].cmdlinep[0][0] == 's') { colnr_T cur_col = -1;
split = false; MatchedLine current;
}
// Place cursor on the first match after the cursor for (size_t j = 0; j < matched_lines.size; j++) {
// If all matches are before the cursor, then do_sub did current = matched_lines.items[j];
// already place the cursor on the last match cur_lnum = current.lnum;
linenr_T cur_lnum = 0; // 1. Match on line of the cursor, need to iterate over the
colnr_T cur_col = -1; // matches on this line to see if there is one on a later
MatchedLine current; // column
if (cur_lnum == old_cursor.lnum) {
for (size_t j = 0; j < lmatch.size; j++) { for (size_t i = 0; i < current.cols.size; i++) {
current = lmatch.items[j]; if (current.cols.items[i] >= old_cursor.col) {
cur_lnum = current.lnum; cur_col = current.cols.items[i];
// 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; 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; incsub_buf = incsub_display(pat, sub, eap->line1, eap->line2,
&matched_lines);
} else if (*p_ics != NUL && eap->is_live) {
curwin->w_cursor = old_cursor; // don't move the cursor
} }
MatchedLine current; MatchedLine current;
for (size_t j = 0; j < lmatch.size; j++) { for (size_t j = 0; j < matched_lines.size; j++) {
current = lmatch.items[j]; current = matched_lines.items[j];
if (current.line) {
if (current.line) { xfree(current.line); } xfree(current.line);
}
kv_destroy(current.start_col); kv_destroy(current.cols);
} }
kv_destroy(matched_lines);
kv_destroy(lmatch); return incsub_buf;
} // NOLINT(readability/fn_size) } // 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. /// Shows a preview of :substitute (for 'incsubstitute').
/// /// With incsubstitute=split, shows a special buffer in a window, draws the
/// Does not allow editing in the window. Closes the window and restores /// screen, then restores the layout.
/// the window layout before returning. static buf_T *incsub_display(char_u *pat, char_u *sub,
/// linenr_T line1, linenr_T line2,
/// @param pat The pattern word MatchedLineVec *matched_lines)
/// @param sub The replacement word FUNC_ATTR_NONNULL_ALL
/// @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; static handle_T bufnr = 0; // special buffer, re-used on each visit
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. garray_T save_winsizes;
if (cmdline_star > 0) { win_T *save_curwin = curwin;
beep_flush(); cmdmod_T save_cmdmod = cmdmod;
return; 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;
} }
// Save current window sizes. if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) {
win_size_save(&winsizes); buf_open_special(incsub_buf ? bufnr : 0, "[inc_sub]", "incsub");
buf_clear();
// Save the current window to restore it later incsub_buf = curbuf;
win_T *oldwin = curwin; set_option_value((char_u *)"bl", 0L, NULL, OPT_LOCAL);
set_option_value((char_u *)"bh", 0L, (char_u *)"hide", OPT_LOCAL);
if (split) { bufnr = incsub_buf->handle;
// don't use a new tab page curbuf->b_p_ma = true;
cmdmod.tab = 0; curbuf->b_p_ul = -1;
// 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_fen = false;
curwin->w_p_rl = cmdmsg_rl; curbuf->b_p_tw = 0; // Reset 'textwidth' (was set by ftplugin)
cmdmsg_rl = false;
RESET_BINDING(curwin);
// Showing the prompt may have set need_wait_return, reset it. // Width of the "| lnum|..." column which displays the line numbers.
need_wait_return = false; linenr_T highest_num_line = kv_last(*matched_lines).lnum;
// 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; int col_width = log10(highest_num_line) + 1 + 3;
// will be allocated in the loop
char *str = NULL; char *str = NULL;
size_t old_line_size = 0; size_t old_line_size = 0;
size_t line_size; 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 // Dump the lines into the incsub buffer.
for (size_t i = 0; i < (*lmatch).size; i++) { for (size_t line = 0; line < matched_lines->size; line++) {
MatchedLine mat = (*lmatch).items[i]; MatchedLine mat = matched_lines->items[line];
line_size = STRLEN(mat.line) + col_width + 1; 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) { if (line_size > old_line_size) {
str = xrealloc(str, line_size * sizeof(char)); str = xrealloc(str, line_size * sizeof(char));
old_line_size = line_size; 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); 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 // highlight the replaced part
if (sub_size > 0) { if (sub_size > 0) {
int hlgroup_ls = syn_check_group((char_u *)"IncSubstitute", 13); for (size_t i = 0; i < mat.cols.size; i++) {
colnr_T col_start = mat.cols.items[i] + col_width
for (size_t j = 0; j < mat.start_col.size; j++) { + i * (sub_size - pat_size) + 1;
src_id_highlight = colnr_T col_end = col_start - 1 + sub_size;
bufhl_add_hl(curbuf, src_id_highlight = bufhl_add_hl(curbuf, src_id_highlight, hl_id,
src_id_highlight, line + 1, col_start, col_end);
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); xfree(str);
redraw_later(SOME_VALID);
} }
// Restore the old window redraw_later(SOME_VALID);
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; 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);
// Update screen now. Must do this _before_ close_windows().
int save_rd = RedrawingDisabled; int save_rd = RedrawingDisabled;
RedrawingDisabled = 0; RedrawingDisabled = 0;
update_screen(0); update_screen(NOT_VALID);
RedrawingDisabled = save_rd; RedrawingDisabled = save_rd;
setmouse(); cmdmod = save_cmdmod;
return incsub_buf;
} }
/// :substitute command
/// :substitute command implementation
/// ///
/// Uses do_sub() to do the actual substitution. Undoes the substitution and /// If 'incsubstitute' is empty, this just calls do_sub().
/// removes it from the undo history unless finishing the command. If /// If 'incsubstitute' is set, substitutes as-you-type ("live").
/// ics is set to "", it just calls do_sub(). /// If the command is cancelled the changes are removed from undo history.
void do_inc_sub(exarg_T *eap) void ex_substitute(exarg_T *eap)
{ {
// if incsubstitute disabled, do it the classical way if (*p_ics == NUL || !eap->is_live) { // 'incsubstitute' is disabled
if (*p_ics == NUL || !eap->is_live) { (void)do_sub(eap);
do_sub(eap);
return; return;
} }
// Save the state of eap char_u *save_eap = eap->arg;
char_u *tmp = eap->arg;
save_search_patterns(); 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 buf_T *incsub_buf = do_sub(eap);
// 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 if (save_changedtick != curbuf->b_changedtick
// we need to start it before the call && !u_undo_and_forget(1)) {
block_autocmds(); abort();
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 (buf_valid(incsub_buf)) {
// Put back eap in first state // XXX: Must do this *after* u_undo_and_forget(), why?
eap->arg = tmp; close_windows(incsub_buf, false);
}
curbuf->b_p_ul = save_b_p_ul;
eap->arg = save_eap;
restore_search_patterns(); restore_search_patterns();
curbuf->b_p_ul = b_p_ul_save; emsg_off--;
update_screen(0);
if (livebuf != NULL && buf_valid(livebuf)) {
close_windows(livebuf, false);
wipe_buffer(livebuf, false);
}
unblock_autocmds(); unblock_autocmds();
redraw_later(SOME_VALID);
} }

View File

@@ -6,8 +6,6 @@
#include "nvim/os/time.h" #include "nvim/os/time.h"
#include "nvim/eval_defs.h" #include "nvim/eval_defs.h"
#include "nvim/pos.h" #include "nvim/pos.h"
#include "nvim/lib/klist.h"
#include "nvim/lib/kvec.h"
// flags for do_ecmd() // flags for do_ecmd()
#define ECMD_HIDE 0x01 // don't free the current buffer #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_ADDBUF 0x10 // don't edit, just add to buffer list
#define ECMD_RESERVED_BUFNR 0x20 // bufnr argument is reserved bufnr #define ECMD_RESERVED_BUFNR 0x20 // bufnr argument is reserved bufnr
/* for lnum argument in do_ecmd() */ /* for lnum argument in do_ecmd() */
#define ECMD_LASTL (linenr_T)0 /* use last position in loaded file */ #define ECMD_LASTL (linenr_T)0 /* use last position in loaded file */
#define ECMD_LAST (linenr_T)-1 /* use last position in all files */ #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. list_T *additional_elements; ///< Additional data left from ShaDa file.
} SubReplacementString; } 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 #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.h.generated.h" # include "ex_cmds.h.generated.h"
#endif #endif

View File

@@ -2194,7 +2194,7 @@ return {
command='substitute', command='substitute',
flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN), flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN),
addr_type=ADDR_LINES, addr_type=ADDR_LINES,
func='do_inc_sub', func='ex_substitute',
}, },
{ {
command='sNext', command='sNext',
@@ -3181,7 +3181,7 @@ return {
enum='CMD_and', enum='CMD_and',
flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY),
addr_type=ADDR_LINES, addr_type=ADDR_LINES,
func='do_sub', func='ex_substitute',
}, },
{ {
command='<', command='<',
@@ -3222,6 +3222,6 @@ return {
enum='CMD_tilde', enum='CMD_tilde',
flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY),
addr_type=ADDR_LINES, addr_type=ADDR_LINES,
func='do_sub', func='ex_substitute',
}, },
} }

View File

@@ -1248,7 +1248,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
memset(&ea, 0, sizeof(ea)); memset(&ea, 0, sizeof(ea));
ea.line1 = 1; ea.line1 = 1;
ea.line2 = 1; ea.line2 = 1;
ea.is_live = flags & DOCMD_LIVE_PREVIEW; ea.is_live = flags & DOCMD_LIVE;
ex_nesting_level++; ex_nesting_level++;
/* When the last file has not been edited :q has to be typed twice. */ /* 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 /// 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 /// @return True if first command is a live command
/// Currently :s is the only one bool cmd_is_live(char_u *cmd)
bool is_live(char_u *cmd_live)
{ {
exarg_T ea; if (cmd == NULL) {
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 false;
} }
exarg_T ea;
// parse the command line
ea.cmd = skip_range(cmd, NULL);
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
find_command(&ea, NULL);
return (ea.cmdidx == CMD_substitute); return (ea.cmdidx == CMD_substitute);
} }

View File

@@ -10,8 +10,7 @@
#define DOCMD_KEYTYPED 0x08 // don't reset KeyTyped #define DOCMD_KEYTYPED 0x08 // don't reset KeyTyped
#define DOCMD_EXCRESET 0x10 // reset exception environment (for debugging #define DOCMD_EXCRESET 0x10 // reset exception environment (for debugging
#define DOCMD_KEEPLINE 0x20 // keep typed line for repeating with "." #define DOCMD_KEEPLINE 0x20 // keep typed line for repeating with "."
#define DOCMD_LIVE_PREVIEW 0x40 // Command is a live preview like #define DOCMD_LIVE 0x40 // show updates as-you-type ("live" command)
// incsubstitution
/* defines for eval_vars() */ /* defines for eval_vars() */
#define VALID_PATH 1 #define VALID_PATH 1

View File

@@ -1591,9 +1591,12 @@ static int command_line_changed(CommandLineState *s)
msg_starthere(); msg_starthere();
redrawcmdline(); redrawcmdline();
s->did_incsearch = true; s->did_incsearch = true;
} else if (*p_ics != NUL && s->firstc == ':' && is_live(ccline.cmdbuff)) { } else if (s->firstc == ':'
// compute a live action && *p_ics != NUL // 'incsubstitute' is set
do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_LIVE_PREVIEW); && 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(); redrawcmdline();
} }
@@ -5141,16 +5144,12 @@ static int ex_window(void)
} }
cmdwin_type = get_cmdline_type(); cmdwin_type = get_cmdline_type();
/* Create the command-line buffer empty. */ // Create empty command-line buffer.
(void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); buf_open_special(0, "[Command Line]", "nofile");
(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;
curwin->w_p_rl = cmdmsg_rl; curwin->w_p_rl = cmdmsg_rl;
cmdmsg_rl = FALSE; cmdmsg_rl = false;
RESET_BINDING(curwin); curbuf->b_p_ma = true;
curwin->w_p_fen = false;
/* Do execute autocommands for setting the filetype (load syntax). */ /* Do execute autocommands for setting the filetype (load syntax). */
unblock_autocmds(); unblock_autocmds();

View File

@@ -5067,7 +5067,7 @@ void buf_reload(buf_T *buf, int orig_mode)
savebuf = NULL; savebuf = NULL;
} else { } else {
// Allocate a buffer without putting it in the buffer list. // 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) { if (savebuf != NULL && buf == curbuf) {
/* Open the memline. */ /* Open the memline. */
curbuf = savebuf; curbuf = savebuf;
@@ -6409,8 +6409,9 @@ win_found:
&& curbuf != aco->new_curbuf && curbuf != aco->new_curbuf
&& buf_valid(aco->new_curbuf) && buf_valid(aco->new_curbuf)
&& aco->new_curbuf->b_ml.ml_mfp != NULL) { && 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; curwin->w_s = &aco->new_curbuf->b_s;
}
--curbuf->b_nwindows; --curbuf->b_nwindows;
curbuf = aco->new_curbuf; curbuf = aco->new_curbuf;
curwin->w_buffer = curbuf; curwin->w_buffer = curbuf;

View File

@@ -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 *firstbuf INIT(= NULL); // first buffer
EXTERN buf_T *lastbuf INIT(= NULL); // last buffer EXTERN buf_T *lastbuf INIT(= NULL); // last buffer
EXTERN buf_T *curbuf INIT(= NULL); // currently active 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. // Iterates over all buffers in the buffer list.
# define FOR_ALL_BUFFERS(buf) for (buf_T *buf = firstbuf; buf != NULL; buf = buf->b_next) # define FOR_ALL_BUFFERS(buf) for (buf_T *buf = firstbuf; buf != NULL; buf = buf->b_next)

View File

@@ -474,7 +474,7 @@ static void fname2fnum(xfmark_T *fm)
p = path_shorten_fname(NameBuff, IObuff); p = path_shorten_fname(NameBuff, IObuff);
// buflist_new() will call fmarks_check_names() // 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);
} }
} }

View File

@@ -2340,14 +2340,13 @@ int ml_replace(linenr_T lnum, char_u *line, int copy)
return OK; return OK;
} }
/* /// Delete line `lnum` in the current buffer.
* Delete line 'lnum' in the current buffer. ///
* /// @note The caller of this function should probably also call
* Check: The caller of this function should probably also call /// deleted_lines() after this.
* deleted_lines() after this. ///
* /// @param message Show "--No lines in buffer--" message.
* return FAIL for failure, OK otherwise /// @return FAIL for failure, OK otherwise
*/
int ml_delete(linenr_T lnum, int message) int ml_delete(linenr_T lnum, int message)
{ {
ml_flush_line(curbuf); ml_flush_line(curbuf);

View File

@@ -3327,7 +3327,7 @@ load_dummy_buffer (
aco_save_T aco; aco_save_T aco;
// Allocate a buffer without putting it in the buffer list. // 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) { if (newbuf == NULL) {
return NULL; return NULL;
} }

View File

@@ -1405,7 +1405,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
cur_entry.data.buffer_list.buffers[i].fname); cur_entry.data.buffer_list.buffers[i].fname);
buf_T *const buf = buflist_new( buf_T *const buf = buflist_new(
cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, cur_entry.data.buffer_list.buffers[i].fname, sfname, 0,
BLN_LISTED, 0); BLN_LISTED);
if (buf != NULL) { if (buf != NULL) {
RESET_FMARK(&buf->b_last_cursor, RESET_FMARK(&buf->b_last_cursor,
cur_entry.data.buffer_list.buffers[i].pos, 0); cur_entry.data.buffer_list.buffers[i].pos, 0);

View File

@@ -7160,16 +7160,13 @@ int syn_namen2id(char_u *linep, int len)
*/ */
int syn_check_group(char_u *pp, int len) int syn_check_group(char_u *pp, int len)
{ {
int id; char_u *name = vim_strnsave(pp, len);
char_u *name; int id = syn_name2id(name);
if (id == 0) { // doesn't exist yet
name = vim_strnsave(pp, len);
id = syn_name2id(name);
if (id == 0) /* doesn't exist yet */
id = syn_add_group(name); id = syn_add_group(name);
else } else {
xfree(name); xfree(name);
}
return id; return id;
} }

View File

@@ -1685,7 +1685,7 @@ void u_redo(int count)
u_doit(count, false); u_doit(count, false);
} }
/// undo and forget. /// undo, and remove the undo branch from the undo tree.
bool u_undo_and_forget(int count) bool u_undo_and_forget(int count)
{ {
if (curbuf->b_u_synced == false) { if (curbuf->b_u_synced == false) {
@@ -1726,9 +1726,7 @@ bool u_undo_and_forget(int count)
return true; 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) static void u_doit(int startcount, bool quiet)
{ {
int count = startcount; int count = startcount;
@@ -2342,17 +2340,13 @@ static void u_undoredo(int undo)
#endif #endif
} }
/* /// If we deleted or added lines, report the number of less/more lines.
* If we deleted or added lines, report the number of less/more lines. /// Otherwise, report the number of changes (this may be incorrect
* Otherwise, report the number of changes (this may be incorrect /// in some cases, but it's better than nothing).
* in some cases, but it's better than nothing). static void u_undo_end(
*/ int did_undo, //< just did an undo
static void int absolute, //< used ":undo N"
u_undo_end( bool quiet)
int did_undo, // just did an undo
int absolute, // used ":undo N"
bool quiet
)
{ {
char *msgstr; char *msgstr;
u_header_T *uhp; u_header_T *uhp;
@@ -2361,9 +2355,9 @@ u_undo_end(
if ((fdo_flags & FDO_UNDO) && KeyTyped) if ((fdo_flags & FDO_UNDO) && KeyTyped)
foldOpenCursor(); foldOpenCursor();
if (global_busy // no messages now, wait until global is finished if (quiet
|| !messaging() // 'lazyredraw' set, don't do messages now || global_busy // no messages until global is finished
|| quiet) { // livemode doesn't show messages || !messaging()) { // 'lazyredraw' set, don't do messages now
return; return;
} }

View File

@@ -2903,7 +2903,7 @@ static int win_alloc_firstwin(win_T *oldwin)
if (oldwin == NULL) { if (oldwin == NULL) {
/* Very first window, need to create an empty buffer for it and /* Very first window, need to create an empty buffer for it and
* initialize from scratch. */ * initialize from scratch. */
curbuf = buflist_new(NULL, NULL, 1L, BLN_LISTED, 0); curbuf = buflist_new(NULL, NULL, 1L, BLN_LISTED);
if (curbuf == NULL) { if (curbuf == NULL) {
return FAIL; return FAIL;
} }

View File

@@ -42,19 +42,14 @@ local function common_setup(screen, incsub, text)
}) })
end end
-- default for incsub is off execute("set incsubstitute=" .. (incsub and incsub or ""))
if not incsub then
incsub = ""
end
execute("set incsubstitute=" .. incsub)
if text then if text then
insert(text) insert(text)
end end
end end
describe('IncSubstitution preserves', function() describe("'incsubstitute' preserves", function()
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
before_each(clear) before_each(clear)
@@ -98,7 +93,7 @@ describe('IncSubstitution preserves', function()
end end
end) end)
it('the undolevels setting', function() it("'undolevels' setting", function()
for _, case in pairs{"", "split", "nosplit"} do for _, case in pairs{"", "split", "nosplit"} do
clear() clear()
execute("set undolevels=139") execute("set undolevels=139")
@@ -113,7 +108,7 @@ describe('IncSubstitution preserves', function()
end) end)
describe('IncSubstitution preserves undo functionality', function() describe("'incsubstitute' preserves undo", function()
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
local cases = { "", "split", "nosplit" } local cases = { "", "split", "nosplit" }
@@ -241,21 +236,16 @@ describe('IncSubstitution preserves undo functionality', function()
2]]) 2]])
end end
it("at a non-leaf of the undo tree", function() -- TODO(vim): This does not work, even in Vim.
-- this does not work even in standard vim -- Waiting for fix (perhaps from upstream).
-- if fixed, easily combined with the test below pending("at a non-leaf of the undo tree", function()
if true then for _, case in pairs(cases) do
pending("vim") for _, str in pairs(substrings) do
return for _, redoable in pairs({true}) do
end test_sub(str, case, redoable)
end
-- for _, case in pairs(cases) do end
-- for _, str in pairs(substrings) do end
-- for _, redoable in pairs({true}) do
-- test_sub(str, case, redoable)
-- end
-- end
-- end
end) end)
it("at a leaf of the undo tree", function() it("at a leaf of the undo tree", function()
@@ -555,7 +545,7 @@ describe('IncSubstitution preserves undo functionality', function()
end) end)
describe('IncSubstitution with incsubstitute=split', function() describe("incsubstitute=split", function()
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
local screen = Screen.new(30,15) local screen = Screen.new(30,15)
@@ -693,7 +683,7 @@ describe('IncSubstitution with incsubstitute=split', function()
]]) ]])
end) end)
it('highlights the patterg up with :set hlsearch', function() it('highlights the pattern with :set hlsearch', function()
execute("set hlsearch") execute("set hlsearch")
feed(":%s/tw") feed(":%s/tw")
screen:expect([[ screen:expect([[
@@ -815,7 +805,7 @@ describe('IncSubstitution with incsubstitute=split', function()
end) end)
describe('Incsubstitution with incsubstitute=nosplit', function() describe("incsubstitute=nosplit", function()
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
local screen = Screen.new(20,10) local screen = Screen.new(20,10)
@@ -891,7 +881,7 @@ describe('Incsubstitution with incsubstitute=nosplit', function()
end) end)
describe('Incsubstitution with a failing expression', function() describe("'incsubstitute' with a failing expression", function()
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
local screen = Screen.new(20,10) local screen = Screen.new(20,10)
@@ -927,7 +917,7 @@ describe('Incsubstitution with a failing expression', function()
end) end)
describe('Incsubstitution and cnoremap', function() describe("'incsubstitute' and :cnoremap", function()
local cases = { "", "split", "nosplit" } local cases = { "", "split", "nosplit" }
local function refresh(case) local function refresh(case)
@@ -1041,7 +1031,7 @@ describe('Incsubstitution and cnoremap', function()
end) end)
describe('Incsubstitute: autocommands', function() describe("'incsubstitute': autocommands", function()
before_each(clear) before_each(clear)
-- keys are events to be tested -- keys are events to be tested
@@ -1129,7 +1119,7 @@ describe('Incsubstitute: autocommands', function()
end) end)
describe('Incsubstitute splits', function() describe("'incsubstitute': split windows", function()
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
local screen local screen
@@ -1143,7 +1133,7 @@ describe('Incsubstitute splits', function()
screen:detach() screen:detach()
end) end)
it('work after other splittings', function() it('work after more splits', function()
refresh() refresh()
execute("vsplit") execute("vsplit")

View File

@@ -13,7 +13,7 @@ describe('buffer functions', function()
local buflist_new = function(file, flags) local buflist_new = function(file, flags)
local c_file = to_cstr(file) local c_file = to_cstr(file)
return buffer.buflist_new(c_file, c_file, 1, flags, 0) return buffer.buflist_new(c_file, c_file, 1, flags)
end end
local close_buffer = function(win, buf, action, abort_if_last) local close_buffer = function(win, buf, action, abort_if_last)