diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 33efa6b326..5ab0beec9d 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -505,6 +505,11 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'"); OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'"); + if (cmdinfo.magic.file) { + ea.argt |= EX_XFILE; + } else { + ea.argt &= ~EX_XFILE; + } } else { cmdinfo.magic.file = ea.argt & EX_XFILE; cmdinfo.magic.bar = ea.argt & EX_TRLBAR; diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 052926fa1f..e80e47bcff 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -37,34 +37,34 @@ // 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and // long name of the command. -#define EX_RANGE 0x001 // allow a linespecs -#define EX_BANG 0x002 // allow a ! after the command name -#define EX_EXTRA 0x004 // allow extra args after command name -#define EX_XFILE 0x008 // expand wildcards in extra part -#define EX_NOSPC 0x010 // no spaces allowed in the extra part -#define EX_DFLALL 0x020 // default file range is 1,$ -#define EX_WHOLEFOLD 0x040 // extend range to include whole fold also - // when less than two numbers given -#define EX_NEEDARG 0x080 // argument required -#define EX_TRLBAR 0x100 // check for trailing vertical bar -#define EX_REGSTR 0x200 // allow "x for register designation -#define EX_COUNT 0x400 // allow count in argument, after command -#define EX_NOTRLCOM 0x800 // no trailing comment allowed -#define EX_ZEROR 0x1000 // zero line number allowed -#define EX_CTRLV 0x2000 // do not remove CTRL-V from argument -#define EX_CMDARG 0x4000 // allow "+command" argument -#define EX_BUFNAME 0x8000 // accepts buffer name -#define EX_BUFUNL 0x10000 // accepts unlisted buffer too -#define EX_ARGOPT 0x20000 // allow "++opt=val" argument -#define EX_SBOXOK 0x40000 // allowed in the sandbox -#define EX_CMDWIN 0x80000 // allowed in cmdline window -#define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer -#define EX_FLAGS 0x200000 // allow flags after count in argument -#define EX_LOCK_OK 0x1000000 // command can be executed when textlock is - // set; when missing disallows editing another - // buffer when current buffer is locked -#define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked -#define EX_PREVIEW 0x8000000 // allow incremental command preview +#define EX_RANGE 0x001u // allow a linespecs +#define EX_BANG 0x002u // allow a ! after the command name +#define EX_EXTRA 0x004u // allow extra args after command name +#define EX_XFILE 0x008u // expand wildcards in extra part +#define EX_NOSPC 0x010u // no spaces allowed in the extra part +#define EX_DFLALL 0x020u // default file range is 1,$ +#define EX_WHOLEFOLD 0x040u // extend range to include whole fold also + // when less than two numbers given +#define EX_NEEDARG 0x080u // argument required +#define EX_TRLBAR 0x100u // check for trailing vertical bar +#define EX_REGSTR 0x200u // allow "x for register designation +#define EX_COUNT 0x400u // allow count in argument, after command +#define EX_NOTRLCOM 0x800u // no trailing comment allowed +#define EX_ZEROR 0x1000u // zero line number allowed +#define EX_CTRLV 0x2000u // do not remove CTRL-V from argument +#define EX_CMDARG 0x4000u // allow "+command" argument +#define EX_BUFNAME 0x8000u // accepts buffer name +#define EX_BUFUNL 0x10000u // accepts unlisted buffer too +#define EX_ARGOPT 0x20000u // allow "++opt=val" argument +#define EX_SBOXOK 0x40000u // allowed in the sandbox +#define EX_CMDWIN 0x80000u // allowed in cmdline window +#define EX_MODIFY 0x100000u // forbidden in non-'modifiable' buffer +#define EX_FLAGS 0x200000u // allow flags after count in argument +#define EX_LOCK_OK 0x1000000u // command can be executed when textlock is + // set; when missing disallows editing another + // buffer when current buffer is locked +#define EX_KEEPSCRIPT 0x4000000u // keep sctx of where command was invoked +#define EX_PREVIEW 0x8000000u // allow incremental command preview #define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed #define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file #define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index a7d91a47d7..31314fd8b9 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1563,6 +1563,92 @@ err: return false; } +static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) +{ + // If filename expansion is enabled, expand filenames + if (eap->argt & EX_XFILE) { + if (expand_filename(eap, eap->cmdlinep, errormsg) == FAIL) { + return FAIL; + } + } + + // Accept buffer name. Cannot be used at the same time with a buffer + // number. Don't do this for a user command. + if ((eap->argt & EX_BUFNAME) && *eap->arg != NUL && eap->addr_count == 0 + && !IS_USER_CMDIDX(eap->cmdidx)) { + if (eap->args == NULL) { + // If argument positions are not specified, search the argument for the buffer name. + // :bdelete, :bwipeout and :bunload take several arguments, separated by spaces: + // find next space (skipping over escaped characters). + // The others take one argument: ignore trailing spaces. + char *p; + + if (eap->cmdidx == CMD_bdelete || eap->cmdidx == CMD_bwipeout + || eap->cmdidx == CMD_bunload) { + p = skiptowhite_esc(eap->arg); + } else { + p = eap->arg + STRLEN(eap->arg); + while (p > eap->arg && ascii_iswhite(p[-1])) { + p--; + } + } + eap->line2 = buflist_findpat(eap->arg, p, (eap->argt & EX_BUFUNL) != 0, + false, false); + eap->addr_count = 1; + eap->arg = skipwhite(p); + } else { + // If argument positions are specified, just use the first argument + eap->line2 = buflist_findpat(eap->args[0], + eap->args[0] + eap->arglens[0], + (eap->argt & EX_BUFUNL) != 0, false, false); + eap->addr_count = 1; + // Shift each argument by 1 + for (size_t i = 0; i < eap->argc - 1; i++) { + eap->args[i] = eap->args[i + 1]; + } + // Make the last argument point to the NUL terminator at the end of string + eap->args[eap->argc - 1] = eap->args[eap->argc - 1] + eap->arglens[eap->argc - 1]; + eap->argc -= 1; + + eap->arg = eap->args[0]; + } + if (eap->line2 < 0) { // failed + return FAIL; + } + } + + // The :try command saves the emsg_silent flag, reset it here when + // ":silent! try" was used, it should only apply to :try itself. + if (eap->cmdidx == CMD_try && cmdmod.cmod_did_esilent > 0) { + emsg_silent -= cmdmod.cmod_did_esilent; + if (emsg_silent < 0) { + emsg_silent = 0; + } + cmdmod.cmod_did_esilent = 0; + } + + // Execute the command + if (IS_USER_CMDIDX(eap->cmdidx)) { + // Execute a user-defined command. + *retv = do_ucmd(eap, preview); + } else { + // Call the function to execute the command or the preview callback. + eap->errmsg = NULL; + + if (preview) { + *retv = (cmdnames[eap->cmdidx].cmd_preview_func)(eap, cmdpreview_get_ns(), + cmdpreview_get_bufnr()); + } else { + (cmdnames[eap->cmdidx].cmd_func)(eap); + } + if (eap->errmsg != NULL) { + *errormsg = _(eap->errmsg); + } + } + + return OK; +} + /// Execute an Ex command using parsed command line information. /// Does not do any validation of the Ex command arguments. /// @@ -1622,76 +1708,8 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) (void)hasFolding(eap->line2, NULL, &eap->line2); } - // If filename expansion is enabled, expand filenames - if (cmdinfo->magic.file) { - if (expand_filename(eap, eap->cmdlinep, &errormsg) == FAIL) { - goto end; - } - } - - // Accept buffer name. Cannot be used at the same time with a buffer - // number. Don't do this for a user command. - if ((eap->argt & EX_BUFNAME) && *eap->arg != NUL && eap->addr_count == 0 - && !IS_USER_CMDIDX(eap->cmdidx)) { - if (eap->args == NULL) { - // If argument positions are not specified, search the argument for the buffer name. - // :bdelete, :bwipeout and :bunload take several arguments, separated by spaces: - // find next space (skipping over escaped characters). - // The others take one argument: ignore trailing spaces. - char *p; - - if (eap->cmdidx == CMD_bdelete || eap->cmdidx == CMD_bwipeout - || eap->cmdidx == CMD_bunload) { - p = skiptowhite_esc(eap->arg); - } else { - p = eap->arg + STRLEN(eap->arg); - while (p > eap->arg && ascii_iswhite(p[-1])) { - p--; - } - } - eap->line2 = buflist_findpat(eap->arg, p, (eap->argt & EX_BUFUNL) != 0, - false, false); - eap->addr_count = 1; - eap->arg = skipwhite(p); - } else { - // If argument positions are specified, just use the first argument - eap->line2 = buflist_findpat(eap->args[0], - eap->args[0] + eap->arglens[0], - (eap->argt & EX_BUFUNL) != 0, false, false); - eap->addr_count = 1; - // Shift each argument by 1 - for (size_t i = 0; i < eap->argc - 1; i++) { - eap->args[i] = eap->args[i + 1]; - } - // Make the last argument point to the NUL terminator at the end of string - eap->args[eap->argc - 1] = eap->args[eap->argc - 1] + eap->arglens[eap->argc - 1]; - eap->argc -= 1; - - eap->arg = eap->args[0]; - } - if (eap->line2 < 0) { // failed - goto end; - } - } - // Execute the command - if (IS_USER_CMDIDX(eap->cmdidx)) { - // Execute a user-defined command. - retv = do_ucmd(eap, preview); - } else { - // Call the function to execute the command or the preview callback. - eap->errmsg = NULL; - - if (preview) { - retv = (cmdnames[eap->cmdidx].cmd_preview_func)(eap, cmdpreview_get_ns(), - cmdpreview_get_bufnr()); - } else { - (cmdnames[eap->cmdidx].cmd_func)(eap); - } - if (eap->errmsg != NULL) { - errormsg = _(eap->errmsg); - } - } + execute_cmd0(&retv, eap, &errormsg, preview); end: if (errormsg != NULL && *errormsg != NUL) { @@ -1704,117 +1722,30 @@ end: #undef ERROR } -/// 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! -/// -/// @param cookie argument for fgetline() -static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter fgetline, - void *cookie) +static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetline, void *cookie) { - char *p; - linenr_T lnum; - char *errormsg = NULL; // error message - char *after_modifier = NULL; - exarg_T ea; - cmdmod_T save_cmdmod; - const int save_reg_executing = reg_executing; - const bool save_pending_end_reg_executing = pending_end_reg_executing; - char *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. - 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; - - // "#!anything" is handled like a comment. - if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { - goto doend; - } - - // 1. Skip comment lines and leading white space and colons. - // 2. Handle command modifiers. - - // The "ea" structure holds the arguments that can be used. - ea.cmd = *cmdlinep; - ea.cmdlinep = cmdlinep; - ea.getline = fgetline; - ea.cookie = cookie; - ea.cstack = cstack; - - if (parse_command_modifiers(&ea, &errormsg, &cmdmod, false) == FAIL) { - goto doend; - } - apply_cmdmod(&cmdmod); - - 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_ex_command(&ea, NULL); - // Count this line for profiling if skip is TRUE. if (do_profiling == PROF_YES - && (!ea.skip || cstack->cs_idx == 0 + && (!eap->skip || cstack->cs_idx == 0 || (cstack->cs_idx > 0 && (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) { int skip = did_emsg || got_int || current_exception; - if (ea.cmdidx == CMD_catch) { + if (eap->cmdidx == CMD_catch) { skip = !skip && !(cstack->cs_idx >= 0 && (cstack->cs_flags[cstack->cs_idx] & CSF_THROWN) && !(cstack->cs_flags[cstack->cs_idx] & CSF_CAUGHT)); - } else if (ea.cmdidx == CMD_else || ea.cmdidx == CMD_elseif) { + } else if (eap->cmdidx == CMD_else || eap->cmdidx == CMD_elseif) { skip = skip || !(cstack->cs_idx >= 0 && !(cstack->cs_flags[cstack->cs_idx] & (CSF_ACTIVE | CSF_TRUE))); - } else if (ea.cmdidx == CMD_finally) { + } else if (eap->cmdidx == CMD_finally) { skip = false; - } else if (ea.cmdidx != CMD_endif - && ea.cmdidx != CMD_endfor - && ea.cmdidx != CMD_endtry - && ea.cmdidx != CMD_endwhile) { - skip = ea.skip; + } else if (eap->cmdidx != CMD_endif + && eap->cmdidx != CMD_endfor + && eap->cmdidx != CMD_endtry + && eap->cmdidx != CMD_endwhile) { + skip = eap->skip; } if (!skip) { @@ -1825,380 +1756,16 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter } } } +} - // May go to debug mode. If this happens and the ">quit" debug command is - // used, throw an interrupt exception and skip the next command. - dbg_check_breakpoint(&ea); - if (!ea.skip && got_int) { - ea.skip = TRUE; - (void)do_intthrow(cstack); - } - - // 4. Parse a range specifier of the form: addr [,addr] [;addr] .. - // - // where 'addr' is: - // - // % (entire file) - // $ [+-NUM] - // 'x [+-NUM] (where x denotes a currently defined mark) - // . [+-NUM] - // [+-NUM].. - // NUM - // - // The ea.cmd pointer is updated to point to the first character following the - // range spec. If an initial address is found, but no second, the upper bound - // is equal to the lower. - set_cmd_addr_type(&ea, p); - - ea.cmd = cmd; - if (parse_cmd_address(&ea, &errormsg, false) == FAIL) { - goto doend; - } - - /* - * 5. Parse the command. - */ - - /* - * Skip ':' and any white space - */ - ea.cmd = skip_colon_white(ea.cmd, true); - - /* - * If we got a line, but no command, then go to the line. - * If we find a '|' or '\n' we set ea.nextcmd. - */ - if (*ea.cmd == NUL || *ea.cmd == '"' - || (ea.nextcmd = (char *)check_nextcmd((char_u *)ea.cmd)) != NULL) { - // strange vi behaviour: - // ":3" jumps to line 3 - // ":3|..." prints line 3 - // ":|" prints current line - if (ea.skip) { // skip this if inside :if - goto doend; - } - if (*ea.cmd == '|' || (exmode_active && ea.line1 != ea.line2)) { - ea.cmdidx = CMD_print; - ea.argt = EX_RANGE | EX_COUNT | EX_TRLBAR; - if ((errormsg = invalid_range(&ea)) == NULL) { - correct_range(&ea); - ex_print(&ea); - } - } else if (ea.addr_count != 0) { - if (ea.line2 > curbuf->b_ml.ml_line_count) { - ea.line2 = curbuf->b_ml.ml_line_count; - } - - if (ea.line2 < 0) { - errormsg = _(e_invrange); - } else { - if (ea.line2 == 0) { - curwin->w_cursor.lnum = 1; - } else { - curwin->w_cursor.lnum = ea.line2; - } - beginline(BL_SOL | BL_FIX); - } - } - goto doend; - } - - // If this looks like an undefined user command and there are CmdUndefined - // autocommands defined, trigger the matching autocommands. - if (p != NULL && ea.cmdidx == CMD_SIZE && !ea.skip - && ASCII_ISUPPER(*ea.cmd) - && has_event(EVENT_CMDUNDEFINED)) { - p = ea.cmd; - while (ASCII_ISALNUM(*p)) { - ++p; - } - p = xstrnsave(ea.cmd, (size_t)(p - ea.cmd)); - int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL); - xfree(p); - // If the autocommands did something and didn't cause an error, try - // finding the command again. - p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd; - } - - if (p == NULL) { - if (!ea.skip) { - errormsg = _(e_ambiguous_use_of_user_defined_command); - } - goto doend; - } - // Check for wrong commands. - if (ea.cmdidx == CMD_SIZE) { - if (!ea.skip) { - STRCPY(IObuff, _(e_not_an_editor_command)); - // If the modifier was parsed OK the error must be in the following - // command - char *cmdname = after_modifier ? after_modifier : *cmdlinep; - if (!(flags & DOCMD_VERBOSE)) { - append_command(cmdname); - } - errormsg = (char *)IObuff; - did_emsg_syntax = true; - verify_command(cmdname); - } - goto doend; - } - - // set when Not Implemented - const int ni = is_cmd_ni(ea.cmdidx); - - // Forced commands. - if (*p == '!' && ea.cmdidx != CMD_substitute - && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) { - p++; - ea.forceit = true; - } else { - ea.forceit = false; - } - - // 6. Parse arguments. Then check for errors. - if (!IS_USER_CMDIDX(ea.cmdidx)) { - ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt; - } - - if (!ea.skip) { - if (sandbox != 0 && !(ea.argt & EX_SBOXOK)) { - // Command not allowed in sandbox. - errormsg = _(e_sandbox); - goto doend; - } - if (!MODIFIABLE(curbuf) && (ea.argt & EX_MODIFY) - // allow :put in terminals - && (!curbuf->terminal || ea.cmdidx != CMD_put)) { - // Command not allowed in non-'modifiable' buffer - errormsg = _(e_modifiable); - goto doend; - } - - if (!IS_USER_CMDIDX(ea.cmdidx)) { - if (cmdwin_type != 0 && !(ea.argt & EX_CMDWIN)) { - // Command not allowed in the command line window - errormsg = _(e_cmdwin); - goto doend; - } - if (text_locked() && !(ea.argt & EX_LOCK_OK)) { - // Command not allowed when text is locked - errormsg = _(get_text_locked_msg()); - goto doend; - } - } - - // Disallow editing another buffer when "curbuf->b_ro_locked" is set. - // Do allow ":checktime" (it is postponed). - // Do allow ":edit" (check for an argument later). - // Do allow ":file" with no arguments (check for an argument later). - if (!(ea.argt & EX_CMDWIN) - && ea.cmdidx != CMD_checktime - && ea.cmdidx != CMD_edit - && ea.cmdidx != CMD_file - && !IS_USER_CMDIDX(ea.cmdidx) - && curbuf_locked()) { - goto doend; - } - - if (!ni && !(ea.argt & EX_RANGE) && ea.addr_count > 0) { - // no range allowed - errormsg = _(e_norange); - goto doend; - } - } - - if (!ni && !(ea.argt & EX_BANG) && ea.forceit) { // no allowed - errormsg = _(e_nobang); - goto doend; - } - - /* - * Don't complain about the range if it is not used - * (could happen if line_count is accidentally set to 0). - */ - if (!ea.skip && !ni && (ea.argt & EX_RANGE)) { - // If the range is backwards, ask for confirmation and, if given, swap - // ea.line1 & ea.line2 so it's forwards again. - // When global command is busy, don't ask, will fail below. - if (!global_busy && ea.line1 > ea.line2) { - if (msg_silent == 0) { - if ((flags & DOCMD_VERBOSE) || exmode_active) { - errormsg = _("E493: Backwards range given"); - goto doend; - } - if (ask_yesno(_("Backwards range given, OK to swap"), false) != 'y') { - goto doend; - } - } - lnum = ea.line1; - ea.line1 = ea.line2; - ea.line2 = lnum; - } - if ((errormsg = invalid_range(&ea)) != NULL) { - goto doend; - } - } - - if ((ea.addr_type == ADDR_OTHER) && ea.addr_count == 0) { - // default is 1, not cursor - ea.line2 = 1; - } - - correct_range(&ea); - - if (((ea.argt & EX_WHOLEFOLD) || ea.addr_count >= 2) && !global_busy - && ea.addr_type == ADDR_LINES) { - // Put the first line at the start of a closed fold, put the last line - // at the end of a closed fold. - (void)hasFolding(ea.line1, &ea.line1, NULL); - (void)hasFolding(ea.line2, NULL, &ea.line2); - } - - /* - * For the ":make" and ":grep" commands we insert the 'makeprg'/'grepprg' - * option here, so things like % get expanded. - */ - p = replace_makeprg(&ea, p, cmdlinep); - if (p == NULL) { - goto doend; - } - - /* - * Skip to start of argument. - * Don't do this for the ":!" command, because ":!! -l" needs the space. - */ - if (ea.cmdidx == CMD_bang) { - ea.arg = p; - } else { - ea.arg = skipwhite(p); - } - - // ":file" cannot be run with an argument when "curbuf->b_ro_locked" is set - if (ea.cmdidx == CMD_file && *ea.arg != NUL && curbuf_locked()) { - goto doend; - } - - /* - * Check for "++opt=val" argument. - * Must be first, allow ":w ++enc=utf8 !cmd" - */ - if (ea.argt & EX_ARGOPT) { - while (ea.arg[0] == '+' && ea.arg[1] == '+') { - if (getargopt(&ea) == FAIL && !ni) { - errormsg = _(e_invarg); - goto doend; - } - } - } - - if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { - if (*ea.arg == '>') { // append - if (*++ea.arg != '>') { // typed wrong - errormsg = _("E494: Use w or w>>"); - goto doend; - } - ea.arg = skipwhite(ea.arg + 1); - ea.append = true; - } else if (*ea.arg == '!' && ea.cmdidx == CMD_write) { // :w !filter - ++ea.arg; - ea.usefilter = TRUE; - } - } - - if (ea.cmdidx == CMD_read) { - if (ea.forceit) { - ea.usefilter = TRUE; // :r! filter if ea.forceit - ea.forceit = FALSE; - } else if (*ea.arg == '!') { // :r !filter - ++ea.arg; - ea.usefilter = TRUE; - } - } - - if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { - ea.amount = 1; - while (*ea.arg == *ea.cmd) { // count number of '>' or '<' - ea.arg++; - ea.amount++; - } - ea.arg = skipwhite(ea.arg); - } - - /* - * Check for "+command" argument, before checking for next command. - * Don't do this for ":read !cmd" and ":write !cmd". - */ - if ((ea.argt & EX_CMDARG) && !ea.usefilter) { - ea.do_ecmd_cmd = getargcmd(&ea.arg); - } - - /* - * Check for '|' to separate commands and '"' to start comments. - * Don't do this for ":read !cmd" and ":write !cmd". - */ - if ((ea.argt & EX_TRLBAR) && !ea.usefilter) { - separate_nextcmd(&ea); - } else if (ea.cmdidx == CMD_bang - || ea.cmdidx == CMD_terminal - || ea.cmdidx == CMD_global - || ea.cmdidx == CMD_vglobal - || ea.usefilter) { - // Check for to end a shell command. - // Also do this for ":read !cmd", ":write !cmd" and ":global". - // Any others? - for (p = ea.arg; *p; p++) { - // Remove one backslash before a newline, so that it's possible to - // pass a newline to the shell and also a newline that is preceded - // with a backslash. This makes it impossible to end a shell - // command in a backslash, but that doesn't appear useful. - // Halving the number of backslashes is incompatible with previous - // versions. - if (*p == '\\' && p[1] == '\n') { - STRMOVE(p, p + 1); - } else if (*p == '\n') { - ea.nextcmd = p + 1; - *p = NUL; - break; - } - } - } - - if ((ea.argt & EX_DFLALL) && ea.addr_count == 0) { - set_cmd_dflall_range(&ea); - } - - // Parse register and count - parse_register(&ea); - if (parse_count(&ea, &errormsg, true) == FAIL) { - goto doend; - } - - /* - * Check for flags: 'l', 'p' and '#'. - */ - if (ea.argt & EX_FLAGS) { - get_flags(&ea); - } - if (!ni && !(ea.argt & EX_EXTRA) && *ea.arg != NUL - && *ea.arg != '"' && (*ea.arg != '|' || (ea.argt & EX_TRLBAR) == 0)) { - // no arguments allowed but there is something - errormsg = _(e_trailing); - goto doend; - } - - if (!ni && (ea.argt & EX_NEEDARG) && *ea.arg == NUL) { - errormsg = _(e_argreq); - goto doend; - } - - /* - * Skip the command when it's not going to be executed. - * The commands like :if, :endif, etc. always need to be executed. - * Also make an exception for commands that handle a trailing command - * themselves. - */ - if (ea.skip) { - switch (ea.cmdidx) { +static bool skip_cmd(const exarg_T *eap) +{ + // Skip the command when it's not going to be executed. + // The commands like :if, :endif, etc. always need to be executed. + // Also make an exception for commands that handle a trailing command + // themselves. + if (eap->skip) { + switch (eap->cmdidx) { // commands that need evaluation case CMD_while: case CMD_endwhile: @@ -2285,79 +1852,448 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter break; default: - goto doend; + return true; } } + return false; +} - if (ea.argt & EX_XFILE) { - if (expand_filename(&ea, cmdlinep, &errormsg) == FAIL) { - goto doend; - } +/// 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! +/// +/// @param cookie argument for fgetline() +static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter fgetline, + void *cookie) +{ + char *errormsg = NULL; // error message + const int save_reg_executing = reg_executing; + const bool save_pending_end_reg_executing = pending_end_reg_executing; + + exarg_T ea; + 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. + 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; } - /* - * Accept buffer name. Cannot be used at the same time with a buffer - * number. Don't do this for a user command. - */ - if ((ea.argt & EX_BUFNAME) && *ea.arg != NUL && ea.addr_count == 0 - && !IS_USER_CMDIDX(ea.cmdidx)) { - /* - * :bdelete, :bwipeout and :bunload take several arguments, separated - * by spaces: find next space (skipping over escaped characters). - * The others take one argument: ignore trailing spaces. - */ - if (ea.cmdidx == CMD_bdelete || ea.cmdidx == CMD_bwipeout - || ea.cmdidx == CMD_bunload) { - p = skiptowhite_esc(ea.arg); - } else { - p = ea.arg + STRLEN(ea.arg); - while (p > ea.arg && ascii_iswhite(p[-1])) { - p--; + // Reset browse, confirm, etc.. They are restored when returning, for + // recursive calls. + cmdmod_T save_cmdmod = cmdmod; + + // "#!anything" is handled like a comment. + if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { + goto doend; + } + + // 1. Skip comment lines and leading white space and colons. + // 2. Handle command modifiers. + + // The "ea" structure holds the arguments that can be used. + ea.cmd = *cmdlinep; + ea.cmdlinep = cmdlinep; + ea.getline = fgetline; + ea.cookie = cookie; + ea.cstack = cstack; + + if (parse_command_modifiers(&ea, &errormsg, &cmdmod, false) == FAIL) { + goto doend; + } + apply_cmdmod(&cmdmod); + + char *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. + char *cmd = ea.cmd; + ea.cmd = skip_range(ea.cmd, NULL); + if (*ea.cmd == '*') { + ea.cmd = skipwhite(ea.cmd + 1); + } + char *p = find_ex_command(&ea, NULL); + + profile_cmd(&ea, cstack, fgetline, cookie); + + // May go to debug mode. If this happens and the ">quit" debug command is + // used, throw an interrupt exception and skip the next command. + dbg_check_breakpoint(&ea); + if (!ea.skip && got_int) { + ea.skip = TRUE; + (void)do_intthrow(cstack); + } + + // 4. Parse a range specifier of the form: addr [,addr] [;addr] .. + // + // where 'addr' is: + // + // % (entire file) + // $ [+-NUM] + // 'x [+-NUM] (where x denotes a currently defined mark) + // . [+-NUM] + // [+-NUM].. + // NUM + // + // The ea.cmd pointer is updated to point to the first character following the + // range spec. If an initial address is found, but no second, the upper bound + // is equal to the lower. + set_cmd_addr_type(&ea, p); + + ea.cmd = cmd; + if (parse_cmd_address(&ea, &errormsg, false) == FAIL) { + goto doend; + } + + // 5. Parse the command. + + // Skip ':' and any white space + ea.cmd = skip_colon_white(ea.cmd, true); + + // If we got a line, but no command, then go to the line. + // If we find a '|' or '\n' we set ea.nextcmd. + if (*ea.cmd == NUL || *ea.cmd == '"' + || (ea.nextcmd = (char *)check_nextcmd((char_u *)ea.cmd)) != NULL) { + // strange vi behaviour: + // ":3" jumps to line 3 + // ":3|..." prints line 3 + // ":|" prints current line + if (ea.skip) { // skip this if inside :if + goto doend; + } + if (*ea.cmd == '|' || (exmode_active && ea.line1 != ea.line2)) { + ea.cmdidx = CMD_print; + ea.argt = EX_RANGE | EX_COUNT | EX_TRLBAR; + if ((errormsg = invalid_range(&ea)) == NULL) { + correct_range(&ea); + ex_print(&ea); + } + } else if (ea.addr_count != 0) { + if (ea.line2 > curbuf->b_ml.ml_line_count) { + ea.line2 = curbuf->b_ml.ml_line_count; + } + + if (ea.line2 < 0) { + errormsg = _(e_invrange); + } else { + if (ea.line2 == 0) { + curwin->w_cursor.lnum = 1; + } else { + curwin->w_cursor.lnum = ea.line2; + } + beginline(BL_SOL | BL_FIX); } } - ea.line2 = buflist_findpat(ea.arg, p, (ea.argt & EX_BUFUNL) != 0, - false, false); - if (ea.line2 < 0) { // failed - goto doend; - } - ea.addr_count = 1; - ea.arg = skipwhite(p); + goto doend; } - // 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 && cmdmod.cmod_did_esilent > 0) { - emsg_silent -= cmdmod.cmod_did_esilent; - if (emsg_silent < 0) { - emsg_silent = 0; + // If this looks like an undefined user command and there are CmdUndefined + // autocommands defined, trigger the matching autocommands. + if (p != NULL && ea.cmdidx == CMD_SIZE && !ea.skip + && ASCII_ISUPPER(*ea.cmd) + && has_event(EVENT_CMDUNDEFINED)) { + p = ea.cmd; + while (ASCII_ISALNUM(*p)) { + ++p; } - cmdmod.cmod_did_esilent = 0; + p = xstrnsave(ea.cmd, (size_t)(p - ea.cmd)); + int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL); + xfree(p); + // If the autocommands did something and didn't cause an error, try + // finding the command again. + p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd; + } + + if (p == NULL) { + if (!ea.skip) { + errormsg = _(e_ambiguous_use_of_user_defined_command); + } + goto doend; + } + // Check for wrong commands. + if (ea.cmdidx == CMD_SIZE) { + if (!ea.skip) { + STRCPY(IObuff, _(e_not_an_editor_command)); + // If the modifier was parsed OK the error must be in the following + // command + char *cmdname = after_modifier ? after_modifier : *cmdlinep; + if (!(flags & DOCMD_VERBOSE)) { + append_command(cmdname); + } + errormsg = (char *)IObuff; + did_emsg_syntax = true; + verify_command(cmdname); + } + goto doend; + } + + // set when Not Implemented + const int ni = is_cmd_ni(ea.cmdidx); + + // Forced commands. + ea.forceit = *p == '!' + && ea.cmdidx != CMD_substitute + && ea.cmdidx != CMD_smagic + && ea.cmdidx != CMD_snomagic; + if (ea.forceit) { + p++; + } + + // 6. Parse arguments. Then check for errors. + if (!IS_USER_CMDIDX(ea.cmdidx)) { + ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt; + } + + if (!ea.skip) { + if (sandbox != 0 && !(ea.argt & EX_SBOXOK)) { + // Command not allowed in sandbox. + errormsg = _(e_sandbox); + goto doend; + } + if (!MODIFIABLE(curbuf) && (ea.argt & EX_MODIFY) + // allow :put in terminals + && (!curbuf->terminal || ea.cmdidx != CMD_put)) { + // Command not allowed in non-'modifiable' buffer + errormsg = _(e_modifiable); + goto doend; + } + + if (!IS_USER_CMDIDX(ea.cmdidx)) { + if (cmdwin_type != 0 && !(ea.argt & EX_CMDWIN)) { + // Command not allowed in the command line window + errormsg = _(e_cmdwin); + goto doend; + } + if (text_locked() && !(ea.argt & EX_LOCK_OK)) { + // Command not allowed when text is locked + errormsg = _(get_text_locked_msg()); + goto doend; + } + } + + // Disallow editing another buffer when "curbuf->b_ro_locked" is set. + // Do allow ":checktime" (it is postponed). + // Do allow ":edit" (check for an argument later). + // Do allow ":file" with no arguments (check for an argument later). + if (!(ea.argt & EX_CMDWIN) + && ea.cmdidx != CMD_checktime + && ea.cmdidx != CMD_edit + && ea.cmdidx != CMD_file + && !IS_USER_CMDIDX(ea.cmdidx) + && curbuf_locked()) { + goto doend; + } + + if (!ni && !(ea.argt & EX_RANGE) && ea.addr_count > 0) { + // no range allowed + errormsg = _(e_norange); + goto doend; + } + } + + if (!ni && !(ea.argt & EX_BANG) && ea.forceit) { // no allowed + errormsg = _(e_nobang); + goto doend; + } + + // Don't complain about the range if it is not used + // (could happen if line_count is accidentally set to 0). + if (!ea.skip && !ni && (ea.argt & EX_RANGE)) { + // If the range is backwards, ask for confirmation and, if given, swap + // ea.line1 & ea.line2 so it's forwards again. + // When global command is busy, don't ask, will fail below. + if (!global_busy && ea.line1 > ea.line2) { + if (msg_silent == 0) { + if ((flags & DOCMD_VERBOSE) || exmode_active) { + errormsg = _("E493: Backwards range given"); + goto doend; + } + if (ask_yesno(_("Backwards range given, OK to swap"), false) != 'y') { + goto doend; + } + } + linenr_T lnum = ea.line1; + ea.line1 = ea.line2; + ea.line2 = lnum; + } + if ((errormsg = invalid_range(&ea)) != NULL) { + goto doend; + } + } + + if ((ea.addr_type == ADDR_OTHER) && ea.addr_count == 0) { + // default is 1, not cursor + ea.line2 = 1; + } + + correct_range(&ea); + + if (((ea.argt & EX_WHOLEFOLD) || ea.addr_count >= 2) && !global_busy + && ea.addr_type == ADDR_LINES) { + // Put the first line at the start of a closed fold, put the last line + // at the end of a closed fold. + (void)hasFolding(ea.line1, &ea.line1, NULL); + (void)hasFolding(ea.line2, NULL, &ea.line2); + } + + // For the ":make" and ":grep" commands we insert the 'makeprg'/'grepprg' + // option here, so things like % get expanded. + p = replace_makeprg(&ea, p, cmdlinep); + if (p == NULL) { + goto doend; + } + + // Skip to start of argument. + // Don't do this for the ":!" command, because ":!! -l" needs the space. + ea.arg = ea.cmdidx == CMD_bang ? p : skipwhite(p); + + // ":file" cannot be run with an argument when "curbuf->b_ro_locked" is set + if (ea.cmdidx == CMD_file && *ea.arg != NUL && curbuf_locked()) { + goto doend; + } + + // Check for "++opt=val" argument. + // Must be first, allow ":w ++enc=utf8 !cmd" + if (ea.argt & EX_ARGOPT) { + while (ea.arg[0] == '+' && ea.arg[1] == '+') { + if (getargopt(&ea) == FAIL && !ni) { + errormsg = _(e_invarg); + goto doend; + } + } + } + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { + if (*ea.arg == '>') { // append + if (*++ea.arg != '>') { // typed wrong + errormsg = _("E494: Use w or w>>"); + goto doend; + } + ea.arg = skipwhite(ea.arg + 1); + ea.append = true; + } else if (*ea.arg == '!' && ea.cmdidx == CMD_write) { // :w !filter + ++ea.arg; + ea.usefilter = TRUE; + } + } else if (ea.cmdidx == CMD_read) { + if (ea.forceit) { + ea.usefilter = TRUE; // :r! filter if ea.forceit + ea.forceit = FALSE; + } else if (*ea.arg == '!') { // :r !filter + ++ea.arg; + ea.usefilter = TRUE; + } + } else if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { + ea.amount = 1; + while (*ea.arg == *ea.cmd) { // count number of '>' or '<' + ea.arg++; + ea.amount++; + } + ea.arg = skipwhite(ea.arg); + } + + // Check for "+command" argument, before checking for next command. + // Don't do this for ":read !cmd" and ":write !cmd". + if ((ea.argt & EX_CMDARG) && !ea.usefilter) { + ea.do_ecmd_cmd = getargcmd(&ea.arg); + } + + // Check for '|' to separate commands and '"' to start comments. + // Don't do this for ":read !cmd" and ":write !cmd". + if ((ea.argt & EX_TRLBAR) && !ea.usefilter) { + separate_nextcmd(&ea); + } else if (ea.cmdidx == CMD_bang + || ea.cmdidx == CMD_terminal + || ea.cmdidx == CMD_global + || ea.cmdidx == CMD_vglobal + || ea.usefilter) { + // Check for to end a shell command. + // Also do this for ":read !cmd", ":write !cmd" and ":global". + // Any others? + for (char *s = ea.arg; *s; s++) { + // Remove one backslash before a newline, so that it's possible to + // pass a newline to the shell and also a newline that is preceded + // with a backslash. This makes it impossible to end a shell + // command in a backslash, but that doesn't appear useful. + // Halving the number of backslashes is incompatible with previous + // versions. + if (*s == '\\' && s[1] == '\n') { + STRMOVE(s, s + 1); + } else if (*s == '\n') { + ea.nextcmd = s + 1; + *s = NUL; + break; + } + } + } + + if ((ea.argt & EX_DFLALL) && ea.addr_count == 0) { + set_cmd_dflall_range(&ea); + } + + // Parse register and count + parse_register(&ea); + if (parse_count(&ea, &errormsg, true) == FAIL) { + goto doend; + } + + // Check for flags: 'l', 'p' and '#'. + if (ea.argt & EX_FLAGS) { + get_flags(&ea); + } + if (!ni && !(ea.argt & EX_EXTRA) && *ea.arg != NUL + && *ea.arg != '"' && (*ea.arg != '|' || (ea.argt & EX_TRLBAR) == 0)) { + // no arguments allowed but there is something + errormsg = _(e_trailing); + goto doend; + } + + if (!ni && (ea.argt & EX_NEEDARG) && *ea.arg == NUL) { + errormsg = _(e_argreq); + goto doend; + } + + if (skip_cmd(&ea)) { + goto doend; } // 7. Execute the command. - if (IS_USER_CMDIDX(ea.cmdidx)) { - /* - * Execute a user-defined command. - */ - do_ucmd(&ea, false); - } else { - /* - * Call the function to execute the command. - */ - ea.errmsg = NULL; - (cmdnames[ea.cmdidx].cmd_func)(&ea); - if (ea.errmsg != NULL) { - errormsg = _(ea.errmsg); - } + int retv = 0; + if (execute_cmd0(&retv, &ea, &errormsg, false) == FAIL) { + goto doend; } - /* - * If the command just executed called do_cmdline(), any throw or ":return" - * or ":finish" encountered there must also check the cstack of the still - * active do_cmdline() that called this do_one_cmd(). Rethrow an uncaught - * exception, or reanimate a returned function or finished script file and - * return or finish it again. - */ + // If the command just executed called do_cmdline(), any throw or ":return" + // or ":finish" encountered there must also check the cstack of the still + // active do_cmdline() that called this do_one_cmd(). Rethrow an uncaught + // exception, or reanimate a returned function or finished script file and + // return or finish it again. if (need_rethrow) { do_throw(cstack); } else if (check_cstack) { @@ -2383,7 +2319,7 @@ doend: STRCPY(IObuff, errormsg); errormsg = (char *)IObuff; } - append_command(*cmdlinep); + append_command(*ea.cmdlinep); } emsg(errormsg); }