blob: 9ad7d77c50fc2bd97706b7a718659815f78b1afb [file] [log] [blame]
/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* ex_docmd.c: functions for executing an Ex command line.
*/
#include "vim.h"
static int quitmore = 0;
static int ex_pressedreturn = FALSE;
#ifndef FEAT_PRINTER
# define ex_hardcopy ex_ni
#endif
#ifdef FEAT_USR_CMDS
typedef struct ucmd
{
char_u *uc_name; /* The command name */
long_u uc_argt; /* The argument type */
char_u *uc_rep; /* The command's replacement string */
long uc_def; /* The default value for a range/count */
int uc_compl; /* completion type */
int uc_addr_type; /* The command's address type */
# ifdef FEAT_EVAL
scid_T uc_scriptID; /* SID where the command was defined */
# ifdef FEAT_CMDL_COMPL
char_u *uc_compl_arg; /* completion argument if any */
# endif
# endif
} ucmd_T;
#define UC_BUFFER 1 /* -buffer: local to current buffer */
static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
static void do_ucmd(exarg_T *eap);
static void ex_command(exarg_T *eap);
static void ex_delcommand(exarg_T *eap);
# ifdef FEAT_CMDL_COMPL
static char_u *get_user_command_name(int idx);
# endif
/* Wether a command index indicates a user command. */
# define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
#else
# define ex_command ex_ni
# define ex_comclear ex_ni
# define ex_delcommand ex_ni
/* Wether a command index indicates a user command. */
# define IS_USER_CMDIDX(idx) (FALSE)
#endif
static int compute_buffer_local_count(int addr_type, int lnum, int local);
#ifdef FEAT_EVAL
static char_u *do_one_cmd(char_u **, int, struct condstack *, char_u *(*fgetline)(int, void *, int), void *cookie);
#else
static char_u *do_one_cmd(char_u **, int, char_u *(*fgetline)(int, void *, int), void *cookie);
static int if_level = 0; /* depth in :if */
#endif
static void append_command(char_u *cmd);
static char_u *find_command(exarg_T *eap, int *full);
static void ex_abbreviate(exarg_T *eap);
static void ex_map(exarg_T *eap);
static void ex_unmap(exarg_T *eap);
static void ex_mapclear(exarg_T *eap);
static void ex_abclear(exarg_T *eap);
#ifndef FEAT_MENU
# define ex_emenu ex_ni
# define ex_menu ex_ni
# define ex_menutranslate ex_ni
#endif
#ifdef FEAT_AUTOCMD
static void ex_autocmd(exarg_T *eap);
static void ex_doautocmd(exarg_T *eap);
#else
# define ex_autocmd ex_ni
# define ex_doautocmd ex_ni
# define ex_doautoall ex_ni
#endif
#ifdef FEAT_LISTCMDS
static void ex_bunload(exarg_T *eap);
static void ex_buffer(exarg_T *eap);
static void ex_bmodified(exarg_T *eap);
static void ex_bnext(exarg_T *eap);
static void ex_bprevious(exarg_T *eap);
static void ex_brewind(exarg_T *eap);
static void ex_blast(exarg_T *eap);
#else
# define ex_bunload ex_ni
# define ex_buffer ex_ni
# define ex_bmodified ex_ni
# define ex_bnext ex_ni
# define ex_bprevious ex_ni
# define ex_brewind ex_ni
# define ex_blast ex_ni
# define buflist_list ex_ni
# define ex_checktime ex_ni
#endif
#if !defined(FEAT_LISTCMDS) || !defined(FEAT_WINDOWS)
# define ex_buffer_all ex_ni
#endif
static char_u *getargcmd(char_u **);
static char_u *skip_cmd_arg(char_u *p, int rembs);
static int getargopt(exarg_T *eap);
#ifndef FEAT_QUICKFIX
# define ex_make ex_ni
# define ex_cbuffer ex_ni
# define ex_cc ex_ni
# define ex_cnext ex_ni
# define ex_cfile ex_ni
# define qf_list ex_ni
# define qf_age ex_ni
# define qf_history ex_ni
# define ex_helpgrep ex_ni
# define ex_vimgrep ex_ni
#endif
#if !defined(FEAT_QUICKFIX) || !defined(FEAT_WINDOWS)
# define ex_cclose ex_ni
# define ex_copen ex_ni
# define ex_cwindow ex_ni
# define ex_cbottom ex_ni
#endif
#if !defined(FEAT_QUICKFIX) || !defined(FEAT_EVAL)
# define ex_cexpr ex_ni
#endif
static int check_more(int, int);
static linenr_T get_address(exarg_T *, char_u **, int addr_type, int skip, int to_other_file);
static void get_flags(exarg_T *eap);
#if !defined(FEAT_PERL) \
|| !defined(FEAT_PYTHON) || !defined(FEAT_PYTHON3) \
|| !defined(FEAT_TCL) \
|| !defined(FEAT_RUBY) \
|| !defined(FEAT_LUA) \
|| !defined(FEAT_MZSCHEME)
# define HAVE_EX_SCRIPT_NI
static void ex_script_ni(exarg_T *eap);
#endif
static char_u *invalid_range(exarg_T *eap);
static void correct_range(exarg_T *eap);
#ifdef FEAT_QUICKFIX
static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep);
#endif
static char_u *repl_cmdline(exarg_T *eap, char_u *src, int srclen, char_u *repl, char_u **cmdlinep);
static void ex_highlight(exarg_T *eap);
static void ex_colorscheme(exarg_T *eap);
static void ex_quit(exarg_T *eap);
static void ex_cquit(exarg_T *eap);
static void ex_quit_all(exarg_T *eap);
#ifdef FEAT_WINDOWS
static void ex_close(exarg_T *eap);
static void ex_win_close(int forceit, win_T *win, tabpage_T *tp);
static void ex_only(exarg_T *eap);
static void ex_resize(exarg_T *eap);
static void ex_stag(exarg_T *eap);
static void ex_tabclose(exarg_T *eap);
static void ex_tabonly(exarg_T *eap);
static void ex_tabnext(exarg_T *eap);
static void ex_tabmove(exarg_T *eap);
static void ex_tabs(exarg_T *eap);
#else
# define ex_close ex_ni
# define ex_only ex_ni
# define ex_all ex_ni
# define ex_resize ex_ni
# define ex_splitview ex_ni
# define ex_stag ex_ni
# define ex_tabnext ex_ni
# define ex_tabmove ex_ni
# define ex_tabs ex_ni
# define ex_tabclose ex_ni
# define ex_tabonly ex_ni
#endif
#if defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX)
static void ex_pclose(exarg_T *eap);
static void ex_ptag(exarg_T *eap);
static void ex_pedit(exarg_T *eap);
#else
# define ex_pclose ex_ni
# define ex_ptag ex_ni
# define ex_pedit ex_ni
#endif
static void ex_hide(exarg_T *eap);
static void ex_stop(exarg_T *eap);
static void ex_exit(exarg_T *eap);
static void ex_print(exarg_T *eap);
#ifdef FEAT_BYTEOFF
static void ex_goto(exarg_T *eap);
#else
# define ex_goto ex_ni
#endif
static void ex_shell(exarg_T *eap);
static void ex_preserve(exarg_T *eap);
static void ex_recover(exarg_T *eap);
#ifndef FEAT_LISTCMDS
# define ex_argedit ex_ni
# define ex_argadd ex_ni
# define ex_argdelete ex_ni
# define ex_listdo ex_ni
#endif
static void ex_mode(exarg_T *eap);
static void ex_wrongmodifier(exarg_T *eap);
static void ex_find(exarg_T *eap);
static void ex_open(exarg_T *eap);
static void ex_edit(exarg_T *eap);
#if !defined(FEAT_GUI) && !defined(FEAT_CLIENTSERVER)
# define ex_drop ex_ni
#endif
#ifndef FEAT_GUI
# define ex_gui ex_nogui
static void ex_nogui(exarg_T *eap);
#endif
#if defined(FEAT_GUI_W32) && defined(FEAT_MENU) && defined(FEAT_TEAROFF)
static void ex_tearoff(exarg_T *eap);
#else
# define ex_tearoff ex_ni
#endif
#if (defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK)) && defined(FEAT_MENU)
static void ex_popup(exarg_T *eap);
#else
# define ex_popup ex_ni
#endif
#ifndef FEAT_GUI_MSWIN
# define ex_simalt ex_ni
#endif
#if !defined(FEAT_GUI_MSWIN) && !defined(FEAT_GUI_GTK) && !defined(FEAT_GUI_MOTIF)
# define gui_mch_find_dialog ex_ni
# define gui_mch_replace_dialog ex_ni
#endif
#if !defined(FEAT_GUI_GTK)
# define ex_helpfind ex_ni
#endif
#ifndef FEAT_CSCOPE
# define do_cscope ex_ni
# define do_scscope ex_ni
# define do_cstag ex_ni
#endif
#ifndef FEAT_SYN_HL
# define ex_syntax ex_ni
# define ex_ownsyntax ex_ni
#endif
#if !defined(FEAT_SYN_HL) || !defined(FEAT_PROFILE)
# define ex_syntime ex_ni
#endif
#ifndef FEAT_SPELL
# define ex_spell ex_ni
# define ex_mkspell ex_ni
# define ex_spelldump ex_ni
# define ex_spellinfo ex_ni
# define ex_spellrepall ex_ni
#endif
#ifndef FEAT_PERSISTENT_UNDO
# define ex_rundo ex_ni
# define ex_wundo ex_ni
#endif
#ifndef FEAT_LUA
# define ex_lua ex_script_ni
# define ex_luado ex_ni
# define ex_luafile ex_ni
#endif
#ifndef FEAT_MZSCHEME
# define ex_mzscheme ex_script_ni
# define ex_mzfile ex_ni
#endif
#ifndef FEAT_PERL
# define ex_perl ex_script_ni
# define ex_perldo ex_ni
#endif
#ifndef FEAT_PYTHON
# define ex_python ex_script_ni
# define ex_pydo ex_ni
# define ex_pyfile ex_ni
#endif
#ifndef FEAT_PYTHON3
# define ex_py3 ex_script_ni
# define ex_py3do ex_ni
# define ex_py3file ex_ni
#endif
#ifndef FEAT_TCL
# define ex_tcl ex_script_ni
# define ex_tcldo ex_ni
# define ex_tclfile ex_ni
#endif
#ifndef FEAT_RUBY
# define ex_ruby ex_script_ni
# define ex_rubydo ex_ni
# define ex_rubyfile ex_ni
#endif
#ifndef FEAT_KEYMAP
# define ex_loadkeymap ex_ni
#endif
static void ex_swapname(exarg_T *eap);
static void ex_syncbind(exarg_T *eap);
static void ex_read(exarg_T *eap);
static void ex_pwd(exarg_T *eap);
static void ex_equal(exarg_T *eap);
static void ex_sleep(exarg_T *eap);
static void do_exmap(exarg_T *eap, int isabbrev);
static void ex_winsize(exarg_T *eap);
#ifdef FEAT_WINDOWS
static void ex_wincmd(exarg_T *eap);
#else
# define ex_wincmd ex_ni
#endif
#if defined(FEAT_GUI) || defined(UNIX) || defined(VMS) || defined(MSWIN)
static void ex_winpos(exarg_T *eap);
#else
# define ex_winpos ex_ni
#endif
static void ex_operators(exarg_T *eap);
static void ex_put(exarg_T *eap);
static void ex_copymove(exarg_T *eap);
static void ex_submagic(exarg_T *eap);
static void ex_join(exarg_T *eap);
static void ex_at(exarg_T *eap);
static void ex_bang(exarg_T *eap);
static void ex_undo(exarg_T *eap);
#ifdef FEAT_PERSISTENT_UNDO
static void ex_wundo(exarg_T *eap);
static void ex_rundo(exarg_T *eap);
#endif
static void ex_redo(exarg_T *eap);
static void ex_later(exarg_T *eap);
static void ex_redir(exarg_T *eap);
static void ex_redrawstatus(exarg_T *eap);
static void close_redir(void);
static void ex_mkrc(exarg_T *eap);
static void ex_mark(exarg_T *eap);
#ifdef FEAT_USR_CMDS
static char_u *uc_fun_cmd(void);
static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *compl);
#endif
static void ex_startinsert(exarg_T *eap);
static void ex_stopinsert(exarg_T *eap);
#ifdef FEAT_FIND_ID
static void ex_checkpath(exarg_T *eap);
static void ex_findpat(exarg_T *eap);
#else
# define ex_findpat ex_ni
# define ex_checkpath ex_ni
#endif
#if defined(FEAT_FIND_ID) && defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX)
static void ex_psearch(exarg_T *eap);
#else
# define ex_psearch ex_ni
#endif
static void ex_tag(exarg_T *eap);
static void ex_tag_cmd(exarg_T *eap, char_u *name);
#ifndef FEAT_EVAL
# define ex_scriptnames ex_ni
# define ex_finish ex_ni
# define ex_echo ex_ni
# define ex_echohl ex_ni
# define ex_execute ex_ni
# define ex_call ex_ni
# define ex_if ex_ni
# define ex_endif ex_ni
# define ex_else ex_ni
# define ex_while ex_ni
# define ex_continue ex_ni
# define ex_break ex_ni
# define ex_endwhile ex_ni
# define ex_throw ex_ni
# define ex_try ex_ni
# define ex_catch ex_ni
# define ex_finally ex_ni
# define ex_endtry ex_ni
# define ex_endfunction ex_ni
# define ex_let ex_ni
# define ex_unlet ex_ni
# define ex_lockvar ex_ni
# define ex_unlockvar ex_ni
# define ex_function ex_ni
# define ex_delfunction ex_ni
# define ex_return ex_ni
# define ex_oldfiles ex_ni
#endif
static char_u *arg_all(void);
#ifdef FEAT_SESSION
static int makeopens(FILE *fd, char_u *dirnow);
static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int current_arg_idx);
static void ex_loadview(exarg_T *eap);
static char_u *get_view_file(int c);
static int did_lcd; /* whether ":lcd" was produced for a session */
#else
# define ex_loadview ex_ni
#endif
#ifndef FEAT_EVAL
# define ex_compiler ex_ni
#endif
#ifdef FEAT_VIMINFO
static void ex_viminfo(exarg_T *eap);
#else
# define ex_viminfo ex_ni
#endif
static void ex_behave(exarg_T *eap);
#ifdef FEAT_AUTOCMD
static void ex_filetype(exarg_T *eap);
static void ex_setfiletype(exarg_T *eap);
#else
# define ex_filetype ex_ni
# define ex_setfiletype ex_ni
#endif
#ifndef FEAT_DIFF
# define ex_diffoff ex_ni
# define ex_diffpatch ex_ni
# define ex_diffgetput ex_ni
# define ex_diffsplit ex_ni
# define ex_diffthis ex_ni
# define ex_diffupdate ex_ni
#endif
static void ex_digraphs(exarg_T *eap);
static void ex_set(exarg_T *eap);
#if !defined(FEAT_EVAL) || !defined(FEAT_AUTOCMD)
# define ex_options ex_ni
#endif
#ifdef FEAT_SEARCH_EXTRA
static void ex_nohlsearch(exarg_T *eap);
static void ex_match(exarg_T *eap);
#else
# define ex_nohlsearch ex_ni
# define ex_match ex_ni
#endif
#ifdef FEAT_CRYPT
static void ex_X(exarg_T *eap);
#else
# define ex_X ex_ni
#endif
#ifdef FEAT_FOLDING
static void ex_fold(exarg_T *eap);
static void ex_foldopen(exarg_T *eap);
static void ex_folddo(exarg_T *eap);
#else
# define ex_fold ex_ni
# define ex_foldopen ex_ni
# define ex_folddo ex_ni
#endif
#if !((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \
&& (defined(FEAT_GETTEXT) || defined(FEAT_MBYTE)))
# define ex_language ex_ni
#endif
#ifndef FEAT_SIGNS
# define ex_sign ex_ni
#endif
#ifndef FEAT_SUN_WORKSHOP
# define ex_wsverb ex_ni
#endif
#ifndef FEAT_NETBEANS_INTG
# define ex_nbclose ex_ni
# define ex_nbkey ex_ni
# define ex_nbstart ex_ni
#endif
#ifndef FEAT_EVAL
# define ex_debug ex_ni
# define ex_breakadd ex_ni
# define ex_debuggreedy ex_ni
# define ex_breakdel ex_ni
# define ex_breaklist ex_ni
#endif
#ifndef FEAT_CMDHIST
# define ex_history ex_ni
#endif
#ifndef FEAT_JUMPLIST
# define ex_jumps ex_ni
# define ex_clearjumps ex_ni
# define ex_changes ex_ni
#endif
#ifndef FEAT_PROFILE
# define ex_profile ex_ni
#endif
/*
* Declare cmdnames[].
*/
#define DO_DECLARE_EXCMD
#include "ex_cmds.h"
/*
* Table used to quickly search for a command, based on its first character.
*/
static cmdidx_T cmdidxs[27] =
{
CMD_append,
CMD_buffer,
CMD_change,
CMD_delete,
CMD_edit,
CMD_file,
CMD_global,
CMD_help,
CMD_insert,
CMD_join,
CMD_k,
CMD_list,
CMD_move,
CMD_next,
CMD_open,
CMD_print,
CMD_quit,
CMD_read,
CMD_substitute,
CMD_t,
CMD_undo,
CMD_vglobal,
CMD_write,
CMD_xit,
CMD_yank,
CMD_z,
CMD_bang
};
static char_u dollar_command[2] = {'$', 0};
#ifdef FEAT_EVAL
/* Struct for storing a line inside a while/for loop */
typedef struct
{
char_u *line; /* command line */
linenr_T lnum; /* sourcing_lnum of the line */
} wcmd_T;
/*
* 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_u *(*getline)(int, void *, int);
void *cookie;
};
static char_u *get_loop_line(int c, void *cookie, int indent);
static int store_loop_line(garray_T *gap, char_u *line);
static void free_cmdlines(garray_T *gap);
/* 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_u *vv_exception;
char_u *vv_throwpoint;
int did_emsg;
int got_int;
int did_throw;
int need_rethrow;
int check_cstack;
except_T *current_exception;
};
static void save_dbg_stuff(struct dbg_stuff *dsp);
static void restore_dbg_stuff(struct dbg_stuff *dsp);
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;
}
#endif
/*
* do_exmode(): Repeatedly get commands for the "Ex" mode, until the ":vi"
* command is given.
*/
void
do_exmode(
int improved) /* TRUE for "improved Ex" mode */
{
int save_msg_scroll;
int prev_msg_row;
linenr_T prev_line;
int changedtick;
if (improved)
exmode_active = EXMODE_VIM;
else
exmode_active = EXMODE_NORMAL;
State = NORMAL;
/* When using ":global /pat/ visual" and then "Q" we return to continue
* the :global command. */
if (global_busy)
return;
save_msg_scroll = msg_scroll;
++RedrawingDisabled; /* don't redisplay the window */
++no_wait_return; /* don't wait for return */
#ifdef FEAT_GUI
/* Ignore scrollbar and mouse events in Ex mode */
++hold_gui_events;
#endif
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;
changedtick = curbuf->b_changedtick;
prev_msg_row = msg_row;
prev_line = curwin->w_cursor.lnum;
if (improved)
{
cmdline_row = msg_row;
do_cmdline(NULL, getexline, NULL, 0);
}
else
do_cmdline(NULL, getexmodeline, NULL, DOCMD_NOWAIT);
lines_left = Rows - 1;
if ((prev_line != curwin->w_cursor.lnum
|| changedtick != curbuf->b_changedtick) && !ex_no_reprint)
{
if (curbuf->b_ml.ml_flags & ML_EMPTY)
EMSG(_(e_emptybuf));
else
{
if (ex_pressedreturn)
{
/* 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_emptybuf));
else
EMSG(_("E501: At end-of-file"));
}
}
#ifdef FEAT_GUI
--hold_gui_events;
#endif
--RedrawingDisabled;
--no_wait_return;
update_screen(CLEAR);
need_wait_return = FALSE;
msg_scroll = save_msg_scroll;
}
/*
* Execute a simple command line. Used for translated commands like "*".
*/
int
do_cmdline_cmd(char_u *cmd)
{
return do_cmdline(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 ".").
*
* return FAIL if cmdline could not be executed, OK otherwise
*/
int
do_cmdline(
char_u *cmdline,
char_u *(*fgetline)(int, void *, int),
void *cookie, /* argument for fgetline() */
int flags)
{
char_u *next_cmdline; /* next cmd to execute */
char_u *cmdline_copy = NULL; /* copy of cmd line */
int used_getline = FALSE; /* used "fgetline" to obtain command */
static int recursive = 0; /* recursive depth */
int msg_didout_before_start = 0;
int count = 0; /* line number count */
int did_inc = FALSE; /* incremented RedrawingDisabled */
int retval = OK;
#ifdef FEAT_EVAL
struct condstack cstack; /* conditional stack */
garray_T lines_ga; /* keep lines for ":while"/":for" */
int current_line = 0; /* active line in lines_ga */
char_u *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;
struct msglist **saved_msg_list = NULL;
struct msglist *private_msg_list;
/* "fgetline" and "cookie" passed to do_one_cmd() */
char_u *(*cmd_getline)(int, void *, int);
void *cmd_cookie;
struct loop_cookie cmd_loop_cookie;
void *real_cookie;
int getline_is_func;
#else
# define cmd_getline fgetline
# define cmd_cookie cookie
#endif
static int call_depth = 0; /* recursiveness */
#ifdef FEAT_EVAL
/* 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;
#endif
/* It's possible to create an endless loop with ":execute", catch that
* here. The value of 200 allows nested function calls, ":source", etc. */
if (call_depth == 200)
{
EMSG(_("E169: Command too recursive"));
#ifdef FEAT_EVAL
/* When converting to an exception, we do not include the command name
* since this is not an error of the specific command. */
do_errthrow((struct condstack *)NULL, (char_u *)NULL);
msg_list = saved_msg_list;
#endif
return FAIL;
}
++call_depth;
#ifdef FEAT_EVAL
cstack.cs_idx = -1;
cstack.cs_looplevel = 0;
cstack.cs_trylevel = 0;
cstack.cs_emsg_silent_list = NULL;
cstack.cs_lflags = 0;
ga_init2(&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
vim_memset(&debug_saved, 0, sizeof(debug_saved));
initial_trylevel = trylevel;
/*
* "did_throw" will be set to TRUE when an exception is being thrown.
*/
did_throw = FALSE;
#endif
/*
* "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
{
#ifdef FEAT_EVAL
getline_is_func = getline_equal(fgetline, cookie, get_func_line);
#endif
/* stop skipping cmds for an error msg after all endif/while/for */
if (next_cmdline == NULL
#ifdef FEAT_EVAL
&& !force_abort
&& cstack.cs_idx < 0
&& !(getline_is_func && func_has_abort(real_cookie))
#endif
)
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.
*/
#ifdef FEAT_EVAL
/* 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. */
vim_free(cmdline_copy);
cmdline_copy = NULL;
/* Check if a function has returned or, unless it has an unclosed
* try conditional, aborted. */
if (getline_is_func)
{
# ifdef FEAT_PROFILE
if (do_profiling == PROF_YES)
func_line_end(real_cookie);
# endif
if (func_has_ended(real_cookie))
{
retval = FAIL;
break;
}
}
#ifdef FEAT_PROFILE
else if (do_profiling == PROF_YES
&& getline_equal(fgetline, cookie, getsourceline))
script_line_end();
#endif
/* 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;
}
# ifdef FEAT_PROFILE
if (do_profiling == PROF_YES)
{
if (getline_is_func)
func_line_start(real_cookie);
else if (getline_equal(fgetline, cookie, getsourceline))
script_line_start();
}
# endif
}
if (cstack.cs_looplevel > 0)
{
/* Inside a while/for loop we need to store the lines and use them
* again. Pass a different "fgetline" function to do_one_cmd()
* below, so that it stores lines in or reads them from
* "lines_ga". Makes it possible to define a function inside a
* while/for loop. */
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);
}
else
{
cmd_getline = fgetline;
cmd_cookie = cookie;
}
#endif
/* 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,
#ifdef FEAT_EVAL
cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2
#else
0
#endif
)) == 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)
{
vim_free(repeat_cmdline);
if (count == 0)
repeat_cmdline = vim_strsave(next_cmdline);
else
repeat_cmdline = NULL;
}
}
/* 3. Make a copy of the command so we can mess with it. */
else if (cmdline_copy == NULL)
{
next_cmdline = vim_strsave(next_cmdline);
if (next_cmdline == NULL)
{
EMSG(_(e_outofmem));
retval = FAIL;
break;
}
}
cmdline_copy = next_cmdline;
#ifdef FEAT_EVAL
/*
* Save the current line when inside a ":while" or ":for", and when
* the command looks like a ":while" or ":for", because we may need it
* later. 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.
*/
if (current_line == lines_ga.ga_len
&& (cstack.cs_looplevel || has_loop_cmd(next_cmdline)))
{
if (store_loop_line(&lines_ga, next_cmdline) == FAIL)
{
retval = FAIL;
break;
}
}
did_endif = FALSE;
#endif
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)
{
++no_wait_return;
verbose_enter_scroll();
smsg((char_u *)_("line %ld: %s"),
(long)sourcing_lnum, cmdline_copy);
if (msg_silent == 0)
msg_puts((char_u *)"\n"); /* don't overwrite this */
verbose_leave_scroll();
--no_wait_return;
}
/*
* 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 & DOCMD_VERBOSE,
#ifdef FEAT_EVAL
&cstack,
#endif
cmd_getline, cmd_cookie);
--recursive;
#ifdef FEAT_EVAL
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;
#endif
if (next_cmdline == NULL)
{
vim_free(cmdline_copy);
cmdline_copy = NULL;
#ifdef FEAT_CMDHIST
/*
* 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)
{
vim_free(last_cmdline);
last_cmdline = new_last_cmdline;
new_last_cmdline = NULL;
}
#endif
}
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;
}
#ifdef FEAT_EVAL
/* 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)
{
*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);
}
}
/*
* For a ":while" or ":for" we need to remember the line number.
*/
else if (cstack.cs_lflags & CSL_HAD_LOOP)
{
cstack.cs_lflags &= ~CSL_HAD_LOOP;
cstack.cs_line[cstack.cs_idx] = current_line - 1;
}
}
/*
* When not inside any ":while" loop, clear remembered lines.
*/
if (cstack.cs_looplevel == 0)
{
if (lines_ga.ga_len > 0)
{
sourcing_lnum =
((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum;
free_cmdlines(&lines_ga);
}
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 ? (void *)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);
#endif /* FEAT_EVAL */
}
/*
* 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
#ifdef FEAT_EVAL
|| (did_emsg && force_abort) || did_throw
#endif
)
#ifdef FEAT_EVAL
&& cstack.cs_trylevel == 0
#endif
)
&& !(did_emsg
#ifdef FEAT_EVAL
/* 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)
#endif
&& used_getline
&& (getline_equal(fgetline, cookie, getexmodeline)
|| getline_equal(fgetline, cookie, getexline)))
&& (next_cmdline != NULL
#ifdef FEAT_EVAL
|| cstack.cs_idx >= 0
#endif
|| (flags & DOCMD_REPEAT)));
vim_free(cmdline_copy);
did_emsg_syntax = FALSE;
#ifdef FEAT_EVAL
free_cmdlines(&lines_ga);
ga_clear(&lines_ga);
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
&& ((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)
? (char_u *)"endfunction" : (char_u *)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)
{
void *p = NULL;
char_u *saved_sourcing_name;
int saved_sourcing_lnum;
struct msglist *messages = NULL, *next;
/*
* If the uncaught exception is a user exception, report it as an
* error. If it is an error exception, display the saved error
* message now. For an interrupt exception, do nothing; the
* interrupt message is given elsewhere.
*/
switch (current_exception->type)
{
case ET_USER:
vim_snprintf((char *)IObuff, IOSIZE,
_("E605: Exception not caught: %s"),
current_exception->value);
p = vim_strsave(IObuff);
break;
case ET_ERROR:
messages = current_exception->messages;
current_exception->messages = NULL;
break;
case ET_INTERRUPT:
break;
default:
p = vim_strsave((char_u *)_(e_internal));
}
saved_sourcing_name = sourcing_name;
saved_sourcing_lnum = sourcing_lnum;
sourcing_name = current_exception->throw_name;
sourcing_lnum = current_exception->throw_lnum;
current_exception->throw_name = NULL;
discard_current_exception(); /* uses IObuff if 'verbose' */
suppress_errthrow = TRUE;
force_abort = TRUE;
if (messages != NULL)
{
do
{
next = messages->next;
emsg(messages->msg);
vim_free(messages->msg);
vim_free(messages);
messages = next;
}
while (messages != NULL);
}
else if (p != NULL)
{
emsg(p);
vim_free(p);
}
vim_free(sourcing_name);
sourcing_name = saved_sourcing_name;
sourcing_lnum = saved_sourcing_lnum;
}
/*
* 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.
*/
else if (got_int || (did_emsg && force_abort))
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)
? (char_u *)_("End of sourced file")
: (char_u *)_("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;
#endif /* FEAT_EVAL */
/*
* 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
#ifdef FEAT_EVAL
|| (did_endif && KeyTyped && !did_emsg)
#endif
)
{
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);
}
}
#ifdef FEAT_EVAL
did_endif = FALSE; /* in case do_cmdline used recursively */
#else
/*
* Reset if_level, in case a sourced script file contains more ":if" than
* ":endif" (could be ":if x | foo | endif").
*/
if_level = 0;
#endif
--call_depth;
return retval;
}
#ifdef FEAT_EVAL
/*
* Obtain a line when inside a ":while" or ":for" loop.
*/
static char_u *
get_loop_line(int c, void *cookie, int indent)
{
struct loop_cookie *cp = (struct loop_cookie *)cookie;
wcmd_T *wp;
char_u *line;
if (cp->current_line + 1 >= cp->lines_gap->ga_len)
{
if (cp->repeating)
return NULL; /* trying to read past ":endwhile"/":endfor" */
/* First time inside the ":while"/":for": get line normally. */
if (cp->getline == NULL)
line = getcmdline(c, 0L, indent);
else
line = cp->getline(c, cp->cookie, indent);
if (line != NULL && store_loop_line(cp->lines_gap, line) == OK)
++cp->current_line;
return line;
}
KeyTyped = FALSE;
++cp->current_line;
wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line;
sourcing_lnum = wp->lnum;
return vim_strsave(wp->line);
}
/*
* Store a line in "gap" so that a ":while" loop can execute it again.
*/
static int
store_loop_line(garray_T *gap, char_u *line)
{
if (ga_grow(gap, 1) == FAIL)
return FAIL;
((wcmd_T *)(gap->ga_data))[gap->ga_len].line = vim_strsave(line);
((wcmd_T *)(gap->ga_data))[gap->ga_len].lnum = sourcing_lnum;
++gap->ga_len;
return OK;
}
/*
* Free the lines stored for a ":while" or ":for" loop.
*/
static void
free_cmdlines(garray_T *gap)
{
while (gap->ga_len > 0)
{
vim_free(((wcmd_T *)(gap->ga_data))[gap->ga_len - 1].line);
--gap->ga_len;
}
}
#endif
/*
* If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals
* "func". * Otherwise return TRUE when "fgetline" equals "func".
*/
int
getline_equal(
char_u *(*fgetline)(int, void *, int),
void *cookie UNUSED, /* argument for fgetline() */
char_u *(*func)(int, void *, int))
{
#ifdef FEAT_EVAL
char_u *(*gp)(int, void *, int);
struct loop_cookie *cp;
/* 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. */
gp = fgetline;
cp = (struct loop_cookie *)cookie;
while (gp == get_loop_line)
{
gp = cp->getline;
cp = cp->cookie;
}
return gp == func;
#else
return fgetline == func;
#endif
}
#if defined(FEAT_EVAL) || defined(FEAT_MBYTE) || defined(PROTO)
/*
* If "fgetline" is get_loop_line(), return the cookie used by the original
* getline function. Otherwise return "cookie".
*/
void *
getline_cookie(
char_u *(*fgetline)(int, void *, int) UNUSED,
void *cookie) /* argument for fgetline() */
{
# ifdef FEAT_EVAL
char_u *(*gp)(int, void *, int);
struct loop_cookie *cp;
/* 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. */
gp = fgetline;
cp = (struct loop_cookie *)cookie;
while (gp == get_loop_line)
{
gp = cp->getline;
cp = cp->cookie;
}
return cp;
# else
return cookie;
# endif
}
#endif
/*
* Helper function to apply an offset for buffer commands, i.e. ":bdelete",
* ":bwipeout", etc.
* Returns the buffer number.
*/
static int
compute_buffer_local_count(int addr_type, int lnum, int offset)
{
buf_T *buf;
buf_T *nextbuf;
int count = offset;
buf = firstbuf;
while (buf->b_next != NULL && buf->b_fnum < lnum)
buf = buf->b_next;
while (count != 0)
{
count += (offset < 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 loadedd */
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;
}
#ifdef FEAT_WINDOWS
static int current_win_nr(win_T *win);
static int current_tab_nr(tabpage_T *tab);
static int
current_win_nr(win_T *win)
{
win_T *wp;
int nr = 0;
FOR_ALL_WINDOWS(wp)
{
++nr;
if (wp == win)
break;
}
return nr;
}
static int
current_tab_nr(tabpage_T *tab)
{
tabpage_T *tp;
int nr = 0;
FOR_ALL_TABPAGES(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)
#else
# define CURRENT_WIN_NR 1
# define LAST_WIN_NR 1
# define CURRENT_TAB_NR 1
# define LAST_TAB_NR 1
#endif
/*
* Execute one Ex command.
*
* If 'sourcing' is TRUE, the command will be included in the error message.
*
* 1. skip comment lines and leading space
* 2. handle command modifiers
* 3. find the command
* 4. parse range
* 5. Parse the command.
* 6. parse arguments
* 7. switch on command name
*
* Note: "fgetline" can be NULL.
*
* This function may be called recursively!
*/
#if (_MSC_VER == 1200)
/*
* Avoid optimisation bug in VC++ version 6.0
*/
#pragma optimize( "g", off )
#endif
static char_u *
do_one_cmd(
char_u **cmdlinep,
int sourcing,
#ifdef FEAT_EVAL
struct condstack *cstack,
#endif
char_u *(*fgetline)(int, void *, int),
void *cookie) /* argument for fgetline() */
{
char_u *p;
linenr_T lnum;
long n;
char_u *errormsg = NULL; /* error message */
char_u *after_modifier = NULL;
exarg_T ea; /* Ex command arguments */
long verbose_save = -1;
int save_msg_scroll = msg_scroll;
int save_msg_silent = -1;
int did_esilent = 0;
#ifdef HAVE_SANDBOX
int did_sandbox = FALSE;
#endif
cmdmod_T save_cmdmod;
int ni; /* set when Not Implemented */
char_u *cmd;
vim_memset(&ea, 0, sizeof(ea));
ea.line1 = 1;
ea.line2 = 1;
#ifdef FEAT_EVAL
++ex_nesting_level;
#endif
/* When the last file has not been edited :q has to be typed twice. */
if (quitmore
#ifdef FEAT_EVAL
/* avoid that a function call in 'statusline' does this */
&& !getline_equal(fgetline, cookie, get_func_line)
#endif
#ifdef FEAT_AUTOCMD
/* avoid that an autocommand, e.g. QuitPre, does this */
&& !getline_equal(fgetline, cookie, getnextac)
#endif
)
--quitmore;
/*
* Reset browse, confirm, etc.. They are restored when returning, for
* recursive calls.
*/
save_cmdmod = cmdmod;
vim_memset(&cmdmod, 0, sizeof(cmdmod));
/* "#!anything" is handled like a comment. */
if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!')
goto doend;
/*
* Repeat until no more command modifiers are found.
*/
ea.cmd = *cmdlinep;
for (;;)
{
/*
* 1. Skip comment lines and leading white space and colons.
*/
while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':')
++ea.cmd;
/* in ex mode, an empty line works like :+ */
if (*ea.cmd == NUL && exmode_active
&& (getline_equal(fgetline, cookie, getexmodeline)
|| getline_equal(fgetline, cookie, getexline))
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count)
{
ea.cmd = (char_u *)"+";
ex_pressedreturn = TRUE;
}
/* ignore comment and empty lines */
if (*ea.cmd == '"')
goto doend;
if (*ea.cmd == NUL)
{
ex_pressedreturn = TRUE;
goto doend;
}
/*
* 2. Handle command modifiers.
*/
p = skip_range(ea.cmd, NULL);
switch (*p)
{
/* When adding an entry, also modify cmd_exists(). */
case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3))
break;
#ifdef FEAT_WINDOWS
cmdmod.split |= WSP_ABOVE;
#endif
continue;
case 'b': if (checkforcmd(&ea.cmd, "belowright", 3))
{
#ifdef FEAT_WINDOWS
cmdmod.split |= WSP_BELOW;
#endif
continue;
}
if (checkforcmd(&ea.cmd, "browse", 3))
{
#ifdef FEAT_BROWSE_CMD
cmdmod.browse = TRUE;
#endif
continue;
}
if (!checkforcmd(&ea.cmd, "botright", 2))
break;
#ifdef FEAT_WINDOWS
cmdmod.split |= WSP_BOT;
#endif
continue;
case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4))
break;
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
cmdmod.confirm = TRUE;
#endif
continue;
case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3))
{
cmdmod.keepmarks = TRUE;
continue;
}
if (checkforcmd(&ea.cmd, "keepalt", 5))
{
cmdmod.keepalt = TRUE;
continue;
}
if (checkforcmd(&ea.cmd, "keeppatterns", 5))
{
cmdmod.keeppatterns = TRUE;
continue;
}
if (!checkforcmd(&ea.cmd, "keepjumps", 5))
break;
cmdmod.keepjumps = TRUE;
continue;
case 'f': /* only accept ":filter {pat} cmd" */
{
char_u *reg_pat;
if (!checkforcmd(&p, "filter", 4)
|| *p == NUL || ends_excmd(*p))
break;
if (*p == '!')
{
cmdmod.filter_force = TRUE;
p = skipwhite(p + 1);
if (*p == NUL || ends_excmd(*p))
break;
}
p = skip_vimgrep_pat(p, &reg_pat, NULL);
if (p == NULL || *p == NUL)
break;
cmdmod.filter_regmatch.regprog =
vim_regcomp(reg_pat, RE_MAGIC);
if (cmdmod.filter_regmatch.regprog == NULL)
break;
ea.cmd = p;
continue;
}
/* ":hide" and ":hide | cmd" are not modifiers */
case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3)
|| *p == NUL || ends_excmd(*p))
break;
ea.cmd = p;
cmdmod.hide = TRUE;
continue;
case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3))
{
cmdmod.lockmarks = TRUE;
continue;
}
if (!checkforcmd(&ea.cmd, "leftabove", 5))
break;
#ifdef FEAT_WINDOWS
cmdmod.split |= WSP_ABOVE;
#endif
continue;
case 'n': if (checkforcmd(&ea.cmd, "noautocmd", 3))
{
#ifdef FEAT_AUTOCMD
if (cmdmod.save_ei == NULL)
{
/* Set 'eventignore' to "all". Restore the
* existing option value later. */
cmdmod.save_ei = vim_strsave(p_ei);
set_string_option_direct((char_u *)"ei", -1,
(char_u *)"all", OPT_FREE, SID_NONE);
}
#endif
continue;
}
if (!checkforcmd(&ea.cmd, "noswapfile", 3))
break;
cmdmod.noswapfile = TRUE;
continue;
case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6))
break;
#ifdef FEAT_WINDOWS
cmdmod.split |= WSP_BELOW;
#endif
continue;
case 's': if (checkforcmd(&ea.cmd, "sandbox", 3))
{
#ifdef HAVE_SANDBOX
if (!did_sandbox)
++sandbox;
did_sandbox = TRUE;
#endif
continue;
}
if (!checkforcmd(&ea.cmd, "silent", 3))
break;
if (save_msg_silent == -1)
save_msg_silent = msg_silent;
++msg_silent;
if (*ea.cmd == '!' && !vim_iswhite(ea.cmd[-1]))
{
/* ":silent!", but not "silent !cmd" */
ea.cmd = skipwhite(ea.cmd + 1);
++emsg_silent;
++did_esilent;
}
continue;
case 't': if (checkforcmd(&p, "tab", 3))
{
#ifdef FEAT_WINDOWS
long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS,
ea.skip, FALSE);
if (tabnr == MAXLNUM)
cmdmod.tab = tabpage_index(curtab) + 1;
else
{
if (tabnr < 0 || tabnr > LAST_TAB_NR)
{
errormsg = (char_u *)_(e_invrange);
goto doend;
}
cmdmod.tab = tabnr + 1;
}
ea.cmd = p;
#endif
continue;
}
if (!checkforcmd(&ea.cmd, "topleft", 2))
break;
#ifdef FEAT_WINDOWS
cmdmod.split |= WSP_TOP;
#endif
continue;
case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3))
break;
if (save_msg_silent == -1)
save_msg_silent = msg_silent;
msg_silent = 0;
continue;
case 'v': if (checkforcmd(&ea.cmd, "vertical", 4))
{
#ifdef FEAT_WINDOWS
cmdmod.split |= WSP_VERT;
#endif
continue;
}
if (!checkforcmd(&p, "verbose", 4))
break;
if (verbose_save < 0)
verbose_save = p_verbose;
if (vim_isdigit(*ea.cmd))
p_verbose = atoi((char *)ea.cmd);
else
p_verbose = 1;
ea.cmd = p;
continue;
}
break;
}
after_modifier = ea.cmd;
#ifdef FEAT_EVAL
ea.skip = did_emsg || got_int || did_throw || (cstack->cs_idx >= 0
&& !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE));
#else
ea.skip = (if_level > 0);
#endif
#ifdef FEAT_EVAL
# ifdef FEAT_PROFILE
/* Count this line for profiling if ea.skip is FALSE. */
if (do_profiling == PROF_YES && !ea.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();
}
#endif
/* 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);
}
#endif
/*
* 3. Skip over the range to find the command. Let "p" point to after it.
*
* We need the command to know what kind of range it uses.
*/
cmd = ea.cmd;
ea.cmd = skip_range(ea.cmd, NULL);
if (*ea.cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL)
ea.cmd = skipwhite(ea.cmd + 1);
p = find_command(&ea, NULL);
/*
* 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.
*/
/* ea.addr_type for user commands is set by find_ucmd */
if (!IS_USER_CMDIDX(ea.cmdidx))
{
if (ea.cmdidx != CMD_SIZE)
ea.addr_type = cmdnames[(int)ea.cmdidx].cmd_addr_type;
else
ea.addr_type = ADDR_LINES;
#ifdef FEAT_WINDOWS
/* :wincmd range depends on the argument. */
if (ea.cmdidx == CMD_wincmd && p != NULL)
get_wincmd_addr_type(skipwhite(p), &ea);
#endif
}
/* repeat for all ',' or ';' separated addresses */
ea.cmd = cmd;
for (;;)
{
ea.line1 = ea.line2;
switch (ea.addr_type)
{
case ADDR_LINES:
/* default is current line number */
ea.line2 = curwin->w_cursor.lnum;
break;
case ADDR_WINDOWS:
lnum = CURRENT_WIN_NR;
ea.line2 = lnum;
break;
case ADDR_ARGUMENTS:
ea.line2 = curwin->w_arg_idx + 1;
if (ea.line2 > ARGCOUNT)
ea.line2 = ARGCOUNT;
break;
case ADDR_LOADED_BUFFERS:
case ADDR_BUFFERS:
ea.line2 = curbuf->b_fnum;
break;
case ADDR_TABS:
lnum = CURRENT_TAB_NR;
ea.line2 = lnum;
break;
#ifdef FEAT_QUICKFIX
case ADDR_QUICKFIX:
ea.line2 = qf_get_cur_valid_idx(&ea);
break;
#endif
}
ea.cmd = skipwhite(ea.cmd);
lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip,
ea.addr_count == 0);
if (ea.cmd == NULL) /* error detected */
goto doend;
if (lnum == MAXLNUM)
{
if (*ea.cmd == '%') /* '%' - all lines */
{
++ea.cmd;
switch (ea.addr_type)
{
case ADDR_LINES:
ea.line1 = 1;
ea.line2 = curbuf->b_ml.ml_line_count;
break;
case ADDR_LOADED_BUFFERS:
{
buf_T *buf = firstbuf;
while (buf->b_next != NULL
&& buf->b_ml.ml_mfp == NULL)
buf = buf->b_next;
ea.line1 = buf->b_fnum;
buf = lastbuf;
while (buf->b_prev != NULL
&& buf->b_ml.ml_mfp == NULL)
buf = buf->b_prev;
ea.line2 = buf->b_fnum;
break;
}
case ADDR_BUFFERS:
ea.line1 = firstbuf->b_fnum;
ea.line2 = lastbuf->b_fnum;
break;
case ADDR_WINDOWS:
case ADDR_TABS:
if (IS_USER_CMDIDX(ea.cmdidx))
{
ea.line1 = 1;
ea.line2 = ea.addr_type == ADDR_WINDOWS
? LAST_WIN_NR : LAST_TAB_NR;
}
else
{
/* there is no Vim command which uses '%' and
* ADDR_WINDOWS or ADDR_TABS */
errormsg = (char_u *)_(e_invrange);
goto doend;
}
break;
case ADDR_ARGUMENTS:
if (ARGCOUNT == 0)
ea.line1 = ea.line2 = 0;
else
{
ea.line1 = 1;
ea.line2 = ARGCOUNT;
}
break;
#ifdef FEAT_QUICKFIX
case ADDR_QUICKFIX:
ea.line1 = 1;
ea.line2 = qf_get_size(&ea);
if (ea.line2 == 0)
ea.line2 = 1;
break;
#endif
}
++ea.addr_count;
}
/* '*' - visual area */
else if (*ea.cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL)
{
pos_T *fp;
if (ea.addr_type != ADDR_LINES)
{
errormsg = (char_u *)_(e_invrange);
goto doend;
}
++ea.cmd;
if (!ea.skip)
{
fp = getmark('<', FALSE);
if (check_mark(fp) == FAIL)
goto doend;
ea.line1 = fp->lnum;
fp = getmark('>', FALSE);
if (check_mark(fp) == FAIL)
goto doend;
ea.line2 = fp->lnum;
++ea.addr_count;
}
}
}
else
ea.line2 = lnum;
ea.addr_count++;
if (*ea.cmd == ';')
{
if (!ea.skip)
curwin->w_cursor.lnum = ea.line2;
}
else if (*ea.cmd != ',')
break;
++ea.cmd;
}
/* One address given: set start and end lines */
if (ea.addr_count == 1)
{
ea.line1 = ea.line2;
/* ... but only implicit: really no address given */
if (lnum == MAXLNUM)
ea.addr_count = 0;
}
/* Don't leave the cursor on an illegal line (caused by ';') */
check_cursor_lnum();
/*
* 5. Parse the command.
*/
/*
* Skip ':' and any white space
*/
ea.cmd = skipwhite(ea.cmd);
while (*ea.cmd == ':')
ea.cmd = skipwhite(ea.cmd + 1);
/*
* 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 = RANGE+COUNT+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)
{
/* With '-' in 'cpoptions' a line number past the file is an
* error, otherwise put it at the end of the file. */
if (vim_strchr(p_cpo, CPO_MINUS) != NULL)
ea.line2 = -1;
else
ea.line2 = curbuf->b_ml.ml_line_count;
}
if (ea.line2 < 0)
errormsg = (char_u *)_(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;
}
#ifdef FEAT_AUTOCMD
/* 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_cmdundefined())
{
int ret;
p = ea.cmd;
while (ASCII_ISALNUM(*p))
++p;
p = vim_strnsave(ea.cmd, (int)(p - ea.cmd));
ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, TRUE, NULL);
vim_free(p);
/* If the autocommands did something and didn't cause an error, try
* finding the command again. */
p = (ret && !aborting()) ? find_command(&ea, NULL) : ea.cmd;
}
#endif
#ifdef FEAT_USR_CMDS
if (p == NULL)
{
if (!ea.skip)
errormsg = (char_u *)_("E464: Ambiguous use of user-defined command");
goto doend;
}
/* Check for wrong commands. */
if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78)
{
errormsg = uc_fun_cmd();
goto doend;
}
#endif
if (ea.cmdidx == CMD_SIZE)
{
if (!ea.skip)
{
STRCPY(IObuff, _("E492: Not an editor command"));
if (!sourcing)
{
/* If the modifier was parsed OK the error must be in the
* following command */
if (after_modifier != NULL)
append_command(after_modifier);
else
append_command(*cmdlinep);
}
errormsg = IObuff;
did_emsg_syntax = TRUE;
}
goto doend;
}
ni = (!IS_USER_CMDIDX(ea.cmdidx)
&& (cmdnames[ea.cmdidx].cmd_func == ex_ni
#ifdef HAVE_EX_SCRIPT_NI
|| cmdnames[ea.cmdidx].cmd_func == ex_script_ni
#endif
));
#ifndef FEAT_EVAL
/*
* When the expression evaluation is disabled, recognize the ":if" and
* ":endif" commands and ignore everything in between it.
*/
if (ea.cmdidx == CMD_if)
++if_level;
if (if_level)
{
if (ea.cmdidx == CMD_endif)
--if_level;
goto doend;
}
#endif
/* forced commands */
if (*p == '!' && ea.cmdidx != CMD_substitute
&& ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic)
{
++p;
ea.forceit = TRUE;
}
else
ea.forceit = FALSE;
/*
* 6. Parse arguments.
*/
if (!IS_USER_CMDIDX(ea.cmdidx))
ea.argt = (long)cmdnames[(int)ea.cmdidx].cmd_argt;
if (!ea.skip)
{
#ifdef HAVE_SANDBOX
if (sandbox != 0 && !(ea.argt & SBOXOK))
{
/* Command not allowed in sandbox. */
errormsg = (char_u *)_(e_sandbox);
goto doend;
}
#endif
if (!curbuf->b_p_ma && (ea.argt & MODIFY))
{
/* Command not allowed in non-'modifiable' buffer */
errormsg = (char_u *)_(e_modifiable);
goto doend;
}
if (text_locked() && !(ea.argt & CMDWIN)
&& !IS_USER_CMDIDX(ea.cmdidx))
{
/* Command not allowed when editing the command line. */
errormsg = get_text_locked_msg();
goto doend;
}
#ifdef FEAT_AUTOCMD
/* Disallow editing another buffer when "curbuf_lock" is set.
* Do allow ":edit" (check for argument later).
* Do allow ":checktime" (it's postponed). */
if (!(ea.argt & CMDWIN)
&& ea.cmdidx != CMD_edit
&& ea.cmdidx != CMD_checktime
&& !IS_USER_CMDIDX(ea.cmdidx)
&& curbuf_locked())
goto doend;
#endif
if (!ni && !(ea.argt & RANGE) && ea.addr_count > 0)
{
/* no range allowed */
errormsg = (char_u *)_(e_norange);
goto doend;
}
}
if (!ni && !(ea.argt & BANG) && ea.forceit) /* no <!> allowed */
{
errormsg = (char_u *)_(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)
{
/*
* 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 (sourcing || exmode_active)
{
errormsg = (char_u *)_("E493: Backwards range given");
goto doend;
}
if (ask_yesno((char_u *)
_("Backwards range given, OK to swap"), FALSE) != 'y')
goto doend;
}
lnum = ea.line1;
ea.line1 = ea.line2;
ea.line2 = lnum;
}
if ((errormsg = invalid_range(&ea)) != NULL)
goto doend;
}
if ((ea.argt & NOTADR) && ea.addr_count == 0) /* default is 1, not cursor */
ea.line2 = 1;
correct_range(&ea);
#ifdef FEAT_FOLDING
if (((ea.argt & 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);
}
#endif
#ifdef FEAT_QUICKFIX
/*
* 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;
#endif
/*
* Skip to start of argument.
* Don't do this for the ":!" command, because ":!! -l" needs the space.
*/
if (ea.cmdidx == CMD_bang)
ea.arg = p;
else
ea.arg = skipwhite(p);
/*
* Check for "++opt=val" argument.
* Must be first, allow ":w ++enc=utf8 !cmd"
*/
if (ea.argt & ARGOPT)
while (ea.arg[0] == '+' && ea.arg[1] == '+')
if (getargopt(&ea) == FAIL && !ni)
{
errormsg = (char_u *)_(e_invarg);
goto doend;
}
if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update)
{
if (*ea.arg == '>') /* append */
{
if (*++ea.arg != '>') /* typed wrong */
{
errormsg = (char_u *)_("E494: Use w or w>>");
goto doend;
}
ea.arg = skipwhite(ea.arg + 1);
ea.append = TRUE;
}
else if (*ea.arg == '!' && ea.cmdidx == CMD_write) /* :w !filter */
{
++ea.arg;
ea.usefilter = TRUE;
}
}
if (ea.cmdidx == CMD_read)
{
if (ea.forceit)
{
ea.usefilter = TRUE; /* :r! filter if ea.forceit */
ea.forceit = FALSE;
}
else if (*ea.arg == '!') /* :r !filter */
{
++ea.arg;
ea.usefilter = TRUE;
}
}
if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift)
{
ea.amount = 1;
while (*ea.arg == *ea.cmd) /* count number of '>' or '<' */
{
++ea.arg;
++ea.amount;
}
ea.arg = skipwhite(ea.arg);
}
/*
* Check for "+command" argument, before checking for next command.
* Don't do this for ":read !cmd" and ":write !cmd".
*/
if ((ea.argt & EDITCMD) && !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 & TRLBAR) && !ea.usefilter)
separate_nextcmd(&ea);
/*
* Check for <newline> to end a shell command.
* Also do this for ":read !cmd", ":write !cmd" and ":global".
* Any others?
*/
else if (ea.cmdidx == CMD_bang
|| ea.cmdidx == CMD_global
|| ea.cmdidx == CMD_vglobal
|| ea.usefilter)
{
for (p = ea.arg; *p; ++p)
{
/* Remove one backslash before a newline, so that it's possible to
* pass a newline to the shell and also a newline that is preceded
* with a backslash. This makes it impossible to end a shell
* command in a backslash, but that doesn't appear useful.
* Halving the number of backslashes is incompatible with previous
* versions. */
if (*p == '\\' && p[1] == '\n')
STRMOVE(p, p + 1);
else if (*p == '\n')
{
ea.nextcmd = p + 1;
*p = NUL;
break;
}
}
}
if ((ea.argt & DFLALL) && ea.addr_count == 0)
{
buf_T *buf;
ea.line1 = 1;
switch (ea.addr_type)
{
case ADDR_LINES:
ea.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;
ea.line1 = buf->b_fnum;
buf = lastbuf;
while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL)
buf = buf->b_prev;
ea.line2 = buf->b_fnum;
break;
case ADDR_BUFFERS:
ea.line1 = firstbuf->b_fnum;
ea.line2 = lastbuf->b_fnum;
break;
case ADDR_WINDOWS:
ea.line2 = LAST_WIN_NR;
break;
case ADDR_TABS:
ea.line2 = LAST_TAB_NR;
break;
case ADDR_ARGUMENTS:
if (ARGCOUNT == 0)
ea.line1 = ea.line2 = 0;
else
ea.line2 = ARGCOUNT;
break;
#ifdef FEAT_QUICKFIX
case ADDR_QUICKFIX:
ea.line2 = qf_get_size(&ea);
if (ea.line2 == 0)
ea.line2 = 1;
break;
#endif
}
}
/* accept numbered register only when no count allowed (:put) */
if ( (ea.argt & REGSTR)
&& *ea.arg != NUL
/* Do not allow register = for user commands */
&& (!IS_USER_CMDIDX(ea.cmdidx) || *ea.arg != '=')
&& !((ea.argt & COUNT) && VIM_ISDIGIT(*ea.arg)))
{
#ifndef FEAT_CLIPBOARD
/* check these explicitly for a more specific error message */
if (*ea.arg == '*' || *ea.arg == '+')
{
errormsg = (char_u *)_(e_invalidreg);
goto doend;
}
#endif
if (valid_yank_reg(*ea.arg, (ea.cmdidx != CMD_put
&& !IS_USER_CMDIDX(ea.cmdidx))))
{
ea.regname = *ea.arg++;
#ifdef FEAT_EVAL
/* for '=' register: accept the rest of the line as an expression */
if (ea.arg[-1] == '=' && ea.arg[0] != NUL)
{
set_expr_line(vim_strsave(ea.arg));
ea.arg += STRLEN(ea.arg);
}
#endif
ea.arg = skipwhite(ea.arg);
}
}
/*
* Check for a count. When accepting a BUFNAME, don't use "123foo" as a
* count, it's a buffer name.
*/
if ((ea.argt & COUNT) && VIM_ISDIGIT(*ea.arg)
&& (!(ea.argt & BUFNAME) || *(p = skipdigits(ea.arg)) == NUL
|| vim_iswhite(*p)))
{
n = getdigits(&ea.arg);
ea.arg = skipwhite(ea.arg);
if (n <= 0 && !ni && (ea.argt & ZEROR) == 0)
{
errormsg = (char_u *)_(e_zerocount);
goto doend;
}
if (ea.argt & NOTADR) /* e.g. :buffer 2, :sleep 3 */
{
ea.line2 = n;
if (ea.addr_count == 0)
ea.addr_count = 1;
}
else
{
ea.line1 = ea.line2;
ea.line2 += n - 1;
++ea.addr_count;
/*
* Be vi compatible: no error message for out of range.
*/
if (ea.line2 > curbuf->b_ml.ml_line_count)
ea.line2 = curbuf->b_ml.ml_line_count;
}
}
/*
* Check for flags: 'l', 'p' and '#'.
*/
if (ea.argt & EXFLAGS)
get_flags(&ea);
/* no arguments allowed */
if (!ni && !(ea.argt & EXTRA) && *ea.arg != NUL
&& *ea.arg != '"' && (*ea.arg != '|' || (ea.argt & TRLBAR) == 0))
{
errormsg = (char_u *)_(e_trailing);
goto doend;
}
if (!ni && (ea.argt & NEEDARG) && *ea.arg == NUL)
{
errormsg = (char_u *)_(e_argreq);
goto doend;
}
#ifdef FEAT_EVAL
/*
* Skip the command when it's not going to be executed.
* The commands like :if, :endif, etc. always need to be executed.
* Also make an exception for commands that handle a trailing command
* themselves.
*/
if (ea.skip)
{
switch (ea.cmdidx)
{
/* 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 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_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_execute:
case CMD_filter:
case CMD_help:
case CMD_hide:
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_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_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: