diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/ex_cmds_defs.h | 4 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 712 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 624 | ||||
-rw-r--r-- | src/nvim/globals.h | 8 | ||||
-rw-r--r-- | src/nvim/screen.c | 6 | ||||
-rw-r--r-- | src/nvim/search.c | 14 | ||||
-rw-r--r-- | src/nvim/testdir/test_search.vim | 382 |
7 files changed, 1186 insertions, 564 deletions
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 1f0560ae48..ff5088ea5e 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -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" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 1160ab835b..dfebd13868 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -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, ®_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); + // "#!anything" is handled like a comment. + if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { + goto doend; + } - // Update locals from parse_one_cmd() - errormsg = parsed.errormsg; - p = parsed.parsed_upto; + // 1. Skip comment lines and leading white space and colons. + // 2. Handle command modifiers. - if (!parse_success) { + // 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, ®_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; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 93684e8606..c966c780a0 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -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)) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 91a0eab947..205be4b811 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -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 diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 5f3423989e..3c2e1ccaf5 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -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. diff --git a/src/nvim/search.c b/src/nvim/search.c index fc82e81472..23d84038d6 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -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); diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 8036dea29f..767cf99be3 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -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 |