Merge pull request #12721 from aufarg/vim-8.1.0265

[RDY] vim-patch:8.1.{265,271,273,274,275,277,278,279,280,281,282,284,286,291,295,296,320,321,339,351,392,399,552}
This commit is contained in:
Jan Edmund Lazo
2020-09-12 10:51:59 -04:00
committed by GitHub
9 changed files with 1259 additions and 568 deletions

View File

@@ -169,6 +169,10 @@ struct exarg {
LineGetter getline; ///< Function used to get the next line
void *cookie; ///< argument for getline()
cstack_T *cstack; ///< condition stack for ":if" etc.
long verbose_save; ///< saved value of p_verbose
int save_msg_silent; ///< saved value of msg_silent
int did_esilent; ///< how many times emsg_silent was incremented
bool did_sandbox; ///< when true did sandbox++
};
#define FORCE_BIN 1 // ":edit ++bin file"

View File

@@ -137,32 +137,6 @@ struct dbg_stuff {
except_T *current_exception;
};
typedef struct {
// parsed results
exarg_T *eap;
char_u *parsed_upto; // local we've parsed up to so far
char_u *cmd; // start of command
char_u *after_modifier;
// errors
char_u *errormsg;
// globals that need to be updated
cmdmod_T cmdmod;
int sandbox;
int msg_silent;
int emsg_silent;
bool ex_pressedreturn;
long p_verbose;
// other side-effects
bool set_eventignore;
long verbose_save;
int save_msg_silent;
int did_esilent;
bool did_sandbox;
} parse_state_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.c.generated.h"
#endif
@@ -1235,292 +1209,6 @@ static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite)
return (char_u *)p;
}
static void parse_state_to_global(const parse_state_T *parse_state)
{
cmdmod = parse_state->cmdmod;
sandbox = parse_state->sandbox;
msg_silent = parse_state->msg_silent;
emsg_silent = parse_state->emsg_silent;
ex_pressedreturn = parse_state->ex_pressedreturn;
p_verbose = parse_state->p_verbose;
if (parse_state->set_eventignore) {
set_string_option_direct(
(char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
}
}
static void parse_state_from_global(parse_state_T *parse_state)
{
memset(parse_state, 0, sizeof(*parse_state));
parse_state->cmdmod = cmdmod;
parse_state->sandbox = sandbox;
parse_state->msg_silent = msg_silent;
parse_state->emsg_silent = emsg_silent;
parse_state->ex_pressedreturn = ex_pressedreturn;
parse_state->p_verbose = p_verbose;
}
//
// Parse one Ex command.
//
// This has no side-effects, except for modifying parameters
// passed in by pointer.
//
// The `out` should be zeroed, and its `ea` member initialised,
// before calling this function.
//
static bool parse_one_cmd(
char_u **cmdlinep,
parse_state_T *const out,
LineGetter fgetline,
void *fgetline_cookie)
{
exarg_T ea = {
.line1 = 1,
.line2 = 1,
};
*out->eap = ea;
// "#!anything" is handled like a comment.
if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') {
return false;
}
/*
* Repeat until no more command modifiers are found.
*/
ea.cmd = *cmdlinep;
for (;; ) {
/*
* 1. Skip comment lines and leading white space and colons.
*/
while (*ea.cmd == ' '
|| *ea.cmd == '\t'
|| *ea.cmd == ':') {
ea.cmd++;
}
// in ex mode, an empty line works like :+
if (*ea.cmd == NUL && exmode_active
&& (getline_equal(fgetline, fgetline_cookie, getexmodeline)
|| getline_equal(fgetline, fgetline_cookie, getexline))
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
ea.cmd = (char_u *)"+";
out->ex_pressedreturn = true;
}
// ignore comment and empty lines
if (*ea.cmd == '"') {
return false;
}
if (*ea.cmd == NUL) {
out->ex_pressedreturn = true;
return false;
}
/*
* 2. Handle command modifiers.
*/
char_u *p = skip_range(ea.cmd, NULL);
switch (*p) {
// When adding an entry, also modify cmd_exists().
case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3))
break;
out->cmdmod.split |= WSP_ABOVE;
continue;
case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) {
out->cmdmod.split |= WSP_BELOW;
continue;
}
if (checkforcmd(&ea.cmd, "browse", 3)) {
out->cmdmod.browse = true;
continue;
}
if (!checkforcmd(&ea.cmd, "botright", 2)) {
break;
}
out->cmdmod.split |= WSP_BOT;
continue;
case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4))
break;
out->cmdmod.confirm = true;
continue;
case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) {
out->cmdmod.keepmarks = true;
continue;
}
if (checkforcmd(&ea.cmd, "keepalt", 5)) {
out->cmdmod.keepalt = true;
continue;
}
if (checkforcmd(&ea.cmd, "keeppatterns", 5)) {
out->cmdmod.keeppatterns = true;
continue;
}
if (!checkforcmd(&ea.cmd, "keepjumps", 5)) {
break;
}
out->cmdmod.keepjumps = true;
continue;
case 'f': { // only accept ":filter {pat} cmd"
char_u *reg_pat;
if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) {
break;
}
if (*p == '!') {
out->cmdmod.filter_force = true;
p = skipwhite(p + 1);
if (*p == NUL || ends_excmd(*p)) {
break;
}
}
p = skip_vimgrep_pat(p, &reg_pat, NULL);
if (p == NULL || *p == NUL) {
break;
}
out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
if (out->cmdmod.filter_regmatch.regprog == NULL) {
break;
}
ea.cmd = p;
continue;
}
// ":hide" and ":hide | cmd" are not modifiers
case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3)
|| *p == NUL || ends_excmd(*p))
break;
ea.cmd = p;
out->cmdmod.hide = true;
continue;
case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) {
out->cmdmod.lockmarks = true;
continue;
}
if (!checkforcmd(&ea.cmd, "leftabove", 5)) {
break;
}
out->cmdmod.split |= WSP_ABOVE;
continue;
case 'n':
if (checkforcmd(&ea.cmd, "noautocmd", 3)) {
if (out->cmdmod.save_ei == NULL) {
// Set 'eventignore' to "all". Restore the
// existing option value later.
out->cmdmod.save_ei = vim_strsave(p_ei);
out->set_eventignore = true;
}
continue;
}
if (!checkforcmd(&ea.cmd, "noswapfile", 3)) {
break;
}
out->cmdmod.noswapfile = true;
continue;
case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6))
break;
out->cmdmod.split |= WSP_BELOW;
continue;
case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) {
if (!out->did_sandbox) {
out->sandbox++;
}
out->did_sandbox = true;
continue;
}
if (!checkforcmd(&ea.cmd, "silent", 3)) {
break;
}
if (out->save_msg_silent == -1) {
out->save_msg_silent = out->msg_silent;
}
out->msg_silent++;
if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) {
// ":silent!", but not "silent !cmd"
ea.cmd = skipwhite(ea.cmd + 1);
out->emsg_silent++;
out->did_esilent++;
}
continue;
case 't': if (checkforcmd(&p, "tab", 3)) {
long tabnr = get_address(
&ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1);
if (tabnr == MAXLNUM) {
out->cmdmod.tab = tabpage_index(curtab) + 1;
} else {
if (tabnr < 0 || tabnr > LAST_TAB_NR) {
out->errormsg = (char_u *)_(e_invrange);
return false;
}
out->cmdmod.tab = tabnr + 1;
}
ea.cmd = p;
continue;
}
if (!checkforcmd(&ea.cmd, "topleft", 2)) {
break;
}
out->cmdmod.split |= WSP_TOP;
continue;
case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3))
break;
if (out->save_msg_silent == -1) {
out->save_msg_silent = out->msg_silent;
}
out->msg_silent = 0;
continue;
case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) {
out->cmdmod.split |= WSP_VERT;
continue;
}
if (!checkforcmd(&p, "verbose", 4))
break;
if (out->verbose_save < 0) {
out->verbose_save = out->p_verbose;
}
if (ascii_isdigit(*ea.cmd)) {
out->p_verbose = atoi((char *)ea.cmd);
} else {
out->p_verbose = 1;
}
ea.cmd = p;
continue;
}
break;
}
out->after_modifier = ea.cmd;
// 3. Skip over the range to find the command. Let "p" point to after it.
//
// We need the command to know what kind of range it uses.
out->cmd = ea.cmd;
ea.cmd = skip_range(ea.cmd, NULL);
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
out->parsed_upto = find_command(&ea, NULL);
*out->eap = ea;
return true;
}
/*
* Execute one Ex command.
*
@@ -1549,12 +1237,16 @@ static char_u * do_one_cmd(char_u **cmdlinep,
linenr_T lnum;
long n;
char_u *errormsg = NULL; // error message
char_u *after_modifier = NULL;
exarg_T ea;
int save_msg_scroll = msg_scroll;
parse_state_T parsed;
cmdmod_T save_cmdmod;
const int save_reg_executing = reg_executing;
char_u *cmd;
memset(&ea, 0, sizeof(ea));
ea.line1 = 1;
ea.line2 = 1;
ex_nesting_level++;
/* When the last file has not been edited :q has to be typed twice. */
@@ -1571,31 +1263,44 @@ static char_u * do_one_cmd(char_u **cmdlinep,
* recursive calls.
*/
save_cmdmod = cmdmod;
memset(&cmdmod, 0, sizeof(cmdmod));
parse_state_from_global(&parsed);
parsed.eap = &ea;
parsed.verbose_save = -1;
parsed.save_msg_silent = -1;
parsed.did_esilent = 0;
parsed.did_sandbox = false;
bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie);
parse_state_to_global(&parsed);
// Update locals from parse_one_cmd()
errormsg = parsed.errormsg;
p = parsed.parsed_upto;
if (!parse_success) {
// "#!anything" is handled like a comment.
if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') {
goto doend;
}
// 1. Skip comment lines and leading white space and colons.
// 2. Handle command modifiers.
// The "ea" structure holds the arguments that can be used.
ea.cmd = *cmdlinep;
ea.cmdlinep = cmdlinep;
ea.getline = fgetline;
ea.cookie = cookie;
ea.cstack = cstack;
if (parse_command_modifiers(&ea, &errormsg, false) == FAIL) {
goto doend;
}
after_modifier = ea.cmd;
ea.skip = (did_emsg
|| got_int
|| current_exception
|| (cstack->cs_idx >= 0
&& !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
// 3. Skip over the range to find the command. Let "p" point to after it.
//
// We need the command to know what kind of range it uses.
cmd = ea.cmd;
ea.cmd = skip_range(ea.cmd, NULL);
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
p = find_command(&ea, NULL);
// Count this line for profiling if skip is TRUE.
if (do_profiling == PROF_YES
&& (!ea.skip || cstack->cs_idx == 0
@@ -1665,8 +1370,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
}
}
ea.cmd = parsed.cmd;
if (parse_cmd_address(&ea, &errormsg) == FAIL) {
ea.cmd = cmd;
if (parse_cmd_address(&ea, &errormsg, false) == FAIL) {
goto doend;
}
@@ -1746,8 +1451,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (!(flags & DOCMD_VERBOSE)) {
// If the modifier was parsed OK the error must be in the following
// command
if (parsed.after_modifier != NULL) {
append_command(parsed.after_modifier);
if (after_modifier != NULL) {
append_command(after_modifier);
} else {
append_command(*cmdlinep);
}
@@ -2222,22 +1927,15 @@ static char_u * do_one_cmd(char_u **cmdlinep,
// The :try command saves the emsg_silent flag, reset it here when
// ":silent! try" was used, it should only apply to :try itself.
if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) {
emsg_silent -= parsed.did_esilent;
if (ea.cmdidx == CMD_try && ea.did_esilent > 0) {
emsg_silent -= ea.did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
parsed.did_esilent = 0;
ea.did_esilent = 0;
}
// 7. Execute the command.
//
// The "ea" structure holds the arguments that can be used.
ea.cmdlinep = cmdlinep;
ea.getline = fgetline;
ea.cookie = cookie;
ea.cstack = cstack;
if (IS_USER_CMDIDX(ea.cmdidx)) {
/*
* Execute a user-defined command.
@@ -2293,30 +1991,21 @@ doend:
? cmdnames[(int)ea.cmdidx].cmd_name
: (char_u *)NULL);
if (parsed.verbose_save >= 0) {
p_verbose = parsed.verbose_save;
}
if (cmdmod.save_ei != NULL) {
/* Restore 'eventignore' to the value before ":noautocmd". */
set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei,
OPT_FREE, SID_NONE);
free_string_option(cmdmod.save_ei);
}
if (cmdmod.filter_regmatch.regprog != NULL) {
vim_regfree(cmdmod.filter_regmatch.regprog);
if (ea.verbose_save >= 0) {
p_verbose = ea.verbose_save;
}
free_cmdmod();
cmdmod = save_cmdmod;
reg_executing = save_reg_executing;
if (parsed.save_msg_silent != -1) {
if (ea.save_msg_silent != -1) {
// messages could be enabled for a serious error, need to check if the
// counters don't become negative
if (!did_emsg || msg_silent > parsed.save_msg_silent) {
msg_silent = parsed.save_msg_silent;
if (!did_emsg || msg_silent > ea.save_msg_silent) {
msg_silent = ea.save_msg_silent;
}
emsg_silent -= parsed.did_esilent;
emsg_silent -= ea.did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
@@ -2330,7 +2019,7 @@ doend:
msg_col = 0;
}
if (parsed.did_sandbox) {
if (ea.did_sandbox) {
sandbox--;
}
@@ -2342,9 +2031,279 @@ doend:
return ea.nextcmd;
}
// Parse and skip over command modifiers:
// - update eap->cmd
// - store flags in "cmdmod".
// - Set ex_pressedreturn for an empty command line.
// - set msg_silent for ":silent"
// - set 'eventignore' to "all" for ":noautocmd"
// - set p_verbose for ":verbose"
// - Increment "sandbox" for ":sandbox"
// When "skip_only" is true the global variables are not changed, except for
// "cmdmod".
// Return FAIL when the command is not to be executed.
// May set "errormsg" to an error message.
int parse_command_modifiers(exarg_T *eap, char_u **errormsg, bool skip_only)
{
char_u *p;
memset(&cmdmod, 0, sizeof(cmdmod));
eap->verbose_save = -1;
eap->save_msg_silent = -1;
// Repeat until no more command modifiers are found.
for (;; ) {
while (*eap->cmd == ' '
|| *eap->cmd == '\t'
|| *eap->cmd == ':') {
eap->cmd++;
}
// in ex mode, an empty line works like :+
if (*eap->cmd == NUL && exmode_active
&& (getline_equal(eap->getline, eap->cookie, getexmodeline)
|| getline_equal(eap->getline, eap->cookie, getexline))
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
eap->cmd = (char_u *)"+";
if (!skip_only) {
ex_pressedreturn = true;
}
}
// ignore comment and empty lines
if (*eap->cmd == '"') {
return FAIL;
}
if (*eap->cmd == NUL) {
if (!skip_only) {
ex_pressedreturn = true;
}
return FAIL;
}
p = skip_range(eap->cmd, NULL);
switch (*p) {
// When adding an entry, also modify cmd_exists().
case 'a': if (!checkforcmd(&eap->cmd, "aboveleft", 3))
break;
cmdmod.split |= WSP_ABOVE;
continue;
case 'b': if (checkforcmd(&eap->cmd, "belowright", 3)) {
cmdmod.split |= WSP_BELOW;
continue;
}
if (checkforcmd(&eap->cmd, "browse", 3)) {
cmdmod.browse = true;
continue;
}
if (!checkforcmd(&eap->cmd, "botright", 2)) {
break;
}
cmdmod.split |= WSP_BOT;
continue;
case 'c': if (!checkforcmd(&eap->cmd, "confirm", 4))
break;
cmdmod.confirm = true;
continue;
case 'k': if (checkforcmd(&eap->cmd, "keepmarks", 3)) {
cmdmod.keepmarks = true;
continue;
}
if (checkforcmd(&eap->cmd, "keepalt", 5)) {
cmdmod.keepalt = true;
continue;
}
if (checkforcmd(&eap->cmd, "keeppatterns", 5)) {
cmdmod.keeppatterns = true;
continue;
}
if (!checkforcmd(&eap->cmd, "keepjumps", 5)) {
break;
}
cmdmod.keepjumps = true;
continue;
case 'f': { // only accept ":filter {pat} cmd"
char_u *reg_pat;
if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) {
break;
}
if (*p == '!') {
cmdmod.filter_force = true;
p = skipwhite(p + 1);
if (*p == NUL || ends_excmd(*p)) {
break;
}
}
if (skip_only) {
p = skip_vimgrep_pat(p, NULL, NULL);
} else {
// NOTE: This puts a NUL after the pattern.
p = skip_vimgrep_pat(p, &reg_pat, NULL);
}
if (p == NULL || *p == NUL) {
break;
}
if (!skip_only) {
cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
if (cmdmod.filter_regmatch.regprog == NULL) {
break;
}
}
eap->cmd = p;
continue;
}
// ":hide" and ":hide | cmd" are not modifiers
case 'h': if (p != eap->cmd || !checkforcmd(&p, "hide", 3)
|| *p == NUL || ends_excmd(*p))
break;
eap->cmd = p;
cmdmod.hide = true;
continue;
case 'l': if (checkforcmd(&eap->cmd, "lockmarks", 3)) {
cmdmod.lockmarks = true;
continue;
}
if (!checkforcmd(&eap->cmd, "leftabove", 5)) {
break;
}
cmdmod.split |= WSP_ABOVE;
continue;
case 'n':
if (checkforcmd(&eap->cmd, "noautocmd", 3)) {
if (cmdmod.save_ei == NULL && !skip_only) {
// Set 'eventignore' to "all". Restore the
// existing option value later.
cmdmod.save_ei = vim_strsave(p_ei);
set_string_option_direct((char_u *)"ei", -1,
(char_u *)"all", OPT_FREE, SID_NONE);
}
continue;
}
if (!checkforcmd(&eap->cmd, "noswapfile", 3)) {
break;
}
cmdmod.noswapfile = true;
continue;
case 'r': if (!checkforcmd(&eap->cmd, "rightbelow", 6))
break;
cmdmod.split |= WSP_BELOW;
continue;
case 's': if (checkforcmd(&eap->cmd, "sandbox", 3)) {
if (!skip_only) {
if (!eap->did_sandbox) {
sandbox++;
}
eap->did_sandbox = true;
}
continue;
}
if (!checkforcmd(&eap->cmd, "silent", 3)) {
break;
}
if (!skip_only) {
if (eap->save_msg_silent == -1) {
eap->save_msg_silent = msg_silent;
}
msg_silent++;
}
if (*eap->cmd == '!' && !ascii_iswhite(eap->cmd[-1])) {
// ":silent!", but not "silent !cmd"
eap->cmd = skipwhite(eap->cmd + 1);
if (!skip_only) {
emsg_silent++;
eap->did_esilent++;
}
}
continue;
case 't': if (checkforcmd(&p, "tab", 3)) {
long tabnr = get_address(
eap, &eap->cmd, ADDR_TABS, eap->skip, skip_only, false, 1);
if (tabnr == MAXLNUM) {
cmdmod.tab = tabpage_index(curtab) + 1;
} else {
if (tabnr < 0 || tabnr > LAST_TAB_NR) {
*errormsg = (char_u *)_(e_invrange);
return false;
}
cmdmod.tab = tabnr + 1;
}
eap->cmd = p;
continue;
}
if (!checkforcmd(&eap->cmd, "topleft", 2)) {
break;
}
cmdmod.split |= WSP_TOP;
continue;
case 'u': if (!checkforcmd(&eap->cmd, "unsilent", 3))
break;
if (!skip_only) {
if (eap->save_msg_silent == -1) {
eap->save_msg_silent = msg_silent;
}
msg_silent = 0;
}
continue;
case 'v': if (checkforcmd(&eap->cmd, "vertical", 4)) {
cmdmod.split |= WSP_VERT;
continue;
}
if (!checkforcmd(&p, "verbose", 4))
break;
if (!skip_only) {
if (eap->verbose_save < 0) {
eap->verbose_save = p_verbose;
}
if (ascii_isdigit(*eap->cmd)) {
p_verbose = atoi((char *)eap->cmd);
} else {
p_verbose = 1;
}
}
eap->cmd = p;
continue;
}
break;
}
return OK;
}
// Free contents of "cmdmod".
static void free_cmdmod(void)
{
if (cmdmod.save_ei != NULL) {
/* Restore 'eventignore' to the value before ":noautocmd". */
set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei,
OPT_FREE, SID_NONE);
free_string_option(cmdmod.save_ei);
}
if (cmdmod.filter_regmatch.regprog != NULL) {
vim_regfree(cmdmod.filter_regmatch.regprog);
}
}
// Parse the address range, if any, in "eap".
// May set the last search pattern, unless "silent" is true.
// Return FAIL and set "errormsg" or return OK.
int parse_cmd_address(exarg_T *eap, char_u **errormsg)
int parse_cmd_address(exarg_T *eap, char_u **errormsg, bool silent)
FUNC_ATTR_NONNULL_ALL
{
int address_count = 1;
@@ -2382,7 +2341,7 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg)
break;
}
eap->cmd = skipwhite(eap->cmd);
lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip,
lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent,
eap->addr_count == 0, address_count++);
if (eap->cmd == NULL) { // error detected
return FAIL;
@@ -3725,18 +3684,18 @@ char_u *skip_range(
return (char_u *)cmd;
}
/*
* get a single EX address
*
* Set ptr to the next character after the part that was interpreted.
* Set ptr to NULL when an error is encountered.
*
* Return MAXLNUM when no Ex address was found.
*/
// Get a single EX address
//
// Set ptr to the next character after the part that was interpreted.
// Set ptr to NULL when an error is encountered.
// This may set the last used search pattern.
//
// Return MAXLNUM when no Ex address was found.
static linenr_T get_address(exarg_T *eap,
char_u **ptr,
int addr_type, // flag: one of ADDR_LINES, ...
int skip, // only skip the address, don't use it
bool silent, // no errors or side effects
int to_other_file, // flag: may jump to other file
int address_count) // 1 for first, >1 after comma
{
@@ -3868,13 +3827,15 @@ static linenr_T get_address(exarg_T *eap,
if (*cmd == c)
++cmd;
} else {
pos = curwin->w_cursor; /* save curwin->w_cursor */
/*
* When '/' or '?' follows another address, start
* from there.
*/
if (lnum != MAXLNUM)
int flags;
pos = curwin->w_cursor; // save curwin->w_cursor
// When '/' or '?' follows another address, start from
// there.
if (lnum != MAXLNUM) {
curwin->w_cursor.lnum = lnum;
}
// Start a forward search at the end of the line (unless
// before the first line).
@@ -3888,7 +3849,8 @@ static linenr_T get_address(exarg_T *eap,
curwin->w_cursor.col = 0;
}
searchcmdlen = 0;
if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) {
flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG;
if (!do_search(NULL, c, cmd, 1L, flags, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
@@ -7793,7 +7755,7 @@ static void ex_put(exarg_T *eap)
*/
static void ex_copymove(exarg_T *eap)
{
long n = get_address(eap, &eap->arg, eap->addr_type, false, false, 1);
long n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1);
if (eap->arg == NULL) { // error detected
eap->nextcmd = NULL;
return;

View File

@@ -137,6 +137,7 @@ struct cmdline_info {
/// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0;
// Struct to store the viewstate during 'incsearch' highlighting.
typedef struct {
colnr_T vs_curswant;
colnr_T vs_leftcol;
@@ -146,6 +147,19 @@ typedef struct {
int vs_empty_rows;
} viewstate_T;
// Struct to store the state of 'incsearch' highlighting.
typedef struct {
pos_T search_start; // where 'incsearch' starts searching
pos_T save_cursor;
viewstate_T init_viewstate;
viewstate_T old_viewstate;
pos_T match_start;
pos_T match_end;
bool did_incsearch;
bool incsearch_postponed;
int magic_save;
} incsearch_state_T;
typedef struct command_line_state {
VimState state;
int firstc;
@@ -159,14 +173,7 @@ typedef struct command_line_state {
int save_hiscnt; // history line before attempting
// to jump to next match
int histype; // history type to be used
pos_T search_start; // where 'incsearch' starts searching
pos_T save_cursor;
viewstate_T init_viewstate;
viewstate_T old_viewstate;
pos_T match_start;
pos_T match_end;
int did_incsearch;
int incsearch_postponed;
incsearch_state_T is_state;
int did_wild_list; // did wild_list() recently
int wim_index; // index in wim_flags[]
int res;
@@ -252,6 +259,382 @@ static void restore_viewstate(viewstate_T *vs)
curwin->w_empty_rows = vs->vs_empty_rows;
}
static void init_incsearch_state(incsearch_state_T *s)
{
s->match_start = curwin->w_cursor;
s->did_incsearch = false;
s->incsearch_postponed = false;
s->magic_save = p_magic;
clearpos(&s->match_end);
s->save_cursor = curwin->w_cursor; // may be restored later
s->search_start = curwin->w_cursor;
save_viewstate(&s->init_viewstate);
save_viewstate(&s->old_viewstate);
}
// Return true when 'incsearch' highlighting is to be done.
// Sets search_first_line and search_last_line to the address range.
static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s,
int *skiplen, int *patlen)
{
char_u *cmd;
cmdmod_T save_cmdmod = cmdmod;
char_u *p;
bool delim_optional = false;
int delim;
char_u *end;
char_u *dummy;
exarg_T ea;
pos_T save_cursor;
bool use_last_pat;
*skiplen = 0;
*patlen = ccline.cmdlen;
if (!p_is || cmd_silent) {
return false;
}
// by default search all lines
search_first_line = 0;
search_last_line = MAXLNUM;
if (firstc == '/' || firstc == '?') {
return true;
}
if (firstc != ':') {
return false;
}
memset(&ea, 0, sizeof(ea));
ea.line1 = 1;
ea.line2 = 1;
ea.cmd = ccline.cmdbuff;
ea.addr_type = ADDR_LINES;
parse_command_modifiers(&ea, &dummy, true);
cmdmod = save_cmdmod;
cmd = skip_range(ea.cmd, NULL);
if (vim_strchr((char_u *)"sgvl", *cmd) == NULL) {
return false;
}
// Skip over "substitute" to find the pattern separator.
for (p = cmd; ASCII_ISALPHA(*p); p++) {}
if (*skipwhite(p) == NUL) {
return false;
}
if (STRNCMP(cmd, "substitute", p - cmd) == 0
|| STRNCMP(cmd, "smagic", p - cmd) == 0
|| STRNCMP(cmd, "snomagic", MAX(p - cmd, 3)) == 0
|| STRNCMP(cmd, "vglobal", p - cmd) == 0) {
if (*cmd == 's' && cmd[1] == 'm') {
p_magic = true;
} else if (*cmd == 's' && cmd[1] == 'n') {
p_magic = false;
}
} else if (STRNCMP(cmd, "sort", MAX(p - cmd, 3)) == 0) {
// skip over flags.
while (ASCII_ISALPHA(*(p = skipwhite(p)))) {
p++;
}
if (*p == NUL) {
return false;
}
} else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0
|| STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0
|| STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0
|| STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0
|| STRNCMP(cmd, "global", p - cmd) == 0) {
// skip over "!/".
if (*p == '!') {
p++;
if (*skipwhite(p) == NUL) {
return false;
}
}
if (*cmd != 'g') {
delim_optional = true;
}
} else {
return false;
}
p = skipwhite(p);
delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++;
end = skip_regexp(p, delim, p_magic, NULL);
use_last_pat = end == p && *end == delim;
if (end == p && !use_last_pat) {
return false;
}
// Don't do 'hlsearch' highlighting if the pattern matches everything.
if (!use_last_pat) {
char_u c = *end;
int empty;
*end = NUL;
empty = empty_pattern(p);
*end = c;
if (empty) {
return false;
}
}
// found a non-empty pattern or //
*skiplen = (int)(p - ccline.cmdbuff);
*patlen = (int)(end - p);
// parse the address range
save_cursor = curwin->w_cursor;
curwin->w_cursor = s->search_start;
parse_cmd_address(&ea, &dummy, true);
if (ea.addr_count > 0) {
// Allow for reverse match.
if (ea.line2 < ea.line1) {
search_first_line = ea.line2;
search_last_line = ea.line1;
} else {
search_first_line = ea.line1;
search_last_line = ea.line2;
}
} else if (cmd[0] == 's' && cmd[1] != 'o') {
// :s defaults to the current line
search_first_line = curwin->w_cursor.lnum;
search_last_line = curwin->w_cursor.lnum;
}
curwin->w_cursor = save_cursor;
return true;
}
// May do 'incsearch' highlighting if desired.
static void may_do_incsearch_highlighting(int firstc, long count,
incsearch_state_T *s)
{
pos_T end_pos;
proftime_T tm;
searchit_arg_T sia;
int skiplen, patlen;
char_u next_char;
char_u use_last_pat;
// Parsing range may already set the last search pattern.
// NOTE: must call restore_last_search_pattern() before returning!
save_last_search_pattern();
if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
restore_last_search_pattern();
finish_incsearch_highlighting(false, s, true);
return;
}
// if there is a character waiting, search and redraw later
if (char_avail()) {
restore_last_search_pattern();
s->incsearch_postponed = true;
return;
}
s->incsearch_postponed = false;
if (search_first_line == 0) {
// start at the original cursor position
curwin->w_cursor = s->search_start;
} else {
// start at the first line in the range
curwin->w_cursor.lnum = search_first_line;
curwin->w_cursor.col = 0;
}
int found; // do_search() result
// Use the previous pattern for ":s//".
next_char = ccline.cmdbuff[skiplen + patlen];
use_last_pat = patlen == 0 && skiplen > 0
&& ccline.cmdbuff[skiplen - 1] == next_char;
// If there is no pattern, don't do anything.
if (patlen == 0 && !use_last_pat) {
found = 0;
set_no_hlsearch(true); // turn off previous highlight
redraw_all_later(SOME_VALID);
} else {
int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK;
ui_busy_start();
ui_flush();
emsg_off++; // So it doesn't beep if bad expr
// Set the time limit to half a second.
tm = profile_setlimit(500L);
if (!p_hls) {
search_flags += SEARCH_KEEP;
}
if (search_first_line != 0) {
search_flags += SEARCH_START;
}
ccline.cmdbuff[skiplen + patlen] = NUL;
memset(&sia, 0, sizeof(sia));
sia.sa_tm = &tm;
found = do_search(NULL, firstc == ':' ? '/' : firstc,
ccline.cmdbuff + skiplen, count,
search_flags, &sia);
ccline.cmdbuff[skiplen + patlen] = next_char;
emsg_off--;
if (curwin->w_cursor.lnum < search_first_line
|| curwin->w_cursor.lnum > search_last_line) {
// match outside of address range
found = 0;
curwin->w_cursor = s->search_start;
}
// if interrupted while searching, behave like it failed
if (got_int) {
(void)vpeekc(); // remove <C-C> from input stream
got_int = false; // don't abandon the command line
found = 0;
} else if (char_avail()) {
// cancelled searching because a char was typed
s->incsearch_postponed = true;
}
ui_busy_stop();
}
if (found != 0) {
highlight_match = true; // highlight position
} else {
highlight_match = false; // remove highlight
}
// first restore the old curwin values, so the screen is
// positioned in the same way as the actual search command
restore_viewstate(&s->old_viewstate);
changed_cline_bef_curs();
update_topline();
if (found != 0) {
pos_T save_pos = curwin->w_cursor;
s->match_start = curwin->w_cursor;
set_search_match(&curwin->w_cursor);
validate_cursor();
end_pos = curwin->w_cursor;
s->match_end = end_pos;
curwin->w_cursor = save_pos;
} else {
end_pos = curwin->w_cursor; // shutup gcc 4
}
//
// Disable 'hlsearch' highlighting if the pattern matches
// everything. Avoids a flash when typing "foo\|".
if (!use_last_pat) {
next_char = ccline.cmdbuff[skiplen + patlen];
ccline.cmdbuff[skiplen + patlen] = NUL;
if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) {
redraw_all_later(SOME_VALID);
set_no_hlsearch(true);
}
ccline.cmdbuff[skiplen + patlen] = next_char;
}
validate_cursor();
// May redraw the status line to show the cursor position.
if (p_ru && curwin->w_status_height > 0) {
curwin->w_redr_status = true;
}
update_screen(SOME_VALID);
restore_last_search_pattern();
// Leave it at the end to make CTRL-R CTRL-W work. But not when beyond the
// end of the pattern, e.g. for ":s/pat/".
if (ccline.cmdbuff[skiplen + patlen] != NUL) {
curwin->w_cursor = s->search_start;
} else if (found != 0) {
curwin->w_cursor = end_pos;
}
msg_starthere();
redrawcmdline();
s->did_incsearch = true;
}
// When CTRL-L typed: add character from the match to the pattern.
// May set "*c" to the added character.
// Return OK when calling command_line_not_changed.
static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s)
{
int skiplen, patlen;
// Parsing range may already set the last search pattern.
// NOTE: must call restore_last_search_pattern() before returning!
save_last_search_pattern();
// Add a character from under the cursor for 'incsearch'
if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
restore_last_search_pattern();
return FAIL;
}
restore_last_search_pattern();
if (s->did_incsearch) {
curwin->w_cursor = s->match_end;
if (!equalpos(curwin->w_cursor, s->search_start)) {
*c = gchar_cursor();
// If 'ignorecase' and 'smartcase' are set and the
// command line has no uppercase characters, convert
// the character to lowercase
if (p_ic && p_scs
&& !pat_has_uppercase(ccline.cmdbuff + skiplen)) {
*c = mb_tolower(*c);
}
if (*c != NUL) {
if (*c == firstc
|| vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c)
!= NULL) {
// put a backslash before special characters
stuffcharReadbuff(*c);
*c = '\\';
}
return FAIL;
}
}
}
return OK;
}
static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s,
bool call_update_screen)
{
if (s->did_incsearch) {
s->did_incsearch = false;
if (gotesc) {
curwin->w_cursor = s->save_cursor;
} else {
if (!equalpos(s->save_cursor, s->search_start)) {
// put the '" mark at the original position
curwin->w_cursor = s->save_cursor;
setpcmark();
}
curwin->w_cursor = s->search_start; // -V519
}
restore_viewstate(&s->old_viewstate);
highlight_match = false;
// by default search all lines
search_first_line = 0;
search_last_line = MAXLNUM;
p_magic = s->magic_save;
validate_cursor(); // needed for TAB
redraw_all_later(SOME_VALID);
if (call_update_screen) {
update_screen(SOME_VALID);
}
}
}
/// Internal entry point for cmdline mode.
///
/// caller must use save_cmdline and restore_cmdline. Best is to use
@@ -269,11 +652,10 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
.save_msg_scroll = msg_scroll,
.save_State = State,
.ignore_drag_release = true,
.match_start = curwin->w_cursor,
};
CommandLineState *s = &state;
s->save_p_icm = vim_strsave(p_icm);
save_viewstate(&state.init_viewstate);
init_incsearch_state(&s->is_state);
if (s->firstc == -1) {
s->firstc = NUL;
@@ -288,10 +670,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ccline.prompt_id = last_prompt_id++;
ccline.level = cmdline_level;
ccline.overstrike = false; // always start in insert mode
clearpos(&s->match_end);
s->save_cursor = curwin->w_cursor; // may be restored later
s->search_start = curwin->w_cursor;
save_viewstate(&state.old_viewstate);
assert(indent >= 0);
@@ -455,22 +833,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
close_preview_windows();
}
if (s->did_incsearch) {
if (s->gotesc) {
curwin->w_cursor = s->save_cursor;
} else {
if (!equalpos(s->save_cursor, s->search_start)) {
// put the '" mark at the original position
curwin->w_cursor = s->save_cursor;
setpcmark();
}
curwin->w_cursor = s->search_start; // -V519
}
restore_viewstate(&s->old_viewstate);
highlight_match = false;
validate_cursor(); // needed for TAB
redraw_all_later(SOME_VALID);
}
finish_incsearch_highlighting(s->gotesc, &s->is_state, false);
if (ccline.cmdbuff != NULL) {
// Put line in history buffer (":" and "=" only when it was typed).
@@ -1075,24 +1438,45 @@ static int command_line_execute(VimState *state, int key)
return command_line_handle_key(s);
}
static void command_line_next_incsearch(CommandLineState *s, bool next_match)
// May adjust 'incsearch' highlighting for typing CTRL-G and CTRL-T, go to next
// or previous match.
// Returns FAIL when calling command_line_not_changed.
static int may_do_command_line_next_incsearch(int firstc, long count,
incsearch_state_T *s,
bool next_match)
{
int skiplen, patlen;
// Parsing range may already set the last search pattern.
// NOTE: must call restore_last_search_pattern() before returning!
save_last_search_pattern();
if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
restore_last_search_pattern();
return OK;
}
if (patlen == 0 && ccline.cmdbuff[skiplen] == NUL) {
restore_last_search_pattern();
return FAIL;
}
ui_busy_start();
ui_flush();
pos_T t;
char_u *pat;
int search_flags = SEARCH_NOOF;
char_u save;
if (s->firstc == ccline.cmdbuff[0]) {
if (firstc == ccline.cmdbuff[skiplen]) {
pat = last_search_pattern();
skiplen = 0;
patlen = (int)STRLEN(pat);
} else {
pat = ccline.cmdbuff;
pat = ccline.cmdbuff + skiplen;
}
save_last_search_pattern();
if (next_match) {
t = s->match_end;
if (lt(s->match_start, s->match_end)) {
@@ -1108,23 +1492,26 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
search_flags += SEARCH_KEEP;
}
emsg_off++;
save = pat[patlen];
pat[patlen] = NUL;
int found = searchit(curwin, curbuf, &t, NULL,
next_match ? FORWARD : BACKWARD,
pat, s->count, search_flags,
pat, count, search_flags,
RE_SEARCH, NULL);
emsg_off--;
pat[patlen] = save;
ui_busy_stop();
if (found) {
s->search_start = s->match_start;
s->match_end = t;
s->match_start = t;
if (!next_match && s->firstc == '/') {
if (!next_match && firstc != '?') {
// move just before the current match, so that
// when nv_search finishes the cursor will be
// put back on the match
s->search_start = t;
(void)decl(&s->search_start);
} else if (next_match && s->firstc == '?') {
} else if (next_match && firstc == '?') {
// move just after the current match, so that
// when nv_search finishes the cursor will be
// put back on the match
@@ -1134,7 +1521,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
if (lt(t, s->search_start) && next_match) {
// wrap around
s->search_start = t;
if (s->firstc == '?') {
if (firstc == '?') {
(void)incl(&s->search_start);
} else {
(void)decl(&s->search_start);
@@ -1154,7 +1541,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
vim_beep(BO_ERROR);
}
restore_last_search_pattern();
return;
return FAIL;
}
static void command_line_next_histidx(CommandLineState *s, bool next_match)
@@ -1265,10 +1652,10 @@ static int command_line_handle_key(CommandLineState *s)
// Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
if (ccline.cmdlen == 0) {
s->search_start = s->save_cursor;
s->is_state.search_start = s->is_state.save_cursor;
// save view settings, so that the screen won't be restored at the
// wrong position
s->old_viewstate = s->init_viewstate;
s->is_state.old_viewstate = s->is_state.init_viewstate;
}
redrawcmd();
} else if (ccline.cmdlen == 0 && s->c != Ctrl_W
@@ -1287,7 +1674,7 @@ static int command_line_handle_key(CommandLineState *s)
}
msg_putchar(' '); // delete ':'
}
s->search_start = s->save_cursor;
s->is_state.search_start = s->is_state.save_cursor;
redraw_cmdline = true;
return 0; // back to cmd mode
}
@@ -1337,7 +1724,7 @@ static int command_line_handle_key(CommandLineState *s)
// Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
if (ccline.cmdlen == 0) {
s->search_start = s->save_cursor;
s->is_state.search_start = s->is_state.save_cursor;
}
redrawcmd();
return command_line_changed(s);
@@ -1565,31 +1952,7 @@ static int command_line_handle_key(CommandLineState *s)
return command_line_changed(s);
case Ctrl_L:
if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
// Add a character from under the cursor for 'incsearch'
if (s->did_incsearch) {
curwin->w_cursor = s->match_end;
if (!equalpos(curwin->w_cursor, s->search_start)) {
s->c = gchar_cursor();
// If 'ignorecase' and 'smartcase' are set and the
// command line has no uppercase characters, convert
// the character to lowercase
if (p_ic && p_scs
&& !pat_has_uppercase(ccline.cmdbuff)) {
s->c = mb_tolower(s->c);
}
if (s->c != NUL) {
if (s->c == s->firstc
|| vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), s->c)
!= NULL) {
// put a backslash before special characters
stuffcharReadbuff(s->c);
s->c = '\\';
}
break;
}
}
}
if (may_add_char_to_search(s->firstc, &s->c, &s->is_state) == OK) {
return command_line_not_changed(s);
}
@@ -1703,10 +2066,8 @@ static int command_line_handle_key(CommandLineState *s)
case Ctrl_G: // next match
case Ctrl_T: // previous match
if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
if (ccline.cmdlen != 0) {
command_line_next_incsearch(s, s->c == Ctrl_G);
}
if (may_do_command_line_next_incsearch(s->firstc, s->count, &s->is_state,
s->c == Ctrl_G) == FAIL) {
return command_line_not_changed(s);
}
break;
@@ -1791,7 +2152,7 @@ static int command_line_not_changed(CommandLineState *s)
// command line did not change. Then we only search and redraw if something
// changed in the past.
// Enter command_line_changed() when the command line did change.
if (!s->incsearch_postponed) {
if (!s->is_state.incsearch_postponed) {
return 1;
}
return command_line_changed(s);
@@ -1843,108 +2204,13 @@ static int command_line_changed(CommandLineState *s)
}
// 'incsearch' highlighting.
if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
pos_T end_pos;
proftime_T tm;
searchit_arg_T sia;
// if there is a character waiting, search and redraw later
if (char_avail()) {
s->incsearch_postponed = true;
return 1;
}
s->incsearch_postponed = false;
curwin->w_cursor = s->search_start; // start at old position
save_last_search_pattern();
int i;
// If there is no command line, don't do anything
if (ccline.cmdlen == 0) {
i = 0;
set_no_hlsearch(true); // turn off previous highlight
redraw_all_later(SOME_VALID);
} else {
int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK;
ui_busy_start();
ui_flush();
++emsg_off; // So it doesn't beep if bad expr
// Set the time limit to half a second.
tm = profile_setlimit(500L);
if (!p_hls) {
search_flags += SEARCH_KEEP;
}
memset(&sia, 0, sizeof(sia));
sia.sa_tm = &tm;
i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count,
search_flags, &sia);
emsg_off--;
// if interrupted while searching, behave like it failed
if (got_int) {
(void)vpeekc(); // remove <C-C> from input stream
got_int = false; // don't abandon the command line
i = 0;
} else if (char_avail()) {
// cancelled searching because a char was typed
s->incsearch_postponed = true;
}
ui_busy_stop();
}
if (i != 0) {
highlight_match = true; // highlight position
} else {
highlight_match = false; // remove highlight
}
// first restore the old curwin values, so the screen is
// positioned in the same way as the actual search command
restore_viewstate(&s->old_viewstate);
changed_cline_bef_curs();
update_topline();
if (i != 0) {
pos_T save_pos = curwin->w_cursor;
s->match_start = curwin->w_cursor;
set_search_match(&curwin->w_cursor);
validate_cursor();
end_pos = curwin->w_cursor;
s->match_end = end_pos;
curwin->w_cursor = save_pos;
} else {
end_pos = curwin->w_cursor; // shutup gcc 4
}
// Disable 'hlsearch' highlighting if the pattern matches
// everything. Avoids a flash when typing "foo\|".
if (empty_pattern(ccline.cmdbuff)) {
set_no_hlsearch(true);
}
validate_cursor();
// May redraw the status line to show the cursor position.
if (p_ru && curwin->w_status_height > 0) {
curwin->w_redr_status = true;
}
update_screen(SOME_VALID);
restore_last_search_pattern();
// Leave it at the end to make CTRL-R CTRL-W work.
if (i != 0) {
curwin->w_cursor = end_pos;
}
msg_starthere();
redrawcmdline();
s->did_incsearch = true;
} else if (s->firstc == ':'
&& current_sctx.sc_sid == 0 // only if interactive
&& *p_icm != NUL // 'inccommand' is set
&& curbuf->b_p_ma // buffer is modifiable
&& cmdline_star == 0 // not typing a password
&& cmd_can_preview(ccline.cmdbuff)
&& !vpeekc_any()) {
if (s->firstc == ':'
&& current_sctx.sc_sid == 0 // only if interactive
&& *p_icm != NUL // 'inccommand' is set
&& curbuf->b_p_ma // buffer is modifiable
&& cmdline_star == 0 // not typing a password
&& cmd_can_preview(ccline.cmdbuff)
&& !vpeekc_any()) {
// Show 'inccommand' preview. It works like this:
// 1. Do the command.
// 2. Command implementation detects CMDPREVIEW state, then:
@@ -1958,8 +2224,8 @@ static int command_line_changed(CommandLineState *s)
emsg_silent--; // Unblock error reporting
// Restore the window "view".
curwin->w_cursor = s->save_cursor;
restore_viewstate(&s->old_viewstate);
curwin->w_cursor = s->is_state.save_cursor;
restore_viewstate(&s->is_state.old_viewstate);
update_topline();
redrawcmdline();
@@ -1968,6 +2234,8 @@ static int command_line_changed(CommandLineState *s)
State = (State & ~CMDPREVIEW);
close_preview_windows();
update_screen(SOME_VALID); // Clear 'inccommand' preview.
} else {
may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state);
}
if (cmdmsg_rl || (p_arshape && !p_tbidi && enc_utf8)) {

View File

@@ -353,9 +353,11 @@ EXTERN int t_colors INIT(= 256); // int value of T_CCO
// position. Search_match_lines is the number of lines after the match (0 for
// a match within one line), search_match_endcol the column number of the
// character just after the match in the last line.
EXTERN int highlight_match INIT(= false); // show search match pos
EXTERN linenr_T search_match_lines; // lines of of matched string
EXTERN colnr_T search_match_endcol; // col nr of match end
EXTERN bool highlight_match INIT(= false); // show search match pos
EXTERN linenr_T search_match_lines; // lines of of matched string
EXTERN colnr_T search_match_endcol; // col nr of match end
EXTERN linenr_T search_first_line INIT(= 0); // for :{FIRST},{last}s/pat
EXTERN linenr_T search_last_line INIT(= MAXLNUM); // for :{first},{LAST}s/pat
EXTERN int no_smartcase INIT(= false); // don't use 'smartcase' once

View File

@@ -5918,6 +5918,12 @@ next_search_hl (
long nmatched = 0;
int save_called_emsg = called_emsg;
// for :{range}s/pat only highlight inside the range
if (lnum < search_first_line || lnum > search_last_line) {
shl->lnum = 0;
return;
}
if (shl->lnum != 0) {
// Check for three situations:
// 1. If the "lnum" is below a previous match, start a new search.

View File

@@ -99,6 +99,7 @@ static struct spat saved_spats[2];
// copy of spats[RE_SEARCH], for keeping the search patterns while incremental
// searching
static struct spat saved_last_search_spat;
static int did_save_last_search_spat = 0;
static int saved_last_idx = 0;
static bool saved_no_hlsearch = false;
@@ -316,6 +317,12 @@ void free_search_patterns(void)
/// cancelling incremental searching even if it's called inside user functions.
void save_last_search_pattern(void)
{
if (did_save_last_search_spat != 0) {
IEMSG("did_save_last_search_spat is not zero");
} else {
did_save_last_search_spat++;
}
saved_last_search_spat = spats[RE_SEARCH];
if (spats[RE_SEARCH].pat != NULL) {
saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat);
@@ -326,8 +333,15 @@ void save_last_search_pattern(void)
void restore_last_search_pattern(void)
{
if (did_save_last_search_spat != 1) {
IEMSG("did_save_last_search_spat is not one");
return;
}
did_save_last_search_spat--;
xfree(spats[RE_SEARCH].pat);
spats[RE_SEARCH] = saved_last_search_spat;
saved_last_search_spat.pat = NULL;
set_vv_searchforward();
last_idx = saved_last_idx;
set_no_hlsearch(saved_no_hlsearch);

View File

@@ -346,25 +346,104 @@ func Test_searchc()
bw!
endfunc
func Test_search_cmdline3()
func Cmdline3_prep()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
new
call setline(1, [' 1', ' 2 the~e', ' 3 the theother'])
set incsearch
endfunc
func Incsearch_cleanup()
throw 'skipped: Nvim does not support test_override()'
set noincsearch
call test_override("char_avail", 0)
bw!
endfunc
func Test_search_cmdline3()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
call Cmdline3_prep()
1
" first match
call feedkeys("/the\<c-l>\<cr>", 'tx')
call assert_equal(' 2 the~e', getline('.'))
" clean up
set noincsearch
call test_override("char_avail", 0)
bw!
call Incsearch_cleanup()
endfunc
func Test_search_cmdline3s()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
call Cmdline3_prep()
1
call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx')
call assert_equal(' 2 xxxe', getline('.'))
undo
call feedkeys(":%subs/the\<c-l>/xxx\<cr>", 'tx')
call assert_equal(' 2 xxxe', getline('.'))
undo
call feedkeys(":%substitute/the\<c-l>/xxx\<cr>", 'tx')
call assert_equal(' 2 xxxe', getline('.'))
undo
call feedkeys(":%smagic/the.e/xxx\<cr>", 'tx')
call assert_equal(' 2 xxx', getline('.'))
undo
call assert_fails(":%snomagic/the.e/xxx\<cr>", 'E486')
"
call feedkeys(":%snomagic/the\\.e/xxx\<cr>", 'tx')
call assert_equal(' 2 xxx', getline('.'))
call Incsearch_cleanup()
endfunc
func Test_search_cmdline3g()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
call Cmdline3_prep()
1
call feedkeys(":g/the\<c-l>/d\<cr>", 'tx')
call assert_equal(' 3 the theother', getline(2))
undo
call feedkeys(":global/the\<c-l>/d\<cr>", 'tx')
call assert_equal(' 3 the theother', getline(2))
undo
call feedkeys(":g!/the\<c-l>/d\<cr>", 'tx')
call assert_equal(1, line('$'))
call assert_equal(' 2 the~e', getline(1))
undo
call feedkeys(":global!/the\<c-l>/d\<cr>", 'tx')
call assert_equal(1, line('$'))
call assert_equal(' 2 the~e', getline(1))
call Incsearch_cleanup()
endfunc
func Test_search_cmdline3v()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
call Cmdline3_prep()
1
call feedkeys(":v/the\<c-l>/d\<cr>", 'tx')
call assert_equal(1, line('$'))
call assert_equal(' 2 the~e', getline(1))
undo
call feedkeys(":vglobal/the\<c-l>/d\<cr>", 'tx')
call assert_equal(1, line('$'))
call assert_equal(' 2 the~e', getline(1))
call Incsearch_cleanup()
endfunc
func Test_search_cmdline4()
@@ -423,6 +502,45 @@ func Test_search_cmdline5()
bw!
endfunc
func Test_search_cmdline7()
throw 'skipped: Nvim does not support test_override()'
" Test that an pressing <c-g> in an empty command line
" does not move the cursor
if !exists('+incsearch')
return
endif
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
new
let @/ = 'b'
call setline(1, [' bbvimb', ''])
set incsearch
" first match
norm! gg0
" moves to next match of previous search pattern, just like /<cr>
call feedkeys("/\<c-g>\<cr>", 'tx')
call assert_equal([0,1,2,0], getpos('.'))
" moves to next match of previous search pattern, just like /<cr>
call feedkeys("/\<cr>", 'tx')
call assert_equal([0,1,3,0], getpos('.'))
" moves to next match of previous search pattern, just like /<cr>
call feedkeys("/\<c-t>\<cr>", 'tx')
call assert_equal([0,1,7,0], getpos('.'))
" using an offset uses the last search pattern
call cursor(1, 1)
call setline(1, ['1 bbvimb', ' 2 bbvimb'])
let @/ = 'b'
call feedkeys("//e\<c-g>\<cr>", 'tx')
call assert_equal('1 bbvimb', getline('.'))
call assert_equal(4, col('.'))
set noincsearch
call test_override("char_avail", 0)
bw!
endfunc
" Tests for regexp with various magic settings
func Test_search_regexp()
enew!
@@ -504,6 +622,7 @@ func Test_incsearch_substitute_dump()
\ 'for n in range(1, 10)',
\ ' call setline(n, "foo " . n)',
\ 'endfor',
\ 'call setline(11, "bar 11")',
\ '3',
\ ], 'Xis_subst_script')
let buf = RunVimInTerminal('-S Xis_subst_script', {'rows': 9, 'cols': 70})
@@ -512,6 +631,7 @@ func Test_incsearch_substitute_dump()
sleep 100m
" Need to send one key at a time to force a redraw.
" Select three lines at the cursor with typed pattern.
call term_sendkeys(buf, ':.,.+2s/')
sleep 100m
call term_sendkeys(buf, 'f')
@@ -520,12 +640,203 @@ func Test_incsearch_substitute_dump()
sleep 100m
call term_sendkeys(buf, 'o')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_01', {})
call term_sendkeys(buf, "\<Esc>")
" Select three lines at the cursor using previous pattern.
call term_sendkeys(buf, "/foo\<CR>")
sleep 100m
call term_sendkeys(buf, ':.,.+2s//')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_02', {})
" Deleting last slash should remove the match.
call term_sendkeys(buf, "\<BS>")
call VerifyScreenDump(buf, 'Test_incsearch_substitute_03', {})
call term_sendkeys(buf, "\<Esc>")
" Reverse range is accepted
call term_sendkeys(buf, ':5,2s/foo')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_04', {})
call term_sendkeys(buf, "\<Esc>")
" White space after the command is skipped
call term_sendkeys(buf, ':2,3sub /fo')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_05', {})
call term_sendkeys(buf, "\<Esc>")
" Command modifiers are skipped
call term_sendkeys(buf, ':above below browse botr confirm keepmar keepalt keeppat keepjum filter xxx hide lockm leftabove noau noswap rightbel sandbox silent silent! $tab top unsil vert verbose 4,5s/fo.')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_06', {})
call term_sendkeys(buf, "\<Esc>")
" Cursorline highlighting at match
call term_sendkeys(buf, ":set cursorline\<CR>")
call term_sendkeys(buf, 'G9G')
call term_sendkeys(buf, ':9,11s/bar')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_07', {})
call term_sendkeys(buf, "\<Esc>")
" Cursorline highlighting at cursor when no match
call term_sendkeys(buf, ':9,10s/bar')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_08', {})
call term_sendkeys(buf, "\<Esc>")
" Only \v handled as empty pattern, does not move cursor
call term_sendkeys(buf, '3G4G')
call term_sendkeys(buf, ":nohlsearch\<CR>")
call term_sendkeys(buf, ':6,7s/\v')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_09', {})
call term_sendkeys(buf, "\<Esc>")
call term_sendkeys(buf, ":set nocursorline\<CR>")
" All matches are highlighted for 'hlsearch' after the incsearch canceled
call term_sendkeys(buf, "1G*")
call term_sendkeys(buf, ":2,5s/foo")
sleep 100m
call term_sendkeys(buf, "\<Esc>")
call VerifyScreenDump(buf, 'Test_incsearch_substitute_10', {})
call term_sendkeys(buf, ":split\<CR>")
call term_sendkeys(buf, ":let @/ = 'xyz'\<CR>")
call term_sendkeys(buf, ":%s/.")
call VerifyScreenDump(buf, 'Test_incsearch_substitute_11', {})
call term_sendkeys(buf, "\<BS>")
call VerifyScreenDump(buf, 'Test_incsearch_substitute_12', {})
call term_sendkeys(buf, "\<Esc>")
call VerifyScreenDump(buf, 'Test_incsearch_substitute_13', {})
call StopVimInTerminal(buf)
call delete('Xis_subst_script')
endfunc
" Similar to Test_incsearch_substitute_dump() for :sort
func Test_incsearch_sort_dump()
if !exists('+incsearch')
return
endif
if !CanRunVimInTerminal()
throw 'Skipped: cannot make screendumps'
endif
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
\ ], 'Xis_sort_script')
let buf = RunVimInTerminal('-S Xis_sort_script', {'rows': 9, 'cols': 70})
" Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
" the 'ambiwidth' check.
sleep 100m
" Need to send one key at a time to force a redraw.
call term_sendkeys(buf, ':sort ni u /on')
call VerifyScreenDump(buf, 'Test_incsearch_sort_01', {})
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
call delete('Xis_sort_script')
endfunc
" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry
func Test_incsearch_vimgrep_dump()
if !exists('+incsearch')
return
endif
if !CanRunVimInTerminal()
throw 'Skipped: cannot make screendumps'
endif
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
\ ], 'Xis_vimgrep_script')
let buf = RunVimInTerminal('-S Xis_vimgrep_script', {'rows': 9, 'cols': 70})
" Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
" the 'ambiwidth' check.
sleep 100m
" Need to send one key at a time to force a redraw.
call term_sendkeys(buf, ':vimgrep on')
call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_01', {})
call term_sendkeys(buf, "\<Esc>")
call term_sendkeys(buf, ':vimg /on/ *.txt')
call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_02', {})
call term_sendkeys(buf, "\<Esc>")
call term_sendkeys(buf, ':vimgrepadd "\<on')
call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_03', {})
call term_sendkeys(buf, "\<Esc>")
call term_sendkeys(buf, ':lv "tha')
call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_04', {})
call term_sendkeys(buf, "\<Esc>")
call term_sendkeys(buf, ':lvimgrepa "the" **/*.txt')
call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_05', {})
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
call delete('Xis_vimgrep_script')
endfunc
func Test_keep_last_search_pattern()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
new
call setline(1, ['foo', 'foo', 'foo'])
set incsearch
call test_override("char_avail", 1)
let @/ = 'bar'
call feedkeys(":/foo/s//\<Esc>", 'ntx')
call assert_equal('bar', @/)
" no error message if pattern not found
call feedkeys(":/xyz/s//\<Esc>", 'ntx')
call assert_equal('bar', @/)
bwipe!
call test_override("ALL", 0)
set noincsearch
endfunc
func Test_word_under_cursor_after_match()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
new
call setline(1, 'foo bar')
set incsearch
call test_override("char_avail", 1)
try
call feedkeys("/foo\<C-R>\<C-W>\<CR>", 'ntx')
catch /E486:/
endtry
call assert_equal('foobar', @/)
bwipe!
call test_override("ALL", 0)
set noincsearch
endfunc
func Test_subst_word_under_cursor()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
new
call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)'])
set incsearch
call test_override("char_avail", 1)
call feedkeys("/LongName\<CR>", 'ntx')
call feedkeys(":%s/xxx/\<C-R>\<C-W>/g\<CR>", 'ntx')
call assert_equal('for (SomeLongName = 1; SomeLongName < len; ++SomeLongName)', getline(2))
bwipe!
call test_override("ALL", 0)
set noincsearch
endfunc
func Test_incsearch_with_change()
if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal()
throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing'
@@ -580,6 +891,61 @@ func Test_incsearch_scrolling()
call delete('Xscript')
endfunc
func Test_incsearch_search_dump()
if !exists('+incsearch')
return
endif
if !CanRunVimInTerminal()
return
endif
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'for n in range(1, 8)',
\ ' call setline(n, "foo " . n)',
\ 'endfor',
\ '3',
\ ], 'Xis_search_script')
let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
" Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
" the 'ambiwidth' check.
sleep 100m
" Need to send one key at a time to force a redraw.
call term_sendkeys(buf, '/fo')
call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
call term_sendkeys(buf, "\<Esc>")
sleep 100m
call term_sendkeys(buf, '/\v')
call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
call delete('Xis_search_script')
endfunc
func Test_incsearch_substitute()
throw 'skipped: Nvim does not support test_override()'
if !exists('+incsearch')
return
endif
call test_override("char_avail", 1)
new
set incsearch
for n in range(1, 10)
call setline(n, 'foo ' . n)
endfor
4
call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
call assert_equal('foo 3', getline(3))
call assert_equal('xxx 4', getline(4))
call assert_equal('xxx 5', getline(5))
call assert_equal('xxx 6', getline(6))
call assert_equal('foo 7', getline(7))
call Incsearch_cleanup()
endfunc
func Test_search_undefined_behaviour()
if !has("terminal")
return

View File

@@ -21,6 +21,7 @@ describe('search cmdline', function()
err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
more = { bold = true, foreground = Screen.colors.SeaGreen4 },
tilde = { bold = true, foreground = Screen.colors.Blue1 },
hl = { background = Screen.colors.Yellow },
})
end)
@@ -570,4 +571,72 @@ describe('search cmdline', function()
?the |
]])
end)
it('incsearch works with :sort', function()
-- oldtest: Test_incsearch_sort_dump().
screen:try_resize(20, 4)
command('set incsearch hlsearch scrolloff=0')
funcs.setline(1, {'another one 2', 'that one 3', 'the one 1'})
feed(':sort ni u /on')
screen:expect([[
another {inc:on}e 2 |
that {hl:on}e 3 |
the {hl:on}e 1 |
:sort ni u /on^ |
]])
feed('<esc>')
end)
it('incsearch works with :vimgrep family', function()
-- oldtest: Test_incsearch_vimgrep_dump().
screen:try_resize(30, 4)
command('set incsearch hlsearch scrolloff=0')
funcs.setline(1, {'another one 2', 'that one 3', 'the one 1'})
feed(':vimgrep on')
screen:expect([[
another {inc:on}e 2 |
that {hl:on}e 3 |
the {hl:on}e 1 |
:vimgrep on^ |
]])
feed('<esc>')
feed(':vimg /on/ *.txt')
screen:expect([[
another {inc:on}e 2 |
that {hl:on}e 3 |
the {hl:on}e 1 |
:vimg /on/ *.txt^ |
]])
feed('<esc>')
feed(':vimgrepadd "\\<LT>on')
screen:expect([[
another {inc:on}e 2 |
that {hl:on}e 3 |
the {hl:on}e 1 |
:vimgrepadd "\<on^ |
]])
feed('<esc>')
feed(':lv "tha')
screen:expect([[
another one 2 |
{inc:tha}t one 3 |
the one 1 |
:lv "tha^ |
]])
feed('<esc>')
feed(':lvimgrepa "the" **/*.txt')
screen:expect([[
ano{inc:the}r one 2 |
that one 3 |
{hl:the} one 1 |
:lvimgrepa "the" **/*.txt^ |
]])
feed('<esc>')
end)
end)

View File

@@ -162,7 +162,7 @@ describe(":substitute, 'inccommand' preserves", function()
insert(default_text)
feed_command("set inccommand=" .. case)
local delims = { '/', '#', ';', '%', ',', '@', '!', ''}
local delims = { '/', '#', ';', '%', ',', '@', '!' }
for _,delim in pairs(delims) do
feed_command("%s"..delim.."lines"..delim.."LINES"..delim.."g")
expect([[