Files
neovim/src/nvim/ex_docmd.c
Gregory Anders cffdf102d4 feat(terminal): allow :terminal to take modifiers (#15427)
The following modifiers are all now supported:

    :tab term
    :vertical term
    :horizontal term
    :botright term
    :topleft term

Fixes: https://github.com/neovim/neovim/issues/11385
2023-08-28 07:22:19 -05:00

7651 lines
218 KiB
C

// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// 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/cursor.h"
#include "nvim/debugger.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/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/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/input.h"
#include "nvim/keycodes.h"
#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/os.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/search.h"
#include "nvim/shada.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
#include "nvim/vim.h"
#include "nvim/window.h"
static const char e_ambiguous_use_of_user_defined_command[]
= N_("E464: Ambiguous use of user-defined command");
static const char e_no_call_stack_to_substitute_for_stack[]
= N_("E489: No call stack to substitute for \"<stack>\"");
static const char e_not_an_editor_command[]
= N_("E492: Not an editor command");
static const char e_no_autocommand_file_name_to_substitute_for_afile[]
= N_("E495: No autocommand file name to substitute for \"<afile>\"");
static const char e_no_autocommand_buffer_number_to_substitute_for_abuf[]
= N_("E496: No autocommand buffer number to substitute for \"<abuf>\"");
static const char e_no_autocommand_match_name_to_substitute_for_amatch[]
= N_("E497: No autocommand match name to substitute for \"<amatch>\"");
static const char e_no_source_file_name_to_substitute_for_sfile[]
= N_("E498: No :source file name to substitute for \"<sfile>\"");
static const char e_no_line_number_to_use_for_slnum[]
= N_("E842: No line number to use for \"<slnum>\"");
static const char e_no_line_number_to_use_for_sflnum[]
= N_("E961: No line number to use for \"<sflnum>\"");
static const 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;
// Struct for storing a line inside a while/for loop
typedef struct {
char *line; // command line
linenr_T lnum; // sourcing_lnum of the line
} wcmd_T;
#define FREE_WCMD(wcmd) xfree((wcmd)->line)
/// Structure used to store info for line position in a while or for loop.
/// This is required, because do_one_cmd() may invoke ex_function(), which
/// 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 *(*getline)(int, void *, int, bool);
void *cookie;
};
// Struct to save a few things while debugging. Used in do_cmdline() only.
struct dbg_stuff {
int trylevel;
int force_abort;
except_T *caught_stack;
char *vv_exception;
char *vv_throwpoint;
int did_emsg;
int got_int;
bool did_throw;
int need_rethrow;
int check_cstack;
except_T *current_exception;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.c.generated.h"
#endif
#ifndef HAVE_WORKING_LIBINTL
# define ex_language ex_ni
#endif
// Declare cmdnames[].
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds_defs.generated.h"
#endif
static char dollar_command[2] = { '$', 0 };
static void save_dbg_stuff(struct dbg_stuff *dsp)
{
dsp->trylevel = trylevel; trylevel = 0;
dsp->force_abort = force_abort; force_abort = false;
dsp->caught_stack = caught_stack; caught_stack = NULL;
dsp->vv_exception = v_exception(NULL);
dsp->vv_throwpoint = v_throwpoint(NULL);
// 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;
}
static void restore_dbg_stuff(struct dbg_stuff *dsp)
{
suppress_errthrow = false;
trylevel = dsp->trylevel;
force_abort = dsp->force_abort;
caught_stack = dsp->caught_stack;
(void)v_exception(dsp->vv_exception);
(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;
}
/// Repeatedly get commands for Ex mode, until the ":vi" command is given.
void do_exmode(void)
{
exmode_active = true;
State = MODE_NORMAL;
may_trigger_modechanged();
// When using ":global /pat/ visual" and then "Q" we return to continue
// the :global command.
if (global_busy) {
return;
}
int save_msg_scroll = msg_scroll;
RedrawingDisabled++; // don't redisplay the window
no_wait_return++; // don't wait for return
msg(_("Entering Ex mode. Type \"visual\" to go to Normal mode."));
while (exmode_active) {
// Check for a ":normal" command and no more characters left.
if (ex_normal_busy > 0 && typebuf.tb_len == 0) {
exmode_active = false;
break;
}
msg_scroll = true;
need_wait_return = false;
ex_pressedreturn = false;
ex_no_reprint = false;
varnumber_T changedtick = buf_get_changedtick(curbuf);
int prev_msg_row = msg_row;
linenr_T prev_line = curwin->w_cursor.lnum;
cmdline_row = msg_row;
do_cmdline(NULL, getexline, NULL, 0);
lines_left = Rows - 1;
if ((prev_line != curwin->w_cursor.lnum
|| changedtick != buf_get_changedtick(curbuf)) && !ex_no_reprint) {
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
emsg(_(e_empty_buffer));
} else {
if (ex_pressedreturn) {
// Make sure the message overwrites the right line and isn't throttled.
msg_scroll_flush();
// go up one line, to overwrite the ":<CR>" line, so the
// output doesn't contain empty lines.
msg_row = prev_msg_row;
if (prev_msg_row == Rows - 1) {
msg_row--;
}
}
msg_col = 0;
print_line_no_prefix(curwin->w_cursor.lnum, false, false);
msg_clr_eos();
}
} else if (ex_pressedreturn && !ex_no_reprint) { // must be at EOF
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
emsg(_(e_empty_buffer));
} else {
emsg(_("E501: At end-of-file"));
}
}
}
RedrawingDisabled--;
no_wait_return--;
redraw_all_later(UPD_NOT_VALID);
update_screen();
need_wait_return = false;
msg_scroll = save_msg_scroll;
}
/// Print the executed command for when 'verbose' is set.
///
/// @param lnum if 0, only print the command.
static void msg_verbose_cmd(linenr_T lnum, char *cmd)
FUNC_ATTR_NONNULL_ALL
{
no_wait_return++;
verbose_enter_scroll();
if (lnum == 0) {
smsg(_("Executing: %s"), cmd);
} else {
smsg(_("line %" PRIdLINENR ": %s"), lnum, cmd);
}
if (msg_silent == 0) {
msg_puts("\n"); // don't overwrite this
}
verbose_leave_scroll();
no_wait_return--;
}
/// Execute a simple command line. Used for translated commands like "*".
int do_cmdline_cmd(const char *cmd)
{
return do_cmdline((char *)cmd, NULL, NULL, DOCMD_VERBOSE|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 ".").
///
/// @param cookie argument for fgetline()
///
/// @return FAIL if cmdline could not be executed, OK otherwise
int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
{
char *next_cmdline; // next cmd to execute
char *cmdline_copy = NULL; // copy of cmd line
bool used_getline = false; // used "fgetline" to obtain command
static int recursive = 0; // recursive depth
bool msg_didout_before_start = false;
int count = 0; // line number count
bool did_inc = false; // incremented RedrawingDisabled
int retval = OK;
cstack_T cstack = { // conditional stack
.cs_idx = -1,
};
garray_T lines_ga; // keep lines for ":while"/":for"
int current_line = 0; // active line in lines_ga
char *fname = NULL; // function or script name
linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie
int *dbg_tick = NULL; // ptr to dbg_tick field in cookie
struct dbg_stuff debug_saved; // saved things for debug mode
int initial_trylevel;
msglist_T **saved_msg_list = NULL;
msglist_T *private_msg_list;
// "fgetline" and "cookie" passed to do_one_cmd()
char *(*cmd_getline)(int, void *, int, bool);
void *cmd_cookie;
struct loop_cookie cmd_loop_cookie;
void *real_cookie;
int getline_is_func;
static int call_depth = 0; // recursiveness
// For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory
// location for storing error messages to be converted to an exception.
// This ensures that the do_errthrow() call in do_one_cmd() does not
// combine the messages stored by an earlier invocation of do_one_cmd()
// with the command name of the later one. This would happen when
// BufWritePost autocommands are executed after a write error.
saved_msg_list = msg_list;
msg_list = &private_msg_list;
private_msg_list = NULL;
// It's possible to create an endless loop with ":execute", catch that
// here. The value of 200 allows nested function calls, ":source", etc.
// Allow 200 or 'maxfuncdepth', whatever is larger.
if (call_depth >= 200 && call_depth >= p_mfd) {
emsg(_(e_command_too_recursive));
// When converting to an exception, we do not include the command name
// since this is not an error of the specific command.
do_errthrow((cstack_T *)NULL, NULL);
msg_list = saved_msg_list;
return FAIL;
}
call_depth++;
start_batch_changes();
ga_init(&lines_ga, (int)sizeof(wcmd_T), 10);
real_cookie = getline_cookie(fgetline, cookie);
// Inside a function use a higher nesting level.
getline_is_func = getline_equal(fgetline, cookie, get_func_line);
if (getline_is_func && ex_nesting_level == func_level(real_cookie)) {
ex_nesting_level++;
}
// 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 = func_name(real_cookie);
breakpoint = func_breakpoint(real_cookie);
dbg_tick = func_dbg_tick(real_cookie);
} else if (getline_equal(fgetline, cookie, getsourceline)) {
fname = SOURCING_NAME;
breakpoint = source_breakpoint(real_cookie);
dbg_tick = source_dbg_tick(real_cookie);
}
// Initialize "force_abort" and "suppress_errthrow" at the top level.
if (!recursive) {
force_abort = false;
suppress_errthrow = false;
}
// If requested, store and reset the global values controlling the
// exception handling (used when debugging). Otherwise clear it to avoid
// a bogus compiler warning when the optimizer uses inline functions...
if (flags & DOCMD_EXCRESET) {
save_dbg_stuff(&debug_saved);
} else {
CLEAR_FIELD(debug_saved);
}
initial_trylevel = trylevel;
// "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.
did_emsg = false;
// 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)) {
KeyTyped = false;
}
// Continue executing command lines:
// - when inside an ":if", ":while" or ":for"
// - for multiple commands on one line, separated with '|'
// - when repeating until there are no more lines (for ":source")
next_cmdline = cmdline;
do {
getline_is_func = getline_equal(fgetline, cookie, get_func_line);
// stop skipping cmds for an error msg after all endif/while/for
if (next_cmdline == NULL
&& !force_abort
&& cstack.cs_idx < 0
&& !(getline_is_func
&& func_has_abort(real_cookie))) {
did_emsg = false;
}
// 1. If repeating a line in a loop, get a line from lines_ga.
// 2. If no line given: Get an allocated line with fgetline().
// 3. If a line is given: Make a copy, so we can mess with it.
// 1. If repeating, get a previous line from lines_ga.
if (cstack.cs_looplevel > 0 && current_line < lines_ga.ga_len) {
// Each '|' separated command is stored separately in lines_ga, to
// be able to jump to it. Don't use next_cmdline now.
XFREE_CLEAR(cmdline_copy);
// Check if a function has returned or, unless it has an unclosed
// try conditional, aborted.
if (getline_is_func) {
if (do_profiling == PROF_YES) {
func_line_end(real_cookie);
}
if (func_has_ended(real_cookie)) {
retval = FAIL;
break;
}
} else if (do_profiling == PROF_YES
&& getline_equal(fgetline, cookie, getsourceline)) {
script_line_end();
}
// Check if a sourced file hit a ":finish" command.
if (source_finished(fgetline, cookie)) {
retval = FAIL;
break;
}
// If breakpoints have been added/deleted need to check for it.
if (breakpoint != NULL && dbg_tick != NULL
&& *dbg_tick != debug_tick) {
*breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline),
fname, SOURCING_LNUM);
*dbg_tick = debug_tick;
}
next_cmdline = ((wcmd_T *)(lines_ga.ga_data))[current_line].line;
SOURCING_LNUM = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum;
// Did we encounter a breakpoint?
if (breakpoint != NULL && *breakpoint != 0 && *breakpoint <= SOURCING_LNUM) {
dbg_breakpoint(fname, SOURCING_LNUM);
// Find next breakpoint.
*breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline),
fname, SOURCING_LNUM);
*dbg_tick = debug_tick;
}
if (do_profiling == PROF_YES) {
if (getline_is_func) {
func_line_start(real_cookie);
} else if (getline_equal(fgetline, cookie, getsourceline)) {
script_line_start();
}
}
}
// 2. If no line given, get an allocated line with fgetline().
if (next_cmdline == NULL) {
// 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,
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;
}
used_getline = true;
// Keep the first typed line. Clear it when more lines are typed.
if (flags & DOCMD_KEEPLINE) {
xfree(repeat_cmdline);
if (count == 0) {
repeat_cmdline = xstrdup(next_cmdline);
} else {
repeat_cmdline = NULL;
}
}
} else if (cmdline_copy == NULL) {
// 3. Make a copy of the command so we can mess with it.
next_cmdline = xstrdup(next_cmdline);
}
cmdline_copy = next_cmdline;
int current_line_before = 0;
// Inside a while/for loop, and when the command looks like a ":while"
// or ":for", the line is stored, because we may need it later when
// looping.
//
// When there is a '|' and another command, it is stored separately,
// because we need to be able to jump back to it from an
// :endwhile/:endfor.
//
// Pass a different "fgetline" function to do_one_cmd() below,
// that it stores lines in or reads them from "lines_ga". Makes it
// possible to define a function inside a while/for loop.
if ((cstack.cs_looplevel > 0 || has_loop_cmd(next_cmdline))) {
cmd_getline = get_loop_line;
cmd_cookie = (void *)&cmd_loop_cookie;
cmd_loop_cookie.lines_gap = &lines_ga;
cmd_loop_cookie.current_line = current_line;
cmd_loop_cookie.getline = fgetline;
cmd_loop_cookie.cookie = cookie;
cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len);
// Save the current line when encountering it the first time.
if (current_line == lines_ga.ga_len) {
store_loop_line(&lines_ga, next_cmdline);
}
current_line_before = current_line;
} else {
cmd_getline = fgetline;
cmd_cookie = cookie;
}
did_endif = false;
if (count++ == 0) {
// All output from the commands is put below each other, without
// waiting for a return. Don't do this when executing commands
// from a script or when being called recursive (e.g. for ":e
// +command file").
if (!(flags & DOCMD_NOWAIT) && !recursive) {
msg_didout_before_start = msg_didout;
msg_didany = false; // no output yet
msg_start();
msg_scroll = true; // put messages below each other
no_wait_return++; // don't wait for return until finished
RedrawingDisabled++;
did_inc = true;
}
}
if ((p_verbose >= 15 && SOURCING_NAME != NULL) || p_verbose >= 16) {
msg_verbose_cmd(SOURCING_LNUM, cmdline_copy);
}
// 2. Execute one '|' separated command.
// do_one_cmd() will return NULL if there is no trailing '|'.
// "cmdline_copy" can change, e.g. for '%' and '#' expansion.
recursive++;
next_cmdline = do_one_cmd(&cmdline_copy, flags, &cstack, cmd_getline, cmd_cookie);
recursive--;
if (cmd_cookie == (void *)&cmd_loop_cookie) {
// Use "current_line" from "cmd_loop_cookie", it may have been
// incremented when defining a function.
current_line = cmd_loop_cookie.current_line;
}
if (next_cmdline == NULL) {
XFREE_CLEAR(cmdline_copy);
// If the command was typed, remember it for the ':' register.
// Do this AFTER executing the command to make :@: work.
if (getline_equal(fgetline, cookie, getexline)
&& new_last_cmdline != NULL) {
xfree(last_cmdline);
last_cmdline = new_last_cmdline;
new_last_cmdline = NULL;
}
} else {
// need to copy the command after the '|' to cmdline_copy, for the
// next do_one_cmd()
STRMOVE(cmdline_copy, next_cmdline);
next_cmdline = cmdline_copy;
}
// reset did_emsg for a function that is not aborted by an error
if (did_emsg && !force_abort
&& getline_equal(fgetline, cookie, get_func_line)
&& !func_has_abort(real_cookie)) {
did_emsg = false;
}
if (cstack.cs_looplevel > 0) {
current_line++;
// An ":endwhile", ":endfor" and ":continue" is handled here.
// If we were executing commands, jump back to the ":while" or
// ":for".
// If we were not executing commands, decrement cs_looplevel.
if (cstack.cs_lflags & (CSL_HAD_CONT | CSL_HAD_ENDLOOP)) {
cstack.cs_lflags &= ~(CSL_HAD_CONT | CSL_HAD_ENDLOOP);
// Jump back to the matching ":while" or ":for". Be careful
// 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 && !did_throw
&& cstack.cs_idx >= 0
&& (cstack.cs_flags[cstack.cs_idx]
& (CSF_WHILE | CSF_FOR))
&& cstack.cs_line[cstack.cs_idx] >= 0
&& (cstack.cs_flags[cstack.cs_idx] & CSF_ACTIVE)) {
current_line = cstack.cs_line[cstack.cs_idx];
// remember we jumped there
cstack.cs_lflags |= CSL_HAD_LOOP;
line_breakcheck(); // check if CTRL-C typed
// Check for the next breakpoint at or after the ":while"
// or ":for".
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;
}
} else {
// can only get here with ":endwhile" or ":endfor"
if (cstack.cs_idx >= 0) {
rewind_conditionals(&cstack, cstack.cs_idx - 1,
CSF_WHILE | CSF_FOR, &cstack.cs_looplevel);
}
}
} else if (cstack.cs_lflags & CSL_HAD_LOOP) {
// For a ":while" or ":for" we need to remember the line number.
cstack.cs_lflags &= ~CSL_HAD_LOOP;
cstack.cs_line[cstack.cs_idx] = current_line_before;
}
}
// When not inside any ":while" loop, clear remembered lines.
if (cstack.cs_looplevel == 0) {
if (!GA_EMPTY(&lines_ga)) {
SOURCING_LNUM = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum;
GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD);
}
current_line = 0;
}
// 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
// ":endfor" was detected by the ":finally" itself.
if (cstack.cs_lflags & CSL_HAD_FINA) {
cstack.cs_lflags &= ~CSL_HAD_FINA;
report_make_pending((cstack.cs_pending[cstack.cs_idx]
& (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)),
did_throw ? current_exception : NULL);
did_emsg = got_int = did_throw = false;
cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY;
}
// Update global "trylevel" for recursive calls to do_cmdline() from
// within this loop.
trylevel = initial_trylevel + cstack.cs_trylevel;
// If the outermost try conditional (across function calls and sourced
// files) is aborted because of an error, an interrupt, or an uncaught
// 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 && !did_throw) {
force_abort = false;
}
// Convert an interrupt to an exception if appropriate.
(void)do_intthrow(&cstack);
// Continue executing command lines when:
// - no CTRL-C typed, no aborting error, no exception thrown or try
// conditionals need to be checked for executing finally clauses or
// catching an interrupt exception
// - 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) || did_throw)
&& cstack.cs_trylevel == 0)
&& !(did_emsg
// Keep going when inside try/catch, so that the error can be
// deal with, except when it is a syntax error, it may cause
// the :endtry to be missed.
&& (cstack.cs_trylevel == 0 || did_emsg_syntax)
&& used_getline
&& getline_equal(fgetline, cookie, getexline))
&& (next_cmdline != NULL
|| cstack.cs_idx >= 0
|| (flags & DOCMD_REPEAT)));
xfree(cmdline_copy);
did_emsg_syntax = false;
GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD);
if (cstack.cs_idx >= 0) {
// If a sourced file or executed function ran to its end, report the
// unclosed conditional.
if (!got_int && !did_throw && !aborting()
&& ((getline_equal(fgetline, cookie, getsourceline)
&& !source_finished(fgetline, cookie))
|| (getline_equal(fgetline, cookie, get_func_line)
&& !func_has_ended(real_cookie)))) {
if (cstack.cs_flags[cstack.cs_idx] & CSF_TRY) {
emsg(_(e_endtry));
} else if (cstack.cs_flags[cstack.cs_idx] & CSF_WHILE) {
emsg(_(e_endwhile));
} else if (cstack.cs_flags[cstack.cs_idx] & CSF_FOR) {
emsg(_(e_endfor));
} else {
emsg(_(e_endif));
}
}
// Reset "trylevel" in case of a ":finish" or ":return" or a missing
// ":endtry" in a sourced file or executed function. If the try
// conditional is in its finally clause, ignore anything pending.
// If it is in a catch clause, finish the caught exception.
// Also cleanup any "cs_forinfo" structures.
do {
int idx = cleanup_conditionals(&cstack, 0, true);
if (idx >= 0) {
idx--; // remove try block not in its finally clause
}
rewind_conditionals(&cstack, idx, CSF_WHILE | CSF_FOR,
&cstack.cs_looplevel);
} while (cstack.cs_idx >= 0);
trylevel = initial_trylevel;
}
// If a missing ":endtry", ":endwhile", ":endfor", or ":endif" or a memory
// lack was reported above and the error message is to be converted to an
// exception, do this now after rewinding the cstack.
do_errthrow(&cstack, getline_equal(fgetline, cookie, get_func_line) ? "endfunction" : NULL);
if (trylevel == 0) {
// When an exception is being thrown out of the outermost try
// conditional, discard the uncaught exception, disable the conversion
// of interrupts or errors to exceptions, and ensure that no more
// commands are executed.
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
// converted any more, here.) This enables also the interrupt message
// when force_abort is set and did_emsg unset in case of an interrupt
// from a finally clause after an error.
suppress_errthrow = true;
}
}
// The current cstack will be freed when do_cmdline() returns. An uncaught
// exception will have to be rethrown in the previous cstack. If a function
// has just returned or a script file was just finished and the previous
// 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 (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 (!did_throw) {
check_cstack = true;
}
} else {
// When leaving a function, reduce nesting level.
if (getline_equal(fgetline, cookie, get_func_line)) {
ex_nesting_level--;
}
// Go to debug mode when returning from a function in which we are
// single-stepping.
if ((getline_equal(fgetline, cookie, getsourceline)
|| getline_equal(fgetline, cookie, get_func_line))
&& ex_nesting_level + 1 <= debug_break_level) {
do_debug(getline_equal(fgetline, cookie, getsourceline)
? _("End of sourced file")
: _("End of function"));
}
}
// Restore the exception environment (done after returning from the
// debugger).
if (flags & DOCMD_EXCRESET) {
restore_dbg_stuff(&debug_saved);
}
msg_list = saved_msg_list;
// Cleanup if "cs_emsg_silent_list" remains.
if (cstack.cs_emsg_silent_list != NULL) {
eslist_T *elem, *temp;
for (elem = cstack.cs_emsg_silent_list; elem != NULL; elem = temp) {
temp = elem->next;
xfree(elem);
}
}
// If there was too much output to fit on the command line, ask the user to
// hit return before redrawing the screen. With the ":global" command we do
// this only once after the command is finished.
if (did_inc) {
RedrawingDisabled--;
no_wait_return--;
msg_scroll = false;
// When just finished an ":if"-":else" which was typed, no need to
// wait for hit-return. Also for an error situation.
if (retval == FAIL
|| (did_endif && KeyTyped && !did_emsg)) {
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
// here should not overwrite the command that may be shown before
// doing that.
msg_didout |= msg_didout_before_start;
wait_return(false);
}
}
did_endif = false; // in case do_cmdline used recursively
call_depth--;
end_batch_changes();
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)
{
struct loop_cookie *cp = (struct loop_cookie *)cookie;
if (cp->current_line + 1 >= cp->lines_gap->ga_len) {
if (cp->repeating) {
return NULL; // trying to read past ":endwhile"/":endfor"
}
char *line;
// 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++;
}
return line;
}
KeyTyped = false;
cp->current_line++;
wcmd_T *wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line;
SOURCING_LNUM = wp->lnum;
return xstrdup(wp->line);
}
/// Store a line in "gap" so that a ":while" loop can execute it again.
static void store_loop_line(garray_T *gap, char *line)
{
wcmd_T *p = GA_APPEND_VIA_PTR(wcmd_T, gap);
p->line = xstrdup(line);
p->lnum = SOURCING_LNUM;
}
/// If "fgetline" is get_loop_line(), return true if the getline it uses equals
/// "func". * Otherwise return true when "fgetline" equals "func".
///
/// @param cookie argument for fgetline()
bool getline_equal(LineGetter fgetline, void *cookie, LineGetter func)
{
// When "fgetline" is "get_loop_line()" use the "cookie" to find the
// function that's originally used to obtain the lines. This may be
// nested several levels.
LineGetter gp = fgetline;
struct loop_cookie *cp = (struct loop_cookie *)cookie;
while (gp == get_loop_line) {
gp = cp->getline;
cp = cp->cookie;
}
return gp == func;
}
/// If "fgetline" is get_loop_line(), return the cookie used by the original
/// getline function. Otherwise return "cookie".
///
/// @param cookie argument for fgetline()
void *getline_cookie(LineGetter fgetline, void *cookie)
{
// When "fgetline" is "get_loop_line()" use the "cookie" to find the
// cookie that's originally used to obtain the lines. This may be nested
// several levels.
LineGetter gp = fgetline;
struct loop_cookie *cp = (struct loop_cookie *)cookie;
while (gp == get_loop_line) {
gp = cp->getline;
cp = cp->cookie;
}
return cp;
}
/// Helper function to apply an offset for buffer commands, i.e. ":bdelete",
/// ":bwipeout", etc.
///
/// @return the buffer number.
static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, int offset)
{
buf_T *nextbuf;
int count = offset;
buf_T *buf = firstbuf;
while (buf->b_next != NULL && buf->b_fnum < lnum) {
buf = buf->b_next;
}
while (count != 0) {
count += (count < 0) ? 1 : -1;
nextbuf = (offset < 0) ? buf->b_prev : buf->b_next;
if (nextbuf == NULL) {
break;
}
buf = nextbuf;
if (addr_type == ADDR_LOADED_BUFFERS) {
// skip over unloaded buffers
while (buf->b_ml.ml_mfp == NULL) {
nextbuf = (offset < 0) ? buf->b_prev : buf->b_next;
if (nextbuf == NULL) {
break;
}
buf = nextbuf;
}
}
}
// we might have gone too far, last buffer is not loaded
if (addr_type == ADDR_LOADED_BUFFERS) {
while (buf->b_ml.ml_mfp == NULL) {
nextbuf = (offset >= 0) ? buf->b_prev : buf->b_next;
if (nextbuf == NULL) {
break;
}
buf = nextbuf;
}
}
return buf->b_fnum;
}
/// @return the window number of "win" or,
/// the number of windows if "win" is NULL
static int current_win_nr(const win_T *win)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
int nr = 0;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
nr++;
if (wp == win) {
break;
}
}
return nr;
}
static int current_tab_nr(tabpage_T *tab)
{
int nr = 0;
FOR_ALL_TABS(tp) {
nr++;
if (tp == tab) {
break;
}
}
return nr;
}
#define CURRENT_WIN_NR current_win_nr(curwin)
#define LAST_WIN_NR current_win_nr(NULL)
#define CURRENT_TAB_NR current_tab_nr(curtab)
#define LAST_TAB_NR current_tab_nr(NULL)
/// Figure out the address type for ":wincmd".
static void get_wincmd_addr_type(const char *arg, exarg_T *eap)
{
switch (*arg) {
case 'S':
case Ctrl_S:
case 's':
case Ctrl_N:
case 'n':
case 'j':
case Ctrl_J:
case 'k':
case Ctrl_K:
case 'T':
case Ctrl_R:
case 'r':
case 'R':
case 'K':
case 'J':
case '+':
case '-':
case Ctrl__:
case '_':
case '|':
case ']':
case Ctrl_RSB:
case 'g':
case Ctrl_G:
case Ctrl_V:
case 'v':
case 'h':
case Ctrl_H:
case 'l':
case Ctrl_L:
case 'H':
case 'L':
case '>':
case '<':
case '}':
case 'f':
case 'F':
case Ctrl_F:
case 'i':
case Ctrl_I:
case 'd':
case Ctrl_D:
// window size or any count
eap->addr_type = ADDR_OTHER; // -V1037
break;
case Ctrl_HAT:
case '^':
// buffer number
eap->addr_type = ADDR_BUFFERS;
break;
case Ctrl_Q:
case 'q':
case Ctrl_C:
case 'c':
case Ctrl_O:
case 'o':
case Ctrl_W:
case 'w':
case 'W':
case 'x':
case Ctrl_X:
// window number
eap->addr_type = ADDR_WINDOWS;
break;
case Ctrl_Z:
case 'z':
case 'P':
case 't':
case Ctrl_T:
case 'b':
case Ctrl_B:
case 'p':
case Ctrl_P:
case '=':
case CAR:
// no count
eap->addr_type = ADDR_NONE;
break;
}
}
/// Skip colons and trailing whitespace, returning a pointer to the first
/// non-colon, non-whitespace character.
//
/// @param skipleadingwhite Skip leading whitespace too
static char *skip_colon_white(const char *p, bool skipleadingwhite)
{
if (skipleadingwhite) {
p = skipwhite(p);
}
while (*p == ':') {
p = skipwhite(p + 1);
}
return (char *)p;
}
/// Set the addr type for command
///
/// @param p pointer to character after command name in cmdline
void set_cmd_addr_type(exarg_T *eap, char *p)
{
// ea.addr_type for user commands is set by find_ucmd
if (IS_USER_CMDIDX(eap->cmdidx)) {
return;
}
if (eap->cmdidx != CMD_SIZE) {
eap->addr_type = cmdnames[(int)eap->cmdidx].cmd_addr_type;
} else {
eap->addr_type = ADDR_LINES;
}
// :wincmd range depends on the argument
if (eap->cmdidx == CMD_wincmd && p != NULL) {
get_wincmd_addr_type(skipwhite(p), eap);
}
// :.cc in quickfix window uses line number
if ((eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll) && bt_quickfix(curbuf)) {
eap->addr_type = ADDR_OTHER;
}
}
/// Get default range number for command based on its address type
linenr_T get_cmd_default_range(exarg_T *eap)
{
switch (eap->addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
// Default is the cursor line number. Avoid using an invalid
// line number though.
return MIN(curwin->w_cursor.lnum, curbuf->b_ml.ml_line_count);
break;
case ADDR_WINDOWS:
return CURRENT_WIN_NR;
break;
case ADDR_ARGUMENTS:
return MIN(curwin->w_arg_idx + 1, ARGCOUNT);
break;
case ADDR_LOADED_BUFFERS:
case ADDR_BUFFERS:
return curbuf->b_fnum;
break;
case ADDR_TABS:
return CURRENT_TAB_NR;
break;
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
return 1;
break;
case ADDR_QUICKFIX:
return (linenr_T)qf_get_cur_idx(eap);
break;
case ADDR_QUICKFIX_VALID:
return qf_get_cur_valid_idx(eap);
break;
default:
return 0;
// Will give an error later if a range is found.
break;
}
}
/// Set default command range for -range=% based on the addr type of the command
void set_cmd_dflall_range(exarg_T *eap)
{
buf_T *buf;
eap->line1 = 1;
switch (eap->addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
eap->line2 = curbuf->b_ml.ml_line_count;
break;
case ADDR_LOADED_BUFFERS:
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:
eap->line2 = LAST_WIN_NR;
break;
case ADDR_TABS:
eap->line2 = LAST_TAB_NR;
break;
case ADDR_TABS_RELATIVE:
eap->line2 = 1;
break;
case ADDR_ARGUMENTS:
if (ARGCOUNT == 0) {
eap->line1 = eap->line2 = 0;
} else {
eap->line2 = ARGCOUNT;
}
break;
case ADDR_QUICKFIX_VALID:
eap->line2 = (linenr_T)qf_get_valid_size(eap);
if (eap->line2 == 0) {
eap->line2 = 1;
}
break;
case ADDR_NONE:
case ADDR_UNSIGNED:
case ADDR_QUICKFIX:
iemsg(_("INTERNAL: Cannot use EX_DFLALL "
"with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"));
break;
}
}
static void parse_register(exarg_T *eap)
{
// Accept numbered register only when no count allowed (:put)
if ((eap->argt & EX_REGSTR)
&& *eap->arg != NUL
// Do not allow register = for user commands
&& (!IS_USER_CMDIDX(eap->cmdidx) || *eap->arg != '=')
&& !((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg))) {
if (valid_yank_reg(*eap->arg, (eap->cmdidx != CMD_put
&& !IS_USER_CMDIDX(eap->cmdidx)))) {
eap->regname = (uint8_t)(*eap->arg++);
// 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(xstrdup(eap->arg));
}
eap->arg += strlen(eap->arg);
}
eap->arg = skipwhite(eap->arg);
}
}
}
// Change line1 and line2 of Ex command to use count
void set_cmd_count(exarg_T *eap, linenr_T count, bool validate)
{
if (eap->addr_type != ADDR_LINES) { // e.g. :buffer 2, :sleep 3
eap->line2 = count;
if (eap->addr_count == 0) {
eap->addr_count = 1;
}
} else {
eap->line1 = eap->line2;
eap->line2 += count - 1;
eap->addr_count++;
// Be vi compatible: no error message for out of range.
if (validate && eap->line2 > curbuf->b_ml.ml_line_count) {
eap->line2 = curbuf->b_ml.ml_line_count;
}
}
}
static int parse_count(exarg_T *eap, const char **errormsg, bool validate)
{
// Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a
// count, it's a buffer name.
char *p;
if ((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg)
&& (!(eap->argt & EX_BUFNAME) || *(p = skipdigits(eap->arg + 1)) == NUL
|| ascii_iswhite(*p))) {
linenr_T n = getdigits_int32(&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);
}
return FAIL;
}
set_cmd_count(eap, n, validate);
}
return OK;
}
/// Check if command is not implemented
bool is_cmd_ni(cmdidx_T cmdidx)
{
return !IS_USER_CMDIDX(cmdidx) && (cmdnames[cmdidx].cmd_func == ex_ni
|| cmdnames[cmdidx].cmd_func == ex_script_ni);
}
/// Parse command line and return information about the first command.
/// If parsing is done successfully, need to free cmod_filter_pat and cmod_filter_regmatch.regprog
/// after calling, usually done using undo_cmdmod() or execute_cmd().
///
/// @param cmdline Command line string
/// @param[out] eap Ex command arguments
/// @param[out] cmdinfo Command parse information
/// @param[out] errormsg Error message, if any
///
/// @return Success or failure
bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, const 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);
// Initialize eap
*eap = (exarg_T){
.line1 = 1,
.line2 = 1,
.cmd = cmdline,
.cmdlinep = &cmdline,
.getline = NULL,
.cookie = NULL,
};
// Parse command modifiers
if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) {
goto end;
}
after_modifier = eap->cmd;
// Save location after command modifiers
char *cmd = eap->cmd;
// Skip ranges to find command name since we need the command to know what kind of range it uses
eap->cmd = skip_range(eap->cmd, NULL);
if (*eap->cmd == '*') {
eap->cmd = skipwhite(eap->cmd + 1);
}
char *p = find_ex_command(eap, NULL);
if (p == NULL) {
*errormsg = _(e_ambiguous_use_of_user_defined_command);
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, 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 end;
}
// Fail if command is invalid
if (eap->cmdidx == CMD_SIZE) {
xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE);
// 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 = IObuff;
goto end;
}
// Correctly set 'forceit' for commands
if (*p == '!' && eap->cmdidx != CMD_substitute
&& eap->cmdidx != CMD_smagic && eap->cmdidx != CMD_snomagic) {
p++;
eap->forceit = true;
} else {
eap->forceit = false;
}
// Parse arguments.
if (!IS_USER_CMDIDX(eap->cmdidx)) {
eap->argt = cmdnames[(int)eap->cmdidx].cmd_argt;
}
// Skip to start of argument.
// Don't do this for the ":!" command, because ":!! -l" needs the space.
if (eap->cmdidx == CMD_bang) {
eap->arg = p;
} else {
eap->arg = skipwhite(p);
}
// Don't treat ":r! filter" like a bang
if (eap->cmdidx == CMD_read) {
if (eap->forceit) {
eap->forceit = false; // :r! filter
}
}
// Check for '|' to separate commands and '"' to start comments.
// Don't do this for ":read !cmd" and ":write !cmd".
if ((eap->argt & EX_TRLBAR)) {
separate_nextcmd(eap);
}
// Fail if command doesn't support bang but is used with a bang
if (!(eap->argt & EX_BANG) && eap->forceit) {
*errormsg = _(e_nobang);
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 end;
}
// Set default range for command if required
if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) {
set_cmd_dflall_range(eap);
}
// Parse register and count
parse_register(eap);
if (parse_count(eap, errormsg, false) == FAIL) {
goto end;
}
// Remove leading whitespace and colon from next command
if (eap->nextcmd) {
eap->nextcmd = skip_colon_white(eap->nextcmd, true);
}
// Set the "magic" values (characters that get treated specially)
if (eap->argt & EX_XFILE) {
cmdinfo->magic.file = true;
}
if (eap->argt & EX_TRLBAR) {
cmdinfo->magic.bar = true;
}
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, const 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_cmd_args(eap);
}
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 builtin 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.
///
/// @param eap Ex-command arguments
/// @param cmdinfo Command parse information
/// @param preview Execute command preview callback instead of actual command
int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
{
const char *errormsg = NULL;
int retv = 0;
#undef ERROR
#define ERROR(msg) \
do { \
errormsg = msg; \
goto end; \
} while (0)
cmdmod_T save_cmdmod = cmdmod;
cmdmod = cmdinfo->cmdmod;
// Apply command modifiers
apply_cmdmod(&cmdmod);
if (!MODIFIABLE(curbuf) && (eap->argt & EX_MODIFY)
// allow :put in terminals
&& !(curbuf->terminal && eap->cmdidx == CMD_put)) {
ERROR(_(e_modifiable));
}
if (!IS_USER_CMDIDX(eap->cmdidx)) {
if (cmdwin_type != 0 && !(eap->argt & EX_CMDWIN)) {
// Command not allowed in the command line window
ERROR(_(e_cmdwin));
}
if (text_locked() && !(eap->argt & EX_LOCK_OK)) {
// Command not allowed when text is locked
ERROR(_(get_text_locked_msg()));
}
}
// 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
if (!(eap->argt & EX_CMDWIN)
&& eap->cmdidx != CMD_checktime
&& eap->cmdidx != CMD_edit
&& !(eap->cmdidx == CMD_file && *eap->arg == NUL)
&& !IS_USER_CMDIDX(eap->cmdidx)
&& curbuf_locked()) {
goto end;
}
correct_range(eap);
if (((eap->argt & EX_WHOLEFOLD) || eap->addr_count >= 2) && !global_busy
&& eap->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(eap->line1, &eap->line1, NULL);
(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);
end:
if (errormsg != NULL && *errormsg != NUL) {
emsg(errormsg);
}
// Undo command modifiers
undo_cmdmod(&cmdmod);
cmdmod = save_cmdmod;
return retv;
#undef ERROR
}
static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetline, void *cookie)
{
// Count this line for profiling if skip is true.
if (do_profiling == PROF_YES
&& (!eap->skip || cstack->cs_idx == 0
|| (cstack->cs_idx > 0
&& (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) {
bool skip = did_emsg || got_int || did_throw;
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 (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 (eap->cmdidx == CMD_finally) {
skip = false;
} else if (eap->cmdidx != CMD_endif
&& eap->cmdidx != CMD_endfor
&& eap->cmdidx != CMD_endtry
&& eap->cmdidx != CMD_endwhile) {
skip = eap->skip;
}
if (!skip) {
if (getline_equal(fgetline, cookie, get_func_line)) {
func_line_exec(getline_cookie(fgetline, cookie));
} else if (getline_equal(fgetline, cookie, getsourceline)) {
script_line_exec();
}
}
}
}
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:
case CMD_for:
case CMD_endfor:
case CMD_if:
case CMD_elseif:
case CMD_else:
case CMD_endif:
case CMD_try:
case CMD_catch:
case CMD_finally:
case CMD_endtry:
case CMD_function:
break;
// Commands that handle '|' themselves. Check: A command should
// either have the EX_TRLBAR flag, appear in this list or appear in
// the list at ":help :bar".
case CMD_aboveleft:
case CMD_and:
case CMD_belowright:
case CMD_botright:
case CMD_browse:
case CMD_call:
case CMD_confirm:
case CMD_const:
case CMD_delfunction:
case CMD_djump:
case CMD_dlist:
case CMD_dsearch:
case CMD_dsplit:
case CMD_echo:
case CMD_echoerr:
case CMD_echomsg:
case CMD_echon:
case CMD_eval:
case CMD_execute:
case CMD_filter:
case CMD_help:
case CMD_hide:
case CMD_horizontal:
case CMD_ijump:
case CMD_ilist:
case CMD_isearch:
case CMD_isplit:
case CMD_keepalt:
case CMD_keepjumps:
case CMD_keepmarks:
case CMD_keeppatterns:
case CMD_leftabove:
case CMD_let:
case CMD_lockmarks:
case CMD_lockvar:
case CMD_lua:
case CMD_match:
case CMD_mzscheme:
case CMD_noautocmd:
case CMD_noswapfile:
case CMD_perl:
case CMD_psearch:
case CMD_python:
case CMD_py3:
case CMD_python3:
case CMD_pythonx:
case CMD_pyx:
case CMD_return:
case CMD_rightbelow:
case CMD_ruby:
case CMD_silent:
case CMD_smagic:
case CMD_snomagic:
case CMD_substitute:
case CMD_syntax:
case CMD_tab:
case CMD_tcl:
case CMD_throw:
case CMD_tilde:
case CMD_topleft:
case CMD_trust:
case CMD_unlet:
case CMD_unlockvar:
case CMD_verbose:
case CMD_vertical:
case CMD_wincmd:
break;
default:
return true;
}
}
return false;
}
/// Execute one Ex command.
///
/// If "flags" has DOCMD_VERBOSE, 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)
{
const 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 = {
.line1 = 1,
.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.
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
|| did_throw
|| (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);
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);
}
// 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 = check_nextcmd(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) {
xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE);
// 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 = 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 <newline> 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 = ex_errmsg(e_trailing_arg, ea.arg);
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.
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 (need_rethrow) {
do_throw(cstack);
} else if (check_cstack) {
if (source_finished(fgetline, cookie)) {
do_finish(&ea, true);
} else if (getline_equal(fgetline, cookie, get_func_line)
&& current_func_returned()) {
do_return(&ea, true, false, NULL);
}
}
need_rethrow = check_cstack = false;
doend:
// can happen with zero line number
if (curwin->w_cursor.lnum == 0) {
curwin->w_cursor.lnum = 1;
curwin->w_cursor.col = 0;
}
if (errormsg != NULL && *errormsg != NUL && !did_emsg) {
if (flags & DOCMD_VERBOSE) {
if (errormsg != IObuff) {
xstrlcpy(IObuff, errormsg, IOSIZE);
errormsg = IObuff;
}
append_command(*ea.cmdlinep);
}
emsg(errormsg);
}
do_errthrow(cstack,
(ea.cmdidx != CMD_SIZE
&& !IS_USER_CMDIDX(ea.cmdidx)) ? cmdnames[(int)ea.cmdidx].cmd_name : NULL);
undo_cmdmod(&cmdmod);
cmdmod = save_cmdmod;
reg_executing = save_reg_executing;
pending_end_reg_executing = save_pending_end_reg_executing;
if (ea.nextcmd && *ea.nextcmd == NUL) { // not really a next command
ea.nextcmd = NULL;
}
ex_nesting_level--;
xfree(ea.cmdline_tofree);
return ea.nextcmd;
}
static char ex_error_buf[MSG_BUF_LEN];
/// @return an error message with argument included.
/// Uses a static buffer, only the last error will be kept.
/// "msg" will be translated, caller should use N_().
char *ex_errmsg(const char *const msg, const char *const arg)
FUNC_ATTR_NONNULL_ALL
{
vim_snprintf(ex_error_buf, MSG_BUF_LEN, _(msg), arg);
return ex_error_buf;
}
/// Parse and skip over command modifiers:
/// - update eap->cmd
/// - store flags in "cmod".
/// - Set ex_pressedreturn for an empty command line.
///
/// @param skip_only if false, undo_cmdmod() must be called later to free
/// any cmod_filter_pat and cmod_filter_regmatch.regprog,
/// and ex_pressedreturn may be set.
/// @param[out] errormsg potential error message.
///
/// Call apply_cmdmod() to get the side effects of the modifiers:
/// - Increment "sandbox" for ":sandbox"
/// - set p_verbose for ":verbose"
/// - set msg_silent for ":silent"
/// - set 'eventignore' to "all" for ":noautocmd"
///
/// @return FAIL when the command is not to be executed.
int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod, bool skip_only)
{
CLEAR_POINTER(cmod);
// Repeat until no more command modifiers are found.
while (true) {
while (*eap->cmd == ' '
|| *eap->cmd == '\t'
|| *eap->cmd == ':') {
eap->cmd++;
}
// in ex mode, an empty line works like :+
if (*eap->cmd == NUL && exmode_active
&& getline_equal(eap->getline, eap->cookie, getexline)
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
eap->cmd = "+";
if (!skip_only) {
ex_pressedreturn = true;
}
}
// ignore comment and empty lines
if (*eap->cmd == '"') {
return FAIL;
}
if (*eap->cmd == NUL) {
if (!skip_only) {
ex_pressedreturn = true;
}
return FAIL;
}
char *p = skip_range(eap->cmd, NULL);
switch (*p) {
// When adding an entry, also modify cmdmods[]
case 'a':
if (!checkforcmd(&eap->cmd, "aboveleft", 3)) {
break;
}
cmod->cmod_split |= WSP_ABOVE;
continue;
case 'b':
if (checkforcmd(&eap->cmd, "belowright", 3)) {
cmod->cmod_split |= WSP_BELOW;
continue;
}
if (checkforcmd(&eap->cmd, "browse", 3)) {
cmod->cmod_flags |= CMOD_BROWSE;
continue;
}
if (!checkforcmd(&eap->cmd, "botright", 2)) {
break;
}
cmod->cmod_split |= WSP_BOT;
continue;
case 'c':
if (!checkforcmd(&eap->cmd, "confirm", 4)) {
break;
}
cmod->cmod_flags |= CMOD_CONFIRM;
continue;
case 'k':
if (checkforcmd(&eap->cmd, "keepmarks", 3)) {
cmod->cmod_flags |= CMOD_KEEPMARKS;
continue;
}
if (checkforcmd(&eap->cmd, "keepalt", 5)) {
cmod->cmod_flags |= CMOD_KEEPALT;
continue;
}
if (checkforcmd(&eap->cmd, "keeppatterns", 5)) {
cmod->cmod_flags |= CMOD_KEEPPATTERNS;
continue;
}
if (!checkforcmd(&eap->cmd, "keepjumps", 5)) {
break;
}
cmod->cmod_flags |= CMOD_KEEPJUMPS;
continue;
case 'f': { // only accept ":filter {pat} cmd"
char *reg_pat;
if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) {
break;
}
if (*p == '!') {
cmod->cmod_filter_force = true;
p = skipwhite(p + 1);
if (*p == NUL || ends_excmd(*p)) {
break;
}
}
if (skip_only) {
p = skip_vimgrep_pat(p, NULL, NULL);
} else {
// NOTE: This puts a NUL after the pattern.
p = skip_vimgrep_pat(p, &reg_pat, NULL);
}
if (p == NULL || *p == NUL) {
break;
}
if (!skip_only) {
cmod->cmod_filter_pat = xstrdup(reg_pat);
cmod->cmod_filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
if (cmod->cmod_filter_regmatch.regprog == NULL) {
break;
}
}
eap->cmd = p;
continue;
}
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;
}
eap->cmd = p;
cmod->cmod_flags |= CMOD_HIDE;
continue;
case 'l':
if (checkforcmd(&eap->cmd, "lockmarks", 3)) {
cmod->cmod_flags |= CMOD_LOCKMARKS;
continue;
}
if (!checkforcmd(&eap->cmd, "leftabove", 5)) {
break;
}
cmod->cmod_split |= WSP_ABOVE;
continue;
case 'n':
if (checkforcmd(&eap->cmd, "noautocmd", 3)) {
cmod->cmod_flags |= CMOD_NOAUTOCMD;
continue;
}
if (!checkforcmd(&eap->cmd, "noswapfile", 3)) {
break;
}
cmod->cmod_flags |= CMOD_NOSWAPFILE;
continue;
case 'r':
if (!checkforcmd(&eap->cmd, "rightbelow", 6)) {
break;
}
cmod->cmod_split |= WSP_BELOW;
continue;
case 's':
if (checkforcmd(&eap->cmd, "sandbox", 3)) {
cmod->cmod_flags |= CMOD_SANDBOX;
continue;
}
if (!checkforcmd(&eap->cmd, "silent", 3)) {
break;
}
cmod->cmod_flags |= CMOD_SILENT;
if (*eap->cmd == '!' && !ascii_iswhite(eap->cmd[-1])) {
// ":silent!", but not "silent !cmd"
eap->cmd = skipwhite(eap->cmd + 1);
cmod->cmod_flags |= CMOD_ERRSILENT;
}
continue;
case 't':
if (checkforcmd(&p, "tab", 3)) {
if (!skip_only) {
int tabnr = (int)get_address(eap, &eap->cmd, ADDR_TABS, eap->skip, skip_only,
false, 1, errormsg);
if (eap->cmd == NULL) {
return false;
}
if (tabnr == MAXLNUM) {
cmod->cmod_tab = tabpage_index(curtab) + 1;
} else {
if (tabnr < 0 || tabnr > LAST_TAB_NR) {
*errormsg = _(e_invrange);
return false;
}
cmod->cmod_tab = tabnr + 1;
}
}
eap->cmd = p;
continue;
}
if (!checkforcmd(&eap->cmd, "topleft", 2)) {
break;
}
cmod->cmod_split |= WSP_TOP;
continue;
case 'u':
if (!checkforcmd(&eap->cmd, "unsilent", 3)) {
break;
}
cmod->cmod_flags |= CMOD_UNSILENT;
continue;
case 'v':
if (checkforcmd(&eap->cmd, "vertical", 4)) {
cmod->cmod_split |= WSP_VERT;
continue;
}
if (!checkforcmd(&p, "verbose", 4)) {
break;
}
if (ascii_isdigit(*eap->cmd)) {
// zero means not set, one is verbose == 0, etc.
cmod->cmod_verbose = atoi(eap->cmd) + 1;
} else {
cmod->cmod_verbose = 2; // default: verbose == 1
}
eap->cmd = p;
continue;
}
break;
}
return OK;
}
/// Apply the command modifiers. Saves current state in "cmdmod", call
/// undo_cmdmod() later.
static void apply_cmdmod(cmdmod_T *cmod)
{
if ((cmod->cmod_flags & CMOD_SANDBOX) && !cmod->cmod_did_sandbox) {
sandbox++;
cmod->cmod_did_sandbox = true;
}
if (cmod->cmod_verbose > 0) {
if (cmod->cmod_verbose_save == 0) {
cmod->cmod_verbose_save = p_verbose + 1;
}
p_verbose = cmod->cmod_verbose - 1;
}
if ((cmod->cmod_flags & (CMOD_SILENT | CMOD_UNSILENT))
&& cmod->cmod_save_msg_silent == 0) {
cmod->cmod_save_msg_silent = msg_silent + 1;
cmod->cmod_save_msg_scroll = msg_scroll;
}
if (cmod->cmod_flags & CMOD_SILENT) {
msg_silent++;
}
if (cmod->cmod_flags & CMOD_UNSILENT) {
msg_silent = 0;
}
if (cmod->cmod_flags & CMOD_ERRSILENT) {
emsg_silent++;
cmod->cmod_did_esilent++;
}
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 = xstrdup(p_ei);
set_string_option_direct("ei", -1, "all", OPT_FREE, SID_NONE);
}
}
/// Undo and free contents of "cmod".
void undo_cmdmod(cmdmod_T *cmod)
FUNC_ATTR_NONNULL_ALL
{
if (cmod->cmod_verbose_save > 0) {
p_verbose = cmod->cmod_verbose_save - 1;
cmod->cmod_verbose_save = 0;
}
if (cmod->cmod_did_sandbox) {
sandbox--;
cmod->cmod_did_sandbox = false;
}
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(cmod->cmod_save_ei);
cmod->cmod_save_ei = NULL;
}
xfree(cmod->cmod_filter_pat);
vim_regfree(cmod->cmod_filter_regmatch.regprog);
if (cmod->cmod_save_msg_silent > 0) {
// messages could be enabled for a serious error, need to check if the
// counters don't become negative
if (!did_emsg || msg_silent > cmod->cmod_save_msg_silent - 1) {
msg_silent = cmod->cmod_save_msg_silent - 1;
}
emsg_silent -= cmod->cmod_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.
msg_scroll = cmod->cmod_save_msg_scroll;
// "silent reg" or "silent echo x" inside "redir" leaves msg_col
// somewhere in the line. Put it back in the first column.
if (redirecting()) {
msg_col = 0;
}
cmod->cmod_save_msg_silent = 0;
cmod->cmod_did_esilent = 0;
}
}
/// Parse the address range, if any, in "eap".
/// May set the last search pattern, unless "silent" is true.
///
/// @return FAIL and set "errormsg" or return OK.
int parse_cmd_address(exarg_T *eap, const char **errormsg, bool silent)
FUNC_ATTR_NONNULL_ALL
{
int address_count = 1;
linenr_T lnum;
bool need_check_cursor = false;
int ret = FAIL;
// Repeat for all ',' or ';' separated addresses.
while (true) {
eap->line1 = eap->line2;
eap->line2 = get_cmd_default_range(eap);
eap->cmd = skipwhite(eap->cmd);
lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent,
eap->addr_count == 0, address_count++, errormsg);
if (eap->cmd == NULL) { // error detected
goto theend;
}
if (lnum == MAXLNUM) {
if (*eap->cmd == '%') { // '%' - all lines
eap->cmd++;
switch (eap->addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
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 = _(e_invrange);
goto theend;
}
break;
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
case ADDR_QUICKFIX:
*errormsg = _(e_invrange);
goto theend;
case ADDR_ARGUMENTS:
if (ARGCOUNT == 0) {
eap->line1 = eap->line2 = 0;
} else {
eap->line1 = 1;
eap->line2 = ARGCOUNT;
}
break;
case ADDR_QUICKFIX_VALID:
eap->line1 = 1;
eap->line2 = (linenr_T)qf_get_valid_size(eap);
if (eap->line2 == 0) {
eap->line2 = 1;
}
break;
case ADDR_NONE:
// Will give an error later if a range is found.
break;
}
eap->addr_count++;
} else if (*eap->cmd == '*') {
// '*' - visual area
if (eap->addr_type != ADDR_LINES) {
*errormsg = _(e_invrange);
goto theend;
}
eap->cmd++;
if (!eap->skip) {
fmark_T *fm = mark_get_visual(curbuf, '<');
if (!mark_check(fm, errormsg)) {
goto theend;
}
assert(fm != NULL);
eap->line1 = fm->mark.lnum;
fm = mark_get_visual(curbuf, '>');
if (!mark_check(fm, errormsg)) {
goto theend;
}
assert(fm != NULL);
eap->line2 = fm->mark.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, but do
// accept zero as address, so 0;/PATTERN/ works correctly
// (where zero usually means to use the first line).
// Check the cursor position before returning.
if (eap->line2 > 0) {
check_cursor();
} else {
check_cursor_col();
}
need_check_cursor = true;
}
} 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;
}
}
ret = OK;
theend:
if (need_check_cursor) {
check_cursor();
}
return ret;
}
/// Check for an Ex command with optional tail.
/// If there is a match advance "pp" to the argument and return true.
///
/// @param pp start of command
/// @param cmd name of command
/// @param len required length
bool checkforcmd(char **pp, const char *cmd, int len)
{
int i;
for (i = 0; cmd[i] != NUL; i++) {
if ((cmd)[i] != (*pp)[i]) {
break;
}
}
if (i >= len && !ASCII_ISALPHA((*pp)[i])) {
*pp = skipwhite(*pp + i);
return true;
}
return false;
}
/// Append "cmd" to the error message in IObuff.
/// Takes care of limiting the length and handling 0xa0, which would be
/// invisible otherwise.
static void append_command(const char *cmd)
{
size_t len = strlen(IObuff);
const char *s = cmd;
char *d;
if (len > IOSIZE - 100) {
// Not enough space, truncate and put in "...".
d = IObuff + IOSIZE - 100;
d -= utf_head_off(IObuff, d);
STRCPY(d, "...");
}
xstrlcat(IObuff, ": ", IOSIZE);
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 (d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) {
break;
} else {
mb_copy_char(&s, &d);
}
}
*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.
/// "full" is set to true if the whole command name matched.
///
/// @return NULL for an ambiguous user command.
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.
char *p = eap->cmd;
if (one_letter_cmd(p, &eap->cmdidx)) {
p++;
} else {
while (ASCII_ISALPHA(*p)) {
p++;
}
// for python 3.x support ":py3", ":python3", ":py3file", etc.
if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y') {
while (ASCII_ISALNUM(*p)) {
p++;
}
}
// check for non-alpha command
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'.
int i;
for (i = 0; i < len; i++) {
if (eap->cmd[i] != ("delete")[i]) {
break;
}
}
if (i == len - 1) {
len--;
if (p[-1] == 'l') {
eap->flags |= EXFLAG_LIST;
} else {
eap->flags |= EXFLAG_PRINT;
}
}
}
if (ASCII_ISLOWER(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) {
iemsg(_("E943: Command table needs to be updated, run 'make'"));
getout(1);
}
// Use a precomputed index for fast look-up in cmdnames[]
// taking into account the first 2 letters of eap->cmd.
eap->cmdidx = cmdidxs1[CHAR_ORD_LOW(c1)];
if (ASCII_ISLOWER(c2)) {
eap->cmdidx += cmdidxs2[CHAR_ORD_LOW(c1)][CHAR_ORD_LOW(c2)];
}
} else if (ASCII_ISUPPER(eap->cmd[0])) {
eap->cmdidx = CMD_Next;
} else {
eap->cmdidx = CMD_bang;
}
assert(eap->cmdidx >= 0);
if (len == 3 && strncmp("def", eap->cmd, 3) == 0) {
// Make :def an unknown command to avoid confusing behavior. #23149
eap->cmdidx = CMD_SIZE;
}
for (; (int)eap->cmdidx < CMD_SIZE;
eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) {
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) {
*full = true;
}
break;
}
}
// Look for a user defined command as a last resort.
if ((eap->cmdidx == CMD_SIZE)
&& *eap->cmd >= 'A' && *eap->cmd <= 'Z') {
// User defined commands may contain digits.
while (ASCII_ISALNUM(*p)) {
p++;
}
p = find_ucmd(eap, p, full, NULL, NULL);
}
if (p == eap->cmd) {
eap->cmdidx = CMD_SIZE;
}
}
return p;
}
static struct cmdmod {
char *name;
int minlen;
int has_count; // :123verbose :3tab
} cmdmods[] = {
{ "aboveleft", 3, false },
{ "belowright", 3, false },
{ "botright", 2, false },
{ "browse", 3, false },
{ "confirm", 4, false },
{ "filter", 4, false },
{ "hide", 3, false },
{ "horizontal", 3, false },
{ "keepalt", 5, false },
{ "keepjumps", 5, false },
{ "keepmarks", 3, false },
{ "keeppatterns", 5, false },
{ "leftabove", 5, false },
{ "lockmarks", 3, false },
{ "noautocmd", 3, false },
{ "noswapfile", 3, false },
{ "rightbelow", 6, false },
{ "sandbox", 3, false },
{ "silent", 3, false },
{ "tab", 3, true },
{ "topleft", 2, false },
{ "unsilent", 3, false },
{ "verbose", 4, true },
{ "vertical", 4, false },
};
/// @return length of a command modifier (including optional count) or,
/// zero when it's not a modifier.
int modifier_len(char *cmd)
{
char *p = cmd;
if (ascii_isdigit(*cmd)) {
p = skipwhite(skipdigits(cmd + 1));
}
for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) {
int j;
for (j = 0; p[j] != NUL; j++) {
if (p[j] != cmdmods[i].name[j]) {
break;
}
}
if (j >= cmdmods[i].minlen
&& !ASCII_ISALPHA(p[j])
&& (p == cmd || cmdmods[i].has_count)) {
return j + (int)(p - cmd);
}
}
return 0;
}
/// @return > 0 if an Ex command "name" exists or,
/// 2 if there is an exact match or,
/// 3 if there is an ambiguous match.
int cmd_exists(const char *const name)
{
// Check command modifiers.
for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) {
int j;
for (j = 0; name[j] != NUL; j++) {
if (name[j] != cmdmods[i].name[j]) {
break;
}
}
if (name[j] == NUL && j >= cmdmods[i].minlen) {
return cmdmods[i].name[j] == NUL ? 2 : 1;
}
}
// Check built-in commands and user defined commands.
// For ":2match" and ":3match" we need to skip the number.
exarg_T ea;
ea.cmd = (char *)((*name == '2' || *name == '3') ? name + 1 : name);
ea.cmdidx = (cmdidx_T)0;
ea.flags = 0;
int full = false;
char *p = find_ex_command(&ea, &full);
if (p == NULL) {
return 3;
}
if (ascii_isdigit(*name) && ea.cmdidx != CMD_match) {
return 0;
}
if (*skipwhite(p) != NUL) {
return 0; // trailing garbage
}
return ea.cmdidx == CMD_SIZE ? 0 : (full ? 2 : 1);
}
/// "fullcommand" function
void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
char *name = argvars[0].vval.v_string;
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
if (name == NULL) {
return;
}
while (*name == ':') {
name++;
}
name = skip_range(name, NULL);
exarg_T ea;
ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name;
ea.cmdidx = (cmdidx_T)0;
ea.flags = 0;
char *p = find_ex_command(&ea, NULL);
if (p == NULL || ea.cmdidx == CMD_SIZE) {
return;
}
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)
{
if (len == 3 && strncmp("def", cmd, 3) == 0) {
// Make :def an unknown command to avoid confusing behavior. #23149
return CMD_SIZE;
}
cmdidx_T idx;
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;
}
}
}
return idx;
}
uint32_t excmd_get_argt(cmdidx_T idx)
{
return cmdnames[(int)idx].cmd_argt;
}
/// 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.
///
/// @param ctx pointer to xp_context or NULL
///
/// @return the "cmd" pointer advanced to beyond the range.
char *skip_range(const char *cmd, int *ctx)
{
while (vim_strchr(" \t0123456789.$%'/?-+,;\\", (uint8_t)(*cmd)) != NULL) {
if (*cmd == '\\') {
if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') {
cmd++;
} else {
break;
}
} else if (*cmd == '\'') {
if (*++cmd == NUL && ctx != NULL) {
*ctx = EXPAND_NOTHING;
}
} else if (*cmd == '/' || *cmd == '?') {
unsigned delim = (unsigned)(*cmd++);
while (*cmd != NUL && *cmd != (char)delim) {
if (*cmd++ == '\\' && *cmd != NUL) {
cmd++;
}
}
if (*cmd == NUL && ctx != NULL) {
*ctx = EXPAND_NOTHING;
}
}
if (*cmd != NUL) {
cmd++;
}
}
// Skip ":" and white space.
cmd = skip_colon_white(cmd, false);
return (char *)cmd;
}
static const char *addr_error(cmd_addr_T addr_type)
{
if (addr_type == ADDR_NONE) {
return _(e_norange);
} else {
return _(e_invrange);
}
}
/// Gets a single EX address.
///
/// Sets ptr to the next character after the part that was interpreted.
/// Sets ptr to NULL when an error is encountered (stored in `errormsg`).
/// May set the last used search pattern.
///
/// @param skip only skip the address, don't use it
/// @param silent no errors or side effects
/// @param to_other_file flag: may jump to other file
/// @param address_count 1 for first, >1 after comma
/// @param errormsg Error message, if any
///
/// @return MAXLNUM when no Ex address was found.
static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int skip, bool silent,
int to_other_file, int address_count, const char **errormsg)
FUNC_ATTR_NONNULL_ALL
{
int c;
int i;
linenr_T n;
pos_T pos;
buf_T *buf;
char *cmd = skipwhite(*ptr);
linenr_T lnum = MAXLNUM;
do {
switch (*cmd) {
case '.': // '.' - Cursor position
cmd++;
switch (addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
lnum = curwin->w_cursor.lnum;
break;
case ADDR_WINDOWS:
lnum = CURRENT_WIN_NR;
break;
case ADDR_ARGUMENTS:
lnum = curwin->w_arg_idx + 1;
break;
case ADDR_LOADED_BUFFERS:
case ADDR_BUFFERS:
lnum = curbuf->b_fnum;
break;
case ADDR_TABS:
lnum = CURRENT_TAB_NR;
break;
case ADDR_NONE:
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
*errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
break;
case ADDR_QUICKFIX:
lnum = (linenr_T)qf_get_cur_idx(eap);
break;
case ADDR_QUICKFIX_VALID:
lnum = qf_get_cur_valid_idx(eap);
break;
}
break;
case '$': // '$' - last line
cmd++;
switch (addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
lnum = curbuf->b_ml.ml_line_count;
break;
case ADDR_WINDOWS:
lnum = LAST_WIN_NR;
break;
case ADDR_ARGUMENTS:
lnum = ARGCOUNT;
break;
case ADDR_LOADED_BUFFERS:
buf = lastbuf;
while (buf->b_ml.ml_mfp == NULL) {
if (buf->b_prev == NULL) {
break;
}
buf = buf->b_prev;
}
lnum = buf->b_fnum;
break;
case ADDR_BUFFERS:
lnum = lastbuf->b_fnum;
break;
case ADDR_TABS:
lnum = LAST_TAB_NR;
break;
case ADDR_NONE:
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
*errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
break;
case ADDR_QUICKFIX:
lnum = (linenr_T)qf_get_size(eap);
if (lnum == 0) {
lnum = 1;
}
break;
case ADDR_QUICKFIX_VALID:
lnum = (linenr_T)qf_get_valid_size(eap);
if (lnum == 0) {
lnum = 1;
}
break;
}
break;
case '\'': // ''' - mark
if (*++cmd == NUL) {
cmd = NULL;
goto error;
}
if (addr_type != ADDR_LINES) {
*errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
if (skip) {
cmd++;
} else {
// Only accept a mark in another file when it is
// used by itself: ":'M".
MarkGet flag = to_other_file && cmd[1] == NUL ? kMarkAll : kMarkBufLocal;
fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, *cmd);
cmd++;
if (fm != NULL && fm->fnum != curbuf->handle) {
// Jumped to another file.
lnum = curwin->w_cursor.lnum;
} else {
if (!mark_check(fm, errormsg)) {
cmd = NULL;
goto error;
}
assert(fm != NULL);
lnum = fm->mark.lnum;
}
}
break;
case '/':
case '?': // '/' or '?' - search
c = (uint8_t)(*cmd++);
if (addr_type != ADDR_LINES) {
*errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
if (skip) { // skip "/pat/"
cmd = skip_regexp(cmd, c, magic_isset());
if (*cmd == c) {
cmd++;
}
} else {
int flags;
pos = curwin->w_cursor; // save curwin->w_cursor
// When '/' or '?' follows another address, start from
// there.
if (lnum > 0 && lnum != MAXLNUM) {
curwin->w_cursor.lnum
= lnum > curbuf->b_ml.ml_line_count ? curbuf->b_ml.ml_line_count : lnum;
}
// Start a forward search at the end of the line (unless
// before the first line).
// Start a backward search at the start of the line.
// This makes sure we never match in the current
// line, and can match anywhere in the
// next/previous line.
if (c == '/' && curwin->w_cursor.lnum > 0) {
curwin->w_cursor.col = MAXCOL;
} else {
curwin->w_cursor.col = 0;
}
searchcmdlen = 0;
flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG;
if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
}
lnum = curwin->w_cursor.lnum;
curwin->w_cursor = pos;
// adjust command string pointer
cmd += searchcmdlen;
}
break;
case '\\': // "\?", "\/" or "\&", repeat search
cmd++;
if (addr_type != ADDR_LINES) {
*errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
if (*cmd == '&') {
i = RE_SUBST;
} else if (*cmd == '?' || *cmd == '/') {
i = RE_SEARCH;
} else {
*errormsg = _(e_backslash);
cmd = NULL;
goto error;
}
if (!skip) {
// When search follows another address, start from there.
pos.lnum = (lnum != MAXLNUM) ? lnum : curwin->w_cursor.lnum;
// Start the search just like for the above do_search().
pos.col = (*cmd != '?') ? MAXCOL : 0;
pos.coladd = 0;
if (searchit(curwin, curbuf, &pos, NULL,
*cmd == '?' ? BACKWARD : FORWARD,
"", 1L, SEARCH_MSG, i, NULL) != FAIL) {
lnum = pos.lnum;
} else {
cmd = NULL;
goto error;
}
}
cmd++;
break;
default:
if (ascii_isdigit(*cmd)) { // absolute line number
lnum = (linenr_T)getdigits(&cmd, false, 0);
}
}
while (true) {
cmd = skipwhite(cmd);
if (*cmd != '-' && *cmd != '+' && !ascii_isdigit(*cmd)) {
break;
}
if (lnum == MAXLNUM) {
switch (addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
// "+1" is same as ".+1"
lnum = curwin->w_cursor.lnum;
break;
case ADDR_WINDOWS:
lnum = CURRENT_WIN_NR;
break;
case ADDR_ARGUMENTS:
lnum = curwin->w_arg_idx + 1;
break;
case ADDR_LOADED_BUFFERS:
case ADDR_BUFFERS:
lnum = curbuf->b_fnum;
break;
case ADDR_TABS:
lnum = CURRENT_TAB_NR;
break;
case ADDR_TABS_RELATIVE:
lnum = 1;
break;
case ADDR_QUICKFIX:
lnum = (linenr_T)qf_get_cur_idx(eap);
break;
case ADDR_QUICKFIX_VALID:
lnum = qf_get_cur_valid_idx(eap);
break;
case ADDR_NONE:
case ADDR_UNSIGNED:
lnum = 0;
break;
}
}
if (ascii_isdigit(*cmd)) {
i = '+'; // "number" is same as "+number"
} else {
i = (uint8_t)(*cmd++);
}
if (!ascii_isdigit(*cmd)) { // '+' is '+1'
n = 1;
} else {
// "number", "+number" or "-number"
n = getdigits_int32(&cmd, false, MAXLNUM);
if (n == MAXLNUM) {
*errormsg = _(e_line_number_out_of_range);
goto error;
}
}
if (addr_type == ADDR_TABS_RELATIVE) {
*errormsg = _(e_invrange);
cmd = NULL;
goto error;
} else if (addr_type == ADDR_LOADED_BUFFERS || addr_type == ADDR_BUFFERS) {
lnum = compute_buffer_local_count(addr_type, lnum, (i == '-') ? -1 * n : n);
} else {
// Relative line addressing: need to adjust for lines in a
// closed fold after the first address.
if (addr_type == ADDR_LINES && (i == '-' || i == '+')
&& address_count >= 2) {
(void)hasFolding(lnum, NULL, &lnum);
}
if (i == '-') {
lnum -= n;
} else {
if (n >= INT32_MAX - lnum) {
*errormsg = _(e_line_number_out_of_range);
goto error;
}
lnum += n;
}
}
}
} while (*cmd == '/' || *cmd == '?');
error:
*ptr = cmd;
return lnum;
}
/// Get flags from an Ex command argument.
static void get_flags(exarg_T *eap)
{
while (vim_strchr("lp#", (uint8_t)(*eap->arg)) != NULL) {
if (*eap->arg == 'l') {
eap->flags |= EXFLAG_LIST;
} else if (*eap->arg == 'p') {
eap->flags |= EXFLAG_PRINT;
} else {
eap->flags |= EXFLAG_NR;
}
eap->arg = skipwhite(eap->arg + 1);
}
}
/// Stub function for command which is Not Implemented. NI!
void ex_ni(exarg_T *eap)
{
if (!eap->skip) {
eap->errmsg = _("E319: The command is not available in this version");
}
}
/// Stub function for script command which is Not Implemented. NI!
/// Skips over ":perl <<EOF" constructs.
static void ex_script_ni(exarg_T *eap)
{
if (!eap->skip) {
ex_ni(eap);
} else {
size_t len;
xfree(script_get(eap, &len));
}
}
/// Check range in Ex command for validity.
///
/// @return NULL when valid, error message when invalid.
char *invalid_range(exarg_T *eap)
{
buf_T *buf;
if (eap->line1 < 0 || eap->line2 < 0 || eap->line1 > eap->line2) {
return _(e_invrange);
}
if (eap->argt & EX_RANGE) {
switch (eap->addr_type) {
case ADDR_LINES:
if (eap->line2 > (curbuf->b_ml.ml_line_count
+ (eap->cmdidx == CMD_diffget))) {
return _(e_invrange);
}
break;
case ADDR_ARGUMENTS:
// add 1 if ARGCOUNT is 0
if (eap->line2 > ARGCOUNT + (!ARGCOUNT)) {
return _(e_invrange);
}
break;
case ADDR_BUFFERS:
// Only a boundary check, not whether the buffers actually
// exist.
if (eap->line1 < 1 || eap->line2 > get_highest_fnum()) {
return _(e_invrange);
}
break;
case ADDR_LOADED_BUFFERS:
buf = firstbuf;
while (buf->b_ml.ml_mfp == NULL) {
if (buf->b_next == NULL) {
return _(e_invrange);
}
buf = buf->b_next;
}
if (eap->line1 < buf->b_fnum) {
return _(e_invrange);
}
buf = lastbuf;
while (buf->b_ml.ml_mfp == NULL) {
if (buf->b_prev == NULL) {
return _(e_invrange);
}
buf = buf->b_prev;
}
if (eap->line2 > buf->b_fnum) {
return _(e_invrange);
}
break;
case ADDR_WINDOWS:
if (eap->line2 > LAST_WIN_NR) {
return _(e_invrange);
}
break;
case ADDR_TABS:
if (eap->line2 > LAST_TAB_NR) {
return _(e_invrange);
}
break;
case ADDR_TABS_RELATIVE:
case ADDR_OTHER:
// Any range is OK.
break;
case ADDR_QUICKFIX:
assert(eap->line2 >= 0);
// No error for value that is too big, will use the last entry.
if (eap->line2 <= 0) {
if (eap->addr_count == 0) {
return _(e_no_errors);
}
return _(e_invrange);
}
break;
case ADDR_QUICKFIX_VALID:
if ((eap->line2 != 1 && (size_t)eap->line2 > qf_get_valid_size(eap))
|| eap->line2 < 0) {
return _(e_invrange);
}
break;
case ADDR_UNSIGNED:
case ADDR_NONE:
// Will give an error elsewhere.
break;
}
}
return NULL;
}
/// Correct the range for zero line number, if required.
static void correct_range(exarg_T *eap)
{
if (!(eap->argt & EX_ZEROR)) { // zero in range not allowed
if (eap->line1 == 0) {
eap->line1 = 1;
}
if (eap->line2 == 0) {
eap->line2 = 1;
}
}
}
/// For a ":vimgrep" or ":vimgrepadd" command return a pointer past the
/// pattern. Otherwise return eap->arg.
static char *skip_grep_pat(exarg_T *eap)
{
char *p = eap->arg;
if (*p != NUL && (eap->cmdidx == CMD_vimgrep || eap->cmdidx == CMD_lvimgrep
|| eap->cmdidx == CMD_vimgrepadd
|| eap->cmdidx == CMD_lvimgrepadd
|| grep_internal(eap->cmdidx))) {
p = skip_vimgrep_pat(p, NULL, NULL);
if (p == NULL) {
p = eap->arg;
}
}
return p;
}
/// For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option
/// in the command line, so that things like % get expanded.
char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep)
{
bool isgrep = eap->cmdidx == CMD_grep
|| eap->cmdidx == CMD_lgrep
|| eap->cmdidx == CMD_grepadd
|| eap->cmdidx == CMD_lgrepadd;
// Don't do it when ":vimgrep" is used for ":grep".
if ((eap->cmdidx == CMD_make || eap->cmdidx == CMD_lmake || isgrep)
&& !grep_internal(eap->cmdidx)) {
const char *program = isgrep ? (*curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp)
: (*curbuf->b_p_mp == NUL ? p_mp : curbuf->b_p_mp);
arg = skipwhite(arg);
char *new_cmdline;
// Replace $* by given arguments
if ((new_cmdline = strrep(program, "$*", arg)) == NULL) {
// No $* in arg, build "<makeprg> <arg>" instead
new_cmdline = xmalloc(strlen(program) + strlen(arg) + 2);
STRCPY(new_cmdline, program);
STRCAT(new_cmdline, " ");
STRCAT(new_cmdline, arg);
}
msg_make(arg);
// 'eap->cmd' is not set here, because it is not used at CMD_make
xfree(*cmdlinep);
*cmdlinep = new_cmdline;
arg = new_cmdline;
}
return arg;
}
/// Expand file name in Ex command argument.
/// When an error is detected, "errormsgp" is set to a non-NULL pointer.
///
/// @return FAIL for failure, OK otherwise.
int expand_filename(exarg_T *eap, char **cmdlinep, const char **errormsgp)
{
// Skip a regexp pattern for ":vimgrep[add] pat file..."
char *p = skip_grep_pat(eap);
// Decide to expand wildcards *before* replacing '%', '#', etc. If
// the file name contains a wildcard it should not cause expanding.
// (it will be expanded anyway if there is a wildcard before replacing).
int has_wildcards = path_has_wildcard(p);
while (*p != NUL) {
// Skip over `=expr`, wildcards in it are not expanded.
if (p[0] == '`' && p[1] == '=') {
p += 2;
(void)skip_expr(&p, NULL);
if (*p == '`') {
p++;
}
continue;
}
// Quick check if this cannot be the start of a special string.
// Also removes backslash before '%', '#' and '<'.
if (vim_strchr("%#<", (uint8_t)(*p)) == NULL) {
p++;
continue;
}
// Try to find a match at this position.
size_t srclen;
int escaped;
char *repl = eval_vars(p, eap->arg, &srclen, &(eap->do_ecmd_lnum),
errormsgp, &escaped, true);
if (*errormsgp != NULL) { // error detected
return FAIL;
}
if (repl == NULL) { // no match found
p += srclen;
continue;
}
// Wildcards won't be expanded below, the replacement is taken
// literally. But do expand "~/file", "~user/file" and "$HOME/file".
if (vim_strchr(repl, '$') != NULL || vim_strchr(repl, '~') != NULL) {
char *l = repl;
repl = expand_env_save(repl);
xfree(l);
}
// Need to escape white space et al. with a backslash.
// Don't do this for:
// - replacement that already has been escaped: "##"
// - shell commands (may have to use quotes instead).
if (!eap->usefilter
&& !escaped
&& eap->cmdidx != CMD_bang
&& eap->cmdidx != CMD_grep
&& eap->cmdidx != CMD_grepadd
&& eap->cmdidx != CMD_lgrep
&& eap->cmdidx != CMD_lgrepadd
&& eap->cmdidx != CMD_lmake
&& eap->cmdidx != CMD_make
&& eap->cmdidx != CMD_terminal
&& !(eap->argt & EX_NOSPC)) {
char *l;
#ifdef BACKSLASH_IN_FILENAME
// Don't escape a backslash here, because rem_backslash() doesn't
// remove it later.
static char *nobslash = " \t\"|";
# define ESCAPE_CHARS nobslash
#else
# define ESCAPE_CHARS escape_chars
#endif
for (l = repl; *l; l++) {
if (vim_strchr(ESCAPE_CHARS, (uint8_t)(*l)) != NULL) {
l = vim_strsave_escaped(repl, ESCAPE_CHARS);
xfree(repl);
repl = l;
break;
}
}
}
// For a shell command a '!' must be escaped.
if ((eap->usefilter
|| eap->cmdidx == CMD_bang
|| eap->cmdidx == CMD_terminal)
&& strpbrk(repl, "!") != NULL) {
char *l;
l = vim_strsave_escaped(repl, "!");
xfree(repl);
repl = l;
}
p = repl_cmdline(eap, p, srclen, repl, cmdlinep);
xfree(repl);
}
// One file argument: Expand wildcards.
// Don't do this with ":r !command" or ":w !command".
if ((eap->argt & EX_NOSPC) && !eap->usefilter) {
// Replace environment variables.
if (has_wildcards) {
// May expand environment variables. This
// can be done much faster with expand_env() than with
// something else (e.g., calling a shell).
// After expanding environment variables, check again
// if there are still wildcards present.
if (vim_strchr(eap->arg, '$') != NULL
|| vim_strchr(eap->arg, '~') != NULL) {
expand_env_esc(eap->arg, NameBuff, MAXPATHL, true, true, NULL);
has_wildcards = path_has_wildcard(NameBuff);
p = NameBuff;
} else {
p = NULL;
}
if (p != NULL) {
(void)repl_cmdline(eap, eap->arg, strlen(eap->arg), p, cmdlinep);
}
}
// Halve the number of backslashes (this is Vi compatible).
// For Unix, when wildcards are expanded, this is
// done by ExpandOne() below.
#ifdef UNIX
if (!has_wildcards) {
backslash_halve(eap->arg);
}
#else
backslash_halve(eap->arg);
#endif
if (has_wildcards) {
expand_T xpc;
int options = WILD_LIST_NOTFOUND | WILD_NOERROR | WILD_ADD_SLASH;
ExpandInit(&xpc);
xpc.xp_context = EXPAND_FILES;
if (p_wic) {
options += WILD_ICASE;
}
p = ExpandOne(&xpc, eap->arg, NULL, options, WILD_EXPAND_FREE);
if (p == NULL) {
return FAIL;
}
(void)repl_cmdline(eap, eap->arg, strlen(eap->arg), p, cmdlinep);
xfree(p);
}
}
return OK;
}
/// Replace part of the command line, keeping eap->cmd, eap->arg, eap->args and
/// eap->nextcmd correct.
/// "src" points to the part that is to be replaced, of length "srclen".
/// "repl" is the replacement string.
///
/// @return a pointer to the character after the replaced string.
static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, char **cmdlinep)
{
// The new command line is build in new_cmdline[].
// First allocate it.
// Careful: a "+cmd" argument may have been NUL terminated.
size_t len = strlen(repl);
size_t i = (size_t)(src - *cmdlinep) + strlen(src + srclen) + len + 3;
if (eap->nextcmd != NULL) {
i += strlen(eap->nextcmd); // add space for next command
}
char *new_cmdline = xmalloc(i);
size_t offset = (size_t)(src - *cmdlinep);
// Copy the stuff before the expanded part.
// Copy the expanded stuff.
// Copy what came after the expanded part.
// Copy the next commands, if there are any.
i = offset; // length of part before match
memmove(new_cmdline, *cmdlinep, i);
memmove(new_cmdline + i, repl, len);
i += len; // remember the end of the string
STRCPY(new_cmdline + i, src + srclen);
src = new_cmdline + i; // remember where to continue
if (eap->nextcmd != NULL) { // append next command
i = strlen(new_cmdline) + 1;
STRCPY(new_cmdline + i, eap->nextcmd);
eap->nextcmd = new_cmdline + i;
}
eap->cmd = new_cmdline + (eap->cmd - *cmdlinep);
eap->arg = new_cmdline + (eap->arg - *cmdlinep);
for (size_t j = 0; j < eap->argc; j++) {
if (offset >= (size_t)(eap->args[j] - *cmdlinep)) {
// If replaced text is after or in the same position as the argument,
// the argument's position relative to the beginning of the cmdline stays the same.
eap->args[j] = new_cmdline + (eap->args[j] - *cmdlinep);
} else {
// Otherwise, argument gets shifted alongside the replaced text.
// The amount of the shift is equal to the difference of the old and new string length.
eap->args[j] = new_cmdline + ((eap->args[j] - *cmdlinep) + (ptrdiff_t)(len - srclen));
}
}
if (eap->do_ecmd_cmd != NULL && eap->do_ecmd_cmd != dollar_command) {
eap->do_ecmd_cmd = new_cmdline + (eap->do_ecmd_cmd - *cmdlinep);
}
xfree(*cmdlinep);
*cmdlinep = new_cmdline;
return src;
}
/// Check for '|' to separate commands and '"' to start comments.
void separate_nextcmd(exarg_T *eap)
{
char *p = skip_grep_pat(eap);
for (; *p; MB_PTR_ADV(p)) {
if (*p == Ctrl_V) {
if (eap->argt & (EX_CTRLV | EX_XFILE)) {
p++; // skip CTRL-V and next char
} else {
// remove CTRL-V and skip next char
STRMOVE(p, p + 1);
}
if (*p == NUL) { // stop at NUL after CTRL-V
break;
}
} else if (p[0] == '`' && p[1] == '=' && (eap->argt & EX_XFILE)) {
// Skip over `=expr` when wildcards are expanded.
p += 2;
(void)skip_expr(&p, NULL);
if (*p == NUL) { // stop at NUL after CTRL-V
break;
}
} else if (
// Check for '"': start of comment or '|': next command */
// :@" does not start a comment!
// :redir @" doesn't either.
(*p == '"'
&& !(eap->argt & EX_NOTRLCOM)
&& (eap->cmdidx != CMD_at || p != eap->arg)
&& (eap->cmdidx != CMD_redir
|| p != eap->arg + 1 || p[-1] != '@')) || *p == '|' || *p == '\n') {
// We remove the '\' before the '|', unless EX_CTRLV is used
// AND 'b' is present in 'cpoptions'.
if ((vim_strchr(p_cpo, CPO_BAR) == NULL
|| !(eap->argt & EX_CTRLV)) && *(p - 1) == '\\') {
STRMOVE(p - 1, p); // remove the '\'
p--;
} else {
eap->nextcmd = check_nextcmd(p);
*p = NUL;
break;
}
}
}
if (!(eap->argt & EX_NOTRLCOM)) { // remove trailing spaces
del_trailing_spaces(eap->arg);
}
}
/// get + command from ex argument
static char *getargcmd(char **argp)
{
char *arg = *argp;
char *command = NULL;
if (*arg == '+') { // +[command]
arg++;
if (ascii_isspace(*arg) || *arg == '\0') {
command = dollar_command;
} else {
command = arg;
arg = skip_cmd_arg(command, true);
if (*arg != NUL) {
*arg++ = NUL; // terminate command with NUL
}
}
arg = skipwhite(arg); // skip over spaces
*argp = arg;
}
return command;
}
/// Find end of "+command" argument. Skip over "\ " and "\\".
///
/// @param rembs true to halve the number of backslashes
char *skip_cmd_arg(char *p, int rembs)
{
while (*p && !ascii_isspace(*p)) {
if (*p == '\\' && p[1] != NUL) {
if (rembs) {
STRMOVE(p, p + 1);
} else {
p++;
}
}
MB_PTR_ADV(p);
}
return p;
}
int get_bad_opt(const char *p, exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
if (STRICMP(p, "keep") == 0) {
eap->bad_char = BAD_KEEP;
} else if (STRICMP(p, "drop") == 0) {
eap->bad_char = BAD_DROP;
} else if (MB_BYTE2LEN((uint8_t)(*p)) == 1 && p[1] == NUL) {
eap->bad_char = (uint8_t)(*p);
} else {
return FAIL;
}
return OK;
}
/// Get "++opt=arg" argument.
///
/// @return FAIL or OK.
static int getargopt(exarg_T *eap)
{
char *arg = eap->arg + 2;
int *pp = NULL;
int bad_char_idx;
// ":edit ++[no]bin[ary] file"
if (strncmp(arg, "bin", 3) == 0 || strncmp(arg, "nobin", 5) == 0) {
if (*arg == 'n') {
arg += 2;
eap->force_bin = FORCE_NOBIN;
} else {
eap->force_bin = FORCE_BIN;
}
if (!checkforcmd(&arg, "binary", 3)) {
return FAIL;
}
eap->arg = skipwhite(arg);
return OK;
}
// ":read ++edit file"
if (strncmp(arg, "edit", 4) == 0) {
eap->read_edit = true;
eap->arg = skipwhite(arg + 4);
return OK;
}
// ":write ++p foo/bar/file
if (strncmp(arg, "p", 1) == 0) {
eap->mkdir_p = true;
eap->arg = skipwhite(arg + 1);
return OK;
}
if (strncmp(arg, "ff", 2) == 0) {
arg += 2;
pp = &eap->force_ff;
} else if (strncmp(arg, "fileformat", 10) == 0) {
arg += 10;
pp = &eap->force_ff;
} else if (strncmp(arg, "enc", 3) == 0) {
if (strncmp(arg, "encoding", 8) == 0) {
arg += 8;
} else {
arg += 3;
}
pp = &eap->force_enc;
} else if (strncmp(arg, "bad", 3) == 0) {
arg += 3;
pp = &bad_char_idx;
}
if (pp == NULL || *arg != '=') {
return FAIL;
}
arg++;
*pp = (int)(arg - eap->cmd);
arg = skip_cmd_arg(arg, false);
eap->arg = skipwhite(arg);
*arg = NUL;
if (pp == &eap->force_ff) {
if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) {
return FAIL;
}
eap->force_ff = (uint8_t)eap->cmd[eap->force_ff];
} else if (pp == &eap->force_enc) {
// Make 'fileencoding' lower case.
for (char *p = eap->cmd + eap->force_enc; *p != NUL; p++) {
*p = (char)TOLOWER_ASC(*p);
}
} else {
// Check ++bad= argument. Must be a single-byte character, "keep" or
// "drop".
if (get_bad_opt(eap->cmd + bad_char_idx, eap) == FAIL) {
return FAIL;
}
}
return OK;
}
/// Handle the argument for a tabpage related ex command.
/// When an error is encountered then eap->errmsg is set.
///
/// @return a tabpage number.
static int get_tabpage_arg(exarg_T *eap)
{
int tab_number = 0;
int unaccept_arg0 = (eap->cmdidx == CMD_tabmove) ? 0 : 1;
if (eap->arg && *eap->arg != NUL) {
char *p = eap->arg;
int relative = 0; // argument +N/-N means: go to N places to the
// right/left relative to the current position.
if (*p == '-') {
relative = -1;
p++;
} else if (*p == '+') {
relative = 1;
p++;
}
char *p_save = p;
tab_number = (int)getdigits(&p, false, tab_number);
if (relative == 0) {
if (strcmp(p, "$") == 0) {
tab_number = LAST_TAB_NR;
} else if (strcmp(p, "#") == 0) {
if (valid_tabpage(lastused_tabpage)) {
tab_number = tabpage_index(lastused_tabpage);
} else {
eap->errmsg = ex_errmsg(e_invargval, eap->arg);
tab_number = 0;
goto theend;
}
} else if (p == p_save || *p_save == '-' || *p != NUL
|| tab_number > LAST_TAB_NR) {
// No numbers as argument.
eap->errmsg = ex_errmsg(e_invarg2, eap->arg);
goto theend;
}
} else {
if (*p_save == NUL) {
tab_number = 1;
} else if (p == p_save || *p_save == '-' || *p != NUL || tab_number == 0) {
// No numbers as argument.
eap->errmsg = ex_errmsg(e_invarg2, eap->arg);
goto theend;
}
tab_number = tab_number * relative + tabpage_index(curtab);
if (!unaccept_arg0 && relative == -1) {
tab_number--;
}
}
if (tab_number < unaccept_arg0 || tab_number > LAST_TAB_NR) {
eap->errmsg = ex_errmsg(e_invarg2, eap->arg);
}
} else if (eap->addr_count > 0) {
if (unaccept_arg0 && eap->line2 == 0) {
eap->errmsg = _(e_invrange);
tab_number = 0;
} else {
tab_number = (int)eap->line2;
char *cmdp = eap->cmd;
while (--cmdp > *eap->cmdlinep && (*cmdp == ' ' || ascii_isdigit(*cmdp))) {}
if (!unaccept_arg0 && *cmdp == '-') {
tab_number--;
if (tab_number < unaccept_arg0) {
eap->errmsg = _(e_invrange);
}
}
}
} else {
switch (eap->cmdidx) {
case CMD_tabnext:
tab_number = tabpage_index(curtab) + 1;
if (tab_number > LAST_TAB_NR) {
tab_number = 1;
}
break;
case CMD_tabmove:
tab_number = LAST_TAB_NR;
break;
default:
tab_number = tabpage_index(curtab);
}
}
theend:
return tab_number;
}
static void ex_autocmd(exarg_T *eap)
{
// Disallow autocommands in secure mode.
if (secure) {
secure = 2;
eap->errmsg = _(e_curdir);
} else if (eap->cmdidx == CMD_autocmd) {
do_autocmd(eap, eap->arg, eap->forceit);
} else {
do_augroup(eap->arg, eap->forceit);
}
}
/// ":doautocmd": Apply the automatic commands to the current buffer.
static void ex_doautocmd(exarg_T *eap)
{
char *arg = eap->arg;
int call_do_modelines = check_nomodeline(&arg);
bool did_aucmd;
(void)do_doautocmd(arg, false, &did_aucmd);
// Only when there is no <nomodeline>.
if (call_do_modelines && did_aucmd) {
do_modelines(0);
}
}
/// :[N]bunload[!] [N] [bufname] unload buffer
/// :[N]bdelete[!] [N] [bufname] delete buffer from buffer list
/// :[N]bwipeout[!] [N] [bufname] delete buffer really
static void ex_bunload(exarg_T *eap)
{
eap->errmsg = do_bufdel(eap->cmdidx == CMD_bdelete
? DOBUF_DEL
: eap->cmdidx == CMD_bwipeout
? DOBUF_WIPE
: DOBUF_UNLOAD,
eap->arg, eap->addr_count, (int)eap->line1, (int)eap->line2,
eap->forceit);
}
/// :[N]buffer [N] to buffer N
/// :[N]sbuffer [N] to buffer N
static void ex_buffer(exarg_T *eap)
{
if (*eap->arg) {
eap->errmsg = ex_errmsg(e_trailing_arg, eap->arg);
} else {
if (eap->addr_count == 0) { // default is current buffer
goto_buffer(eap, DOBUF_CURRENT, FORWARD, 0);
} else {
goto_buffer(eap, DOBUF_FIRST, FORWARD, (int)eap->line2);
}
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
}
}
/// :[N]bmodified [N] to next mod. buffer
/// :[N]sbmodified [N] to next mod. buffer
static void ex_bmodified(exarg_T *eap)
{
goto_buffer(eap, DOBUF_MOD, FORWARD, (int)eap->line2);
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
}
/// :[N]bnext [N] to next buffer
/// :[N]sbnext [N] split and to next buffer
static void ex_bnext(exarg_T *eap)
{
goto_buffer(eap, DOBUF_CURRENT, FORWARD, (int)eap->line2);
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
}
/// :[N]bNext [N] to previous buffer
/// :[N]bprevious [N] to previous buffer
/// :[N]sbNext [N] split and to previous buffer
/// :[N]sbprevious [N] split and to previous buffer
static void ex_bprevious(exarg_T *eap)
{
goto_buffer(eap, DOBUF_CURRENT, BACKWARD, (int)eap->line2);
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
}
/// :brewind to first buffer
/// :bfirst to first buffer
/// :sbrewind split and to first buffer
/// :sbfirst split and to first buffer
static void ex_brewind(exarg_T *eap)
{
goto_buffer(eap, DOBUF_FIRST, FORWARD, 0);
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
}
/// :blast to last buffer
/// :sblast split and to last buffer
static void ex_blast(exarg_T *eap)
{
goto_buffer(eap, DOBUF_LAST, BACKWARD, 0);
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
}
int ends_excmd(int c) FUNC_ATTR_CONST
{
return c == NUL || c == '|' || c == '"' || c == '\n';
}
/// @return the next command, after the first '|' or '\n' or,
/// NULL if not found.
char *find_nextcmd(const char *p)
{
while (*p != '|' && *p != '\n') {
if (*p == NUL) {
return NULL;
}
p++;
}
return (char *)p + 1;
}
/// Check if *p is a separator between Ex commands, skipping over white space.
///
/// @return NULL if it isn't, the following character if it is.
char *check_nextcmd(char *p)
{
char *s = skipwhite(p);
if (*s == '|' || *s == '\n') {
return s + 1;
}
return NULL;
}
/// - if there are more files to edit
/// - and this is the last window
/// - and forceit not used
/// - and not repeated twice on a row
///
/// @param message when false check only, no messages
///
/// @return FAIL and give error message if 'message' true, return OK otherwise
static int check_more(int message, bool forceit)
{
int n = ARGCOUNT - curwin->w_arg_idx - 1;
if (!forceit && only_one_window()
&& ARGCOUNT > 1 && !arg_had_last && n > 0 && quitmore == 0) {
if (message) {
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && curbuf->b_fname != NULL) {
char buff[DIALOG_MSG_SIZE];
vim_snprintf(buff, DIALOG_MSG_SIZE,
NGETTEXT("%d more file to edit. Quit anyway?",
"%d more files to edit. Quit anyway?", n), n);
if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1) == VIM_YES) {
return OK;
}
return FAIL;
}
semsg(NGETTEXT("E173: %" PRId64 " more file to edit",
"E173: %" PRId64 " more files to edit", n), (int64_t)n);
quitmore = 2; // next try to quit is allowed
}
return FAIL;
}
return OK;
}
/// Function given to ExpandGeneric() to obtain the list of command names.
char *get_command_name(expand_T *xp, int idx)
{
if (idx >= CMD_SIZE) {
return expand_user_command_name(idx);
}
return cmdnames[idx].cmd_name;
}
static void ex_colorscheme(exarg_T *eap)
{
if (*eap->arg == NUL) {
char *expr = xstrdup("g:colors_name");
emsg_off++;
char *p = eval_to_string(expr, false);
emsg_off--;
xfree(expr);
if (p != NULL) {
msg(p);
xfree(p);
} else {
msg("default");
}
} else if (load_colors(eap->arg) == FAIL) {
semsg(_("E185: Cannot find color scheme '%s'"), eap->arg);
}
}
static void ex_highlight(exarg_T *eap)
{
if (*eap->arg == NUL && eap->cmd[2] == '!') {
msg(_("Greetings, Vim user!"));
}
do_highlight(eap->arg, eap->forceit, false);
}
/// Call this function if we thought we were going to exit, but we won't
/// (because of an error). May need to restore the terminal mode.
void not_exiting(void)
{
exiting = false;
}
bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
{
apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
// Bail out when autocommands closed the window.
// Refuse to quit when the buffer in the last window is being closed (can
// only happen in autocommands).
if (!win_valid(wp)
|| curbuf_locked()
|| (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) {
return true;
}
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 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;
}
}
return false;
}
/// ":quit": quit current window, quit Vim if the last window is closed.
/// ":{nr}quit": quit window {nr}
static void ex_quit(exarg_T *eap)
{
if (cmdwin_type != 0) {
cmdwin_result = Ctrl_C;
return;
}
// Don't quit while editing the command line.
if (text_locked()) {
text_locked_msg();
return;
}
win_T *wp;
if (eap->addr_count > 0) {
linenr_T wnr = eap->line2;
for (wp = firstwin; wp->w_next != NULL; wp = wp->w_next) {
if (--wnr <= 0) {
break;
}
}
} else {
wp = curwin;
}
// Refuse to quit when locked.
if (curbuf_locked()) {
return;
}
// Trigger QuitPre and maybe ExitPre
if (before_quit_autocmds(wp, false, eap->forceit)) {
return;
}
// If there is only one relevant window we will exit.
if (check_more(false, eap->forceit) == OK && only_one_window()) {
exiting = true;
}
if ((!buf_hide(wp->w_buffer)
&& check_changed(wp->w_buffer, (p_awa ? CCGD_AW : 0)
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD))
|| check_more(true, eap->forceit) == FAIL
|| (only_one_window() && check_changed_any(eap->forceit, true))) {
not_exiting();
} else {
// quit last window
// Note: only_one_window() returns true, even so a help window is
// still open. In that case only quit, if no address has been
// specified. Example:
// :h|wincmd w|1q - don't quit
// :h|wincmd w|q - quit
if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) {
getout(0);
}
not_exiting();
// close window; may free buffer
win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit, eap->forceit);
}
}
/// ":cquit".
static void ex_cquit(exarg_T *eap)
FUNC_ATTR_NORETURN
{
// this does not always pass on the exit code to the Manx compiler. why?
getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE);
}
/// Do preparations for "qall" and "wqall".
/// Returns FAIL when quitting should be aborted.
int before_quit_all(exarg_T *eap)
{
if (cmdwin_type != 0) {
if (eap->forceit) {
cmdwin_result = K_XF1; // open_cmdwin() takes care of this
} else {
cmdwin_result = K_XF2;
}
return FAIL;
}
// Don't quit while editing the command line.
if (text_locked()) {
text_locked_msg();
return FAIL;
}
if (before_quit_autocmds(curwin, true, eap->forceit)) {
return FAIL;
}
return OK;
}
/// ":qall": try to quit all windows
static void ex_quit_all(exarg_T *eap)
{
if (before_quit_all(eap) == FAIL) {
return;
}
exiting = true;
if (eap->forceit || !check_changed_any(false, false)) {
getout(0);
}
not_exiting();
}
/// ":close": close current window, unless it is the last one
static void ex_close(exarg_T *eap)
{
win_T *win = NULL;
int winnr = 0;
if (cmdwin_type != 0) {
cmdwin_result = Ctrl_C;
} else if (!text_locked() && !curbuf_locked()) {
if (eap->addr_count == 0) {
ex_win_close(eap->forceit, curwin, NULL);
} else {
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
winnr++;
if (winnr == eap->line2) {
win = wp;
break;
}
}
if (win == NULL) {
win = lastwin;
}
ex_win_close(eap->forceit, win, NULL);
}
}
}
/// ":pclose": Close any preview window.
static void ex_pclose(exarg_T *eap)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (win->w_p_pvw) {
ex_win_close(eap->forceit, win, NULL);
break;
}
}
}
/// Close window "win" and take care of handling closing the last window for a
/// modified buffer.
///
/// @param tp NULL or the tab page "win" is in
void ex_win_close(int forceit, win_T *win, tabpage_T *tp)
{
// Never close the autocommand window.
if (is_aucmd_win(win)) {
emsg(_(e_autocmd_close));
return;
}
buf_T *buf = win->w_buffer;
bool need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
if (need_hide && !buf_hide(buf) && !forceit) {
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) {
bufref_T bufref;
set_bufref(&bufref, buf);
dialog_changed(buf, false);
if (bufref_valid(&bufref) && bufIsChanged(buf)) {
return;
}
need_hide = false;
} else {
no_write_message();
return;
}
}
// free buffer when not hiding it or when it's a scratch buffer
if (tp == NULL) {
win_close(win, !need_hide && !buf_hide(buf), forceit);
} else {
win_close_othertab(win, !need_hide && !buf_hide(buf), tp);
}
}
/// ":tabclose": close current tab page, unless it is the last one.
/// ":tabclose N": close tab page N.
static void ex_tabclose(exarg_T *eap)
{
if (cmdwin_type != 0) {
cmdwin_result = K_IGNORE;
return;
}
if (first_tabpage->tp_next == NULL) {
emsg(_("E784: Cannot close last tab page"));
return;
}
int tab_number = get_tabpage_arg(eap);
if (eap->errmsg != NULL) {
return;
}
tabpage_T *tp = find_tabpage(tab_number);
if (tp == NULL) {
beep_flush();
return;
}
if (tp != curtab) {
tabpage_close_other(tp, eap->forceit);
return;
} else if (!text_locked() && !curbuf_locked()) {
tabpage_close(eap->forceit);
}
}
/// ":tabonly": close all tab pages except the current one
static void ex_tabonly(exarg_T *eap)
{
if (cmdwin_type != 0) {
cmdwin_result = K_IGNORE;
return;
}
if (first_tabpage->tp_next == NULL) {
msg(_("Already only one tab page"));
return;
}
int tab_number = get_tabpage_arg(eap);
if (eap->errmsg != NULL) {
return;
}
goto_tabpage(tab_number);
// Repeat this up to a 1000 times, because autocommands may
// mess up the lists.
for (int done = 0; done < 1000; done++) {
FOR_ALL_TABS(tp) {
if (tp->tp_topframe != topframe) {
tabpage_close_other(tp, eap->forceit);
// if we failed to close it quit
if (valid_tabpage(tp)) {
done = 1000;
}
// start over, "tp" is now invalid
break;
}
}
assert(first_tabpage);
if (first_tabpage->tp_next == NULL) {
break;
}
}
}
/// Close the current tab page.
void tabpage_close(int forceit)
{
// First close all the windows but the current one. If that worked then
// close the last window in this tab, that will close it.
while (curwin->w_floating) {
ex_win_close(forceit, curwin, NULL);
}
if (!ONE_WINDOW) {
close_others(true, forceit);
}
if (ONE_WINDOW) {
ex_win_close(forceit, curwin, NULL);
}
}
/// Close tab page "tp", which is not the current tab page.
/// Note that autocommands may make "tp" invalid.
/// Also takes care of the tab pages line disappearing when closing the
/// last-but-one tab page.
void tabpage_close_other(tabpage_T *tp, int forceit)
{
int done = 0;
char prev_idx[NUMBUFLEN];
// Limit to 1000 windows, autocommands may add a window while we close
// one. OK, so I'm paranoid...
while (++done < 1000) {
snprintf(prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp));
win_T *wp = tp->tp_lastwin;
ex_win_close(forceit, wp, tp);
// Autocommands may delete the tab page under our fingers and we may
// fail to close a window with a modified buffer.
if (!valid_tabpage(tp) || tp->tp_lastwin == wp) {
break;
}
}
}
/// ":only".
static void ex_only(exarg_T *eap)
{
win_T *wp;
if (eap->addr_count > 0) {
linenr_T wnr = eap->line2;
for (wp = firstwin; --wnr > 0;) {
if (wp->w_next == NULL) {
break;
}
wp = wp->w_next;
}
} else {
wp = curwin;
}
if (wp != curwin) {
win_goto(wp);
}
close_others(true, eap->forceit);
}
static void ex_hide(exarg_T *eap)
{
// ":hide" or ":hide | cmd": hide current window
if (eap->skip) {
return;
}
if (eap->addr_count == 0) {
win_close(curwin, false, eap->forceit); // don't free buffer
} else {
int winnr = 0;
win_T *win = NULL;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
winnr++;
if (winnr == eap->line2) {
win = wp;
break;
}
}
if (win == NULL) {
win = lastwin;
}
win_close(win, false, eap->forceit);
}
}
/// ":stop" and ":suspend": Suspend Vim.
static void ex_stop(exarg_T *eap)
{
if (!eap->forceit) {
autowrite_all();
}
may_trigger_vim_suspend_resume(true);
ui_call_suspend();
ui_flush();
}
/// ":exit", ":xit" and ":wq": Write file and quit the current window.
static void ex_exit(exarg_T *eap)
{
if (cmdwin_type != 0) {
cmdwin_result = Ctrl_C;
return;
}
// Don't quit while editing the command line.
if (text_locked()) {
text_locked_msg();
return;
}
// we plan to exit if there is only one relevant window
if (check_more(false, eap->forceit) == OK && only_one_window()) {
exiting = true;
}
// Write the buffer for ":wq" or when it was changed.
// Trigger QuitPre and ExitPre.
// Check if we can exit now, after autocommands have changed things.
if (((eap->cmdidx == CMD_wq || curbufIsChanged()) && do_write(eap) == FAIL)
|| before_quit_autocmds(curwin, false, eap->forceit)
|| check_more(true, eap->forceit) == FAIL
|| (only_one_window() && check_changed_any(eap->forceit, false))) {
not_exiting();
} else {
if (only_one_window()) {
// quit last window, exit Vim
getout(0);
}
not_exiting();
// Quit current window, may free the buffer.
win_close(curwin, !buf_hide(curwin->w_buffer), eap->forceit);
}
}
/// ":print", ":list", ":number".
static void ex_print(exarg_T *eap)
{
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
emsg(_(e_empty_buffer));
} else {
for (; !got_int; os_breakcheck()) {
print_line(eap->line1,
(eap->cmdidx == CMD_number || eap->cmdidx == CMD_pound
|| (eap->flags & EXFLAG_NR)),
eap->cmdidx == CMD_list || (eap->flags & EXFLAG_LIST));
if (++eap->line1 > eap->line2) {
break;
}
}
setpcmark();
// put cursor at last line
curwin->w_cursor.lnum = eap->line2;
beginline(BL_SOL | BL_FIX);
}
ex_no_reprint = true;
}
static void ex_goto(exarg_T *eap)
{
goto_byte(eap->line2);
}
/// ":preserve".
static void ex_preserve(exarg_T *eap)
{
ml_preserve(curbuf, true, true);
}
/// ":recover".
static void ex_recover(exarg_T *eap)
{
// 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(true);
}
recoverymode = false;
}
/// Command modifier used in a wrong way.
static void ex_wrongmodifier(exarg_T *eap)
{
eap->errmsg = _(e_invcmd);
}
/// :sview [+command] file split window with new file, read-only
/// :split [[+command] file] split window with current or new file
/// :vsplit [[+command] file] split window vertically with current or new file
/// :new [[+command] file] split window with no or new file
/// :vnew [[+command] file] split vertically window with no or new file
/// :sfind [+command] file split window with file in 'path'
///
/// :tabedit open new Tab page with empty window
/// :tabedit [+command] file open new Tab page and edit "file"
/// :tabnew [[+command] file] just like :tabedit
/// :tabfind [+command] file open new Tab page and find "file"
void ex_splitview(exarg_T *eap)
{
win_T *old_curwin = curwin;
char *fname = NULL;
const bool use_tab = eap->cmdidx == CMD_tabedit
|| eap->cmdidx == CMD_tabfind
|| eap->cmdidx == CMD_tabnew;
// A ":split" in the quickfix window works like ":new". Don't want two
// quickfix windows. But it's OK when doing ":tab split".
if (bt_quickfix(curbuf) && cmdmod.cmod_tab == 0) {
if (eap->cmdidx == CMD_split) {
eap->cmdidx = CMD_new;
}
if (eap->cmdidx == CMD_vsplit) {
eap->cmdidx = CMD_vnew;
}
}
if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) {
char *file_to_find = NULL;
char *search_ctx = NULL;
fname = find_file_in_path(eap->arg, strlen(eap->arg),
FNAME_MESS, true, curbuf->b_ffname,
&file_to_find, &search_ctx);
xfree(file_to_find);
vim_findfile_cleanup(search_ctx);
if (fname == NULL) {
goto theend;
}
eap->arg = fname;
}
// Either open new tab page or split the window.
if (use_tab) {
if (win_new_tabpage(cmdmod.cmod_tab != 0 ? cmdmod.cmod_tab : eap->addr_count == 0
? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) {
do_exedit(eap, old_curwin);
apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf);
// set the alternate buffer for the window we came from
if (curwin != old_curwin
&& win_valid(old_curwin)
&& old_curwin->w_buffer != curbuf
&& (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
old_curwin->w_alt_fnum = curbuf->b_fnum;
}
}
} else if (win_split(eap->addr_count > 0 ? (int)eap->line2 : 0,
*eap->cmd == 'v' ? WSP_VERT : 0) != FAIL) {
// Reset 'scrollbind' when editing another file, but keep it when
// doing ":split" without arguments.
if (*eap->arg != NUL) {
RESET_BINDING(curwin);
} else {
do_check_scrollbind(false);
}
do_exedit(eap, old_curwin);
}
theend:
xfree(fname);
}
/// Open a new tab page.
void tabpage_new(void)
{
exarg_T ea = {
.cmdidx = CMD_tabnew,
.cmd = "tabn",
.arg = "",
};
ex_splitview(&ea);
}
/// :tabnext command
static void ex_tabnext(exarg_T *eap)
{
int tab_number;
switch (eap->cmdidx) {
case CMD_tabfirst:
case CMD_tabrewind:
goto_tabpage(1);
break;
case CMD_tablast:
goto_tabpage(9999);
break;
case CMD_tabprevious:
case CMD_tabNext:
if (eap->arg && *eap->arg != NUL) {
char *p = eap->arg;
char *p_save = p;
tab_number = (int)getdigits(&p, false, 0);
if (p == p_save || *p_save == '-' || *p_save == '+' || *p != NUL
|| tab_number == 0) {
// No numbers as argument.
eap->errmsg = ex_errmsg(e_invarg2, eap->arg);
return;
}
} else {
if (eap->addr_count == 0) {
tab_number = 1;
} else {
tab_number = (int)eap->line2;
if (tab_number < 1) {
eap->errmsg = _(e_invrange);
return;
}
}
}
goto_tabpage(-tab_number);
break;
default: // CMD_tabnext
tab_number = get_tabpage_arg(eap);
if (eap->errmsg == NULL) {
goto_tabpage(tab_number);
}
break;
}
}
/// :tabmove command
static void ex_tabmove(exarg_T *eap)
{
int tab_number = get_tabpage_arg(eap);
if (eap->errmsg == NULL) {
tabpage_move(tab_number);
}
}
/// :tabs command: List tabs and their contents.
static void ex_tabs(exarg_T *eap)
{
int tabcount = 1;
msg_start();
msg_scroll = true;
win_T *lastused_win = valid_tabpage(lastused_tabpage)
? lastused_tabpage->tp_curwin
: NULL;
FOR_ALL_TABS(tp) {
if (got_int) {
break;
}
msg_putchar('\n');
vim_snprintf(IObuff, IOSIZE, _("Tab page %d"), tabcount++);
msg_outtrans_attr(IObuff, HL_ATTR(HLF_T));
os_breakcheck();
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
if (got_int) {
break;
}
msg_putchar('\n');
msg_putchar(wp == curwin ? '>' : wp == lastused_win ? '#' : ' ');
msg_putchar(' ');
msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' ');
msg_putchar(' ');
if (buf_spname(wp->w_buffer) != NULL) {
xstrlcpy(IObuff, buf_spname(wp->w_buffer), IOSIZE);
} else {
home_replace(wp->w_buffer, wp->w_buffer->b_fname, IObuff, IOSIZE, true);
}
msg_outtrans(IObuff);
os_breakcheck();
}
}
}
/// ":mode":
/// If no argument given, get the screen size and redraw.
static void ex_mode(exarg_T *eap)
{
if (*eap->arg == NUL) {
must_redraw = UPD_CLEAR;
ex_redraw(eap);
} else {
emsg(_(e_screenmode));
}
}
/// ":resize".
/// set, increment or decrement current window height
static void ex_resize(exarg_T *eap)
{
win_T *wp = curwin;
if (eap->addr_count > 0) {
int n = (int)eap->line2;
for (wp = firstwin; wp->w_next != NULL && --n > 0; wp = wp->w_next) {}
}
int n = (int)atol(eap->arg);
if (cmdmod.cmod_split & WSP_VERT) {
if (*eap->arg == '-' || *eap->arg == '+') {
n += wp->w_width;
} else if (n == 0 && eap->arg[0] == NUL) { // default is very wide
n = Columns;
}
win_setwidth_win(n, wp);
} else {
if (*eap->arg == '-' || *eap->arg == '+') {
n += wp->w_height;
} else if (n == 0 && eap->arg[0] == NUL) { // default is very high
n = Rows - 1;
}
win_setheight_win(n, wp);
}
}
/// ":find [+command] <file>" command.
static void ex_find(exarg_T *eap)
{
char *file_to_find = NULL;
char *search_ctx = NULL;
char *fname = find_file_in_path(eap->arg, strlen(eap->arg),
FNAME_MESS, true, curbuf->b_ffname,
&file_to_find, &search_ctx);
if (eap->addr_count > 0) {
// Repeat finding the file "count" times. This matters when it appears
// several times in the path.
linenr_T count = eap->line2;
while (fname != NULL && --count > 0) {
xfree(fname);
fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname,
&file_to_find, &search_ctx);
}
}
xfree(file_to_find);
vim_findfile_cleanup(search_ctx);
if (fname == NULL) {
return;
}
eap->arg = fname;
do_exedit(eap, NULL);
xfree(fname);
}
/// ":edit", ":badd", ":balt", ":visual".
static void ex_edit(exarg_T *eap)
{
do_exedit(eap, NULL);
}
/// ":edit <file>" command and alike.
///
/// @param old_curwin curwin before doing a split or NULL
void do_exedit(exarg_T *eap, win_T *old_curwin)
{
int n;
// ":vi" command ends Ex mode.
if (exmode_active && (eap->cmdidx == CMD_visual
|| eap->cmdidx == CMD_view)) {
exmode_active = false;
ex_pressedreturn = false;
if (*eap->arg == NUL) {
// Special case: ":global/pat/visual\NLvi-commands"
if (global_busy) {
if (eap->nextcmd != NULL) {
stuffReadbuff(eap->nextcmd);
eap->nextcmd = NULL;
}
const int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
const int save_nwr = no_wait_return;
no_wait_return = 0;
need_wait_return = false;
const int save_ms = msg_scroll;
msg_scroll = 0;
redraw_all_later(UPD_NOT_VALID);
pending_exmode_active = true;
normal_enter(false, true);
pending_exmode_active = false;
RedrawingDisabled = save_rd;
no_wait_return = save_nwr;
msg_scroll = save_ms;
}
return;
}
}
if ((eap->cmdidx == CMD_new
|| eap->cmdidx == CMD_tabnew
|| eap->cmdidx == CMD_tabedit
|| eap->cmdidx == CMD_vnew) && *eap->arg == NUL) {
// ":new" or ":tabnew" without argument: edit a new empty buffer
setpcmark();
(void)do_ecmd(0, NULL, NULL, eap, ECMD_ONE,
ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0),
old_curwin == NULL ? curwin : NULL);
} else if ((eap->cmdidx != CMD_split && eap->cmdidx != CMD_vsplit)
|| *eap->arg != NUL) {
// Can't edit another file when "textlock" or "curbuf->b_ro_locked" is set.
// Only ":edit" or ":script" can bring us here, others are stopped earlier.
if (*eap->arg != NUL && text_or_buf_locked()) {
return;
}
n = readonlymode;
if (eap->cmdidx == CMD_view || eap->cmdidx == CMD_sview) {
readonlymode = true;
} else if (eap->cmdidx == CMD_enew) {
readonlymode = false; // 'readonly' doesn't make sense
// in an empty buffer
}
if (eap->cmdidx != CMD_balt && eap->cmdidx != CMD_badd) {
setpcmark();
}
if (do_ecmd(0, eap->cmdidx == CMD_enew ? NULL : eap->arg,
NULL, eap, eap->do_ecmd_lnum,
(buf_hide(curbuf) ? ECMD_HIDE : 0)
+ (eap->forceit ? ECMD_FORCEIT : 0)
// After a split we can use an existing buffer.
+ (old_curwin != NULL ? ECMD_OLDBUF : 0)
+ (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0)
+ (eap->cmdidx == CMD_balt ? ECMD_ALTBUF : 0),
old_curwin == NULL ? curwin : NULL) == FAIL) {
// Editing the file failed. If the window was split, close it.
if (old_curwin != NULL) {
bool need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1);
if (!need_hide || buf_hide(curbuf)) {
cleanup_T cs;
// Reset the error/interrupt/exception state here so that
// aborting() returns false when closing a window.
enter_cleanup(&cs);
win_close(curwin, !need_hide && !buf_hide(curbuf), false);
// Restore the error/interrupt/exception state if not
// discarded by a new aborting error, interrupt, or
// uncaught exception.
leave_cleanup(&cs);
}
}
} else if (readonlymode && curbuf->b_nwindows == 1) {
// When editing an already visited buffer, 'readonly' won't be set
// but the previous value is kept. With ":view" and ":sview" we
// want the file to be readonly, except when another window is
// editing the same buffer.
curbuf->b_p_ro = true;
}
readonlymode = n;
} else {
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
n = curwin->w_arg_idx_invalid;
check_arg_idx(curwin);
if (n != curwin->w_arg_idx_invalid) {
maketitle();
}
}
// if ":split file" worked, set alternate file name in old window to new
// file
if (old_curwin != NULL
&& *eap->arg != NUL
&& curwin != old_curwin
&& win_valid(old_curwin)
&& old_curwin->w_buffer != curbuf
&& (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
old_curwin->w_alt_fnum = curbuf->b_fnum;
}
ex_no_reprint = true;
}
/// ":gui" and ":gvim" when there is no GUI.
static void ex_nogui(exarg_T *eap)
{
eap->errmsg = _("E25: Nvim does not have a built-in GUI");
}
static void ex_popup(exarg_T *eap)
{
pum_make_popup(eap->arg, eap->forceit);
}
static void ex_swapname(exarg_T *eap)
{
if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) {
msg(_("No swap file"));
} else {
msg(curbuf->b_ml.ml_mfp->mf_fname);
}
}
/// ":syncbind" forces all 'scrollbind' windows to have the same relative
/// offset.
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
static void ex_syncbind(exarg_T *eap)
{
win_T *save_curwin = curwin;
buf_T *save_curbuf = curbuf;
linenr_T topline;
int y;
linenr_T old_linenr = curwin->w_cursor.lnum;
setpcmark();
// determine max topline
if (curwin->w_p_scb) {
topline = curwin->w_topline;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_scb && wp->w_buffer) {
y = wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value(curwin);
if (topline > y) {
topline = y;
}
}
}
if (topline < 1) {
topline = 1;
}
} else {
topline = 1;
}
// Set all scrollbind windows to the same topline.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
curwin = wp;
if (curwin->w_p_scb) {
curbuf = curwin->w_buffer;
y = topline - curwin->w_topline;
if (y > 0) {
scrollup(y, true);
} else {
scrolldown(-y, true);
}
curwin->w_scbind_pos = topline;
redraw_later(curwin, UPD_VALID);
cursor_correct();
curwin->w_redr_status = true;
}
}
curwin = save_curwin;
curbuf = save_curbuf;
if (curwin->w_p_scb) {
did_syncbind = true;
checkpcmark();
if (old_linenr != curwin->w_cursor.lnum) {
char ctrl_o[2];
ctrl_o[0] = Ctrl_O;
ctrl_o[1] = 0;
ins_typebuf(ctrl_o, REMAP_NONE, 0, true, false);
}
}
}
static void ex_read(exarg_T *eap)
{
int empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
if (eap->usefilter) { // :r!cmd
do_bang(1, eap, false, false, true);
return;
}
if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) {
return;
}
int i;
if (*eap->arg == NUL) {
if (check_fname() == FAIL) { // check for no file name
return;
}
i = readfile(curbuf->b_ffname, curbuf->b_fname,
eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false);
} else {
if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) {
(void)setaltfname(eap->arg, eap->arg, (linenr_T)1);
}
i = readfile(eap->arg, NULL,
eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false);
}
if (i != OK) {
if (!aborting()) {
semsg(_(e_notopen), eap->arg);
}
} else {
if (empty && exmode_active) {
// Delete the empty line that remains. Historically ex does
// this but vi doesn't.
linenr_T lnum;
if (eap->line2 == 0) {
lnum = curbuf->b_ml.ml_line_count;
} else {
lnum = 1;
}
if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) {
ml_delete(lnum, false);
if (curwin->w_cursor.lnum > 1
&& curwin->w_cursor.lnum >= lnum) {
curwin->w_cursor.lnum--;
}
deleted_lines_mark(lnum, 1L);
}
}
redraw_curbuf_later(UPD_VALID);
}
}
static char *prev_dir = NULL;
#if defined(EXITFREE)
void free_cd_dir(void)
{
XFREE_CLEAR(prev_dir);
XFREE_CLEAR(globaldir);
}
#endif
/// Get the previous directory for the given chdir scope.
static char *get_prevdir(CdScope scope)
{
switch (scope) {
case kCdScopeTabpage:
return curtab->tp_prevdir;
break;
case kCdScopeWindow:
return curwin->w_prevdir;
break;
default:
return prev_dir;
}
}
/// Deal with the side effects of changing the current directory.
///
/// @param scope Scope of the function call (global, tab or window).
static void post_chdir(CdScope scope, bool trigger_dirchanged)
{
// Always overwrite the window-local CWD.
XFREE_CLEAR(curwin->w_localdir);
// Overwrite the tab-local CWD for :cd, :tcd.
if (scope >= kCdScopeTabpage) {
XFREE_CLEAR(curtab->tp_localdir);
}
if (scope < kCdScopeGlobal) {
char *pdir = get_prevdir(scope);
// If still in global directory, set CWD as the global directory.
if (globaldir == NULL && pdir != NULL) {
globaldir = xstrdup(pdir);
}
}
char cwd[MAXPATHL];
if (os_dirname(cwd, MAXPATHL) != OK) {
return;
}
switch (scope) {
case kCdScopeGlobal:
// We are now in the global directory, no need to remember its name.
XFREE_CLEAR(globaldir);
break;
case kCdScopeTabpage:
curtab->tp_localdir = xstrdup(cwd);
break;
case kCdScopeWindow:
curwin->w_localdir = xstrdup(cwd);
break;
case kCdScopeInvalid:
abort();
}
last_chdir_reason = NULL;
shorten_fnames(true);
if (trigger_dirchanged) {
do_autocmd_dirchanged(cwd, scope, kCdCauseManual, false);
}
}
/// Change directory function used by :cd/:tcd/:lcd Ex commands and the chdir() function.
/// @param new_dir The directory to change to.
/// @param scope Scope of the function call (global, tab or window).
/// @return true if the directory is successfully changed.
bool changedir_func(char *new_dir, CdScope scope)
{
if (new_dir == NULL || allbuf_locked()) {
return false;
}
char *pdir = NULL;
// ":cd -": Change to previous directory
if (strcmp(new_dir, "-") == 0) {
pdir = get_prevdir(scope);
if (pdir == NULL) {
emsg(_("E186: No previous directory"));
return false;
}
new_dir = pdir;
}
if (os_dirname(NameBuff, MAXPATHL) == OK) {
pdir = xstrdup(NameBuff);
} else {
pdir = NULL;
}
// For UNIX ":cd" means: go to home directory.
// On other systems too if 'cdhome' is set.
#if defined(UNIX)
if (*new_dir == NUL) {
#else
if (*new_dir == NUL && p_cdh) {
#endif
// Use NameBuff for home directory name.
expand_env("$HOME", NameBuff, MAXPATHL);
new_dir = NameBuff;
}
bool dir_differs = pdir == NULL || pathcmp(pdir, new_dir, -1) != 0;
if (dir_differs) {
do_autocmd_dirchanged(new_dir, scope, kCdCauseManual, true);
if (vim_chdir(new_dir) != 0) {
emsg(_(e_failed));
xfree(pdir);
return false;
}
}
char **pp;
switch (scope) {
case kCdScopeTabpage:
pp = &curtab->tp_prevdir;
break;
case kCdScopeWindow:
pp = &curwin->w_prevdir;
break;
default:
pp = &prev_dir;
}
xfree(*pp);
*pp = pdir;
post_chdir(scope, dir_differs);
return true;
}
/// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir".
void ex_cd(exarg_T *eap)
{
char *new_dir = eap->arg;
#if !defined(UNIX)
// for non-UNIX ":cd" means: print current directory unless 'cdhome' is set
if (*new_dir == NUL && !p_cdh) {
ex_pwd(NULL);
return;
}
#endif
CdScope scope = kCdScopeGlobal;
switch (eap->cmdidx) {
case CMD_tcd:
case CMD_tchdir:
scope = kCdScopeTabpage;
break;
case CMD_lcd:
case CMD_lchdir:
scope = kCdScopeWindow;
break;
default:
break;
}
if (changedir_func(new_dir, scope)) {
// Echo the new current directory if the command was typed.
if (KeyTyped || p_verbose >= 5) {
ex_pwd(eap);
}
}
}
/// ":pwd".
static void ex_pwd(exarg_T *eap)
{
if (os_dirname(NameBuff, MAXPATHL) == OK) {
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(NameBuff);
#endif
if (p_verbose > 0) {
char *context = "global";
if (last_chdir_reason != NULL) {
context = last_chdir_reason;
} else if (curwin->w_localdir != NULL) {
context = "window";
} else if (curtab->tp_localdir != NULL) {
context = "tabpage";
}
smsg("[%s] %s", context, NameBuff);
} else {
msg(NameBuff);
}
} else {
emsg(_("E187: Unknown"));
}
}
/// ":=".
static void ex_equal(exarg_T *eap)
{
if (*eap->arg != NUL && *eap->arg != '|') {
// equivalent to :lua= expr
ex_lua(eap);
} else {
eap->nextcmd = find_nextcmd(eap->arg);
smsg("%" PRId64, (int64_t)eap->line2);
}
}
static void ex_sleep(exarg_T *eap)
{
if (cursor_valid()) {
setcursor_mayforce(true);
}
int64_t len = eap->line2;
switch (*eap->arg) {
case 'm':
break;
case NUL:
len *= 1000L; break;
default:
semsg(_(e_invarg2), eap->arg); return;
}
do_sleep(len);
}
/// Sleep for "msec" milliseconds, but return early on CTRL-C.
void do_sleep(int64_t msec)
{
ui_flush(); // flush before waiting
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, msec, got_int);
// If CTRL-C was typed to interrupt the sleep, drop the CTRL-C from the
// input buffer, otherwise a following call to input() fails.
if (got_int) {
(void)vpeekc();
}
}
/// ":winsize" command (obsolete).
static void ex_winsize(exarg_T *eap)
{
char *arg = eap->arg;
if (!ascii_isdigit(*arg)) {
semsg(_(e_invarg2), arg);
return;
}
int w = getdigits_int(&arg, false, 10);
arg = skipwhite(arg);
char *p = arg;
int h = getdigits_int(&arg, false, 10);
if (*p != NUL && *arg == NUL) {
screen_resize(w, h);
} else {
emsg(_("E465: :winsize requires two number arguments"));
}
}
static void ex_wincmd(exarg_T *eap)
{
int xchar = NUL;
char *p;
if (*eap->arg == 'g' || *eap->arg == Ctrl_G) {
// CTRL-W g and CTRL-W CTRL-G have an extra command character
if (eap->arg[1] == NUL) {
emsg(_(e_invarg));
return;
}
xchar = (uint8_t)eap->arg[1];
p = eap->arg + 2;
} else {
p = eap->arg + 1;
}
eap->nextcmd = check_nextcmd(p);
p = skipwhite(p);
if (*p != NUL && *p != '"' && eap->nextcmd == NULL) {
emsg(_(e_invarg));
} else if (!eap->skip) {
// Pass flags on for ":vertical wincmd ]".
postponed_split_flags = cmdmod.cmod_split;
postponed_split_tab = cmdmod.cmod_tab;
do_window(*eap->arg, eap->addr_count > 0 ? eap->line2 : 0L, xchar);
postponed_split_flags = 0;
postponed_split_tab = 0;
}
}
/// Handle command that work like operators: ":delete", ":yank", ":>" and ":<".
static void ex_operators(exarg_T *eap)
{
oparg_T oa;
clear_oparg(&oa);
oa.regname = eap->regname;
oa.start.lnum = eap->line1;
oa.end.lnum = eap->line2;
oa.line_count = eap->line2 - eap->line1 + 1;
oa.motion_type = kMTLineWise;
virtual_op = kFalse;
if (eap->cmdidx != CMD_yank) { // position cursor for undo
setpcmark();
curwin->w_cursor.lnum = eap->line1;
beginline(BL_SOL | BL_FIX);
}
if (VIsual_active) {
end_visual_mode();
}
switch (eap->cmdidx) {
case CMD_delete:
oa.op_type = OP_DELETE;
op_delete(&oa);
break;
case CMD_yank:
oa.op_type = OP_YANK;
(void)op_yank(&oa, true);
break;
default: // CMD_rshift or CMD_lshift
if (
(eap->cmdidx == CMD_rshift) ^ curwin->w_p_rl) {
oa.op_type = OP_RSHIFT;
} else {
oa.op_type = OP_LSHIFT;
}
op_shift(&oa, false, eap->amount);
break;
}
virtual_op = kNone;
ex_may_print(eap);
}
/// ":put".
static void ex_put(exarg_T *eap)
{
// ":0put" works like ":1put!".
if (eap->line2 == 0) {
eap->line2 = 1;
eap->forceit = true;
}
curwin->w_cursor.lnum = eap->line2;
check_cursor_col();
do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1,
PUT_LINE|PUT_CURSLINE);
}
/// Handle ":copy" and ":move".
static void ex_copymove(exarg_T *eap)
{
const char *errormsg = NULL;
linenr_T n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1, &errormsg);
if (eap->arg == NULL) { // error detected
if (errormsg != NULL) {
emsg(errormsg);
}
eap->nextcmd = NULL;
return;
}
get_flags(eap);
// move or copy lines from 'eap->line1'-'eap->line2' to below line 'n'
if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) {
emsg(_(e_invrange));
return;
}
if (eap->cmdidx == CMD_move) {
if (do_move(eap->line1, eap->line2, n) == FAIL) {
return;
}
} else {
ex_copy(eap->line1, eap->line2, n);
}
u_clearline(curbuf);
beginline(BL_SOL | BL_FIX);
ex_may_print(eap);
}
/// Print the current line if flags were given to the Ex command.
void ex_may_print(exarg_T *eap)
{
if (eap->flags != 0) {
print_line(curwin->w_cursor.lnum, (eap->flags & EXFLAG_NR),
(eap->flags & EXFLAG_LIST));
ex_no_reprint = true;
}
}
/// ":smagic" and ":snomagic".
static void ex_submagic(exarg_T *eap)
{
const optmagic_T saved = magic_overruled;
magic_overruled = eap->cmdidx == CMD_smagic ? OPTION_MAGIC_ON : OPTION_MAGIC_OFF;
ex_substitute(eap);
magic_overruled = saved;
}
/// ":smagic" and ":snomagic" preview callback.
static int ex_submagic_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
{
const optmagic_T saved = magic_overruled;
magic_overruled = eap->cmdidx == CMD_smagic ? OPTION_MAGIC_ON : OPTION_MAGIC_OFF;
int retv = ex_substitute_preview(eap, cmdpreview_ns, cmdpreview_bufnr);
magic_overruled = saved;
return retv;
}
/// ":join".
static void ex_join(exarg_T *eap)
{
curwin->w_cursor.lnum = eap->line1;
if (eap->line1 == eap->line2) {
if (eap->addr_count >= 2) { // :2,2join does nothing
return;
}
if (eap->line2 == curbuf->b_ml.ml_line_count) {
beep_flush();
return;
}
eap->line2++;
}
do_join((size_t)((ssize_t)eap->line2 - eap->line1 + 1), !eap->forceit, true, true, true);
beginline(BL_WHITE | BL_FIX);
ex_may_print(eap);
}
/// ":[addr]@r": execute register
static void ex_at(exarg_T *eap)
{
int prev_len = typebuf.tb_len;
curwin->w_cursor.lnum = eap->line2;
check_cursor_col();
// Get the register name. No name means use the previous one.
int c = (uint8_t)(*eap->arg);
if (c == NUL) {
c = '@';
}
// Put the register in the typeahead buffer with the "silent" flag.
if (do_execreg(c, true, vim_strchr(p_cpo, CPO_EXECBUF) != NULL, true) == FAIL) {
beep_flush();
return;
}
const bool save_efr = exec_from_reg;
exec_from_reg = true;
// Execute from the typeahead buffer.
// Continue until the stuff buffer is empty and all added characters
// have been consumed.
while (!stuff_empty() || typebuf.tb_len > prev_len) {
(void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE);
}
exec_from_reg = save_efr;
}
/// ":!".
static void ex_bang(exarg_T *eap)
{
do_bang(eap->addr_count, eap, eap->forceit, true, true);
}
/// ":undo".
static void ex_undo(exarg_T *eap)
{
if (eap->addr_count != 1) {
if (eap->forceit) {
u_undo_and_forget(1, true); // :undo!
} else {
u_undo(1); // :undo
}
return;
}
linenr_T step = eap->line2;
if (eap->forceit) { // undo! 123
// change number for "undo!" must be lesser than current change number
if (step >= curbuf->b_u_seq_cur) {
emsg(_(e_undobang_cannot_redo_or_move_branch));
return;
}
// ensure that target change number is in same branch
// while also counting the amount of undoes it'd take to reach target
u_header_T *uhp;
int count = 0;
for (uhp = curbuf->b_u_curhead ? curbuf->b_u_curhead : curbuf->b_u_newhead;
uhp != NULL && uhp->uh_seq > step;
uhp = uhp->uh_next.ptr, ++count) {}
if (step != 0 && (uhp == NULL || uhp->uh_seq < step)) {
emsg(_(e_undobang_cannot_redo_or_move_branch));
return;
}
u_undo_and_forget(count, true);
} else { // :undo 123
undo_time(step, false, false, true);
}
}
static void ex_wundo(exarg_T *eap)
{
uint8_t hash[UNDO_HASH_SIZE];
u_compute_hash(curbuf, hash);
u_write_undo(eap->arg, eap->forceit, curbuf, hash);
}
static void ex_rundo(exarg_T *eap)
{
uint8_t hash[UNDO_HASH_SIZE];
u_compute_hash(curbuf, hash);
u_read_undo(eap->arg, hash, NULL);
}
/// ":redo".
static void ex_redo(exarg_T *eap)
{
u_redo(1);
}
/// ":earlier" and ":later".
static void ex_later(exarg_T *eap)
{
long count = 0;
bool sec = false;
bool file = false;
char *p = eap->arg;
if (*p == NUL) {
count = 1;
} else if (isdigit((uint8_t)(*p))) {
count = getdigits_long(&p, false, 0);
switch (*p) {
case 's':
p++; sec = true; break;
case 'm':
p++; sec = true; count *= 60; break;
case 'h':
p++; sec = true; count *= 60 * 60; break;
case 'd':
p++; sec = true; count *= 24 * 60 * 60; break;
case 'f':
p++; file = true; break;
}
}
if (*p != NUL) {
semsg(_(e_invarg2), eap->arg);
} else {
undo_time(eap->cmdidx == CMD_earlier ? -count : count,
sec, file, false);
}
}
/// ":redir": start/stop redirection.
static void ex_redir(exarg_T *eap)
{
char *arg = eap->arg;
if (STRICMP(eap->arg, "END") == 0) {
close_redir();
} else {
if (*arg == '>') {
arg++;
char *mode;
if (*arg == '>') {
arg++;
mode = "a";
} else {
mode = "w";
}
arg = skipwhite(arg);
close_redir();
// Expand environment variables and "~/".
char *fname = expand_env_save(arg);
if (fname == NULL) {
return;
}
redir_fd = open_exfile(fname, eap->forceit, mode);
xfree(fname);
} else if (*arg == '@') {
// redirect to a register a-z (resp. A-Z for appending)
close_redir();
arg++;
if (valid_yank_reg(*arg, true) && *arg != '_') {
redir_reg = (uint8_t)(*arg++);
if (*arg == '>' && arg[1] == '>') { // append
arg += 2;
} else {
// Can use both "@a" and "@a>".
if (*arg == '>') {
arg++;
}
// Make register empty when not using @A-@Z and the
// command is valid.
if (*arg == NUL && !isupper(redir_reg)) {
write_reg_contents(redir_reg, "", 0, false);
}
}
}
if (*arg != NUL) {
redir_reg = 0;
semsg(_(e_invarg2), eap->arg);
}
} else if (*arg == '=' && arg[1] == '>') {
int append;
// redirect to a variable
close_redir();
arg += 2;
if (*arg == '>') {
arg++;
append = true;
} else {
append = false;
}
if (var_redir_start(skipwhite(arg), append) == OK) {
redir_vname = 1;
}
} else { // TODO(vim): redirect to a buffer
semsg(_(e_invarg2), eap->arg);
}
}
// Make sure redirection is not off. Can happen for cmdline completion
// that indirectly invokes a command to catch its output.
if (redir_fd != NULL
|| redir_reg || redir_vname) {
redir_off = false;
}
}
/// ":redraw": force redraw
static void ex_redraw(exarg_T *eap)
{
if (cmdpreview) {
return; // Ignore :redraw during 'inccommand' preview. #9777
}
int r = RedrawingDisabled;
int p = p_lz;
RedrawingDisabled = 0;
p_lz = false;
validate_cursor();
update_topline(curwin);
if (eap->forceit) {
redraw_all_later(UPD_NOT_VALID);
redraw_cmdline = true;
} else if (VIsual_active) {
redraw_curbuf_later(UPD_INVERTED);
}
update_screen();
if (need_maketitle) {
maketitle();
}
RedrawingDisabled = r;
p_lz = p;
// Reset msg_didout, so that a message that's there is overwritten.
msg_didout = false;
msg_col = 0;
// No need to wait after an intentional redraw.
need_wait_return = false;
ui_flush();
}
/// ":redrawstatus": force redraw of status line(s) and window bar(s)
static void ex_redrawstatus(exarg_T *eap)
{
if (cmdpreview) {
return; // Ignore :redrawstatus during 'inccommand' preview. #9777
}
int r = RedrawingDisabled;
int p = p_lz;
if (eap->forceit) {
status_redraw_all();
} else {
status_redraw_curbuf();
}
RedrawingDisabled = 0;
p_lz = false;
if (State & MODE_CMDLINE) {
redraw_statuslines();
} else {
if (VIsual_active) {
redraw_curbuf_later(UPD_INVERTED);
}
update_screen();
}
RedrawingDisabled = r;
p_lz = p;
ui_flush();
}
/// ":redrawtabline": force redraw of the tabline
static void ex_redrawtabline(exarg_T *eap FUNC_ATTR_UNUSED)
{
const int r = RedrawingDisabled;
const int p = p_lz;
RedrawingDisabled = 0;
p_lz = false;
draw_tabline();
RedrawingDisabled = r;
p_lz = p;
ui_flush();
}
static void close_redir(void)
{
if (redir_fd != NULL) {
fclose(redir_fd);
redir_fd = NULL;
}
redir_reg = 0;
if (redir_vname) {
var_redir_stop();
redir_vname = 0;
}
}
/// Try creating a directory, give error message on failure
///
/// @param[in] name Directory to create.
/// @param[in] prot Directory permissions.
///
/// @return OK in case of success, FAIL otherwise.
int vim_mkdir_emsg(const char *const name, const int prot)
FUNC_ATTR_NONNULL_ALL
{
int ret;
if ((ret = os_mkdir(name, prot)) != 0) {
semsg(_(e_mkdir), name, os_strerror(ret));
return FAIL;
}
return OK;
}
/// Open a file for writing for an Ex command, with some checks.
///
/// @param mode "w" for create new file or "a" for append
///
/// @return file descriptor, or NULL on failure.
FILE *open_exfile(char *fname, int forceit, char *mode)
{
#ifdef UNIX
// with Unix it is possible to open a directory
if (os_isdir(fname)) {
semsg(_(e_isadir2), fname);
return NULL;
}
#endif
if (!forceit && *mode != 'a' && os_path_exists(fname)) {
semsg(_("E189: \"%s\" exists (add ! to override)"), fname);
return NULL;
}
FILE *fd;
if ((fd = os_fopen(fname, mode)) == NULL) {
semsg(_("E190: Cannot open \"%s\" for writing"), fname);
}
return fd;
}
/// ":mark" and ":k".
static void ex_mark(exarg_T *eap)
{
if (*eap->arg == NUL) { // No argument?
emsg(_(e_argreq));
return;
}
if (eap->arg[1] != NUL) { // more than one character?
semsg(_(e_trailing_arg), eap->arg);
return;
}
pos_T pos = curwin->w_cursor; // save curwin->w_cursor
curwin->w_cursor.lnum = eap->line2;
beginline(BL_WHITE | BL_FIX);
if (setmark(*eap->arg) == FAIL) { // set mark
emsg(_("E191: Argument must be a letter or forward/backward quote"));
}
curwin->w_cursor = pos; // restore curwin->w_cursor
}
/// Update w_topline, w_leftcol and the cursor position.
void update_topline_cursor(void)
{
check_cursor(); // put cursor on valid line
update_topline(curwin);
if (!curwin->w_p_wrap) {
validate_cursor();
}
update_curswant();
}
/// Save the current State and go to Normal mode.
///
/// @return true if the typeahead could be saved.
bool save_current_state(save_state_T *sst)
FUNC_ATTR_NONNULL_ALL
{
sst->save_msg_scroll = msg_scroll;
sst->save_restart_edit = restart_edit;
sst->save_msg_didout = msg_didout;
sst->save_State = State;
sst->save_finish_op = finish_op;
sst->save_opcount = opcount;
sst->save_reg_executing = reg_executing;
sst->save_pending_end_reg_executing = pending_end_reg_executing;
msg_scroll = false; // no msg scrolling in Normal mode
restart_edit = 0; // don't go to Insert mode
// Save the current typeahead. This is required to allow using ":normal"
// from an event handler and makes sure we don't hang when the argument
// ends with half a command.
save_typeahead(&sst->tabuf);
return sst->tabuf.typebuf_valid;
}
void restore_current_state(save_state_T *sst)
FUNC_ATTR_NONNULL_ALL
{
// Restore the previous typeahead.
restore_typeahead(&sst->tabuf);
msg_scroll = sst->save_msg_scroll;
if (force_restart_edit) {
force_restart_edit = false;
} else {
// Some function (terminal_enter()) was aware of ex_normal and decided to
// override the value of restart_edit anyway.
restart_edit = sst->save_restart_edit;
}
finish_op = sst->save_finish_op;
opcount = sst->save_opcount;
reg_executing = sst->save_reg_executing;
pending_end_reg_executing = sst->save_pending_end_reg_executing;
// don't reset msg_didout now
msg_didout |= sst->save_msg_didout;
// Restore the state (needed when called from a function executed for
// 'indentexpr'). Update the mouse and cursor, they may have changed.
State = sst->save_State;
ui_cursor_shape(); // may show different cursor shape
}
bool expr_map_locked(void)
{
return expr_map_lock > 0 && !(curbuf->b_flags & BF_DUMMY);
}
/// ":normal[!] {commands}": Execute normal mode commands.
static void ex_normal(exarg_T *eap)
{
if (curbuf->terminal && State & MODE_TERMINAL) {
emsg("Can't re-enter normal mode from terminal mode");
return;
}
char *arg = NULL;
if (expr_map_locked()) {
emsg(_(e_secure));
return;
}
if (ex_normal_busy >= p_mmd) {
emsg(_("E192: Recursive use of :normal too deep"));
return;
}
// vgetc() expects K_SPECIAL to have been escaped. Don't do
// this for the K_SPECIAL leading byte, otherwise special keys will not
// work.
{
int len = 0;
// Count the number of characters to be escaped.
int l;
char *p;
for (p = eap->arg; *p != NUL; p++) {
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
if (*++p == (char)K_SPECIAL) { // trailbyte K_SPECIAL
len += 2;
}
}
}
if (len > 0) {
arg = xmalloc(strlen(eap->arg) + (size_t)len + 1);
len = 0;
for (p = eap->arg; *p != NUL; p++) {
arg[len++] = *p;
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
arg[len++] = *++p;
if (*p == (char)K_SPECIAL) {
arg[len++] = (char)KS_SPECIAL;
arg[len++] = KE_FILLER;
}
}
arg[len] = NUL;
}
}
}
ex_normal_busy++;
save_state_T save_state;
if (save_current_state(&save_state)) {
// Repeat the :normal command for each line in the range. When no
// range given, execute it just once, without positioning the cursor
// first.
do {
if (eap->addr_count != 0) {
curwin->w_cursor.lnum = eap->line1++;
curwin->w_cursor.col = 0;
check_cursor_moved(curwin);
}
exec_normal_cmd((arg != NULL ? arg : eap->arg),
eap->forceit ? REMAP_NONE : REMAP_YES, false);
} while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int);
}
// Might not return to the main loop when in an event handler.
update_topline_cursor();
restore_current_state(&save_state);
ex_normal_busy--;
setmouse();
ui_cursor_shape(); // may show different cursor shape
xfree(arg);
}
/// ":startinsert", ":startreplace" and ":startgreplace"
static void ex_startinsert(exarg_T *eap)
{
if (eap->forceit) {
// cursor line can be zero on startup
if (!curwin->w_cursor.lnum) {
curwin->w_cursor.lnum = 1;
}
set_cursor_for_append_to_line();
}
// Ignore the command when already in Insert mode. Inserting an
// expression register that invokes a function can do this.
if (State & MODE_INSERT) {
return;
}
if (eap->cmdidx == CMD_startinsert) {
restart_edit = 'a';
} else if (eap->cmdidx == CMD_startreplace) {
restart_edit = 'R';
} else {
restart_edit = 'V';
}
if (!eap->forceit) {
if (eap->cmdidx == CMD_startinsert) {
restart_edit = 'i';
}
curwin->w_curswant = 0; // avoid MAXCOL
}
if (VIsual_active) {
showmode();
}
}
/// ":stopinsert"
static void ex_stopinsert(exarg_T *eap)
{
restart_edit = 0;
stop_insert_mode = true;
clearmode();
}
/// Execute normal mode command "cmd".
/// "remap" can be REMAP_NONE or REMAP_YES.
void exec_normal_cmd(char *cmd, int remap, bool silent)
{
// Stuff the argument into the typeahead buffer.
ins_typebuf(cmd, remap, 0, true, silent);
exec_normal(false);
}
/// Execute normal_cmd() until there is no typeahead left.
///
/// @param was_typed whether or not something was typed
void exec_normal(bool was_typed)
{
oparg_T oa;
clear_oparg(&oa);
finish_op = false;
while ((!stuff_empty()
|| ((was_typed || !typebuf_typed())
&& typebuf.tb_len > 0))
&& !got_int) {
update_topline_cursor();
normal_cmd(&oa, true); // execute a Normal mode cmd
}
}
static void ex_checkpath(exarg_T *eap)
{
find_pattern_in_path(NULL, 0, 0, false, false, CHECK_PATH, 1L,
eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
(linenr_T)1, (linenr_T)MAXLNUM);
}
/// ":psearch"
static void ex_psearch(exarg_T *eap)
{
g_do_tagpreview = (int)p_pvh;
ex_findpat(eap);
g_do_tagpreview = 0;
}
static void ex_findpat(exarg_T *eap)
{
bool whole = true;
int action;
switch (cmdnames[eap->cmdidx].cmd_name[2]) {
case 'e': // ":psearch", ":isearch" and ":dsearch"
if (cmdnames[eap->cmdidx].cmd_name[0] == 'p') {
action = ACTION_GOTO;
} else {
action = ACTION_SHOW;
}
break;
case 'i': // ":ilist" and ":dlist"
action = ACTION_SHOW_ALL;
break;
case 'u': // ":ijump" and ":djump"
action = ACTION_GOTO;
break;
default: // ":isplit" and ":dsplit"
action = ACTION_SPLIT;
break;
}
int n = 1;
if (ascii_isdigit(*eap->arg)) { // get count
n = getdigits_int(&eap->arg, false, 0);
eap->arg = skipwhite(eap->arg);
}
if (*eap->arg == '/') { // Match regexp, not just whole words
whole = false;
eap->arg++;
char *p = skip_regexp(eap->arg, '/', magic_isset());
if (*p) {
*p++ = NUL;
p = skipwhite(p);
// Check for trailing illegal characters.
if (!ends_excmd(*p)) {
eap->errmsg = ex_errmsg(e_trailing_arg, p);
} else {
eap->nextcmd = check_nextcmd(p);
}
}
}
if (!eap->skip) {
find_pattern_in_path(eap->arg, 0, strlen(eap->arg), whole, !eap->forceit,
*eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
n, action, eap->line1, eap->line2);
}
}
/// ":ptag", ":ptselect", ":ptjump", ":ptnext", etc.
static void ex_ptag(exarg_T *eap)
{
g_do_tagpreview = (int)p_pvh; // will be reset to 0 in ex_tag_cmd()
ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1);
}
/// ":pedit"
static void ex_pedit(exarg_T *eap)
{
win_T *curwin_save = curwin;
// Open the preview window or popup and make it the current window.
g_do_tagpreview = (int)p_pvh;
prepare_tagpreview(true);
// Edit the file.
do_exedit(eap, NULL);
if (curwin != curwin_save && win_valid(curwin_save)) {
// Return cursor to where we were
validate_cursor();
redraw_later(curwin, UPD_VALID);
win_enter(curwin_save, true);
}
g_do_tagpreview = 0;
}
/// ":stag", ":stselect" and ":stjump".
static void ex_stag(exarg_T *eap)
{
postponed_split = -1;
postponed_split_flags = cmdmod.cmod_split;
postponed_split_tab = cmdmod.cmod_tab;
ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1);
postponed_split_flags = 0;
postponed_split_tab = 0;
}
/// ":tag", ":tselect", ":tjump", ":tnext", etc.
static void ex_tag(exarg_T *eap)
{
ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name);
}
static void ex_tag_cmd(exarg_T *eap, const char *name)
{
int cmd;
switch (name[1]) {
case 'j':
cmd = DT_JUMP; // ":tjump"
break;
case 's':
cmd = DT_SELECT; // ":tselect"
break;
case 'p': // ":tprevious"
case 'N':
cmd = DT_PREV; // ":tNext"
break;
case 'n':
cmd = DT_NEXT; // ":tnext"
break;
case 'o':
cmd = DT_POP; // ":pop"
break;
case 'f': // ":tfirst"
case 'r':
cmd = DT_FIRST; // ":trewind"
break;
case 'l':
cmd = DT_LAST; // ":tlast"
break;
default: // ":tag"
cmd = DT_TAG;
break;
}
if (name[0] == 'l') {
cmd = DT_LTAG;
}
do_tag(eap->arg, cmd, eap->addr_count > 0 ? (int)eap->line2 : 1,
eap->forceit, true);
}
enum {
SPEC_PERC = 0,
SPEC_HASH,
SPEC_CWORD,
SPEC_CCWORD,
SPEC_CEXPR,
SPEC_CFILE,
SPEC_SFILE,
SPEC_SLNUM,
SPEC_STACK,
SPEC_SCRIPT,
SPEC_AFILE,
SPEC_ABUF,
SPEC_AMATCH,
SPEC_SFLNUM,
SPEC_SID,
// SPEC_CLIENT,
};
/// Check "str" for starting with a special cmdline variable.
/// If found return one of the SPEC_ values and set "*usedlen" to the length of
/// the variable. Otherwise return -1 and "*usedlen" is unchanged.
ssize_t find_cmdline_var(const char *src, size_t *usedlen)
FUNC_ATTR_NONNULL_ALL
{
static char *(spec_str[]) = {
[SPEC_PERC] = "%",
[SPEC_HASH] = "#",
[SPEC_CWORD] = "<cword>", // cursor word
[SPEC_CCWORD] = "<cWORD>", // cursor WORD
[SPEC_CEXPR] = "<cexpr>", // expr under cursor
[SPEC_CFILE] = "<cfile>", // cursor path name
[SPEC_SFILE] = "<sfile>", // ":so" file name
[SPEC_SLNUM] = "<slnum>", // ":so" file line number
[SPEC_STACK] = "<stack>", // call stack
[SPEC_SCRIPT] = "<script>", // script file name
[SPEC_AFILE] = "<afile>", // autocommand file name
[SPEC_ABUF] = "<abuf>", // autocommand buffer number
[SPEC_AMATCH] = "<amatch>", // autocommand match name
[SPEC_SFLNUM] = "<sflnum>", // script file line number
[SPEC_SID] = "<SID>", // script ID: <SNR>123_
// [SPEC_CLIENT] = "<client>",
};
for (size_t i = 0; i < ARRAY_SIZE(spec_str); i++) {
size_t len = strlen(spec_str[i]);
if (strncmp(src, spec_str[i], len) == 0) {
*usedlen = len;
assert(i <= SSIZE_MAX);
return (ssize_t)i;
}
}
return -1;
}
/// Evaluate cmdline variables.
///
/// change "%" to curbuf->b_ffname
/// "#" to curwin->w_alt_fnum
/// "<cword>" to word under the cursor
/// "<cWORD>" to WORD under the cursor
/// "<cexpr>" to C-expression under the cursor
/// "<cfile>" to path name under the cursor
/// "<sfile>" to sourced file name
/// "<stack>" to call stack
/// "<script>" to current script name
/// "<slnum>" to sourced file line number
/// "<afile>" to file name for autocommand
/// "<abuf>" to buffer number for autocommand
/// "<amatch>" to matching name for autocommand
///
/// When an error is detected, "errormsg" is set to a non-NULL pointer (may be
/// "" for error without a message) and NULL is returned.
///
/// @param src pointer into commandline
/// @param srcstart beginning of valid memory for src
/// @param usedlen characters after src that are used
/// @param lnump line number for :e command, or NULL
/// @param errormsg pointer to error message
/// @param escaped return value has escaped white space (can be NULL)
/// @param empty_is_error empty result is considered an error
///
/// @return an allocated string if a valid match was found.
/// Returns NULL if no match was found. "usedlen" then still contains the
/// number of characters to skip.
char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnump,
const char **errormsg, int *escaped, bool empty_is_error)
{
char *result;
char *resultbuf = NULL;
size_t resultlen;
int valid = VALID_HEAD | VALID_PATH; // Assume valid result.
bool tilde_file = false;
bool skip_mod = false;
char strbuf[30];
*errormsg = NULL;
if (escaped != NULL) {
*escaped = false;
}
// Check if there is something to do.
ssize_t spec_idx = find_cmdline_var(src, usedlen);
if (spec_idx < 0) { // no match
*usedlen = 1;
return NULL;
}
// Skip when preceded with a backslash "\%" and "\#".
// Note: In "\\%" the % is also not recognized!
if (src > srcstart && src[-1] == '\\') {
*usedlen = 0;
STRMOVE(src - 1, src); // remove backslash
return NULL;
}
// word or WORD under cursor
if (spec_idx == SPEC_CWORD
|| spec_idx == SPEC_CCWORD
|| spec_idx == SPEC_CEXPR) {
resultlen = find_ident_under_cursor(&result,
spec_idx == SPEC_CWORD
? (FIND_IDENT | FIND_STRING)
: (spec_idx == SPEC_CEXPR
? (FIND_IDENT | FIND_STRING | FIND_EVAL)
: FIND_STRING));
if (resultlen == 0) {
*errormsg = "";
return NULL;
}
//
// '#': Alternate file name
// '%': Current file name
// File name under the cursor
// File name for autocommand
// and following modifiers
//
} else {
switch (spec_idx) {
case SPEC_PERC: // '%': current file
if (curbuf->b_fname == NULL) {
result = "";
valid = 0; // Must have ":p:h" to be valid
} else {
result = curbuf->b_fname;
tilde_file = strcmp(result, "~") == 0;
}
break;
case SPEC_HASH: // '#' or "#99": alternate file
if (src[1] == '#') { // "##": the argument list
result = arg_all();
resultbuf = result;
*usedlen = 2;
if (escaped != NULL) {
*escaped = true;
}
skip_mod = true;
break;
}
char *s = src + 1;
if (*s == '<') { // "#<99" uses v:oldfiles.
s++;
}
int i = getdigits_int(&s, false, 0);
if (s == src + 2 && src[1] == '-') {
// just a minus sign, don't skip over it
s--;
}
*usedlen = (size_t)(s - src); // length of what we expand
if (src[1] == '<' && i != 0) {
if (*usedlen < 2) {
// Should we give an error message for #<text?
*usedlen = 1;
return NULL;
}
result = (char *)tv_list_find_str(get_vim_var_list(VV_OLDFILES), i - 1);
if (result == NULL) {
*errormsg = "";
return NULL;
}
} else {
if (i == 0 && src[1] == '<' && *usedlen > 1) {
*usedlen = 1;
}
buf_T *buf = buflist_findnr(i);
if (buf == NULL) {
*errormsg = _("E194: No alternate file name to substitute for '#'");
return NULL;
}
if (lnump != NULL) {
*lnump = ECMD_LAST;
}
if (buf->b_fname == NULL) {
result = "";
valid = 0; // Must have ":p:h" to be valid
} else {
result = buf->b_fname;
tilde_file = strcmp(result, "~") == 0;
}
}
break;
case SPEC_CFILE: // file name under cursor
result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL);
if (result == NULL) {
*errormsg = "";
return NULL;
}
resultbuf = result; // remember allocated string
break;
case SPEC_AFILE: // file name for autocommand
if (autocmd_fname != NULL && !autocmd_fname_full) {
// Still need to turn the fname into a full path. It was
// postponed to avoid a delay when <afile> is not used.
autocmd_fname_full = true;
result = FullName_save(autocmd_fname, false);
// Copy into `autocmd_fname`, don't reassign it. #8165
xstrlcpy(autocmd_fname, result, MAXPATHL);
xfree(result);
}
result = autocmd_fname;
if (result == NULL) {
*errormsg = _(e_no_autocommand_file_name_to_substitute_for_afile);
return NULL;
}
result = path_try_shorten_fname(result);
break;
case SPEC_ABUF: // buffer number for autocommand
if (autocmd_bufnr <= 0) {
*errormsg = _(e_no_autocommand_buffer_number_to_substitute_for_abuf);
return NULL;
}
snprintf(strbuf, sizeof(strbuf), "%d", autocmd_bufnr);
result = strbuf;
break;
case SPEC_AMATCH: // match name for autocommand
result = autocmd_match;
if (result == NULL) {
*errormsg = _(e_no_autocommand_match_name_to_substitute_for_amatch);
return NULL;
}
break;
case SPEC_SFILE: // file name for ":so" command
result = estack_sfile(ESTACK_SFILE);
if (result == NULL) {
*errormsg = _(e_no_source_file_name_to_substitute_for_sfile);
return NULL;
}
resultbuf = result; // remember allocated string
break;
case SPEC_STACK: // call stack
result = estack_sfile(ESTACK_STACK);
if (result == NULL) {
*errormsg = _(e_no_call_stack_to_substitute_for_stack);
return NULL;
}
resultbuf = result; // remember allocated string
break;
case SPEC_SCRIPT: // script file name
result = estack_sfile(ESTACK_SCRIPT);
if (result == NULL) {
*errormsg = _(e_no_script_file_name_to_substitute_for_script);
return NULL;
}
resultbuf = result; // remember allocated string
break;
case SPEC_SLNUM: // line in file for ":so" command
if (SOURCING_NAME == NULL || SOURCING_LNUM == 0) {
*errormsg = _(e_no_line_number_to_use_for_slnum);
return NULL;
}
snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, SOURCING_LNUM);
result = strbuf;
break;
case SPEC_SFLNUM: // line in script file
if (current_sctx.sc_lnum + SOURCING_LNUM == 0) {
*errormsg = _(e_no_line_number_to_use_for_sflnum);
return NULL;
}
snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR,
current_sctx.sc_lnum + SOURCING_LNUM);
result = strbuf;
break;
case SPEC_SID:
if (current_sctx.sc_sid <= 0) {
*errormsg = _(e_usingsid);
return NULL;
}
snprintf(strbuf, sizeof(strbuf), "<SNR>%" PRIdSCID "_",
current_sctx.sc_sid);
result = strbuf;
break;
default:
// should not happen
*errormsg = "";
result = ""; // avoid gcc warning
break;
}
// Length of new string.
resultlen = strlen(result);
// Remove the file name extension.
if (src[*usedlen] == '<') {
(*usedlen)++;
char *s;
if ((s = strrchr(result, '.')) != NULL
&& s >= path_tail(result)) {
resultlen = (size_t)(s - result);
}
} else if (!skip_mod) {
valid |= modify_fname(src, tilde_file, usedlen, &result,
&resultbuf, &resultlen);
if (result == NULL) {
*errormsg = "";
return NULL;
}
}
}
if (resultlen == 0 || valid != VALID_HEAD + VALID_PATH) {
if (empty_is_error) {
if (valid != VALID_HEAD + VALID_PATH) {
// xgettext:no-c-format
*errormsg = _("E499: Empty file name for '%' or '#', only works with \":p:h\"");
} else {
*errormsg = _("E500: Evaluates to an empty string");
}
}
result = NULL;
} else {
result = xstrnsave(result, resultlen);
}
xfree(resultbuf);
return result;
}
/// Expand the <sfile> string in "arg".
///
/// @return an allocated string, or NULL for any error.
char *expand_sfile(char *arg)
{
char *result = xstrdup(arg);
for (char *p = result; *p;) {
if (strncmp(p, "<sfile>", 7) != 0) {
p++;
} else {
// replace "<sfile>" with the sourced file name, and do ":" stuff
size_t srclen;
const char *errormsg;
char *repl = eval_vars(p, result, &srclen, NULL, &errormsg, NULL, true);
if (errormsg != NULL) {
if (*errormsg) {
emsg(errormsg);
}
xfree(result);
return NULL;
}
if (repl == NULL) { // no match (cannot happen)
p += srclen;
continue;
}
size_t len = strlen(result) - srclen + strlen(repl) + 1;
char *newres = xmalloc(len);
memmove(newres, result, (size_t)(p - result));
STRCPY(newres + (p - result), repl);
len = strlen(newres);
STRCAT(newres, p + srclen);
xfree(repl);
xfree(result);
result = newres;
p = newres + len; // continue after the match
}
}
return result;
}
/// ":rshada" and ":wshada".
static void ex_shada(exarg_T *eap)
{
char *save_shada = p_shada;
if (*p_shada == NUL) {
p_shada = "'100";
}
if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) {
(void)shada_read_everything(eap->arg, eap->forceit, false);
} else {
shada_write_file(eap->arg, eap->forceit);
}
p_shada = save_shada;
}
/// Make a dialog message in "buff[DIALOG_MSG_SIZE]".
/// "format" must contain "%s".
void dialog_msg(char *buff, char *format, char *fname)
{
if (fname == NULL) {
fname = _("Untitled");
}
vim_snprintf(buff, DIALOG_MSG_SIZE, format, fname);
}
static TriState filetype_detect = kNone;
static TriState filetype_plugin = kNone;
static TriState filetype_indent = kNone;
/// ":filetype [plugin] [indent] {on,off,detect}"
/// on: Load the filetype.vim file to install autocommands for file types.
/// off: Load the ftoff.vim file to remove all autocommands for file types.
/// plugin on: load filetype.vim and ftplugin.vim
/// plugin off: load ftplugof.vim
/// indent on: load filetype.vim and indent.vim
/// indent off: load indoff.vim
static void ex_filetype(exarg_T *eap)
{
if (*eap->arg == NUL) {
// Print current status.
smsg("filetype detection:%s plugin:%s indent:%s",
filetype_detect == kTrue ? "ON" : "OFF",
filetype_plugin == kTrue ? (filetype_detect == kTrue ? "ON" : "(on)") : "OFF",
filetype_indent == kTrue ? (filetype_detect == kTrue ? "ON" : "(on)") : "OFF");
return;
}
char *arg = eap->arg;
bool plugin = false;
bool indent = false;
// Accept "plugin" and "indent" in any order.
while (true) {
if (strncmp(arg, "plugin", 6) == 0) {
plugin = true;
arg = skipwhite(arg + 6);
continue;
}
if (strncmp(arg, "indent", 6) == 0) {
indent = true;
arg = skipwhite(arg + 6);
continue;
}
break;
}
if (strcmp(arg, "on") == 0 || strcmp(arg, "detect") == 0) {
if (*arg == 'o' || !filetype_detect) {
source_runtime(FILETYPE_FILE, DIP_ALL);
filetype_detect = kTrue;
if (plugin) {
source_runtime(FTPLUGIN_FILE, DIP_ALL);
filetype_plugin = kTrue;
}
if (indent) {
source_runtime(INDENT_FILE, DIP_ALL);
filetype_indent = kTrue;
}
}
if (*arg == 'd') {
(void)do_doautocmd("filetypedetect BufRead", true, NULL);
do_modelines(0);
}
} else if (strcmp(arg, "off") == 0) {
if (plugin || indent) {
if (plugin) {
source_runtime(FTPLUGOF_FILE, DIP_ALL);
filetype_plugin = kFalse;
}
if (indent) {
source_runtime(INDOFF_FILE, DIP_ALL);
filetype_indent = kFalse;
}
} else {
source_runtime(FTOFF_FILE, DIP_ALL);
filetype_detect = kFalse;
}
} else {
semsg(_(e_invarg2), arg);
}
}
/// Source ftplugin.vim and indent.vim to create the necessary FileType
/// autocommands. We do this separately from filetype.vim so that these
/// autocommands will always fire first (and thus can be overridden) while still
/// allowing general filetype detection to be disabled in the user's init file.
void filetype_plugin_enable(void)
{
if (filetype_plugin == kNone) {
source_runtime(FTPLUGIN_FILE, DIP_ALL);
filetype_plugin = kTrue;
}
if (filetype_indent == kNone) {
source_runtime(INDENT_FILE, DIP_ALL);
filetype_indent = kTrue;
}
}
/// Enable filetype detection if the user did not explicitly disable it.
void filetype_maybe_enable(void)
{
if (filetype_detect == kNone) {
// Normally .vim files are sourced before .lua files when both are
// supported, but we reverse the order here because we want the Lua
// autocommand to be defined first so that it runs first
source_runtime(FILETYPE_FILE, DIP_ALL);
filetype_detect = kTrue;
}
}
/// ":setfiletype [FALLBACK] {name}"
static void ex_setfiletype(exarg_T *eap)
{
if (did_filetype) {
return;
}
char *arg = eap->arg;
if (strncmp(arg, "FALLBACK ", 9) == 0) {
arg += 9;
}
set_option_value_give_err("filetype", CSTR_AS_OPTVAL(arg), OPT_LOCAL);
if (arg != eap->arg) {
did_filetype = false;
}
}
static void ex_digraphs(exarg_T *eap)
{
if (*eap->arg != NUL) {
putdigraph(eap->arg);
} else {
listdigraphs(eap->forceit);
}
}
void set_no_hlsearch(bool flag)
{
no_hlsearch = flag;
set_vim_var_nr(VV_HLSEARCH, !no_hlsearch && p_hls);
}
/// ":nohlsearch"
static void ex_nohlsearch(exarg_T *eap)
{
set_no_hlsearch(true);
redraw_all_later(UPD_SOME_VALID);
}
static void ex_fold(exarg_T *eap)
{
if (foldManualAllowed(true)) {
pos_T start = { eap->line1, 1, 0 };
pos_T end = { eap->line2, 1, 0 };
foldCreate(curwin, start, end);
}
}
static void ex_foldopen(exarg_T *eap)
{
pos_T start = { eap->line1, 1, 0 };
pos_T end = { eap->line2, 1, 0 };
opFoldRange(start, end, eap->cmdidx == CMD_foldopen, eap->forceit, false);
}
static void ex_folddo(exarg_T *eap)
{
// First set the marks for all lines closed/open.
for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
if (hasFolding(lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed)) {
ml_setmarked(lnum);
}
}
global_exe(eap->arg); // Execute the command on the marked lines.
ml_clearmarked(); // clear rest of the marks
}
/// @return 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];
size_t len = 0;
if (cmdmod.cmod_tab > 0 || cmdmod.cmod_split != 0) {
bool multi_mods = false;
// ex_cmd must be a null terminated string before passing to add_win_cmd_modifiers
ex_cmd[0] = '\0';
len = add_win_cmd_modifiers(ex_cmd, &cmdmod, &multi_mods);
assert(len < sizeof(ex_cmd));
int result = snprintf(ex_cmd + len, sizeof(ex_cmd) - len, " new");
assert(result > 0);
len += (size_t)result;
} else {
int result = snprintf(ex_cmd, sizeof(ex_cmd), "enew%s", eap->forceit ? "!" : "");
assert(result > 0);
len += (size_t)result;
}
assert(len < sizeof(ex_cmd));
if (*eap->arg != NUL) { // Run {cmd} in 'shell'.
char *name = vim_strsave_escaped(eap->arg, "\"\\");
snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
" | call termopen(\"%s\")", name);
xfree(name);
} else { // No {cmd}: run the job with tokenized 'shell'.
if (*p_sh == NUL) {
emsg(_(e_shellempty));
return;
}
char **argv = shell_build_argv(NULL, NULL);
char **p = argv;
char tempstring[512];
char shell_argv[512] = { 0 };
while (*p != NULL) {
snprintf(tempstring, sizeof(tempstring), ",\"%s\"", *p);
xstrlcat(shell_argv, tempstring, sizeof(shell_argv));
p++;
}
shell_free_argv(argv);
snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
" | call termopen([%s])", shell_argv + 1);
}
do_cmdline_cmd(ex_cmd);
}
void verify_command(char *cmd)
{
if (strcmp("smile", cmd) != 0) {
return; // acceptable non-existing command
}
msg(" #xxn` #xnxx` ,+x@##@Mz;` .xxx"
"xxxxxxnz+, znnnnnnnnnnnnnnnn.");
msg(" n###z x####` :x##########W+` ,###"
"##########M; W################.");
msg(" n####; x####` `z##############W: ,###"
"############# W################.");
msg(" n####W. x####` ,W#################+ ,###"
"############## W################.");
msg(" n#####n x####` @################### ,###"
"##############i W################.");
msg(" n######i x####` .#########@W@########* ,###"
"##############W`W################.");
msg(" n######@. x####` x######W*. `;n#######: ,###"
"#x,,,,:*M######iW###@:,,,,,,,,,,,`");
msg(" n#######n x####` *######+` :M#####M ,###"
"#n `x#####xW###@`");
msg(" n########* x####``@####@; `x#####i ,###"
"#n ,#####@W###@`");
msg(" n########@ x####`*#####i `M####M ,###"
"#n x#########@`");
msg(" n######### x####`M####z :#####:,###"
"#n z#########@`");
msg(" n#########* x####,#####. n####+,###"
"#n n#########@`");
msg(" n####@####@, x####i####x ;####x,###"
"#n `W#####@####+++++++++++i");
msg(" n####*#####M` x#########* `####@,###"
"#n i#####MW###############W");
msg(" n####.######+ x####z####; W####,###"
"#n i@######W###############W");
msg(" n####.`W#####: x####n####: M####:###"
"#@nnnnnW#######,W###############W");
msg(" n####. :#####M`x####z####; W####,###"
"##############z W###############W");
msg(" n####. #######x#########* `####W,###"
"#############W` W###############W");
msg(" n####. `M#####W####i####x ;####x,###"
"############W, W####+**********i");
msg(" n####. ,##########,#####. n####+,###"
"###########n. W###@`");
msg(" n####. ##########`M####z :#####:,###"
"########Wz: W###@`");
msg(" n####. x#########`*#####i `M####M ,###"
"#x.....` W###@`");
msg(" n####. ,@########``@####@; `x#####i ,###"
"#n W###@`");
msg(" n####. *########` *#####@+` ,M#####M ,###"
"#n W###@`");
msg(" n####. x#######` x######W*. `;n######@: ,###"
"#n W###@,,,,,,,,,,,,`");
msg(" n####. .@######` .#########@W@########* ,###"
"#n W################,");
msg(" n####. i######` @################### ,###"
"#n W################,");
msg(" n####. n#####` ,W#################+ ,###"
"#n W################,");
msg(" n####. .@####` .n##############W; ,###"
"#n W################,");
msg(" n####. i####` :x##########W+` ,###"
"#n W################,");
msg(" +nnnn` +nnn` ,+x@##@Mz;` .nnn"
"n+ zxxxxxxxxxxxxxxxx.");
msg(" ");
msg(" "
" ,+M@#Mi");
msg(" "
" .z########");
msg(" "
" i@#########i");
msg(" "
" `############W`");
msg(" "
" `n#############i");
msg(" "
" `n##############n");
msg(" `` "
" z###############@`");
msg(" `W@z, "
" ##################,");
msg(" *#####` "
" i############@x@###i");
msg(" ######M. "
" :#############n`,W##+");
msg(" +######@: "
" .W#########M@##+ *##z");
msg(" :#######@: "
" `x########@#x###* ,##n");
msg(" `@#######@; "
" z#########M*@nW#i .##x");
msg(" z########@i "
" *###########WM#@#, `##x");
msg(" i##########+ "
" ;###########*n###@ `##x");
msg(" `@#MM#######x, "
" ,@#########zM,`z##M `@#x");
msg(" n##M#W#######n. "
" `.:i*+#zzzz##+i:.` ,W#########Wii,`n@#@` n@##n");
msg(" ;###@#x#######n `,i"
"#nW@#####@@WWW@@####@Mzi. ,W##########@z.. ;zM#+i####z");
msg(" x####nz######## .;#x@##"
"@Wn#*;,.` ``,:*#x@##M+, ;@########xz@WM+#` `n@#######");
msg(" ,@####M########xi#@##@Mzi,"
"` .+x###Mi:n##########Mz```.:i *@######*");
msg(" *#####W#########ix+:` "
" :n#############z: `*.`M######i");
msg(" i#W##nW@+@##@#M@; "
" ;W@@##########W, i`x@#####,");
msg(" `@@n@Wn#@iMW*#*: "
" `iz#z@######x. M######`");
msg(" z##zM###x`*, .` "
" `iW#####W;:` +#####M");
msg(" ,###nn##n` "
" ,#####x;` ,;@######");
msg(" x###xz#. "
" in###+ `:######@.");
msg(" ;####n+ "
" `Mnx##xi` , zM#######");
msg(" `W####+ "
"i. `.+x###@#. :n,z######:");
msg(" z####@` ;"
"#: .ii@###@;.*M*z####@`");
msg(" i####M ` `i@"
"#, :: +#n##@+@##W####n");
msg(" :####x ,i. ##xzM###"
"@` i. .@@, .z####x#######*");
msg(" ,###W; i##Wz########"
"# :## z##n ,@########x###:");
msg(" n##n `W###########M"
"`;n, i#x ,###@i *W########W#@`");
msg(" .@##+ `x###########@."
" z#+ .M#W``x#####n` `;#######@z#x");
msg(" n###z :W############@ "
" z#* @##xM#######@n; `########nW+");
msg(" ;####nW##############W "
":@#* `@#############* :########z@i`");
msg(" M##################### "
"M##: @#############@: *W########M#");
msg(" ;#####################i."
"##x` W#############W, :n########zx");
msg(" x####################@.`"
"x; @#############z. .@########W#");
msg(" ,######################` "
" W###############x*,` W######zM#i");
msg(" #######################: "
" z##################@x+*#zzi `@#########.");
msg(" W########W#z#M#########; "
" *##########################z :@#######@`");
msg(" `@#######x`;#z ,x#######; "
" z###########M###xnM@########* :M######@");
msg(" i########, x#@` z######; "
" *##########i *#@` `+########+` n######.");
msg(" n#######@` M##, `W#####. "
" *#########z ###; z########M: :W####n");
msg(" M#######M n##. x####x "
" `x########: z##+ M#########@; .n###+");
msg(" W#######@` :#W `@####: "
" `@######W i### ;###########@. n##n");
msg(" W########z` ,, .x####z "
" @######@` `W#; `W############* *###;");
msg(" `@#########Mi,:*n@####W` "
" W#######* .. `n#############i i###x");
msg(" .#####################z "
" `@#######@*` .x############n:` ;####.");
msg(" :####################x`,,` "
" `W#########@x#+#@#############i ,####:");
msg(" ;###################x#@###x"
"i` *############################: `####i");
msg(" i##################+#######"
"#M, x##########################@` W###i");
msg(" *################@; @######"
"##@, .W#########################@ x###:");
msg(" .+M#############z. M######"
"###x ,W########################@` ####.");
msg(" *M*;z@########x: :W#####"
"##i .M########################i i###:");
msg(" *##@z;#@####x: :z###"
"@i `########################x .###;");
msg(" *#####n;#@## ;##"
"* ,x#####################@` W##*");
msg(" *#######n;* :M##"
"W*, *W####################` n##z");
msg(" i########@. ,*n####"
"###M*` `###################M *##M");
msg(" i########n `z#####@@"
"#####Wi ,M################; ,##@`");
msg(" ;WMWW@###* .x##@ni.``"
".:+zW##z` `n##############z @##,");
msg(" .*++*i;;;. .M#@+` "
" .##n `x############x` n##i");
msg(" :########* x#W, "
" *#+ *###########M` +##+");
msg(" ,######### :#@: "
" ##: #nzzzzzzzzzz. :##x");
msg(" .#####Wz+` ##+ "
" `MM` .znnnnnnnnn. `@#@`");
msg(" `@@ni;*nMz` @W` "
" :#+ .x#######n x##,");
msg(" i;z@#####, .#* "
" z#: ;;;*zW##; ###i");
msg(" z########: :#; "
" `Wx +###Wni;n. ;##z");
msg(" n########W: .#* "
" ,#, ;#######@+ `@#M");
msg(" .###########n;.MM "
" n* ;iM#######* x#@`");
msg(" :#############@;; "
" .n` ,#W*iW#####W` +##,");
msg(" ,##############. "
" ix. `x###M;####### ,##i");
msg(" .#############@` "
" x@n**#W######z;M###@. W##");
msg(" .##############W: "
" .x############@*;zW#; z#x");
msg(" ,###############@; "
" `##############@n*;. i#@");
msg(" ,#################i "
" :n##############W` .##,");
msg(" ,###################` "
" .+W##########W, `##i");
msg(" :###################@zi,` "
" ;zM@@@WMn*` @#z");
msg(" :#######################@x+"
"*i;;:i#M, `` M#W");
msg(" ;##########################"
"######@x. n##,");
msg(" i#####################@W@@@"
"@Wxz*:` *##+");
msg(" *######################+```"
" :##M");
msg(" ########################M; "
" `@##,");
msg(" z#########################x"
", z###");
msg(" n##########################"
"#n: ;##W`");
msg(" x##########################"
"###Mz#++##* `W##i");
msg(" M##########################"
"##########@` ###x");
msg(" W##########################"
"###########` .###,");
msg(" @##########################"
"##########M n##z");
msg(" @##################z*i@WMMM"
"x#x@#####,. :##@.");
msg(" `#####################@xi` "
" `::,* x##+");
msg(" .#####################@#M. "
" ;##@`");
msg(" ,#####################:. "
" M##i");
msg(" ;###################ni` "
" i##M");
msg(" *#################W#` "
" `W##,");
msg(" z#################@Wx+. "
" +###");
msg(" x######################z. "
" .@#@`");
msg(" `@#######################@; "
" z##;");
msg(" :##########################: "
" :##z");
msg(" +#########################W# "
" M#W");
msg(" W################@n+*i;:,` "
" +##,");
msg(" :##################WMxz+, "
" ,##i");
msg(" n#######################W.., "
" W##");
msg(" +#########################WW@+. .:. "
" z#x");
msg(" `@#############################@@###: "
" *#W");
msg(" #################################Wz: "
" :#@");
msg(",@###############################i "
" .##");
msg("n@@@@@@@#########################+ "
" `##");
msg("` `.:.`.,:iii;;;;;;;;iii;;;:` `.`` "
" `nW");
}
/// Get argt of command with id
uint32_t get_cmd_argt(cmdidx_T cmdidx)
{
return cmdnames[(int)cmdidx].cmd_argt;
}