mirror of
https://github.com/neovim/neovim
synced 2025-07-16 09:11:51 +00:00
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
7651 lines
218 KiB
C
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, ®_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;
|
|
}
|