diff options
Diffstat (limited to 'src/nvim/ex_docmd.c')
| -rw-r--r-- | src/nvim/ex_docmd.c | 1193 |
1 files changed, 654 insertions, 539 deletions
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c2e7b96a53..5d271e4ef6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4,58 +4,57 @@ // ex_docmd.c: functions for executing an Ex command line. #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include "auto/config.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" -#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/debugger.h" -#include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" -#include "nvim/eval/vars.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" +#include "nvim/event/loop.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/ex_session.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/hardcopy.h" -#include "nvim/help.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/if_cscope.h" #include "nvim/input.h" #include "nvim/keycodes.h" -#include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mapping.h" #include "nvim/mark.h" -#include "nvim/match.h" #include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" @@ -65,28 +64,25 @@ #include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" +#include "nvim/os/shell.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/shada.h" -#include "nvim/sign.h" -#include "nvim/spell.h" -#include "nvim/spellfile.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/tag.h" -#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" -#include "nvim/undo_defs.h" #include "nvim/usercmd.h" -#include "nvim/version.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -94,6 +90,12 @@ static char e_ambiguous_use_of_user_defined_command[] = N_("E464: Ambiguous use of user-defined command"); static char e_not_an_editor_command[] = N_("E492: Not an editor command"); +static char e_no_source_file_name_to_substitute_for_sfile[] + = N_("E498: no :source file name to substitute for \"<sfile>\""); +static char e_no_call_stack_to_substitute_for_stack[] + = N_("E489: no call stack to substitute for \"<stack>\""); +static char e_no_script_file_name_to_substitute_for_script[] + = N_("E1274: No script file name to substitute for \"<script>\""); static int quitmore = 0; static bool ex_pressedreturn = false; @@ -127,6 +129,7 @@ struct dbg_stuff { char *vv_throwpoint; int did_emsg; int got_int; + bool did_throw; int need_rethrow; int check_cstack; except_T *current_exception; @@ -158,6 +161,7 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) // Necessary for debugging an inactive ":catch", ":finally", ":endtry". dsp->did_emsg = did_emsg; did_emsg = false; dsp->got_int = got_int; got_int = false; + dsp->did_throw = did_throw; did_throw = false; dsp->need_rethrow = need_rethrow; need_rethrow = false; dsp->check_cstack = check_cstack; check_cstack = false; dsp->current_exception = current_exception; current_exception = NULL; @@ -173,6 +177,7 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) (void)v_throwpoint(dsp->vv_throwpoint); did_emsg = dsp->did_emsg; got_int = dsp->got_int; + did_throw = dsp->did_throw; need_rethrow = dsp->need_rethrow; check_cstack = dsp->check_cstack; current_exception = dsp->current_exception; @@ -243,8 +248,8 @@ void do_exmode(void) RedrawingDisabled--; no_wait_return--; - redraw_all_later(NOT_VALID); - update_screen(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); + update_screen(); need_wait_return = false; msg_scroll = save_msg_scroll; } @@ -364,7 +369,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Get the function or script name and the address where the next breakpoint // line and the debug tick for a function or script are stored. if (getline_is_func) { - fname = (char *)func_name(real_cookie); + fname = func_name(real_cookie); breakpoint = func_breakpoint(real_cookie); dbg_tick = func_dbg_tick(real_cookie); } else if (getline_equal(fgetline, cookie, getsourceline)) { @@ -390,7 +395,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) initial_trylevel = trylevel; - current_exception = NULL; + // "did_throw" will be set to true when an exception is being thrown. + did_throw = false; // "did_emsg" will be set to true when emsg() is used, in which case we // cancel the whole command line, and any if/endif or loop. // If force_abort is set, we cancel everything. @@ -455,7 +461,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (breakpoint != NULL && dbg_tick != NULL && *dbg_tick != debug_tick) { *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, SOURCING_LNUM); + fname, SOURCING_LNUM); *dbg_tick = debug_tick; } @@ -464,10 +470,10 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Did we encounter a breakpoint? if (breakpoint != NULL && *breakpoint != 0 && *breakpoint <= SOURCING_LNUM) { - dbg_breakpoint((char_u *)fname, SOURCING_LNUM); + dbg_breakpoint(fname, SOURCING_LNUM); // Find next breakpoint. *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, SOURCING_LNUM); + fname, SOURCING_LNUM); *dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -509,7 +515,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2, true)) == NULL) { - // Don't call wait_return for aborted command line. The 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)) { @@ -524,7 +530,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (flags & DOCMD_KEEPLINE) { xfree(repeat_cmdline); if (count == 0) { - repeat_cmdline = vim_strsave((char_u *)next_cmdline); + repeat_cmdline = xstrdup(next_cmdline); } else { repeat_cmdline = NULL; } @@ -618,7 +624,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // not to use a cs_line[] from an entry that isn't a ":while" // or ":for": It would make "current_line" invalid and can // cause a crash. - if (!did_emsg && !got_int && !current_exception + if (!did_emsg && !got_int && !did_throw && cstack.cs_idx >= 0 && (cstack.cs_flags[cstack.cs_idx] & (CSF_WHILE | CSF_FOR)) @@ -631,9 +637,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Check for the next breakpoint at or after the ":while" // or ":for". - if (breakpoint != NULL) { - *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, + if (breakpoint != NULL && lines_ga.ga_len > current_line) { + *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), fname, ((wcmd_T *)lines_ga.ga_data)[current_line].lnum - 1); *dbg_tick = debug_tick; } @@ -660,7 +665,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) current_line = 0; } - // A ":finally" makes did_emsg, got_int and current_exception pending for + // A ":finally" makes did_emsg, got_int and did_throw pending for // being restored at the ":endtry". Reset them here and set the // ACTIVE and FINALLY flags, so that the finally clause gets executed. // This includes the case where a missing ":endif", ":endwhile" or @@ -669,9 +674,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) cstack.cs_lflags &= ~CSL_HAD_FINA; report_make_pending((cstack.cs_pending[cstack.cs_idx] & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), - current_exception); - did_emsg = got_int = false; - current_exception = NULL; + did_throw ? current_exception : NULL); + did_emsg = got_int = did_throw = false; cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; } @@ -684,7 +688,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // exception, cancel everything. If it is left normally, reset // force_abort to get the non-EH compatible abortion behavior for // the rest of the script. - if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { + if (trylevel == 0 && !did_emsg && !got_int && !did_throw) { force_abort = false; } @@ -698,7 +702,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // - didn't get an error message or lines are not typed // - there is a command after '|', inside a :if, :while, :for or :try, or // looping for ":source" command or function call. - } while (!((got_int || (did_emsg && force_abort) || current_exception) + } while (!((got_int || (did_emsg && force_abort) || did_throw) && cstack.cs_trylevel == 0) && !(did_emsg // Keep going when inside try/catch, so that the error can be @@ -718,7 +722,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (cstack.cs_idx >= 0) { // If a sourced file or executed function ran to its end, report the // unclosed conditional. - if (!got_int && !current_exception + if (!got_int && !did_throw && !aborting() && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -761,52 +765,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // conditional, discard the uncaught exception, disable the conversion // of interrupts or errors to exceptions, and ensure that no more // commands are executed. - if (current_exception) { - char *p = NULL; - msglist_T *messages = NULL; - msglist_T *next; - - // If the uncaught exception is a user exception, report it as an - // error. If it is an error exception, display the saved error - // message now. For an interrupt exception, do nothing; the - // interrupt message is given elsewhere. - switch (current_exception->type) { - case ET_USER: - vim_snprintf((char *)IObuff, IOSIZE, - _("E605: Exception not caught: %s"), - current_exception->value); - p = (char *)vim_strsave(IObuff); - break; - case ET_ERROR: - messages = current_exception->messages; - current_exception->messages = NULL; - break; - case ET_INTERRUPT: - break; - } - - estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); - current_exception->throw_name = NULL; - - discard_current_exception(); // uses IObuff if 'verbose' - suppress_errthrow = true; - force_abort = true; - msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 - - if (messages != NULL) { - do { - next = messages->next; - emsg(messages->msg); - xfree(messages->msg); - xfree(messages); - messages = next; - } while (messages != NULL); - } else if (p != NULL) { - emsg(p); - xfree(p); - } - xfree(SOURCING_NAME); - estack_pop(); + if (did_throw) { + handle_did_throw(); } else if (got_int || (did_emsg && force_abort)) { // On an interrupt or an aborting error not converted to an exception, // disable the conversion of errors to exceptions. (Interrupts are not @@ -823,14 +783,14 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // cstack belongs to the same function or, respectively, script file, it // will have to be checked for finally clauses to be executed due to the // ":return" or ":finish". This is done in do_one_cmd(). - if (current_exception) { + if (did_throw) { need_rethrow = true; } if ((getline_equal(fgetline, cookie, getsourceline) && ex_nesting_level > source_level(real_cookie)) || (getline_equal(fgetline, cookie, get_func_line) && ex_nesting_level > func_level(real_cookie) + 1)) { - if (!current_exception) { + if (!did_throw) { check_cstack = true; } } else { @@ -844,8 +804,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) || getline_equal(fgetline, cookie, get_func_line)) && ex_nesting_level + 1 <= debug_break_level) { do_debug(getline_equal(fgetline, cookie, getsourceline) - ? (char_u *)_("End of sourced file") - : (char_u *)_("End of function")); + ? _("End of sourced file") + : _("End of function")); } } @@ -881,7 +841,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) need_wait_return = false; msg_didany = false; // don't wait when restarting edit } else if (need_wait_return) { - // The msg_start() above clears msg_didout. The wait_return we do + // The msg_start() above clears msg_didout. The wait_return() we do // here should not overwrite the command that may be shown before // doing that. msg_didout |= msg_didout_before_start; @@ -896,6 +856,57 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) return retval; } +/// Handle when "did_throw" is set after executing commands. +void handle_did_throw(void) +{ + assert(current_exception != NULL); + char *p = NULL; + msglist_T *messages = NULL; + + // If the uncaught exception is a user exception, report it as an + // error. If it is an error exception, display the saved error + // message now. For an interrupt exception, do nothing; the + // interrupt message is given elsewhere. + switch (current_exception->type) { + case ET_USER: + vim_snprintf(IObuff, IOSIZE, + _("E605: Exception not caught: %s"), + current_exception->value); + p = xstrdup(IObuff); + break; + case ET_ERROR: + messages = current_exception->messages; + current_exception->messages = NULL; + break; + case ET_INTERRUPT: + break; + } + + estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); + current_exception->throw_name = NULL; + + discard_current_exception(); // uses IObuff if 'verbose' + suppress_errthrow = true; + force_abort = true; + msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 + + if (messages != NULL) { + do { + msglist_T *next = messages->next; + emsg(messages->msg); + xfree(messages->msg); + xfree(messages->sfile); + xfree(messages); + messages = next; + } while (messages != NULL); + } else if (p != NULL) { + emsg(p); + xfree(p); + } + xfree(SOURCING_NAME); + estack_pop(); +} + /// Obtain a line when inside a ":while" or ":for" loop. static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) { @@ -908,7 +919,7 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) char *line; // First time inside the ":while"/":for": get line normally. if (cp->getline == NULL) { - line = (char *)getcmdline(c, 0L, indent, do_concat); + line = getcmdline(c, 0L, indent, do_concat); } else { line = cp->getline(c, cp->cookie, indent, do_concat); } @@ -1050,7 +1061,7 @@ static int current_tab_nr(tabpage_T *tab) #define LAST_TAB_NR current_tab_nr(NULL) /// Figure out the address type for ":wincmd". -static void get_wincmd_addr_type(char *arg, exarg_T *eap) +static void get_wincmd_addr_type(const char *arg, exarg_T *eap) { switch (*arg) { case 'S': @@ -1290,9 +1301,9 @@ static void parse_register(exarg_T *eap) // for '=' register: accept the rest of the line as an expression if (eap->arg[-1] == '=' && eap->arg[0] != NUL) { if (!eap->skip) { - set_expr_line(vim_strsave((char_u *)eap->arg)); + set_expr_line(xstrdup(eap->arg)); } - eap->arg += STRLEN(eap->arg); + eap->arg += strlen(eap->arg); } eap->arg = skipwhite(eap->arg); } @@ -1329,6 +1340,20 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate) || ascii_iswhite(*p))) { long n = getdigits_long(&eap->arg, false, -1); eap->arg = skipwhite(eap->arg); + + if (eap->args != NULL) { + assert(eap->argc > 0 && eap->arg >= eap->args[0]); + // If eap->arg is still pointing to the first argument, just make eap->args[0] point to the + // same location. This is needed for usecases like vim.cmd.sleep('10m'). If eap->arg is + // pointing outside the first argument, shift arguments by 1. + if (eap->arg < eap->args[0] + eap->arglens[0]) { + eap->arglens[0] -= (size_t)(eap->arg - eap->args[0]); + eap->args[0] = eap->arg; + } else { + shift_cmd_args(eap); + } + } + if (n <= 0 && (eap->argt & EX_ZEROR) == 0) { if (errormsg != NULL) { *errormsg = _(e_zerocount); @@ -1361,6 +1386,13 @@ bool is_cmd_ni(cmdidx_T cmdidx) bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg) { char *after_modifier = NULL; + bool retval = false; + // parsing the command modifiers may set ex_pressedreturn + const bool save_ex_pressedreturn = ex_pressedreturn; + // parsing the command range may require moving the cursor + const pos_T save_cursor = curwin->w_cursor; + // parsing the command range may set the last search pattern + save_last_search_pattern(); // Initialize cmdinfo CLEAR_POINTER(cmdinfo); @@ -1375,13 +1407,10 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er .cookie = NULL, }; - const bool save_ex_pressedreturn = ex_pressedreturn; // Parse command modifiers if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) { - ex_pressedreturn = save_ex_pressedreturn; - goto err; + goto end; } - ex_pressedreturn = save_ex_pressedreturn; after_modifier = eap->cmd; // Save location after command modifiers @@ -1394,21 +1423,21 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er char *p = find_ex_command(eap, NULL); if (p == NULL) { *errormsg = _(e_ambiguous_use_of_user_defined_command); - goto err; + goto end; } // Set command address type and parse command range set_cmd_addr_type(eap, p); eap->cmd = cmd; - if (parse_cmd_address(eap, errormsg, false) == FAIL) { - goto err; + if (parse_cmd_address(eap, errormsg, true) == FAIL) { + goto end; } // Skip colon and whitespace eap->cmd = skip_colon_white(eap->cmd, true); // Fail if command is a comment or if command doesn't exist if (*eap->cmd == NUL || *eap->cmd == '"') { - goto err; + goto end; } // Fail if command is invalid if (eap->cmdidx == CMD_SIZE) { @@ -1416,8 +1445,8 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // If the modifier was parsed OK the error must be in the following command char *cmdname = after_modifier ? after_modifier : cmdline; append_command(cmdname); - *errormsg = (char *)IObuff; - goto err; + *errormsg = IObuff; + goto end; } // Correctly set 'forceit' for commands @@ -1456,12 +1485,12 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Fail if command doesn't support bang but is used with a bang if (!(eap->argt & EX_BANG) && eap->forceit) { *errormsg = _(e_nobang); - goto err; + goto end; } // Fail if command doesn't support a range but it is given a range if (!(eap->argt & EX_RANGE) && eap->addr_count > 0) { *errormsg = _(e_norange); - goto err; + goto end; } // Set default range for command if required if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) { @@ -1471,7 +1500,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Parse register and count parse_register(eap); if (parse_count(eap, errormsg, false) == FAIL) { - goto err; + goto end; } // Remove leading whitespace and colon from next command @@ -1487,10 +1516,39 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er cmdinfo->magic.bar = true; } - return true; -err: - undo_cmdmod(&cmdinfo->cmdmod); - return false; + retval = true; +end: + if (!retval) { + undo_cmdmod(&cmdinfo->cmdmod); + } + ex_pressedreturn = save_ex_pressedreturn; + curwin->w_cursor = save_cursor; + restore_last_search_pattern(); + return retval; +} + +// Shift Ex-command arguments to the right. +static void shift_cmd_args(exarg_T *eap) +{ + assert(eap->args != NULL && eap->argc > 0); + + char **oldargs = eap->args; + size_t *oldarglens = eap->arglens; + + eap->argc--; + eap->args = eap->argc > 0 ? xcalloc(eap->argc, sizeof(char *)) : NULL; + eap->arglens = eap->argc > 0 ? xcalloc(eap->argc, sizeof(size_t)) : NULL; + + for (size_t i = 0; i < eap->argc; i++) { + eap->args[i] = oldargs[i + 1]; + eap->arglens[i] = oldarglens[i + 1]; + } + + // If there are no arguments, make eap->arg point to the end of string. + eap->arg = (eap->argc > 0 ? eap->args[0] : (oldargs[0] + oldarglens[0])); + + xfree(oldargs); + xfree(oldarglens); } static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) @@ -1517,7 +1575,7 @@ static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) || eap->cmdidx == CMD_bunload) { p = skiptowhite_esc(eap->arg); } else { - p = eap->arg + STRLEN(eap->arg); + p = eap->arg + strlen(eap->arg); while (p > eap->arg && ascii_iswhite(p[-1])) { p--; } @@ -1532,15 +1590,7 @@ static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) 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]; + shift_cmd_args(eap); } if (eap->line2 < 0) { // failed return FAIL; @@ -1589,6 +1639,7 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) char *errormsg = NULL; int retv = 0; +#undef ERROR #define ERROR(msg) \ do { \ errormsg = msg; \ @@ -1639,6 +1690,11 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) (void)hasFolding(eap->line2, NULL, &eap->line2); } + // Use first argument as count when possible + if (parse_count(eap, &errormsg, true) == FAIL) { + goto end; + } + // Execute the command execute_cmd0(&retv, eap, &errormsg, preview); @@ -1660,7 +1716,7 @@ static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetlin && (!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; + bool skip = did_emsg || got_int || did_throw; if (eap->cmdidx == CMD_catch) { skip = !skip && !(cstack->cs_idx >= 0 @@ -1738,6 +1794,7 @@ static bool skip_cmd(const exarg_T *eap) case CMD_filter: case CMD_help: case CMD_hide: + case CMD_horizontal: case CMD_ijump: case CMD_ilist: case CMD_isearch: @@ -1775,6 +1832,7 @@ static bool skip_cmd(const exarg_T *eap) case CMD_throw: case CMD_tilde: case CMD_topleft: + case CMD_trust: case CMD_unlet: case CMD_unlockvar: case CMD_verbose: @@ -1857,7 +1915,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ea.skip = (did_emsg || got_int - || current_exception + || did_throw || (cstack->cs_idx >= 0 && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); @@ -1873,9 +1931,11 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter 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 (!exiting) { + // 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); @@ -1910,7 +1970,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // 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) { + || (ea.nextcmd = check_nextcmd(ea.cmd)) != NULL) { // strange vi behaviour: // ":3" jumps to line 3 // ":3|..." prints line 3 @@ -1978,7 +2038,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter if (!(flags & DOCMD_VERBOSE)) { append_command(cmdname); } - errormsg = (char *)IObuff; + errormsg = IObuff; did_emsg_syntax = true; verify_command(cmdname); } @@ -2247,9 +2307,9 @@ doend: if (errormsg != NULL && *errormsg != NUL && !did_emsg) { if (flags & DOCMD_VERBOSE) { - if (errormsg != (char *)IObuff) { + if (errormsg != IObuff) { STRCPY(IObuff, errormsg); - errormsg = (char *)IObuff; + errormsg = IObuff; } append_command(*ea.cmdlinep); } @@ -2419,8 +2479,12 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool continue; } - // ":hide" and ":hide | cmd" are not modifiers case 'h': + if (checkforcmd(&eap->cmd, "horizontal", 3)) { + cmod->cmod_split |= WSP_HOR; + continue; + } + // ":hide" and ":hide | cmd" are not modifiers if (p != eap->cmd || !checkforcmd(&p, "hide", 3) || *p == NUL || ends_excmd(*p)) { break; @@ -2565,7 +2629,7 @@ static void apply_cmdmod(cmdmod_T *cmod) if ((cmod->cmod_flags & CMOD_NOAUTOCMD) && cmod->cmod_save_ei == NULL) { // Set 'eventignore' to "all". // First save the existing option value for restoring it later. - cmod->cmod_save_ei = (char *)vim_strsave(p_ei); + cmod->cmod_save_ei = xstrdup(p_ei); set_string_option_direct("ei", -1, "all", OPT_FREE, SID_NONE); } } @@ -2587,7 +2651,7 @@ void undo_cmdmod(cmdmod_T *cmod) if (cmod->cmod_save_ei != NULL) { // Restore 'eventignore' to the value before ":noautocmd". set_string_option_direct("ei", -1, cmod->cmod_save_ei, OPT_FREE, SID_NONE); - free_string_option((char_u *)cmod->cmod_save_ei); + free_string_option(cmod->cmod_save_ei); cmod->cmod_save_ei = NULL; } @@ -2779,7 +2843,7 @@ theend: /// @param pp start of command /// @param cmd name of command /// @param len required length -bool checkforcmd(char **pp, char *cmd, int len) +bool checkforcmd(char **pp, const char *cmd, int len) { int i; @@ -2788,7 +2852,7 @@ bool checkforcmd(char **pp, char *cmd, int len) break; } } - if (i >= len && !isalpha((*pp)[i])) { + if (i >= len && !ASCII_ISALPHA((*pp)[i])) { *pp = skipwhite(*pp + i); return true; } @@ -2800,24 +2864,24 @@ bool checkforcmd(char **pp, char *cmd, int len) /// invisible otherwise. static void append_command(char *cmd) { - size_t len = STRLEN(IObuff); + size_t len = strlen(IObuff); char *s = cmd; char *d; if (len > IOSIZE - 100) { // Not enough space, truncate and put in "...". - d = (char *)IObuff + IOSIZE - 100; - d -= utf_head_off(IObuff, (const char_u *)d); + d = IObuff + IOSIZE - 100; + d -= utf_head_off(IObuff, d); STRCPY(d, "..."); } STRCAT(IObuff, ": "); - d = (char *)IObuff + STRLEN(IObuff); - while (*s != NUL && (char_u *)d - IObuff + 5 < IOSIZE) { - if ((char_u)s[0] == 0xc2 && (char_u)s[1] == 0xa0) { + d = IObuff + strlen(IObuff); + while (*s != NUL && d - IObuff + 5 < IOSIZE) { + if ((uint8_t)s[0] == 0xc2 && (uint8_t)s[1] == 0xa0) { s += 2; STRCPY(d, "<a0>"); d += 4; - } else if ((char_u *)d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) { + } else if (d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) { break; } else { mb_copy_char((const char **)&s, &d); @@ -2826,6 +2890,33 @@ static void append_command(char *cmd) *d = NUL; } +/// Return true and set "*idx" if "p" points to a one letter command. +/// - The 'k' command can directly be followed by any character. +/// - The 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' +/// but :sre[wind] is another command, as are :scr[iptnames], +/// :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. +static int one_letter_cmd(const char *p, cmdidx_T *idx) +{ + if (*p == 'k') { + *idx = CMD_k; + return true; + } + if (p[0] == 's' + && ((p[1] == 'c' + && (p[2] == NUL + || (p[2] != 's' && p[2] != 'r' + && (p[3] == NUL + || (p[3] != 'i' && p[4] != 'p'))))) + || p[1] == 'g' + || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') + || p[1] == 'I' + || (p[1] == 'r' && p[2] != 'e'))) { + *idx = CMD_substitute; + return true; + } + return false; +} + /// Find an Ex command by its name, either built-in or user. /// Start of the name can be found at eap->cmd. /// Sets eap->cmdidx and returns a pointer to char after the command name. @@ -2836,27 +2927,8 @@ char *find_ex_command(exarg_T *eap, int *full) FUNC_ATTR_NONNULL_ARG(1) { // Isolate the command and search for it in the command table. - // Exceptions: - // - the 'k' command can directly be followed by any character. - // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - // but :sre[wind] is another command, as are :scr[iptnames], - // :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. - // - the "d" command can directly be followed by 'l' or 'p' flag. char *p = eap->cmd; - if (*p == 'k') { - eap->cmdidx = CMD_k; - p++; - } else if (p[0] == 's' - && ((p[1] == 'c' - && (p[2] == NUL - || (p[2] != 's' && p[2] != 'r' - && (p[3] == NUL - || (p[3] != 'i' && p[4] != 'p'))))) - || p[1] == 'g' - || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') - || p[1] == 'I' - || (p[1] == 'r' && p[2] != 'e'))) { - eap->cmdidx = CMD_substitute; + if (one_letter_cmd(p, &eap->cmdidx)) { p++; } else { while (ASCII_ISALPHA(*p)) { @@ -2870,10 +2942,11 @@ char *find_ex_command(exarg_T *eap, int *full) } // check for non-alpha command - if (p == eap->cmd && vim_strchr("@!=><&~#", *p) != NULL) { + if (p == eap->cmd && vim_strchr("@!=><&~#", (uint8_t)(*p)) != NULL) { p++; } int len = (int)(p - eap->cmd); + // The "d" command can directly be followed by 'l' or 'p' flag. if (*eap->cmd == 'd' && (p[-1] == 'l' || p[-1] == 'p')) { // Check for ":dl", ":dell", etc. to ":deletel": that's // :delete with the 'l' flag. Same for 'p'. @@ -2894,7 +2967,7 @@ char *find_ex_command(exarg_T *eap, int *full) } if (ASCII_ISLOWER(eap->cmd[0])) { - const int c1 = (char_u)eap->cmd[0]; + const int c1 = (uint8_t)eap->cmd[0]; const int c2 = len == 1 ? NUL : eap->cmd[1]; if (command_count != CMD_SIZE) { @@ -2917,7 +2990,7 @@ char *find_ex_command(exarg_T *eap, int *full) for (; (int)eap->cmdidx < CMD_SIZE; eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) { - if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd, + if (strncmp(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd, (size_t)len) == 0) { if (full != NULL && cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) { @@ -3038,7 +3111,7 @@ int cmd_exists(const char *const name) } /// "fullcommand" function -void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *name = argvars[0].vval.v_string; @@ -3062,19 +3135,20 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - rettv->vval.v_string = (char *)vim_strsave(IS_USER_CMDIDX(ea.cmdidx) - ? (char_u *)get_user_command_name(ea.useridx, - ea.cmdidx) - : (char_u *)cmdnames[ea.cmdidx].cmd_name); + rettv->vval.v_string = xstrdup(IS_USER_CMDIDX(ea.cmdidx) + ? get_user_command_name(ea.useridx, ea.cmdidx) + : cmdnames[ea.cmdidx].cmd_name); } cmdidx_T excmd_get_cmdidx(const char *cmd, size_t len) { cmdidx_T idx; - for (idx = (cmdidx_T)0; (int)idx < (int)CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) { - if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) { - break; + if (!one_letter_cmd(cmd, &idx)) { + for (idx = (cmdidx_T)0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) { + if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) { + break; + } } } @@ -3098,7 +3172,7 @@ uint32_t excmd_get_argt(cmdidx_T idx) /// @return the "cmd" pointer advanced to beyond the range. char *skip_range(const char *cmd, int *ctx) { - while (vim_strchr(" \t0123456789.$%'/?-+,;\\", *cmd) != NULL) { + while (vim_strchr(" \t0123456789.$%'/?-+,;\\", (uint8_t)(*cmd)) != NULL) { if (*cmd == '\\') { if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') { cmd++; @@ -3287,14 +3361,14 @@ static linenr_T get_ |