diff options
Diffstat (limited to 'src/nvim/ex_docmd.c')
-rw-r--r-- | src/nvim/ex_docmd.c | 943 |
1 files changed, 560 insertions, 383 deletions
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 72d39adb3e..30c1373445 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -77,7 +77,7 @@ #include "nvim/api/private/helpers.h" static int quitmore = 0; -static int ex_pressedreturn = FALSE; +static bool ex_pressedreturn = false; /// Whether ":lcd" or ":tcd" was produced for a session. static int did_lcd; @@ -117,11 +117,11 @@ typedef struct { * reads more lines that may come from the while/for loop. */ struct loop_cookie { - garray_T *lines_gap; /* growarray with line info */ - int current_line; /* last read line from growarray */ - int repeating; /* TRUE when looping a second time */ - /* When "repeating" is FALSE use "getline" and "cookie" to get lines */ - char_u *(*getline)(int, void *, int); + garray_T *lines_gap; // growarray with line info + int current_line; // last read line from growarray + int repeating; // TRUE when looping a second time + // When "repeating" is FALSE use "getline" and "cookie" to get lines + char_u *(*getline)(int, void *, int, bool); void *cookie; }; @@ -140,6 +140,31 @@ 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" @@ -271,25 +296,23 @@ int do_cmdline_cmd(const char *cmd) DOCMD_NOWAIT|DOCMD_KEYTYPED); } -/* - * do_cmdline(): execute one Ex command line - * - * 1. Execute "cmdline" when it is not NULL. - * If "cmdline" is NULL, or more lines are needed, fgetline() is used. - * 2. Split up in parts separated with '|'. - * - * This function can be called recursively! - * - * flags: - * DOCMD_VERBOSE - The command will be included in the error message. - * DOCMD_NOWAIT - Don't call wait_return() and friends. - * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. - * DOCMD_KEYTYPED - Don't reset KeyTyped. - * DOCMD_EXCRESET - Reset the exception environment (used for debugging). - * DOCMD_KEEPLINE - Store first typed line (for repeating with "."). - * - * return FAIL if cmdline could not be executed, OK otherwise - */ +/// do_cmdline(): execute one Ex command line +/// +/// 1. Execute "cmdline" when it is not NULL. +/// If "cmdline" is NULL, or more lines are needed, fgetline() is used. +/// 2. Split up in parts separated with '|'. +/// +/// This function can be called recursively! +/// +/// flags: +/// DOCMD_VERBOSE - The command will be included in the error message. +/// DOCMD_NOWAIT - Don't call wait_return() and friends. +/// DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. +/// DOCMD_KEYTYPED - Don't reset KeyTyped. +/// DOCMD_EXCRESET - Reset the exception environment (used for debugging). +/// DOCMD_KEEPLINE - Store first typed line (for repeating with "."). +/// +/// @return FAIL if cmdline could not be executed, OK otherwise int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, /* argument for fgetline() */ int flags) @@ -313,8 +336,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, struct msglist **saved_msg_list = NULL; struct msglist *private_msg_list; - /* "fgetline" and "cookie" passed to do_one_cmd() */ - char_u *(*cmd_getline)(int, void *, int); + // "fgetline" and "cookie" passed to do_one_cmd() + char_u *(*cmd_getline)(int, void *, int, bool); void *cmd_cookie; struct loop_cookie cmd_loop_cookie; void *real_cookie; @@ -396,13 +419,12 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, // If force_abort is set, we cancel everything. did_emsg = false; - /* - * KeyTyped is only set when calling vgetc(). Reset it here when not - * calling vgetc() (sourced command lines). - */ + // KeyTyped is only set when calling vgetc(). Reset it here when not + // calling vgetc() (sourced command lines). if (!(flags & DOCMD_KEYTYPED) - && !getline_equal(fgetline, cookie, getexline)) + && !getline_equal(fgetline, cookie, getexline)) { KeyTyped = false; + } /* * Continue executing command lines: @@ -507,17 +529,20 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * Need to set msg_didout for the first line after an ":if", * otherwise the ":if" will be overwritten. */ - if (count == 1 && getline_equal(fgetline, cookie, getexline)) - msg_didout = TRUE; - if (fgetline == NULL || (next_cmdline = fgetline(':', cookie, - cstack.cs_idx < - 0 ? 0 : (cstack.cs_idx + 1) * 2 - )) == NULL) { - /* Don't call wait_return for aborted command line. The NULL - * returned for the end of a sourced file or executed function - * doesn't do this. */ - if (KeyTyped && !(flags & DOCMD_REPEAT)) - need_wait_return = FALSE; + if (count == 1 && getline_equal(fgetline, cookie, getexline)) { + msg_didout = true; + } + if (fgetline == NULL + || (next_cmdline = fgetline(':', cookie, + cstack.cs_idx < + 0 ? 0 : (cstack.cs_idx + 1) * 2, + true)) == NULL) { + // Don't call wait_return for aborted command line. The NULL + // returned for the end of a sourced file or executed function + // doesn't do this. + if (KeyTyped && !(flags & DOCMD_REPEAT)) { + need_wait_return = false; + } retval = FAIL; break; } @@ -951,7 +976,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, /* * Obtain a line when inside a ":while" or ":for" loop. */ -static char_u *get_loop_line(int c, void *cookie, int indent) +static char_u *get_loop_line(int c, void *cookie, int indent, bool do_concat) { struct loop_cookie *cp = (struct loop_cookie *)cookie; wcmd_T *wp; @@ -961,11 +986,12 @@ static char_u *get_loop_line(int c, void *cookie, int indent) if (cp->repeating) return NULL; /* trying to read past ":endwhile"/":endfor" */ - /* First time inside the ":while"/":for": get line normally. */ - if (cp->getline == NULL) - line = getcmdline(c, 0L, indent); - else - line = cp->getline(c, cp->cookie, indent); + // First time inside the ":while"/":for": get line normally. + if (cp->getline == NULL) { + line = getcmdline(c, 0L, indent, do_concat); + } else { + line = cp->getline(c, cp->cookie, indent, do_concat); + } if (line != NULL) { store_loop_line(cp->lines_gap, line); ++cp->current_line; @@ -1197,69 +1223,74 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) } } -/* - * Execute one Ex command. - * - * If 'sourcing' is TRUE, the command will be included in the error message. - * - * 1. skip comment lines and leading space - * 2. handle command modifiers - * 3. skip over the range to find the command - * 4. parse the range - * 5. parse the command - * 6. parse arguments - * 7. switch on command name - * - * Note: "fgetline" can be NULL. - * - * This function may be called recursively! - */ -static char_u * do_one_cmd(char_u **cmdlinep, - int flags, - struct condstack *cstack, - LineGetter fgetline, - void *cookie /* argument for fgetline() */ - ) +/// Skip colons and trailing whitespace, returning a pointer to the first +/// non-colon, non-whitespace character. +// +/// @param skipleadingwhite Skip leading whitespace too +static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite) { - char_u *p; - linenr_T lnum; - long n; - char_u *errormsg = NULL; /* error message */ - exarg_T ea; /* Ex command arguments */ - long verbose_save = -1; - int save_msg_scroll = msg_scroll; - int save_msg_silent = -1; - int did_esilent = 0; - int did_sandbox = FALSE; - cmdmod_T save_cmdmod; - const int save_reg_executing = reg_executing; - char_u *cmd; - int address_count = 1; + if (skipleadingwhite) { + p = skipwhite(p); + } - memset(&ea, 0, sizeof(ea)); - ea.line1 = 1; - ea.line2 = 1; - ex_nesting_level++; + while (*p == ':') { + p = skipwhite(p + 1); + } - /* When the last file has not been edited :q has to be typed twice. */ - if (quitmore - /* avoid that a function call in 'statusline' does this */ - && !getline_equal(fgetline, cookie, get_func_line) - /* avoid that an autocommand, e.g. QuitPre, does this */ - && !getline_equal(fgetline, cookie, getnextac) - ) - --quitmore; + return (char_u *)p; +} - /* - * Reset browse, confirm, etc.. They are restored when returning, for - * recursive calls. - */ - save_cmdmod = cmdmod; - memset(&cmdmod, 0, sizeof(cmdmod)); +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; - /* "#!anything" is handled like a comment. */ - if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') - goto doend; + 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. @@ -1269,70 +1300,76 @@ static char_u * do_one_cmd(char_u **cmdlinep, /* * 1. Skip comment lines and leading white space and colons. */ - while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':') - ++ea.cmd; + while (*ea.cmd == ' ' + || *ea.cmd == '\t' + || *ea.cmd == ':') { + ea.cmd++; + } - /* in ex mode, an empty line works like :+ */ + // in ex mode, an empty line works like :+ if (*ea.cmd == NUL && exmode_active - && (getline_equal(fgetline, cookie, getexmodeline) - || getline_equal(fgetline, cookie, getexline)) + && (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 *)"+"; - ex_pressedreturn = TRUE; + out->ex_pressedreturn = true; } - /* ignore comment and empty lines */ - if (*ea.cmd == '"') - goto doend; + // ignore comment and empty lines + if (*ea.cmd == '"') { + return false; + } if (*ea.cmd == NUL) { - ex_pressedreturn = TRUE; - goto doend; + out->ex_pressedreturn = true; + return false; } /* * 2. Handle command modifiers. */ - p = skip_range(ea.cmd, NULL); + char_u *p = skip_range(ea.cmd, NULL); switch (*p) { - /* When adding an entry, also modify cmd_exists(). */ + // When adding an entry, also modify cmd_exists(). case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3)) break; - cmdmod.split |= WSP_ABOVE; + out->cmdmod.split |= WSP_ABOVE; continue; case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) { - cmdmod.split |= WSP_BELOW; + out->cmdmod.split |= WSP_BELOW; continue; } if (checkforcmd(&ea.cmd, "browse", 3)) { - cmdmod.browse = true; + out->cmdmod.browse = true; continue; } - if (!checkforcmd(&ea.cmd, "botright", 2)) + if (!checkforcmd(&ea.cmd, "botright", 2)) { break; - cmdmod.split |= WSP_BOT; + } + out->cmdmod.split |= WSP_BOT; continue; case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4)) break; - cmdmod.confirm = true; + out->cmdmod.confirm = true; continue; case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) { - cmdmod.keepmarks = true; + out->cmdmod.keepmarks = true; continue; } if (checkforcmd(&ea.cmd, "keepalt", 5)) { - cmdmod.keepalt = true; + out->cmdmod.keepalt = true; continue; } if (checkforcmd(&ea.cmd, "keeppatterns", 5)) { - cmdmod.keeppatterns = true; + out->cmdmod.keeppatterns = true; continue; } - if (!checkforcmd(&ea.cmd, "keepjumps", 5)) + if (!checkforcmd(&ea.cmd, "keepjumps", 5)) { break; - cmdmod.keepjumps = true; + } + out->cmdmod.keepjumps = true; continue; case 'f': { // only accept ":filter {pat} cmd" @@ -1342,7 +1379,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, break; } if (*p == '!') { - cmdmod.filter_force = true; + out->cmdmod.filter_force = true; p = skipwhite(p + 1); if (*p == NUL || ends_excmd(*p)) { break; @@ -1352,134 +1389,217 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (p == NULL || *p == NUL) { break; } - cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); - if (cmdmod.filter_regmatch.regprog == NULL) { + 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 */ + // ":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; - cmdmod.hide = true; + out->cmdmod.hide = true; continue; case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) { - cmdmod.lockmarks = true; + out->cmdmod.lockmarks = true; continue; } - if (!checkforcmd(&ea.cmd, "leftabove", 5)) + if (!checkforcmd(&ea.cmd, "leftabove", 5)) { break; - cmdmod.split |= WSP_ABOVE; + } + out->cmdmod.split |= WSP_ABOVE; continue; case 'n': if (checkforcmd(&ea.cmd, "noautocmd", 3)) { - if (cmdmod.save_ei == NULL) { - /* 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); + 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; } - cmdmod.noswapfile = true; + out->cmdmod.noswapfile = true; continue; case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6)) break; - cmdmod.split |= WSP_BELOW; + out->cmdmod.split |= WSP_BELOW; continue; case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) { - if (!did_sandbox) - ++sandbox; - did_sandbox = TRUE; + if (!out->did_sandbox) { + out->sandbox++; + } + out->did_sandbox = true; continue; } - if (!checkforcmd(&ea.cmd, "silent", 3)) + if (!checkforcmd(&ea.cmd, "silent", 3)) { break; - if (save_msg_silent == -1) - save_msg_silent = msg_silent; - ++msg_silent; + } + 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" */ + // ":silent!", but not "silent !cmd" ea.cmd = skipwhite(ea.cmd + 1); - ++emsg_silent; - ++did_esilent; + 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); + long tabnr = get_address( + &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); + if (tabnr == MAXLNUM) { - cmdmod.tab = tabpage_index(curtab) + 1; + out->cmdmod.tab = tabpage_index(curtab) + 1; } else { if (tabnr < 0 || tabnr > LAST_TAB_NR) { - errormsg = (char_u *)_(e_invrange); - goto doend; + out->errormsg = (char_u *)_(e_invrange); + return false; } - cmdmod.tab = tabnr + 1; + out->cmdmod.tab = tabnr + 1; } ea.cmd = p; continue; } - if (!checkforcmd(&ea.cmd, "topleft", 2)) + if (!checkforcmd(&ea.cmd, "topleft", 2)) { break; - cmdmod.split |= WSP_TOP; + } + out->cmdmod.split |= WSP_TOP; continue; case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3)) break; - if (save_msg_silent == -1) - save_msg_silent = msg_silent; - msg_silent = 0; + 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)) { - cmdmod.split |= WSP_VERT; + out->cmdmod.split |= WSP_VERT; continue; } if (!checkforcmd(&p, "verbose", 4)) break; - if (verbose_save < 0) - verbose_save = p_verbose; - if (ascii_isdigit(*ea.cmd)) - p_verbose = atoi((char *)ea.cmd); - else - p_verbose = 1; + 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; } - char_u *after_modifier = ea.cmd; - - ea.skip = (did_emsg - || got_int - || current_exception - || (cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); + 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. - cmd = ea.cmd; + out->cmd = ea.cmd; ea.cmd = skip_range(ea.cmd, NULL); if (*ea.cmd == '*') { ea.cmd = skipwhite(ea.cmd + 1); } - p = find_command(&ea, NULL); + out->parsed_upto = find_command(&ea, NULL); + + *out->eap = ea; + + return true; +} + +/* + * Execute one Ex command. + * + * If 'sourcing' is TRUE, the command will be included in the error message. + * + * 1. skip comment lines and leading space + * 2. handle command modifiers + * 3. skip over the range to find the command + * 4. parse the range + * 5. parse the command + * 6. parse arguments + * 7. switch on command name + * + * Note: "fgetline" can be NULL. + * + * This function may be called recursively! + */ +static char_u * do_one_cmd(char_u **cmdlinep, + int flags, + struct condstack *cstack, + LineGetter fgetline, + void *cookie /* argument for fgetline() */ + ) +{ + char_u *p; + linenr_T lnum; + long n; + char_u *errormsg = NULL; // error message + exarg_T ea; + int save_msg_scroll = msg_scroll; + parse_state_T parsed; + cmdmod_T save_cmdmod; + const int save_reg_executing = reg_executing; + + ex_nesting_level++; + + /* When the last file has not been edited :q has to be typed twice. */ + if (quitmore + /* avoid that a function call in 'statusline' does this */ + && !getline_equal(fgetline, cookie, get_func_line) + /* avoid that an autocommand, e.g. QuitPre, does this */ + && !getline_equal(fgetline, cookie, getnextac) + ) + --quitmore; + + /* + * Reset browse, confirm, etc.. They are restored when returning, for + * 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) { + goto doend; + } + + ea.skip = (did_emsg + || got_int + || current_exception + || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); // Count this line for profiling if skip is TRUE. if (do_profiling == PROF_YES @@ -1550,148 +1670,9 @@ static char_u * do_one_cmd(char_u **cmdlinep, } } - /* repeat for all ',' or ';' separated addresses */ - ea.cmd = cmd; - for (;; ) { - ea.line1 = ea.line2; - switch (ea.addr_type) { - case ADDR_LINES: - // default is current line number - ea.line2 = curwin->w_cursor.lnum; - break; - case ADDR_WINDOWS: - ea.line2 = CURRENT_WIN_NR; - break; - case ADDR_ARGUMENTS: - ea.line2 = curwin->w_arg_idx + 1; - if (ea.line2 > ARGCOUNT) { - ea.line2 = ARGCOUNT; - } - break; - case ADDR_LOADED_BUFFERS: - case ADDR_BUFFERS: - ea.line2 = curbuf->b_fnum; - break; - case ADDR_TABS: - ea.line2 = CURRENT_TAB_NR; - break; - case ADDR_TABS_RELATIVE: - ea.line2 = 1; - break; - case ADDR_QUICKFIX: - ea.line2 = qf_get_cur_valid_idx(&ea); - break; - } - ea.cmd = skipwhite(ea.cmd); - lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip, - ea.addr_count == 0, address_count++); - if (ea.cmd == NULL) { // error detected - goto doend; - } - if (lnum == MAXLNUM) { - if (*ea.cmd == '%') { /* '%' - all lines */ - ++ea.cmd; - switch (ea.addr_type) { - case ADDR_LINES: - ea.line1 = 1; - ea.line2 = curbuf->b_ml.ml_line_count; - break; - case ADDR_LOADED_BUFFERS: { - buf_T *buf = firstbuf; - while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { - buf = buf->b_next; - } - ea.line1 = buf->b_fnum; - buf = lastbuf; - while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { - buf = buf->b_prev; - } - ea.line2 = buf->b_fnum; - break; - } - case ADDR_BUFFERS: - ea.line1 = firstbuf->b_fnum; - ea.line2 = lastbuf->b_fnum; - break; - case ADDR_WINDOWS: - case ADDR_TABS: - if (IS_USER_CMDIDX(ea.cmdidx)) { - ea.line1 = 1; - ea.line2 = - ea.addr_type == ADDR_WINDOWS ? LAST_WIN_NR : LAST_TAB_NR; - } else { - // there is no Vim command which uses '%' and - // ADDR_WINDOWS or ADDR_TABS - errormsg = (char_u *)_(e_invrange); - goto doend; - } - break; - case ADDR_TABS_RELATIVE: - errormsg = (char_u *)_(e_invrange); - goto doend; - break; - case ADDR_ARGUMENTS: - if (ARGCOUNT == 0) { - ea.line1 = ea.line2 = 0; - } else { - ea.line1 = 1; - ea.line2 = ARGCOUNT; - } - break; - case ADDR_QUICKFIX: - ea.line1 = 1; - ea.line2 = qf_get_size(&ea); - if (ea.line2 == 0) { - ea.line2 = 1; - } - break; - } - ++ea.addr_count; - } - /* '*' - visual area */ - else if (*ea.cmd == '*') { - pos_T *fp; - - if (ea.addr_type != ADDR_LINES) { - errormsg = (char_u *)_(e_invrange); - goto doend; - } - - ++ea.cmd; - if (!ea.skip) { - fp = getmark('<', FALSE); - if (check_mark(fp) == FAIL) - goto doend; - ea.line1 = fp->lnum; - fp = getmark('>', FALSE); - if (check_mark(fp) == FAIL) - goto doend; - ea.line2 = fp->lnum; - ++ea.addr_count; - } - } - } else - ea.line2 = lnum; - ea.addr_count++; - - if (*ea.cmd == ';') { - if (!ea.skip) { - curwin->w_cursor.lnum = ea.line2; - // don't leave the cursor on an illegal line or column - check_cursor(); - } - } else if (*ea.cmd != ',') { - break; - } - ea.cmd++; - } - - /* One address given: set start and end lines */ - if (ea.addr_count == 1) { - ea.line1 = ea.line2; - /* ... but only implicit: really no address given */ - if (lnum == MAXLNUM) - ea.addr_count = 0; + ea.cmd = parsed.cmd; + if (parse_cmd_address(&ea, &errormsg) == FAIL) { + goto doend; } /* @@ -1701,9 +1682,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, /* * Skip ':' and any white space */ - ea.cmd = skipwhite(ea.cmd); - while (*ea.cmd == ':') - ea.cmd = skipwhite(ea.cmd + 1); + ea.cmd = skip_colon_white(ea.cmd, true); /* * If we got a line, but no command, then go to the line. @@ -1772,8 +1751,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 (after_modifier != NULL) { - append_command(after_modifier); + if (parsed.after_modifier != NULL) { + append_command(parsed.after_modifier); } else { append_command(*cmdlinep); } @@ -2144,6 +2123,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_browse: case CMD_call: case CMD_confirm: + case CMD_const: case CMD_delfunction: case CMD_djump: case CMD_dlist: @@ -2168,6 +2148,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_leftabove: case CMD_let: case CMD_lockmarks: + case CMD_lockvar: case CMD_lua: case CMD_match: case CMD_mzscheme: @@ -2196,6 +2177,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_tilde: case CMD_topleft: case CMD_unlet: + case CMD_unlockvar: case CMD_verbose: case CMD_vertical: case CMD_wincmd: @@ -2241,12 +2223,12 @@ 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 && did_esilent > 0) { - emsg_silent -= did_esilent; + if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) { + emsg_silent -= parsed.did_esilent; if (emsg_silent < 0) { emsg_silent = 0; } - did_esilent = 0; + parsed.did_esilent = 0; } // 7. Execute the command. @@ -2312,8 +2294,9 @@ doend: ? cmdnames[(int)ea.cmdidx].cmd_name : (char_u *)NULL); - if (verbose_save >= 0) - p_verbose = verbose_save; + 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, @@ -2328,16 +2311,18 @@ doend: cmdmod = save_cmdmod; reg_executing = save_reg_executing; - if (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 > save_msg_silent) - msg_silent = save_msg_silent; - emsg_silent -= did_esilent; - if (emsg_silent < 0) + if (parsed.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; + } + emsg_silent -= parsed.did_esilent; + if (emsg_silent < 0) { emsg_silent = 0; - /* Restore msg_scroll, it's set by file I/O commands, even when no - * message is actually displayed. */ + } + // Restore msg_scroll, it's set by file I/O commands, even when no + // message is actually displayed. msg_scroll = save_msg_scroll; /* "silent reg" or "silent echo x" inside "redir" leaves msg_col @@ -2346,8 +2331,9 @@ doend: msg_col = 0; } - if (did_sandbox) - --sandbox; + if (parsed.did_sandbox) { + sandbox--; + } if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ ea.nextcmd = NULL; @@ -2357,6 +2343,160 @@ doend: return ea.nextcmd; } +// Parse the address range, if any, in "eap". +// Return FAIL and set "errormsg" or return OK. +int parse_cmd_address(exarg_T *eap, char_u **errormsg) + FUNC_ATTR_NONNULL_ALL +{ + int address_count = 1; + linenr_T lnum; + + // Repeat for all ',' or ';' separated addresses. + for (;;) { + eap->line1 = eap->line2; + switch (eap->addr_type) { + case ADDR_LINES: + // default is current line number + eap->line2 = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + eap->line2 = CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + eap->line2 = curwin->w_arg_idx + 1; + if (eap->line2 > ARGCOUNT) { + eap->line2 = ARGCOUNT; + } + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + eap->line2 = curbuf->b_fnum; + break; + case ADDR_TABS: + eap->line2 = CURRENT_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + eap->line2 = 1; + break; + case ADDR_QUICKFIX: + eap->line2 = qf_get_cur_valid_idx(eap); + break; + } + eap->cmd = skipwhite(eap->cmd); + lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, + eap->addr_count == 0, address_count++); + if (eap->cmd == NULL) { // error detected + return FAIL; + } + if (lnum == MAXLNUM) { + if (*eap->cmd == '%') { // '%' - all lines + eap->cmd++; + switch (eap->addr_type) { + case ADDR_LINES: + eap->line1 = 1; + eap->line2 = curbuf->b_ml.ml_line_count; + break; + case ADDR_LOADED_BUFFERS: { + buf_T *buf = firstbuf; + + while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { + buf = buf->b_next; + } + eap->line1 = buf->b_fnum; + buf = lastbuf; + while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { + buf = buf->b_prev; + } + eap->line2 = buf->b_fnum; + break; + } + case ADDR_BUFFERS: + eap->line1 = firstbuf->b_fnum; + eap->line2 = lastbuf->b_fnum; + break; + case ADDR_WINDOWS: + case ADDR_TABS: + if (IS_USER_CMDIDX(eap->cmdidx)) { + eap->line1 = 1; + eap->line2 = eap->addr_type == ADDR_WINDOWS + ? LAST_WIN_NR : LAST_TAB_NR; + } else { + // there is no Vim command which uses '%' and + // ADDR_WINDOWS or ADDR_TABS + *errormsg = (char_u *)_(e_invrange); + return FAIL; + } + break; + case ADDR_TABS_RELATIVE: + *errormsg = (char_u *)_(e_invrange); + return FAIL; + case ADDR_ARGUMENTS: + if (ARGCOUNT == 0) { + eap->line1 = eap->line2 = 0; + } else { + eap->line1 = 1; + eap->line2 = ARGCOUNT; + } + break; + case ADDR_QUICKFIX: + eap->line1 = 1; + eap->line2 = qf_get_size(eap); + if (eap->line2 == 0) { + eap->line2 = 1; + } + break; + } + eap->addr_count++; + } else if (*eap->cmd == '*') { + // '*' - visual area + if (eap->addr_type != ADDR_LINES) { + *errormsg = (char_u *)_(e_invrange); + return FAIL; + } + + eap->cmd++; + if (!eap->skip) { + pos_T *fp = getmark('<', false); + if (check_mark(fp) == FAIL) { + return FAIL; + } + eap->line1 = fp->lnum; + fp = getmark('>', false); + if (check_mark(fp) == FAIL) { + return FAIL; + } + eap->line2 = fp->lnum; + eap->addr_count++; + } + } + } else { + eap->line2 = lnum; + } + eap->addr_count++; + + if (*eap->cmd == ';') { + if (!eap->skip) { + curwin->w_cursor.lnum = eap->line2; + // don't leave the cursor on an illegal line or column + check_cursor(); + } + } else if (*eap->cmd != ',') { + break; + } + eap->cmd++; + } + + // One address given: set start and end lines. + if (eap->addr_count == 1) { + eap->line1 = eap->line2; + // ... but only implicit: really no address given + if (lnum == MAXLNUM) { + eap->addr_count = 0; + } + } + return OK; +} + /* * Check for an Ex command with optional tail. * If there is a match advance "pp" to the argument and return TRUE. @@ -3297,6 +3437,7 @@ const char * set_one_cmd_context( case CMD_syntax: set_context_in_syntax_cmd(xp, arg); break; + case CMD_const: case CMD_let: case CMD_if: case CMD_elseif: @@ -3537,15 +3678,13 @@ const char * set_one_cmd_context( return NULL; } -/* - * skip a range specifier of the form: addr [,addr] [;addr] .. - * - * Backslashed delimiters after / or ? will be skipped, and commands will - * not be expanded between /'s and ?'s or after "'". - * - * Also skip white space and ":" characters. - * Returns the "cmd" pointer advanced to beyond the range. - */ +// Skip a range specifier of the form: addr [,addr] [;addr] .. +// +// Backslashed delimiters after / or ? will be skipped, and commands will +// not be expanded between /'s and ?'s or after "'". +// +// Also skip white space and ":" characters. +// Returns the "cmd" pointer advanced to beyond the range. char_u *skip_range( const char_u *cmd, int *ctx // pointer to xp_context or NULL @@ -3576,9 +3715,8 @@ char_u *skip_range( ++cmd; } - /* Skip ":" and white space. */ - while (*cmd == ':') - cmd = skipwhite(cmd + 1); + // Skip ":" and white space. + cmd = skip_colon_white(cmd, false); return (char_u *)cmd; } @@ -3746,8 +3884,7 @@ 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, NULL)) { + if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; @@ -3784,8 +3921,7 @@ static linenr_T get_address(exarg_T *eap, pos.coladd = 0; if (searchit(curwin, curbuf, &pos, NULL, *cmd == '?' ? BACKWARD : FORWARD, - (char_u *)"", 1L, SEARCH_MSG, - i, (linenr_T)0, NULL, NULL) != FAIL) { + (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) { lnum = pos.lnum; } else { cmd = NULL; @@ -4998,9 +5134,11 @@ static void uc_list(char_u *name, size_t name_len) ucmd_T *cmd; int len; uint32_t a; - garray_T *gap; - gap = &curbuf->b_ucmds; + // In cmdwin, the alternative buffer should be used. + garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL) + ? &prevwin->w_buffer->b_ucmds + : &curbuf->b_ucmds; for (;; ) { for (i = 0; i < gap->ga_len; ++i) { cmd = USER_CMD_GA(gap, i); @@ -5849,13 +5987,21 @@ char_u *get_user_cmd_addr_type(expand_T *xp, int idx) /* * Function given to ExpandGeneric() to obtain the list of user command names. */ -char_u *get_user_commands(expand_T *xp, int idx) +char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - if (idx < curbuf->b_ucmds.ga_len) - return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name; - idx -= curbuf->b_ucmds.ga_len; - if (idx < ucmds.ga_len) + // In cmdwin, the alternative buffer should be used. + const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL) + ? prevwin->w_buffer + : curbuf; + + if (idx < buf->b_ucmds.ga_len) { + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + } + idx -= buf->b_ucmds.ga_len; + if (idx < ucmds.ga_len) { return USER_CMD(idx)->uc_name; + } return NULL; } @@ -6066,9 +6212,11 @@ static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit) if (quit_all || (check_more(false, forceit) == OK && only_one_window())) { apply_autocmds(EVENT_EXITPRE, NULL, NULL, false, curbuf); - // Refuse to quit when locked or when the buffer in the last window is - // being closed (can only happen in autocommands). - if (curbuf_locked() + // Refuse to quit when locked or when the window was closed or the + // buffer in the last window is being closed (can only happen in + // autocommands). + if (!win_valid(wp) + || curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) { return true; } @@ -6708,17 +6856,18 @@ static void ex_preserve(exarg_T *eap) /// ":recover". static void ex_recover(exarg_T *eap) { - /* Set recoverymode right away to avoid the ATTENTION prompt. */ - recoverymode = TRUE; + // Set recoverymode right away to avoid the ATTENTION prompt. + recoverymode = true; if (!check_changed(curbuf, (p_awa ? CCGD_AW : 0) | CCGD_MULTWIN | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD) && (*eap->arg == NUL - || setfname(curbuf, eap->arg, NULL, TRUE) == OK)) - ml_recover(); - recoverymode = FALSE; + || setfname(curbuf, eap->arg, NULL, true) == OK)) { + ml_recover(true); + } + recoverymode = false; } /* @@ -8188,6 +8337,7 @@ static void ex_normal(exarg_T *eap) int save_insertmode = p_im; int save_finish_op = finish_op; long save_opcount = opcount; + const int save_reg_executing = reg_executing; char_u *arg = NULL; int l; char_u *p; @@ -8282,7 +8432,8 @@ static void ex_normal(exarg_T *eap) p_im = save_insertmode; finish_op = save_finish_op; opcount = save_opcount; - msg_didout |= save_msg_didout; /* don't reset msg_didout now */ + reg_executing = save_reg_executing; + msg_didout |= save_msg_didout; // don't reset msg_didout now /* Restore the state (needed when called from a function executed for * 'indentexpr'). Update the mouse and cursor, they may have changed. */ @@ -9553,7 +9704,7 @@ put_view( */ if ((*flagp & SSOP_FOLDS) && wp->w_buffer->b_ffname != NULL - && (*wp->w_buffer->b_p_bt == NUL || bt_help(wp->w_buffer)) + && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) ) { if (put_folds(fd, wp) == FAIL) return FAIL; @@ -10010,10 +10161,11 @@ static void ex_setfiletype(exarg_T *eap) static void ex_digraphs(exarg_T *eap) { - if (*eap->arg != NUL) + if (*eap->arg != NUL) { putdigraph(eap->arg); - else - listdigraphs(); + } else { + listdigraphs(eap->forceit); + } } static void ex_set(exarg_T *eap) @@ -10131,6 +10283,28 @@ static void ex_folddo(exarg_T *eap) ml_clearmarked(); // clear rest of the marks } +// Returns true if the supplied Ex cmdidx is for a location list command +// instead of a quickfix command. +bool is_loclist_cmd(int cmdidx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (cmdidx < 0 || cmdidx >= CMD_SIZE) { + return false; + } + return cmdnames[cmdidx].cmd_name[0] == 'l'; +} + +bool get_pressedreturn(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return ex_pressedreturn; +} + +void set_pressedreturn(bool val) +{ + ex_pressedreturn = val; +} + static void ex_terminal(exarg_T *eap) { char ex_cmd[1024]; @@ -10178,10 +10352,13 @@ bool cmd_can_preview(char_u *cmd) return false; } + // Ignore additional colons at the start... + cmd = skip_colon_white(cmd, true); + // Ignore any leading modifiers (:keeppatterns, :verbose, etc.) for (int len = modifier_len(cmd); len != 0; len = modifier_len(cmd)) { cmd += len; - cmd = skipwhite(cmd); + cmd = skip_colon_white(cmd, true); } exarg_T ea; |