This is ex_docmd.c in view mode; [Download] [Up]
/* vi:set ts=4 sw=4:
*
* 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.
*/
/*
* ex_docmd.c: functions for executing an Ex command line.
*/
#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"
#include "ex_docmd.h"
#include "ex_cmds.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h> /* for chdir() */
#endif
static int quitmore = 0;
static int ex_pressedreturn = FALSE;
/*
* For conditional commands a stack is kept of nested conditionals.
* When cs_idx < 0, there is not conditional command.
*/
#define CSTACK_LEN 50
struct condstack
{
char cs_active[CSTACK_LEN]; /* flag set if executing commands */
int cs_idx; /* current entry, or -1 if none */
};
static char_u *do_one_cmd __ARGS((char_u **, int, struct condstack *, char_u *(*getline)(int, void *, int), void *cookie));
static int buf_write_all __ARGS((BUF *));
static int do_write __ARGS((EXARG *eap));
static char_u *getargcmd __ARGS((char_u **));
static void do_make __ARGS((char_u *));
static int do_arglist __ARGS((char_u *));
static int is_backslash __ARGS((char_u *str));
static int check_readonly __ARGS((int));
static int check_changed __ARGS((BUF *, int, int, int));
static int check_changed_any __ARGS((void));
static int check_more __ARGS((int, int));
static linenr_t get_address __ARGS((char_u **));
static void not_exiting __ARGS((void));
static void do_quit __ARGS((EXARG *eap));
static void do_suspend __ARGS((EXARG *eap));
static void do_exit __ARGS((EXARG *eap));
static void do_wqall __ARGS((EXARG *eap));
static void do_print __ARGS((EXARG *eap));
static void do_argfile __ARGS((EXARG *eap, int argn));
static void do_next __ARGS((EXARG *eap));
static void do_args __ARGS((EXARG *eap));
static void do_resize __ARGS((EXARG *eap));
static void do_exedit __ARGS((EXARG *eap, WIN *old_curwin));
#ifdef USE_GUI
static void do_gui __ARGS((EXARG *eap));
#endif
static void do_read __ARGS((EXARG *eap));
static void do_cd __ARGS((EXARG *eap));
static void do_pwd __ARGS((void));
static void do_sleep __ARGS((EXARG *eap));
static void do_exmap __ARGS((EXARG *eap, int isabbrev));
static void do_exops __ARGS((EXARG *eap));
static void do_copymove __ARGS((EXARG *eap));
static void do_exjoin __ARGS((EXARG *eap));
static void do_at __ARGS((EXARG *eap));
static void do_mkrc __ARGS((EXARG *eap));
static void do_setmark __ARGS((EXARG *eap));
static void do_normal __ARGS((EXARG *eap));
static char_u *do_findpat __ARGS((EXARG *eap, int action));
static char_u *do_if __ARGS((EXARG *eap, struct condstack *cstack));
static char_u *do_else __ARGS((EXARG *eap, struct condstack *cstack));
/*
* do_exmode(): Repeatedly get commands for the "Ex" mode, until the ":vi"
* command is given.
*/
void
do_exmode()
{
int save_msg_scroll;
int prev_msg_row;
linenr_t prev_line;
save_msg_scroll = msg_scroll;
++RedrawingDisabled; /* don't redisplay the window */
++no_wait_return; /* don't wait for return */
settmode(TMODE_COOK);
State = NORMAL;
exmode_active = TRUE;
MSG("Entering Ex mode. Type \"vi\" to get out.");
while (exmode_active)
{
msg_scroll = TRUE;
need_wait_return = FALSE;
ex_pressedreturn = FALSE;
ex_no_reprint = FALSE;
prev_msg_row = msg_row;
prev_line = curwin->w_cursor.lnum;
do_cmdline(NULL, getexmodeline, NULL, DOCMD_NOWAIT);
lines_left = Rows - 1;
if (prev_line != curwin->w_cursor.lnum && !ex_no_reprint)
{
if (ex_pressedreturn)
{
/* go up one line, to overwrite the ":<CR>" line, so the
* output doensn'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);
msg_clr_eos();
}
else if (ex_pressedreturn) /* must be at EOF */
EMSG("At end-of-file");
}
settmode(TMODE_RAW);
--RedrawingDisabled;
--no_wait_return;
update_screen(CLEAR);
need_wait_return = FALSE;
msg_scroll = save_msg_scroll;
}
/*
* do_cmdline(): execute one Ex command line
*
* 1. If no line given, get one from getline().
* 2. Split up in parts separated with '|'.
*
* This function may 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 getline() returns NULL.
*
* return FAIL if commandline could not be executed, OK otherwise
*/
int
do_cmdline(cmdline, getline, cookie, flags)
char_u *cmdline;
char_u *(*getline) __ARGS((int, void *, int));
void *cookie; /* argument for getline() */
int flags;
{
char_u *nextcomm;
static int recursive = 0; /* recursive depth */
int msg_didout_before_start = 0;
struct condstack cstack; /* conditional stack */
int count = 0; /* line number count */
int did_inc = FALSE; /* incremented RedrawingDisabled */
int retval = OK;
/*
* Continue executing command lines when inside an ":if" command.
*/
cstack.cs_idx = -1;
do
{
/*
* 1. If no line given: Get a line in cmdbuff.
* If a line is given: Copy it into cmdbuff.
* After this we don't use cmdbuff but cmdline, because of
* recursiveness
*/
if (cmdline == NULL)
{
/*
* Need to set msg_didout for the first line after an ":if",
* otherwise the ":if" will be overwritten.
*/
if (count == 1 && getline == getexline)
msg_didout = TRUE;
if ((cmdline = getline(':', cookie,
cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2)) == NULL)
{
/* don't call wait_return for aborted command line */
need_wait_return = FALSE;
retval = FAIL;
break;
}
}
else
{
/* Make a copy of the command so we can mess with it. */
if ((cmdline = vim_strsave(cmdline)) == NULL)
{
retval = FAIL;
break;
}
}
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 */
#ifdef SLEEP_IN_EMSG
++dont_sleep; /* don't sleep in emsg() */
#endif
++no_wait_return; /* dont wait for return until finished */
++RedrawingDisabled;
did_inc = TRUE;
}
}
/*
* 2. Loop for each '|' separated command.
* do_one_cmd() will return NULL if there is no trailing '|'.
* "cmdline" may change, e.g. for '%' and '#' expansion.
*/
++recursive;
for (;;)
{
nextcomm = do_one_cmd(&cmdline, flags & DOCMD_VERBOSE, &cstack,
getline, cookie);
if (nextcomm == NULL)
break;
STRCPY(cmdline, nextcomm);
}
--recursive;
vim_free(cmdline);
/*
* If the command was typed, remember it for register :
* Do this AFTER executing the command to make :@: work.
*/
if (getline == getexline && new_last_cmdline != NULL)
{
vim_free(last_cmdline);
last_cmdline = new_last_cmdline;
new_last_cmdline = NULL;
}
cmdline = NULL;
} while (cstack.cs_idx >= 0 || (flags & DOCMD_REPEAT));
/*
* 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;
#ifdef SLEEP_IN_EMSG
--dont_sleep;
#endif
--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 ((count > 1 && KeyTyped) || retval == FAIL)
{
need_wait_return = FALSE;
redraw_later(NOT_VALID);
}
else if ((need_wait_return || (msg_check() && !dont_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);
}
}
return retval;
}
/*
* Execute one Ex command.
*
* If 'sourcing' is TRUE, the command will be included in the error message.
*
* 2. skip comment lines and leading space
* 3. parse range
* 4. parse command
* 5. parse arguments
* 6. switch on command name
*
* This function may be called recursively!
*/
static char_u *
do_one_cmd(cmdlinep, sourcing, cstack, getline, cookie)
char_u **cmdlinep;
int sourcing;
struct condstack *cstack;
char_u *(*getline) __ARGS((int, void *, int));
void *cookie; /* argument for getline() */
{
char_u *p;
char_u *new_cmdline;
int i = 0; /* init to shut up gcc */
int len;
long argt;
linenr_t lnum;
long n = 0; /* init to shut up gcc */
char_u *errormsg = NULL; /* error message */
static int if_level = 0; /* depth in :if */
EXARG ea; /* Ex command arguments */
vim_memset(&ea, 0, sizeof(ea));
ea.line1 = 1;
ea.line2 = 1;
/* when not editing the last file :q has to be typed twice */
if (quitmore)
--quitmore;
did_emsg = FALSE; /* will be set to TRUE when emsg() used, in which
* case we set ea.nextcomm to NULL to cancel the
* whole command line */
/*
* 2. skip comment lines and leading space and colons
*/
for (ea.cmd = *cmdlinep; vim_strchr((char_u *)" \t:", *ea.cmd) != NULL;
ea.cmd++)
;
/* in ex mode, an empty line works like :+ */
if (*ea.cmd == NUL && exmode_active && getline == getexmodeline)
{
ea.cmd = (char_u *)"+";
ex_pressedreturn = TRUE;
}
if (*ea.cmd == '"' || *ea.cmd == NUL) /* ignore comment and empty lines */
goto doend;
/*
* 3. 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.
*/
if (if_level)
goto skip_address;
--ea.cmd;
do
{
ea.line1 = ea.line2;
ea.line2 = curwin->w_cursor.lnum; /* default is current line number */
ea.cmd = skipwhite(ea.cmd + 1); /* skip ',' or ';' and blanks */
lnum = get_address(&ea.cmd);
if (ea.cmd == NULL) /* error detected */
goto doend;
if (lnum == MAXLNUM)
{
if (*ea.cmd == '%') /* '%' - all lines */
{
++ea.cmd;
ea.line1 = 1;
ea.line2 = curbuf->b_ml.ml_line_count;
++ea.addr_count;
}
else if (*ea.cmd == '*') /* '*' - visual area */
{
FPOS *fp;
++ea.cmd;
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.line2 == 0)
curwin->w_cursor.lnum = 1;
else
curwin->w_cursor.lnum = ea.line2;
}
} while (*ea.cmd == ',' || *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;
}
skip_address:
/*
* 4. parse 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.nextcomm.
*/
if (*ea.cmd == NUL || *ea.cmd == '"' ||
(ea.nextcomm = check_nextcomm(ea.cmd)) != NULL)
{
/*
* strange vi behaviour:
* ":3" jumps to line 3
* ":3|..." prints line 3
* ":|" prints current line
*/
if (if_level) /* skip this if inside :if */
goto doend;
if (*ea.cmd == '|')
{
ea.cmdidx = CMD_print;
do_print(&ea);
}
else if (ea.addr_count != 0)
{
if (ea.line2 == 0)
curwin->w_cursor.lnum = 1;
else if (ea.line2 > curbuf->b_ml.ml_line_count)
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
else
curwin->w_cursor.lnum = ea.line2;
beginline(MAYBE);
}
goto doend;
}
/*
* Isolate the command and search for it in the command table.
* Exeptions:
* - the 'k' command can directly be followed by any character.
* - the 's' command can be followed directly by 'c', 'g' or 'r'
* but :sre[wind] is another command.
*/
if (*ea.cmd == 'k')
{
ea.cmdidx = CMD_k;
p = ea.cmd + 1;
}
else if ( *ea.cmd == 's'
&& vim_strchr((char_u *)"cgr", ea.cmd[1]) != NULL
&& STRNCMP("sre", ea.cmd, (size_t)3) != 0)
{
ea.cmdidx = CMD_substitute;
p = ea.cmd + 1;
}
else
{
p = ea.cmd;
while (isalpha(*p))
++p;
/* check for non-alpha command */
if (p == ea.cmd && vim_strchr((char_u *)"@!=><&~#", *p) != NULL)
++p;
i = (int)(p - ea.cmd);
for (ea.cmdidx = 0; ea.cmdidx < CMD_SIZE; ++ea.cmdidx)
if (STRNCMP(cmdnames[ea.cmdidx].cmd_name, (char *)ea.cmd,
(size_t)i) == 0)
break;
if (i == 0 || ea.cmdidx == CMD_SIZE)
{
if (if_level == 0)
{
STRCPY(IObuff, "Not an editor command");
if (!sourcing)
{
STRCAT(IObuff, ": ");
STRNCAT(IObuff, *cmdlinep, 40);
}
errormsg = IObuff;
}
goto doend;
}
}
if (*p == '!') /* forced commands */
{
++p;
ea.forceit = TRUE;
}
else
ea.forceit = FALSE;
/*
* 5. parse arguments
*/
argt = cmdnames[ea.cmdidx].cmd_argt;
if (!(argt & RANGE) && ea.addr_count) /* no range allowed */
{
errormsg = e_norange;
goto doend;
}
if (!(argt & BANG) && ea.forceit) /* no <!> allowed */
{
errormsg = e_nobang;
if (ea.cmdidx == CMD_help)
errormsg = (char_u *)"Don't panic!";
goto doend;
}
/*
* 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 (sourcing)
{
errormsg = (char_u *)"Backwards range given";
goto doend;
}
else 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;
}
/*
* don't complain about the range if it is not used
* (could happen if line_count is accidently set to 0)
*/
if ( ea.line1 < 0
|| ea.line2 < 0
|| ea.line1 > ea.line2
|| ((argt & RANGE)
&& !(argt & NOTADR)
&& ea.line2 > curbuf->b_ml.ml_line_count))
{
errormsg = e_invrange;
goto doend;
}
if ((argt & NOTADR) && ea.addr_count == 0) /* default is 1, not cursor */
ea.line2 = 1;
if (!(argt & ZEROR)) /* zero in range not allowed */
{
if (ea.line1 == 0)
ea.line1 = 1;
if (ea.line2 == 0)
ea.line2 = 1;
}
/*
* For the :make command we insert the 'makeprg' option here,
* so things like % get expanded.
*/
if (ea.cmdidx == CMD_make)
{
if ((new_cmdline = alloc((int)(STRLEN(p_mp) + STRLEN(p) + 2))) == NULL)
goto doend; /* out of memory */
STRCPY(new_cmdline, p_mp);
STRCAT(new_cmdline, " ");
STRCAT(new_cmdline, p);
msg_make(p);
/* 'ea.cmd' is not set here, because it is not used at CMD_make */
vim_free(*cmdlinep);
*cmdlinep = new_cmdline;
p = new_cmdline;
}
/*
* 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);
if (ea.cmdidx == CMD_write)
{
if (*ea.arg == '>') /* append */
{
if (*++ea.arg != '>') /* typed wrong */
{
errormsg = (char_u *)"Use w or w>>";
goto doend;
}
ea.arg = skipwhite(ea.arg + 1);
ea.append = TRUE;
}
else if (*ea.arg == '!') /* :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 ((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 ((argt & TRLBAR) && !ea.usefilter)
{
for (p = ea.arg; *p; ++p)
{
if (*p == Ctrl('V'))
{
if (argt & (USECTRLV | XFILE))
++p; /* skip CTRL-V and next char */
else
STRCPY(p, p + 1); /* remove CTRL-V and skip next char */
if (*p == NUL) /* stop at NUL after CTRL-V */
break;
}
else if ((*p == '"' && !(argt & NOTRLCOM)) ||
*p == '|' || *p == '\n')
{
/*
* We remove the '\' before the '|', unless USECTRLV is used
* AND 'b' is present in 'cpoptions'.
*/
if ((vim_strchr(p_cpo, CPO_BAR) == NULL ||
!(argt & USECTRLV)) && *(p - 1) == '\\')
{
STRCPY(p - 1, p); /* remove the backslash */
--p;
}
else
{
ea.nextcomm = check_nextcomm(p);
*p = NUL;
break;
}
}
}
if (!(argt & NOTRLCOM)) /* remove trailing spaces */
del_trailing_spaces(ea.arg);
}
/*
* Check for <newline> to end a shell command.
* Also do this for ":read !cmd" and ":write !cmd".
*/
else if (ea.cmdidx == CMD_bang || ea.usefilter)
{
for (p = ea.arg; *p; ++p)
{
if (*p == '\\' && p[1])
++p;
else if (*p == '\n')
{
ea.nextcomm = p + 1;
*p = NUL;
break;
}
}
}
if ((argt & DFLALL) && ea.addr_count == 0)
{
ea.line1 = 1;
ea.line2 = curbuf->b_ml.ml_line_count;
}
/* accept numbered register only when no count allowed (:put) */
if ( (argt & REGSTR)
&& *ea.arg != NUL
&& is_yank_register(*ea.arg, FALSE)
&& !((argt & COUNT) && isdigit(*ea.arg)))
{
ea.regname = *ea.arg;
ea.arg = skipwhite(ea.arg + 1);
}
if ((argt & COUNT) && isdigit(*ea.arg))
{
n = getdigits(&ea.arg);
ea.arg = skipwhite(ea.arg);
if (n <= 0)
{
errormsg = e_zerocount;
goto doend;
}
if (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;
}
}
/* no arguments allowed */
if (!(argt & EXTRA) && *ea.arg != NUL &&
vim_strchr((char_u *)"|\"", *ea.arg) == NULL)
{
errormsg = e_trailing;
goto doend;
}
if ((argt & NEEDARG) && *ea.arg == NUL)
{
errormsg = e_argreq;
goto doend;
}
if (argt & XFILE)
{
int expand_wildcards; /* need to expand wildcards */
char_u *repl;
int srclen;
/*
* 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).
*/
expand_wildcards = mch_has_wildcard(ea.arg);
for (p = ea.arg; *p; )
{
/*
* Quick check if this cannot be the start of a special string.
*/
if (vim_strchr((char_u *)"%#<", *p) == NULL)
{
++p;
continue;
}
/*
* Try to find a match at this position.
*/
repl = eval_vars(p, &srclen, &(ea.do_ecmd_lnum), &errormsg);
if (errormsg != NULL) /* error detected */
goto doend;
if (repl == NULL) /* no match found */
{
p += srclen;
continue;
}
/*
* The new command line is build in new_cmdline[].
* First allocate it.
*/
len = STRLEN(repl);
i = STRLEN(*cmdlinep) + len + 3;
if (ea.nextcomm)
i += STRLEN(ea.nextcomm); /* add space for next command */
if ((new_cmdline = alloc(i)) == NULL)
{
vim_free(repl);
goto doend; /* out of memory! */
}
/*
* 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 = p - *cmdlinep; /* length of part before match */
vim_memmove(new_cmdline, *cmdlinep, (size_t)i);
vim_memmove(new_cmdline + i, repl, (size_t)len);
vim_free(repl);
i += len; /* remember the end of the string */
STRCPY(new_cmdline + i, p + srclen);
p = new_cmdline + i; /* remember where to continue */
if (ea.nextcomm) /* append next command */
{
i = STRLEN(new_cmdline) + 1;
STRCPY(new_cmdline + i, ea.nextcomm);
ea.nextcomm = new_cmdline + i;
}
ea.cmd = new_cmdline + (ea.cmd - *cmdlinep);
ea.arg = new_cmdline + (ea.arg - *cmdlinep);
vim_free(*cmdlinep);
*cmdlinep = new_cmdline;
}
/*
* One file argument: Expand wildcards.
* Don't do this with ":r !command" or ":w !command".
*/
if ((argt & NOSPC) && !ea.usefilter)
{
/*
* May do this twice:
* 1. Replace environment variables.
* 2. Replace any other wildcards, remove backslashes.
*/
for (n = 1; n <= 2; ++n)
{
if (n == 2)
{
#if defined(UNIX)
/*
* Only for Unix we check for more than one file name.
* For other systems spaces are considered to be part
* of the file name.
* Only check here if there is no wildcard, otherwise
* ExpandOne will check for errors. This allows
* ":e `ls ve*.c`" on Unix.
*/
if (!expand_wildcards)
for (p = ea.arg; *p; ++p)
{
/* skip escaped characters */
if (p[1] && (*p == '\\' || *p == Ctrl('V')))
++p;
else if (vim_iswhite(*p))
{
errormsg = (char_u *)
"Only one file name allowed";
goto doend;
}
}
#endif
/*
* halve the number of backslashes (this is Vi compatible)
*/
backslash_halve(ea.arg, expand_wildcards);
}
if (expand_wildcards)
{
if (n == 1)
{
/*
* First loop: 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(ea.arg, '$')
#if defined(UNIX) || defined(OS2)
|| vim_strchr(ea.arg, '~')
#endif
)
{
expand_env(ea.arg, NameBuff, MAXPATHL);
expand_wildcards = mch_has_wildcard(NameBuff);
p = NameBuff;
}
else
p = NULL;
}
else /* n == 2 */
{
if ((p = ExpandOne(ea.arg, NULL, WILD_LIST_NOTFOUND,
WILD_EXPAND_FREE)) == NULL)
goto doend;
}
if (p != NULL)
{
/*
* The tricky bit here is to replace the argument,
* while keeping the "ea.cmd" and "ea.nextcomm" the
* pointers correct.
*/
len = ea.arg - *cmdlinep;
i = STRLEN(p) + len;
if (ea.nextcomm)
i += STRLEN(ea.nextcomm);
if ((new_cmdline = alloc((unsigned)i + 2)) != NULL)
{
STRNCPY(new_cmdline, *cmdlinep, len);
STRCPY(new_cmdline + len, p);
if (ea.nextcomm) /* append next command */
{
i = STRLEN(new_cmdline) + 1;
STRCPY(new_cmdline + i, ea.nextcomm);
ea.nextcomm = new_cmdline + i;
}
ea.cmd = new_cmdline + (ea.cmd - *cmdlinep);
ea.arg = new_cmdline + len;
vim_free(*cmdlinep);
*cmdlinep = new_cmdline;
}
if (n == 2) /* p came from ExpandOne() */
vim_free(p);
}
}
}
}
}
/*
* Skip the command when it's not going to be executed.
*
* TODO: Make an exception for commands that handle a trailing command
* themselves.
*/
if (cstack->cs_idx >= 0 && !cstack->cs_active[cstack->cs_idx])
{
switch (ea.cmdidx)
{
case CMD_if:
case CMD_elseif:
case CMD_else:
case CMD_endif: break;
default: goto doend;
}
}
/*
* Accept buffer name. Cannot be used at the same time with a buffer
* number.
*/
if ((argt & BUFNAME) && *ea.arg && ea.addr_count == 0)
{
/*
* :bdelete and :bunload take several arguments, separated by spaces:
* find next space (skipping over escaped characters).
* The others take one argument: ignore trailing spaces.
*/
if (ea.cmdidx == CMD_bdelete || ea.cmdidx == CMD_bunload)
p = skiptowhite_esc(ea.arg);
else
{
p = ea.arg + STRLEN(ea.arg);
while (p > ea.arg && vim_iswhite(p[-1]))
--p;
}
ea.line2 = buflist_findpat(ea.arg, p);
if (ea.line2 < 0) /* failed */
goto doend;
ea.addr_count = 1;
ea.arg = skipwhite(p);
}
/*
* 6. switch on command name
*
* The "ea" structure holds the arguments that can be used.
*/
switch (ea.cmdidx)
{
case CMD_quit:
do_quit(&ea);
break;
/*
* try to quit all windows
*/
case CMD_qall:
exiting = TRUE;
if (ea.forceit || !check_changed_any())
getout(0);
not_exiting();
break;
/*
* close current window, unless it is the last one
*/
case CMD_close:
case CMD_hide:
close_window(curwin, FALSE); /* don't free buffer */
break;
/*
* close all but current window, unless it is the last one
*/
case CMD_only:
close_others(TRUE);
break;
case CMD_stop:
case CMD_suspend:
do_suspend(&ea);
break;
case CMD_exit:
case CMD_xit:
case CMD_wq:
do_exit(&ea);
break;
case CMD_xall: /* write all changed files and exit */
case CMD_wqall: /* write all changed files and quit */
exiting = TRUE;
/* FALLTHROUGH */
case CMD_wall: /* write all changed files */
do_wqall(&ea);
break;
case CMD_preserve: /* put everything in .swp file */
ml_preserve(curbuf, TRUE);
break;
case CMD_recover: /* recover file */
recoverymode = TRUE;
if (!check_changed(curbuf, FALSE, TRUE, ea.forceit) &&
(*ea.arg == NUL || setfname(ea.arg, NULL, TRUE) == OK))
ml_recover();
recoverymode = FALSE;
break;
case CMD_args:
do_args(&ea);
break;
case CMD_wnext:
case CMD_wNext:
case CMD_wprevious:
if (ea.cmd[1] == 'n')
i = curwin->w_arg_idx + (int)ea.line2;
else
i = curwin->w_arg_idx - (int)ea.line2;
ea.line1 = 1;
ea.line2 = curbuf->b_ml.ml_line_count;
if (do_write(&ea) != FAIL)
do_argfile(&ea, i);
break;
case CMD_next:
case CMD_snext:
do_next(&ea);
break;
case CMD_previous:
case CMD_sprevious:
case CMD_Next:
case CMD_sNext:
do_argfile(&ea, curwin->w_arg_idx - (int)ea.line2);
break;
case CMD_rewind:
case CMD_srewind:
do_argfile(&ea, 0);
break;
case CMD_last:
case CMD_slast:
do_argfile(&ea, arg_file_count - 1);
break;
case CMD_argument:
case CMD_sargument:
if (ea.addr_count)
i = ea.line2 - 1;
else
i = curwin->w_arg_idx;
do_argfile(&ea, i);
break;
/*
* open a window for each argument
*/
case CMD_all:
case CMD_sall:
if (ea.addr_count == 0)
ea.line2 = 9999;
do_arg_all((int)ea.line2);
break;
case CMD_buffer: /* :[N]buffer [N] to buffer N */
case CMD_sbuffer: /* :[N]sbuffer [N] to buffer N */
if (*ea.arg)
errormsg = e_trailing;
else
{
if (ea.addr_count == 0) /* default is current buffer */
(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT
: DOBUF_GOTO,
DOBUF_CURRENT, FORWARD, 0, 0);
else
(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT
: DOBUF_GOTO,
DOBUF_FIRST, FORWARD, (int)ea.line2, 0);
}
break;
case CMD_bmodified: /* :[N]bmod [N] to next modified buffer */
case CMD_sbmodified: /* :[N]sbmod [N] to next modified buffer */
(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
DOBUF_MOD, FORWARD, (int)ea.line2, 0);
break;
case CMD_bnext: /* :[N]bnext [N] to next buffer */
case CMD_sbnext: /* :[N]sbnext [N] to next buffer */
(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
DOBUF_CURRENT, FORWARD, (int)ea.line2, 0);
break;
case CMD_bNext: /* :[N]bNext [N] to previous buffer */
case CMD_bprevious: /* :[N]bprevious [N] to previous buffer */
case CMD_sbNext: /* :[N]sbNext [N] to previous buffer */
case CMD_sbprevious: /* :[N]sbprevious [N] to previous buffer */
(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
DOBUF_CURRENT, BACKWARD, (int)ea.line2, 0);
break;
case CMD_brewind: /* :brewind to first buffer */
case CMD_sbrewind: /* :sbrewind to first buffer */
(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
DOBUF_FIRST, FORWARD, 0, 0);
break;
case CMD_blast: /* :blast to last buffer */
case CMD_sblast: /* :sblast to last buffer */
(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
DOBUF_LAST, FORWARD, 0, 0);
break;
case CMD_bunload: /* :[N]bunload[!] [N] [bufname] unload buffer */
case CMD_bdelete: /* :[N]bdelete[!] [N] [bufname] delete buffer */
errormsg = do_bufdel(ea.cmdidx == CMD_bdelete ? DOBUF_DEL
: DOBUF_UNLOAD,
ea.arg, ea.addr_count, (int)ea.line1,
(int)ea.line2, ea.forceit);
break;
case CMD_unhide:
case CMD_sunhide: /* open a window for loaded buffers */
if (ea.addr_count == 0)
ea.line2 = 9999;
(void)do_buffer_all((int)ea.line2, FALSE);
break;
case CMD_ball:
case CMD_sball: /* open a window for every buffer */
if (ea.addr_count == 0)
ea.line2 = 9999;
(void)do_buffer_all((int)ea.line2, TRUE);
break;
case CMD_buffers:
case CMD_files:
case CMD_ls:
buflist_list();
break;
case CMD_write:
if (ea.usefilter) /* input lines to shell command */
do_bang(1, ea.line1, ea.line2, FALSE, ea.arg, TRUE, FALSE);
else
(void)do_write(&ea);
break;
/*
* set screen mode
* if no argument given, just get the screen size and redraw
*/
case CMD_mode:
if (*ea.arg == NUL || mch_screenmode(ea.arg) != FAIL)
set_winsize(0, 0, FALSE);
break;
case CMD_resize:
do_resize(&ea);
break;
/*
* :sview [+command] file split window with new file, read-only
* :split [[+command] file] split window with current or new file
* :new [[+command] file] split window with no or new file
*/
case CMD_sview:
case CMD_split:
case CMD_new:
{
WIN *old_curwin;
old_curwin = curwin;
if (win_split(ea.addr_count ? (int)ea.line2
: 0, FALSE) != FAIL)
do_exedit(&ea, old_curwin);
}
break;
case CMD_edit:
case CMD_ex:
case CMD_visual:
case CMD_view:
do_exedit(&ea, NULL);
break;
#ifdef USE_GUI
/*
* Change from the terminal version to the GUI version. File names
* may be given to redefine the args list -- webb
*/
case CMD_gvim:
case CMD_gui:
do_gui(&ea);
break;
#endif
case CMD_file:
do_file(ea.arg, ea.forceit);
break;
case CMD_swapname:
if (curbuf->b_ml.ml_mfp == NULL ||
(p = curbuf->b_ml.ml_mfp->mf_fname) == NULL)
MSG("No swap file");
else
msg(p);
break;
#if 0
case CMD_mfstat: /* print memfile statistics, for debugging */
mf_statistics();
break;
#endif
case CMD_read:
do_read(&ea);
break;
case CMD_cd:
case CMD_chdir:
do_cd(&ea);
break;
case CMD_pwd:
do_pwd();
break;
case CMD_equal:
smsg((char_u *)"line %ld", (long)ea.line2);
break;
case CMD_list:
i = curwin->w_p_list;
curwin->w_p_list = 1;
do_print(&ea);
curwin->w_p_list = i;
break;
case CMD_number: /* :nu */
case CMD_pound: /* :# */
case CMD_print: /* :p */
do_print(&ea);
break;
case CMD_shell:
do_shell(NULL);
break;
case CMD_sleep:
do_sleep(&ea);
break;
case CMD_stag:
postponed_split = TRUE;
/*FALLTHROUGH*/
case CMD_tag:
do_tag(ea.arg, 0, ea.addr_count ? (int)ea.line2
: 1, ea.forceit);
break;
case CMD_pop:
do_tag((char_u *)"", 1, ea.addr_count ? (int)ea.line2
: 1, ea.forceit);
break;
case CMD_tags:
do_tags();
break;
case CMD_marks:
do_marks(ea.arg);
break;
case CMD_jumps:
do_jumps();
break;
case CMD_ascii:
do_ascii();
break;
case CMD_checkpath:
find_pattern_in_path(NULL, 0, FALSE, FALSE, CHECK_PATH, 1L,
ea.forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
(linenr_t)1, (linenr_t)MAXLNUM);
break;
case CMD_digraphs:
#ifdef DIGRAPHS
if (*ea.arg)
putdigraph(ea.arg);
else
listdigraphs();
#else
EMSG("No digraphs in this version");
#endif /* DIGRAPHS */
break;
case CMD_set:
(void)do_set(ea.arg);
break;
case CMD_fixdel:
do_fixdel();
break;
#ifdef AUTOCMD
case CMD_autocmd:
/*
* Disallow auto commands from .exrc and .vimrc in current
* directory for security reasons.
*/
if (secure)
{
secure = 2;
errormsg = e_curdir;
}
else
do_autocmd(ea.arg, ea.forceit);
break;
case CMD_doautocmd:
do_doautocmd(ea.arg); /* apply the automatic commands */
do_modelines();
break;
#endif
case CMD_abbreviate:
case CMD_noreabbrev:
case CMD_unabbreviate:
case CMD_cabbrev:
case CMD_cnoreabbrev:
case CMD_cunabbrev:
case CMD_iabbrev:
case CMD_inoreabbrev:
case CMD_iunabbrev:
do_exmap(&ea, TRUE); /* almost the same as mapping */
break;
case CMD_nmap:
case CMD_vmap:
case CMD_cmap:
case CMD_imap:
case CMD_map:
case CMD_nnoremap:
case CMD_vnoremap:
case CMD_cnoremap:
case CMD_inoremap:
case CMD_noremap:
/*
* If we are sourcing .exrc or .vimrc in current directory we
* print the mappings for security reasons.
*/
if (secure)
{
secure = 2;
msg_outtrans(ea.cmd);
msg_putchar('\n');
}
case CMD_nunmap:
case CMD_vunmap:
case CMD_cunmap:
case CMD_iunmap:
case CMD_unmap:
do_exmap(&ea, FALSE);
break;
case CMD_mapclear:
case CMD_imapclear:
case CMD_nmapclear:
case CMD_vmapclear:
case CMD_cmapclear:
map_clear(*ea.cmd, ea.forceit, FALSE);
break;
case CMD_abclear:
case CMD_iabclear:
case CMD_cabclear:
map_clear(*ea.cmd, TRUE, TRUE);
break;
#ifdef USE_GUI
case CMD_menu: case CMD_noremenu: case CMD_unmenu:
case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu:
case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu:
case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu:
case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu:
gui_do_menu(ea.cmd, ea.arg, ea.forceit);
break;
#endif /* USE_GUI */
case CMD_display:
case CMD_registers:
do_dis(ea.arg); /* display buffer contents */
break;
case CMD_help:
do_help(ea.arg);
break;
case CMD_version:
do_version(ea.arg);
break;
case CMD_winsize: /* obsolete command */
ea.line1 = getdigits(&ea.arg);
ea.arg = skipwhite(ea.arg);
ea.line2 = getdigits(&ea.arg);
set_winsize((int)ea.line1, (int)ea.line2, TRUE);
break;
case CMD_delete:
case CMD_yank:
case CMD_rshift:
case CMD_lshift:
do_exops(&ea);
break;
case CMD_put:
/*
* ":0put" works like ":1put!".
*/
if (ea.line2 == 0)
{
ea.line2 = 1;
ea.forceit = TRUE;
}
curwin->w_cursor.lnum = ea.line2;
do_put(ea.regname, ea.forceit ? BACKWARD : FORWARD, -1L, FALSE);
break;
case CMD_t:
case CMD_copy:
case CMD_move:
do_copymove(&ea);
break;
case CMD_coffee:
MSG("Insert the beans please.");
break;
case CMD_and: /* :& */
case CMD_tilde: /* :~ */
case CMD_substitute: /* :s */
do_sub(&ea);
break;
case CMD_join:
do_exjoin(&ea);
break;
case CMD_global:
if (ea.forceit)
*ea.cmd = 'v';
case CMD_vglobal:
do_glob(&ea);
break;
case CMD_at: /* :[addr]@r */
do_at(&ea);
break;
case CMD_bang:
do_bang(ea.addr_count, ea.line1, ea.line2,
ea.forceit, ea.arg, TRUE, TRUE);
break;
case CMD_undo:
u_undo(1);
break;
case CMD_redo:
u_redo(1);
break;
case CMD_source:
if (ea.forceit) /* :so! read vi commands */
(void)openscript(ea.arg);
/* :so read ex commands */
else if (do_source(ea.arg, FALSE) == FAIL)
emsg2(e_notopen, ea.arg);
break;
#ifdef VIMINFO
case CMD_rviminfo:
p = p_viminfo;
if (*p_viminfo == NUL)
p_viminfo = (char_u *)"'100";
if (read_viminfo(ea.arg, TRUE, TRUE, ea.forceit) == FAIL)
EMSG("Cannot open viminfo file for reading");
p_viminfo = p;
break;
case CMD_wviminfo:
p = p_viminfo;
if (*p_viminfo == NUL)
p_viminfo = (char_u *)"'100";
write_viminfo(ea.arg, ea.forceit);
p_viminfo = p;
break;
#endif /* VIMINFO */
case CMD_mkvimrc:
if (*ea.arg == NUL)
ea.arg = (char_u *)VIMRC_FILE;
/*FALLTHROUGH*/
case CMD_mkexrc:
do_mkrc(&ea);
break;
case CMD_cc:
qf_jump(0, ea.addr_count ? (int)ea.line2 : 0, ea.forceit);
break;
case CMD_cfile:
if (*ea.arg != NUL)
{
/*
* Great trick: Insert 'ef=' before ea.arg.
* Always ok, because "cf " must be there.
*/
ea.arg -= 3;
ea.arg[0] = 'e';
ea.arg[1] = 'f';
ea.arg[2] = '=';
(void)do_set(ea.arg);
}
if (qf_init() == OK)
qf_jump(0, 0, ea.forceit); /* display first error */
break;
case CMD_clist:
qf_list(ea.forceit);
break;
case CMD_cnext:
qf_jump(FORWARD, ea.addr_count ? (int)ea.line2 : 1, ea.forceit);
break;
case CMD_cNext:
case CMD_cprevious:
qf_jump(BACKWARD, ea.addr_count ? (int)ea.line2 : 1,
ea.forceit);
break;
case CMD_cquit:
getout(1); /* this does not always pass on the exit
code to the Manx compiler. why? */
case CMD_mark:
case CMD_k:
do_setmark(&ea);
break;
case CMD_center:
case CMD_right:
case CMD_left:
do_align(&ea);
break;
case CMD_retab:
do_retab(&ea);
break;
case CMD_make:
do_make(ea.arg);
break;
case CMD_normal:
do_normal(&ea);
break;
case CMD_isearch:
case CMD_dsearch:
errormsg = do_findpat(&ea, ACTION_SHOW);
break;
case CMD_ilist:
case CMD_dlist:
errormsg = do_findpat(&ea, ACTION_SHOW_ALL);
break;
case CMD_ijump:
case CMD_djump:
errormsg = do_findpat(&ea, ACTION_GOTO);
break;
case CMD_isplit:
case CMD_dsplit:
errormsg = do_findpat(&ea, ACTION_SPLIT);
break;
/*
* ":echo arg .." and ":echon arg .."; Echo the arguments.
*/
case CMD_echo:
case CMD_echon:
do_echo(ea.arg, ea.cmdidx == CMD_echo);
break;
case CMD_syntax:
do_syntax(ea.arg, &ea.nextcomm);
break;
case CMD_highlight:
do_highlight(ea.arg);
break;
/*
* The conditional commands.
*/
case CMD_if:
errormsg = do_if(&ea, cstack);
break;
case CMD_elseif:
case CMD_else:
errormsg = do_else(&ea, cstack);
break;
case CMD_endif:
if (cstack->cs_idx < 0)
errormsg = (char_u *)":endif without :if";
else
--cstack->cs_idx;
break;
/*
* ":let var = expr".
*/
case CMD_let:
do_let(ea.arg);
break;
case CMD_unlet:
do_unlet(ea.arg);
break;
case CMD_insert:
do_append(ea.line2 - 1, getline, cookie);
ex_no_reprint = TRUE;
break;
case CMD_append:
do_append(ea.line2, getline, cookie);
ex_no_reprint = TRUE;
break;
case CMD_change:
do_change(ea.line1, ea.line2, getline, cookie);
ex_no_reprint = TRUE;
break;
case CMD_z:
do_z(ea.line2, ea.arg);
ex_no_reprint = TRUE;
break;
case CMD_intro:
do_intro();
break;
#ifdef HAVE_PERL_INTERP
/*
* ":perl cmd"
* ":[range]perldo cmd"
*/
case CMD_perl:
do_perl(&ea);
break;
case CMD_perldo:
do_perldo(&ea);
break;
#endif
#ifdef HAVE_PYTHON
case CMD_python:
do_python(&ea);
break;
case CMD_pyfile:
do_pyfile(&ea);
break;
#endif
default:
/* Normal illegal commands have already been handled */
errormsg = (char_u *)"Sorry, this command is not implemented";
}
doend:
if (errormsg != NULL && *errormsg != NUL)
{
emsg(errormsg);
if (sourcing)
{
MSG_PUTS(": ");
msg_outtrans(*cmdlinep);
}
}
if (did_emsg)
ea.nextcomm = NULL; /* cancel ea.nextcomm at an error */
if (ea.nextcomm && *ea.nextcomm == NUL) /* not really a next command */
ea.nextcomm = NULL;
return ea.nextcomm;
}
/*
* This is all pretty much copied from do_one_cmd(), with all the extra stuff
* we don't need/want deleted. Maybe this could be done better if we didn't
* repeat all this stuff. The only problem is that they may not stay perfectly
* compatible with each other, but then the command line syntax probably won't
* change that much -- webb.
*/
char_u *
set_one_cmd_context(buff)
char_u *buff; /* buffer for command string */
{
char_u *p;
char_u *cmd, *arg;
int i;
int cmdidx;
long argt;
char_u delim;
int forceit = FALSE;
int usefilter = FALSE; /* filter instead of file name */
expand_pattern = buff;
expand_context = EXPAND_COMMANDS; /* Default until we get past command */
/*
* 2. skip comment lines and leading space, colons or bars
*/
for (cmd = buff; vim_strchr((char_u *)" \t:|", *cmd) != NULL; cmd++)
;
expand_pattern = cmd;
if (*cmd == NUL)
return NULL;
if (*cmd == '"') /* ignore comment lines */
{
expand_context = EXPAND_NOTHING;
return NULL;
}
/*
* 3. parse 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 "'". -- webb
*/
while (*cmd != NUL && (vim_isspace(*cmd) || isdigit(*cmd) ||
vim_strchr((char_u *)".$%'/?-+,;", *cmd) != NULL))
{
if (*cmd == '\'')
{
if (*++cmd == NUL)
expand_context = EXPAND_NOTHING;
}
else if (*cmd == '/' || *cmd == '?')
{
delim = *cmd++;
while (*cmd != NUL && *cmd != delim)
if (*cmd++ == '\\' && *cmd != NUL)
++cmd;
if (*cmd == NUL)
expand_context = EXPAND_NOTHING;
}
if (*cmd != NUL)
++cmd;
}
/*
* 4. parse command
*/
cmd = skipwhite(cmd);
expand_pattern = cmd;
if (*cmd == NUL)
return NULL;
if (*cmd == '"')
{
expand_context = EXPAND_NOTHING;
return NULL;
}
if (*cmd == '|' || *cmd == '\n')
return cmd + 1; /* There's another command */
/*
* Isolate the command and search for it in the command table.
* Exeptions:
* - the 'k' command can directly be followed by any character.
* - the 's' command can be followed directly by 'c', 'g' or 'r'
*/
if (*cmd == 'k')
{
cmdidx = CMD_k;
p = cmd + 1;
}
else
{
p = cmd;
while (isalpha(*p) || *p == '*') /* Allow * wild card */
++p;
/* check for non-alpha command */
if (p == cmd && vim_strchr((char_u *)"@!=><&~#", *p) != NULL)
++p;
i = (int)(p - cmd);
if (i == 0)
{
expand_context = EXPAND_UNSUCCESSFUL;
return NULL;
}
for (cmdidx = 0; cmdidx < CMD_SIZE; ++cmdidx)
if (STRNCMP(cmdnames[cmdidx].cmd_name, cmd, (size_t)i) == 0)
break;
}
/*
* If the cursor is touching the command, and it ends in an alphabetic
* character, complete the command name.
*/
if (*p == NUL && isalpha(p[-1]))
return NULL;
if (cmdidx == CMD_SIZE)
{
if (*cmd == 's' && vim_strchr((char_u *)"cgr", cmd[1]) != NULL)
{
cmdidx = CMD_substitute;
p = cmd + 1;
}
else
{
/* Not still touching the command and it was an illegal command */
expand_context = EXPAND_UNSUCCESSFUL;
return NULL;
}
}
expand_context = EXPAND_NOTHING; /* Default now that we're past command */
if (*p == '!') /* forced commands */
{
forceit = TRUE;
++p;
}
/*
* 5. parse arguments
*/
argt = cmdnames[cmdidx].cmd_argt;
arg = skipwhite(p);
if (cmdidx == CMD_write)
{
if (*arg == '>') /* append */
{
if (*++arg == '>') /* It should be */
++arg;
arg = skipwhite(arg);
}
else if (*arg == '!') /* :w !filter */
{
++arg;
usefilter = TRUE;
}
}
if (cmdidx == CMD_read)
{
usefilter = forceit; /* :r! filter if forced */
if (*arg == '!') /* :r !filter */
{
++arg;
usefilter = TRUE;
}
}
if (cmdidx == CMD_lshift || cmdidx == CMD_rshift)
{
while (*arg == *cmd) /* allow any number of '>' or '<' */
++arg;
arg = skipwhite(arg);
}
/* Does command allow "+command"? */
if ((argt & EDITCMD) && !usefilter && *arg == '+')
{
/* Check if we're in the +command */
p = arg + 1;
arg = skiptowhite(arg);
/* Still touching the command after '+'? */
if (*arg == NUL)
return p;
/* Skip space after +command to get to the real argument */
arg = skipwhite(arg);
}
/*
* Check for '|' to separate commands and '"' to start comments.
* Don't do this for ":read !cmd" and ":write !cmd".
*/
if ((argt & TRLBAR) && !usefilter)
{
p = arg;
while (*p)
{
if (*p == Ctrl('V'))
{
if (p[1] != NUL)
++p;
}
else if ((*p == '"' && !(argt & NOTRLCOM)) || *p == '|' || *p == '\n')
{
if (*(p - 1) != '\\')
{
if (*p == '|' || *p == '\n')
return p + 1;
return NULL; /* It's a comment */
}
}
++p;
}
}
/* no arguments allowed */
if (!(argt & EXTRA) && *arg != NUL &&
vim_strchr((char_u *)"|\"", *arg) == NULL)
return NULL;
/* Find start of last argument (argument just before cursor): */
p = buff + STRLEN(buff);
while (p != arg && *p != ' ' && *p != TAB)
p--;
if (*p == ' ' || *p == TAB)
p++;
expand_pattern = p;
if (argt & XFILE)
{
int in_quote = FALSE;
char_u *bow = NULL; /* Beginning of word */
/*
* Allow spaces within back-quotes to count as part of the argument
* being expanded.
*/
expand_pattern = skipwhite(arg);
for (p = expand_pattern; *p; ++p)
{
if (*p == '\\' && p[1])
++p;
#ifdef SPACE_IN_FILENAME
else if (vim_iswhite(*p) && (!(argt & NOSPC) || usefilter))
#else
else if (vim_iswhite(*p))
#endif
{
p = skipwhite(p);
if (in_quote)
bow = p;
else
expand_pattern = p;
--p;
}
else if (*p == '`')
{
if (!in_quote)
{
expand_pattern = p;
bow = p + 1;
}
in_quote = !in_quote;
}
}
/*
* If we are still inside the quotes, and we passed a space, just
* expand from there.
*/
if (bow != NULL && in_quote)
expand_pattern = bow;
expand_context = EXPAND_FILES;
}
/*
* 6. switch on command name
*/
switch (cmdidx)
{
case CMD_cd:
case CMD_chdir:
expand_context = EXPAND_DIRECTORIES;
break;
case CMD_global:
case CMD_vglobal:
delim = *arg; /* get the delimiter */
if (delim)
++arg; /* skip delimiter if there is one */
while (arg[0] != NUL && arg[0] != delim)
{
if (arg[0] == '\\' && arg[1] != NUL)
++arg;
++arg;
}
if (arg[0] != NUL)
return arg + 1;
break;
case CMD_and:
case CMD_substitute:
delim = *arg;
if (delim)
++arg;
for (i = 0; i < 2; i++)
{
while (arg[0] != NUL && arg[0] != delim)
{
if (arg[0] == '\\' && arg[1] != NUL)
++arg;
++arg;
}
if (arg[0] != NUL) /* skip delimiter */
++arg;
}
while (arg[0] && vim_strchr((char_u *)"|\"#", arg[0]) == NULL)
++arg;
if (arg[0] != NUL)
return arg;
break;
case CMD_isearch:
case CMD_dsearch:
case CMD_ilist:
case CMD_dlist:
case CMD_ijump:
case CMD_djump:
case CMD_isplit:
case CMD_dsplit:
arg = skipwhite(skipdigits(arg)); /* skip count */
if (*arg == '/') /* Match regexp, not just whole words */
{
for (++arg; *arg && *arg != '/'; arg++)
if (*arg == '\\' && arg[1] != NUL)
arg++;
if (*arg)
{
arg = skipwhite(arg + 1);
/* Check for trailing illegal characters */
if (*arg && vim_strchr((char_u *)"|\"\n", *arg) == NULL)
expand_context = EXPAND_NOTHING;
else
return arg;
}
}
break;
#ifdef AUTOCMD
case CMD_autocmd:
return set_context_in_autocmd(arg, FALSE);
case CMD_doautocmd:
return set_context_in_autocmd(arg, TRUE);
#endif
case CMD_set:
set_context_in_set_cmd(arg);
break;
case CMD_stag:
case CMD_tag:
expand_context = EXPAND_TAGS;
expand_pattern = arg;
break;
case CMD_help:
expand_context = EXPAND_HELP;
expand_pattern = arg;
break;
case CMD_bdelete:
case CMD_bunload:
while ((expand_pattern = vim_strchr(arg, ' ')) != NULL)
arg = expand_pattern + 1;
case CMD_buffer:
case CMD_sbuffer:
expand_context = EXPAND_BUFFERS;
expand_pattern = arg;
break;
#ifdef USE_GUI
case CMD_menu: case CMD_noremenu: case CMD_unmenu:
case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu:
case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu:
case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu:
case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu:
return gui_set_context_in_menu_cmd(cmd, arg, forceit);
break;
#endif
default:
break;
}
return NULL;
}
/*
* get a single EX address
*
* Set ptr to the next character after the part that was interpreted.
* Set ptr to NULL when an error is encountered.
*
* Return MAXLNUM when no Ex address was found.
*/
static linenr_t
get_address(ptr)
char_u **ptr;
{
int c;
int i;
long n;
char_u *cmd;
FPOS pos;
FPOS *fp;
linenr_t lnum;
cmd = skipwhite(*ptr);
lnum = MAXLNUM;
do
{
switch (*cmd)
{
case '.': /* '.' - Cursor position */
++cmd;
lnum = curwin->w_cursor.lnum;
break;
case '$': /* '$' - last line */
++cmd;
lnum = curbuf->b_ml.ml_line_count;
break;
case '\'': /* ''' - mark */
if (*++cmd == NUL || (check_mark(
fp = getmark(*cmd++, FALSE)) == FAIL))
goto error;
lnum = fp->lnum;
break;
case '/':
case '?': /* '/' or '?' - search */
c = *cmd++;
pos = curwin->w_cursor; /* save curwin->w_cursor */
/*
* When '/' or '?' follows another address, start from
* there.
*/
if (lnum != MAXLNUM)
curwin->w_cursor.lnum = lnum;
/*
* Start a forward search at the end of the 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.col = MAXCOL;
else
curwin->w_cursor.col = 0;
searchcmdlen = 0;
if (!do_search(NULL, c, cmd, 1L,
SEARCH_HIS + SEARCH_MSG + SEARCH_START))
{
cmd = NULL;
curwin->w_cursor = pos;
goto error;
}
lnum = curwin->w_cursor.lnum;
curwin->w_cursor = pos;
/* adjust command string pointer */
cmd += searchcmdlen;
break;
case '\\': /* "\?", "\/" or "\&", repeat search */
++cmd;
if (*cmd == '&')
i = RE_SUBST;
else if (*cmd == '?' || *cmd == '/')
i = RE_SEARCH;
else
{
emsg(e_backslash);
cmd = NULL;
goto error;
}
/*
* When search follows another address, start from
* there.
*/
if (lnum != MAXLNUM)
pos.lnum = lnum;
else
pos.lnum = curwin->w_cursor.lnum;
/*
* Start the search just like for the above do_search().
*/
if (*cmd != '?')
pos.col = MAXCOL;
else
pos.col = 0;
if (searchit(&pos, *cmd == '?' ? BACKWARD : FORWARD,
(char_u *)"", 1L,
SEARCH_MSG + SEARCH_START, i) == OK)
lnum = pos.lnum;
else
{
cmd = NULL;
goto error;
}
++cmd;
break;
default:
if (isdigit(*cmd)) /* absolute line number */
lnum = getdigits(&cmd);
}
for (;;)
{
cmd = skipwhite(cmd);
if (*cmd != '-' && *cmd != '+' && !isdigit(*cmd))
break;
if (lnum == MAXLNUM)
lnum = curwin->w_cursor.lnum; /* "+1" is same as ".+1" */
if (isdigit(*cmd))
i = '+'; /* "number" is same as "+number" */
else
i = *cmd++;
if (!isdigit(*cmd)) /* '+' is '+1', but '+0' is not '+1' */
n = 1;
else
n = getdigits(&cmd);
if (i == '-')
lnum -= n;
else
lnum += n;
}
} while (*cmd == '/' || *cmd == '?');
error:
*ptr = cmd;
return lnum;
}
/*
* If 'autowrite' option set, try to write the file.
*
* return FAIL for failure, OK otherwise
*/
int
autowrite(buf, forceit)
BUF *buf;
int forceit;
{
if (!p_aw || (!forceit && buf->b_p_ro) || buf->b_ffname == NULL)
return FAIL;
return buf_write_all(buf);
}
/*
* flush all buffers, except the ones that are readonly
*/
void
autowrite_all()
{
BUF *buf;
if (!p_aw)
return;
for (buf = firstbuf; buf; buf = buf->b_next)
if (buf->b_changed && !buf->b_p_ro)
{
(void)buf_write_all(buf);
#ifdef AUTOCMD
/* an autocommand may have deleted the buffer */
if (!buf_valid(buf))
buf = firstbuf;
#endif
}
}
static int
check_readonly(forceit)
int forceit;
{
if (!forceit && curbuf->b_p_ro)
{
emsg(e_readonly);
return TRUE;
}
return FALSE;
}
/*
* return TRUE if buffer was changed and cannot be abandoned.
*/
static int
check_changed(buf, checkaw, mult_win, forceit)
BUF *buf;
int checkaw; /* do autowrite if buffer was changed */
int mult_win; /* check also when several windows for the buffer */
int forceit;
{
if ( !forceit &&
buf->b_changed && (mult_win || buf->b_nwindows <= 1) &&
(!checkaw || autowrite(buf, forceit) == FAIL))
{
emsg(e_nowrtmsg);
return TRUE;
}
return FALSE;
}
/*
* return TRUE if any buffer was changed and cannot be abandoned.
* That changed buffer becomes the current buffer.
*/
static int
check_changed_any()
{
BUF *buf;
int save;
for (buf = firstbuf; buf != NULL; buf = buf->b_next)
{
if (buf->b_changed)
{
/* There must be a wait_return for this message, do_buffer
* will cause a redraw */
exiting = FALSE;
if (EMSG2("No write since last change for buffer \"%s\"",
buf->b_fname == NULL ? (char_u *)"No File" :
buf->b_fname))
{
save = no_wait_return;
no_wait_return = FALSE;
wait_return(FALSE);
no_wait_return = save;
}
(void)do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
return TRUE;
}
}
return FALSE;
}
/*
* return FAIL if there is no filename, OK if there is one
* give error message for FAIL
*/
int
check_fname()
{
if (curbuf->b_ffname == NULL)
{
emsg(e_noname);
return FAIL;
}
return OK;
}
/*
* flush the contents of a buffer, unless it has no file name
*
* return FAIL for failure, OK otherwise
*/
static int
buf_write_all(buf)
BUF *buf;
{
int retval;
#ifdef AUTOCMD
BUF *old_curbuf = curbuf;
#endif
retval = (buf_write(buf, buf->b_ffname, buf->b_fname,
(linenr_t)1, buf->b_ml.ml_line_count,
FALSE, FALSE, TRUE, FALSE));
#ifdef AUTOCMD
if (curbuf != old_curbuf)
MSG("Warning: Entered other buffer unexpectedly (check autocommands)");
#endif
return retval;
}
/*
* get + command from ex argument
*/
static char_u *
getargcmd(argp)
char_u **argp;
{
char_u *arg = *argp;
char_u *command = NULL;
if (*arg == '+') /* +[command] */
{
++arg;
if (vim_isspace(*arg))
command = (char_u *)"$";
else
{
/*
* should check for "\ " (but vi has a bug that prevents it to work)
*/
command = arg;
arg = skiptowhite(command);
if (*arg)
*arg++ = NUL; /* terminate command with NUL */
}
arg = skipwhite(arg); /* skip over spaces */
*argp = arg;
}
return command;
}
/*
* Return TRUE if "str" starts with a backslash that should be removed.
* For MS-DOS, WIN32 and OS/2 this is only done when the character after the
* backslash is not a normal file name character.
* Although '$' is a valid filename character, we remove the backslash before
* it, to be able to disginguish between a filename that starts with '$' and
* the name of an environment variable.
*/
static int
is_backslash(str)
char_u *str;
{
#ifdef BACKSLASH_IN_FILENAME
return (str[0] == '\\' && (str[1] == '$' ||
(str[1] != NUL && str[1] != '*' && str[1] != '?'
&& !(isfilechar(str[1]) && str[1] != '\\'))));
#else
return (str[0] == '\\' && str[1] != NUL);
#endif
}
/*
* Halve the number of backslashes in a file name argument.
* For MS-DOS we only do this if the character after the backslash
* is not a normal file character.
* For Unix, when wildcards are going to be expanded, don't remove
* backslashes before special characters.
*/
void
backslash_halve(p, expand_wildcards)
char_u *p;
int expand_wildcards; /* going to expand wildcards later */
{
for ( ; *p; ++p)
if (is_backslash(p)
#if defined(UNIX) || defined(OS2)
&& !(expand_wildcards &&
vim_strchr((char_u *)" *?[{`$\\", p[1]))
#endif
)
STRCPY(p, p + 1);
}
/*
* write current buffer to file 'eap->arg'
* if 'eap->append' is TRUE, append to the file
*
* if *eap->arg == NUL write to current file
* if b_notedited is TRUE, check for overwriting current file
*
* return FAIL for failure, OK otherwise
*/
static int
do_write(eap)
EXARG *eap;
{
int other;
char_u *fname = NULL; /* init to shut up gcc */
char_u *ffname;
int retval = FAIL;
char_u *free_fname = NULL;
ffname = eap->arg;
if (*ffname == NUL)
other = FALSE;
else
{
fname = ffname;
free_fname = fix_fname(ffname);
/*
* When out-of-memory, keep unexpanded filename, because we MUST be
* able to write the file in this situation.
*/
if (free_fname != NULL)
ffname = free_fname;
other = otherfile(ffname);
}
/*
* If we have a new file, name put it in the list of alternate file names.
*/
if (other && vim_strchr(p_cpo, CPO_ALTWRITE) != NULL)
setaltfname(ffname, fname, (linenr_t)1);
/*
* writing to the current file is not allowed in readonly mode
* and need a file name
*/
if (!other && (check_readonly(eap->forceit) || check_fname() == FAIL))
goto theend;
if (!other)
{
ffname = curbuf->b_ffname;
fname = curbuf->b_fname;
/*
* Not writing the whole file is only allowed with '!'.
*/
if ( (eap->line1 != 1
|| eap->line2 != curbuf->b_ml.ml_line_count)
&& !eap->forceit
&& !eap->append
&& !p_wa)
{
EMSG("Use ! to write partial buffer");
goto theend;
}
}
/*
* write to other file or b_notedited set or not writing the whole file:
* overwriting only allowed with '!'
*/
if ( (other
|| curbuf->b_notedited)
&& !eap->forceit
&& !eap->append
&& !p_wa
&& vim_fexists(ffname))
{
#ifdef UNIX
/* with UNIX it is possible to open a directory */
if (mch_isdir(ffname))
EMSG2("\"%s\" is a directory", ffname);
else
#endif
emsg(e_exists);
goto theend;
}
retval = (buf_write(curbuf, ffname, fname, eap->line1, eap->line2,
eap->append, eap->forceit, TRUE, FALSE));
theend:
vim_free(free_fname);
return retval;
}
/*
* try to abandon current file and edit a new or existing file
* 'fnum' is the number of the file, if zero use ffname/sfname
*
* return 1 for "normal" error, 2 for "not written" error, 0 for success
* -1 for succesfully opening another file
* 'lnum' is the line number for the cursor in the new file (if non-zero).
*/
int
getfile(fnum, ffname, sfname, setpm, lnum, forceit)
int fnum;
char_u *ffname;
char_u *sfname;
int setpm;
linenr_t lnum;
int forceit;
{
int other;
int retval;
char_u *free_me = NULL;
if (fnum == 0)
{
fname_expand(&ffname, &sfname); /* make ffname full path, set sfname */
other = otherfile(ffname);
free_me = ffname; /* has been allocated, free() later */
}
else
other = (fnum != curbuf->b_fnum);
if (other)
++no_wait_return; /* don't wait for autowrite message */
if (other && !forceit && curbuf->b_nwindows == 1 &&
!p_hid && curbuf->b_changed && autowrite(curbuf, forceit) == FAIL)
{
if (other)
--no_wait_return;
emsg(e_nowrtmsg);
retval = 2; /* file has been changed */
goto theend;
}
if (other)
--no_wait_return;
if (setpm)
setpcmark();
if (!other)
{
if (lnum != 0)
curwin->w_cursor.lnum = lnum;
check_cursor_lnum();
beginline(MAYBE);
retval = 0; /* it's in the same file */
}
else if (do_ecmd(fnum, ffname, sfname, NULL, lnum,
(p_hid ? ECMD_HIDE : 0) + (forceit ? ECMD_FORCEIT : 0)) == OK)
retval = -1; /* opened another file */
else
retval = 1; /* error encountered */
theend:
vim_free(free_me);
return retval;
}
/*
* start editing a new file
*
* fnum: file number; if zero use ffname/sfname
* ffname: the file name
* - full path if sfname used,
* - any file name if sfname is NULL
* - empty string to re-edit with the same file name (but may be
* in a different directory)
* - NULL to start an empty buffer
* sfname: the short file name (or NULL)
* command: the command to be executed after loading the file
* newlnum: put cursor on this line number (if possible)
* flags:
* ECMD_HIDE: if TRUE don't free the current buffer
* ECMD_SET_HELP: set b_help flag of (new) buffer before opening file
* ECMD_OLDBUF: use existing buffer if it exists
* ECMD_FORCEIT: ! used for Ex command
*
* return FAIL for failure, OK otherwise
*/
int
do_ecmd(fnum, ffname, sfname, command, newlnum, flags)
int fnum;
char_u *ffname;
char_u *sfname;
char_u *command;
linenr_t newlnum;
int flags;
{
int other_file; /* TRUE if editing another file */
int oldbuf; /* TRUE if using existing buffer */
#ifdef AUTOCMD
int auto_buf = FALSE; /* TRUE if autocommands brought us
into the buffer unexpectedly */
#endif
BUF *buf;
char_u *free_fname = NULL;
int retval = FAIL;
if (fnum != 0)
{
if (fnum == curbuf->b_fnum) /* file is already being edited */
return OK; /* nothing to do */
other_file = TRUE;
}
else
{
/* if no short name given, use ffname for short name */
if (sfname == NULL)
sfname = ffname;
#ifdef USE_FNAME_CASE
# ifdef USE_LONG_FNAME
if (USE_LONG_FNAME)
# endif
fname_case(sfname); /* set correct case for short filename */
#endif
if (ffname == NULL)
other_file = TRUE;
/* there is no file name */
else if (*ffname == NUL && curbuf->b_ffname == NULL)
other_file = FALSE;
else
{
if (*ffname == NUL) /* re-edit with same file name */
{
ffname = curbuf->b_ffname;
sfname = curbuf->b_fname;
}
free_fname = fix_fname(ffname); /* may expand to full path name */
if (free_fname != NULL)
ffname = free_fname;
other_file = otherfile(ffname);
}
}
/*
* if the file was changed we may not be allowed to abandon it
* - if we are going to re-edit the same file
* - or if we are the only window on this file and if ECMD_HIDE is FALSE
*/
if (((!other_file && !(flags & ECMD_OLDBUF)) ||
(curbuf->b_nwindows == 1 && !(flags & ECMD_HIDE))) &&
check_changed(curbuf, FALSE, !other_file, (flags & ECMD_FORCEIT)))
{
if (fnum == 0 && other_file && ffname != NULL)
setaltfname(ffname, sfname, (linenr_t)1);
goto theend;
}
/*
* End Visual mode before switching to another buffer, so the text can be
* copied into the GUI selection buffer.
*/
if (VIsual_active)
end_visual_mode();
/*
* If we are starting to edit another file, open a (new) buffer.
* Otherwise we re-use the current buffer.
*/
if (other_file)
{
curwin->w_alt_fnum = curbuf->b_fnum;
buflist_altlnum();
if (fnum)
buf = buflist_findnr(fnum);
else
buf = buflist_new(ffname, sfname, 1L, TRUE);
if (buf == NULL)
goto theend;
if (buf->b_ml.ml_mfp == NULL) /* no memfile yet */
{
oldbuf = FALSE;
buf->b_nwindows = 0;
}
else /* existing memfile */
{
oldbuf = TRUE;
buf_check_timestamp(buf);
}
/*
* Make the (new) buffer the one used by the current window.
* If the old buffer becomes unused, free it if ECMD_HIDE is FALSE.
* If the current buffer was empty and has no file name, curbuf
* is returned by buflist_new().
*/
if (buf != curbuf)
{
#ifdef AUTOCMD
BUF *old_curbuf;
char_u *new_name = NULL;
/*
* Be careful: The autocommands may delete any buffer and change
* the current buffer.
* - If the buffer we are going to edit is deleted, give up.
* - If we ended up in the new buffer already, need to skip a few
* things, set auto_buf.
*/
old_curbuf = curbuf;
if (buf->b_fname != NULL)
new_name = vim_strsave(buf->b_fname);
apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE);
if (!buf_valid(buf)) /* new buffer has been deleted */
{
EMSG2("Autocommands unexpectedly deleted new buffer %s",
new_name == NULL ? (char_u *)"" : new_name);
vim_free(new_name);
goto theend;
}
vim_free(new_name);
if (buf == curbuf) /* already in new buffer */
auto_buf = TRUE;
else
{
if (curbuf == old_curbuf)
#endif
buf_copy_options(curbuf, buf, TRUE, FALSE);
close_buffer(curwin, curbuf, !(flags & ECMD_HIDE), FALSE);
curwin->w_buffer = buf;
curbuf = buf;
++curbuf->b_nwindows;
#ifdef AUTOCMD
}
#endif
}
else
++curbuf->b_nwindows;
curwin->w_pcmark.lnum = 1;
curwin->w_pcmark.col = 0;
}
else
{
if (check_fname() == FAIL)
goto theend;
oldbuf = (flags & ECMD_OLDBUF);
}
/*
* If we get here we are sure to start editing
*/
/* don't redraw until the cursor is in the right line */
++RedrawingDisabled;
if (flags & ECMD_SET_HELP)
curbuf->b_help = TRUE;
/*
* other_file oldbuf
* FALSE FALSE re-edit same file, buffer is re-used
* FALSE TRUE re-edit same file, nothing changes
* TRUE FALSE start editing new file, new buffer
* TRUE TRUE start editing in existing buffer (nothing to do)
*/
if (!other_file && !oldbuf) /* re-use the buffer */
{
if (newlnum == 0)
newlnum = curwin->w_cursor.lnum;
buf_freeall(curbuf); /* free all things for buffer */
buf_clear(curbuf);
curbuf->b_op_start.lnum = 0; /* clear '[ and '] marks */
curbuf->b_op_end.lnum = 0;
}
/*
* Reset cursor position, could be used by autocommands.
*/
adjust_cursor();
/*
* Check if we are editing the w_arg_idx file in the argument list.
*/
check_arg_idx();
#ifdef AUTOCMD
if (!auto_buf)
#endif
{
/*
* Set cursor and init window before reading the file and executing
* autocommands. This allows for the autocommands to position the
* cursor.
*/
win_init(curwin);
/*
* Careful: open_buffer() and apply_autocmds() may change the current
* buffer and window.
*/
if (!oldbuf) /* need to read the file */
(void)open_buffer(FALSE);
#ifdef AUTOCMD
else
apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE);
check_arg_idx();
#endif
maketitle();
}
if (command == NULL)
{
if (newlnum)
{
curwin->w_cursor.lnum = newlnum;
check_cursor_lnum();
beginline(MAYBE);
}
else
{
if (exmode_active)
{
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
check_cursor_lnum();
}
beginline(TRUE);
}
}
/*
* Did not read the file, need to show some info about the file.
* Do this after setting the cursor.
*/
if (oldbuf
#ifdef AUTOCMD
&& !auto_buf
#endif
)
fileinfo(FALSE, TRUE, FALSE);
if (command != NULL)
do_cmdline(command, NULL, NULL, DOCMD_VERBOSE);
--RedrawingDisabled;
if (!skip_redraw)
{
update_topline();
update_screen(NOT_VALID); /* redraw now */
}
if (p_im)
need_start_insertmode = TRUE;
retval = OK;
theend:
vim_free(free_fname);
return retval;
}
static void
do_make(arg)
char_u *arg;
{
if (*p_ef == NUL)
{
EMSG("errorfile option not set");
return;
}
autowrite_all();
vim_remove(p_ef);
/*
* If 'shellpipe' empty: don't redirect to 'errorfile'.
*/
if (*p_sp == NUL)
sprintf((char *)IObuff, "%s%s%s", p_shq, arg, p_shq);
else
sprintf((char *)IObuff, "%s%s%s %s %s", p_shq, arg, p_shq, p_sp, p_ef);
/*
* Output a newline if there's something else than the :make command that
* was typed (in which case the cursor is in column 0).
*/
if (msg_col != 0)
msg_putchar('\n');
MSG_PUTS(":!");
msg_outtrans(IObuff); /* show what we are doing */
do_shell(IObuff);
#ifdef AMIGA
flushbuf();
/* read window status report and redraw before message */
(void)char_avail();
#endif
if (qf_init() == OK)
qf_jump(0, 0, FALSE); /* display first error */
vim_remove(p_ef);
}
/*
* Redefine the argument list to 'str'.
*
* Return FAIL for failure, OK otherwise.
*/
static int
do_arglist(str)
char_u *str;
{
int new_count = 0;
char_u **new_files = NULL;
int exp_count;
char_u **exp_files;
char_u **t;
char_u *p;
int inquote;
int i;
while (*str)
{
/*
* create a new entry in new_files[]
*/
t = (char_u **)lalloc((long_u)(sizeof(char_u *) * (new_count + 1)), TRUE);
if (t != NULL)
for (i = new_count; --i >= 0; )
t[i] = new_files[i];
vim_free(new_files);
if (t == NULL)
return FAIL;
new_files = t;
new_files[new_count++] = str;
/*
* isolate one argument, taking quotes
*/
inquote = FALSE;
for (p = str; *str; ++str)
{
/*
* for MSDOS et.al. a backslash is part of a file name.
* Only skip ", space and tab.
*/
if (is_backslash(str))
*p++ = *++str;
else
{
if (!inquote && vim_isspace(*str))
break;
if (*str == '"')
inquote ^= TRUE;
else
*p++ = *str;
}
}
str = skipwhite(str);
*p = NUL;
}
i = ExpandWildCards(new_count, new_files, &exp_count,
&exp_files, FALSE, TRUE);
vim_free(new_files);
if (i == FAIL)
return FAIL;
if (exp_count == 0)
{
emsg(e_nomatch);
return FAIL;
}
FreeWild(arg_file_count, arg_files);
arg_files = exp_files;
arg_file_count = exp_count;
arg_had_last = FALSE;
/*
* put all file names in the buffer list
*/
for (i = 0; i < arg_file_count; ++i)
(void)buflist_add(arg_files[i]);
return OK;
}
/*
* Check if we are editing the w_arg_idx file in the argument list.
*/
void
check_arg_idx()
{
int t;
if (arg_file_count > 1 && (curbuf->b_ffname == NULL ||
curwin->w_arg_idx >= arg_file_count ||
(t = fullpathcmp(arg_files[curwin->w_arg_idx],
curbuf->b_ffname)) == FPC_DIFF || t == FPC_DIFFX))
curwin->w_arg_idx_invalid = TRUE;
else
curwin->w_arg_idx_invalid = FALSE;
}
int
ends_excmd(c)
int c;
{
return (c == NUL || c == '|' || c == '\"' || c == '\n');
}
/*
* Check if *p is a separator between Ex commands.
* Return NULL if it isn't, (p + 1) if it is.
*/
char_u *
check_nextcomm(p)
char_u *p;
{
if (*p == '|' || *p == '\n')
return (p + 1);
else
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
* return FAIL and give error message if 'message' TRUE
* return OK otherwise
*/
static int
check_more(message, forceit)
int message; /* when FALSE check only, no messages */
int forceit;
{
if (!forceit && only_one_window() && arg_file_count > 1 && !arg_had_last &&
quitmore == 0)
{
if (message)
{
EMSGN("%ld more files to edit",
arg_file_count - curwin->w_arg_idx - 1);
quitmore = 2; /* next try to quit is allowed */
}
return FAIL;
}
return OK;
}
/*
* Structure used to store info for each sourced file.
* It is shared between do_source() and getsourceline().
* This is required, because it needs to be handed to do_cmdline() and
* sourcing can be done recursively.
*/
struct source_cookie
{
FILE *fp; /* opened file for sourcing */
#ifdef USE_CRNL
int textmode; /* -1 = unknown, 0 = NL, 1 = CR-NL */
int error; /* TRUE if LF found after CR-LF */
#endif
};
/*
* do_source: Read the file "fname" and execute its lines as EX commands.
*
* This function may be called recursively!
*
* return FAIL if file could not be opened, OK otherwise
*/
int
do_source(fname, check_other)
char_u *fname;
int check_other; /* check for .vimrc and _vimrc */
{
struct source_cookie *cookie;
char_u *save_sourcing_name;
linenr_t save_sourcing_lnum;
char_u *p;
cookie = (struct source_cookie *)alloc((unsigned)sizeof(struct source_cookie));
if (cookie == NULL)
return FAIL;
/* use NameBuff for expanded name */
expand_env(fname, NameBuff, MAXPATHL);
cookie->fp = fopen((char *)NameBuff, READBIN);
if (cookie->fp == NULL && check_other)
{
/*
* Try again, replacing file name ".vimrc" by "_vimrc" or vice versa,
* and ".exrc" by "_exrc" or vice versa.
*/
p = gettail(NameBuff);
if ((*p == '.' || *p == '_') &&
(STRICMP(p + 1, "vimrc") == 0 ||
STRICMP(p + 1, "gvimrc") == 0 ||
STRICMP(p + 1, "exrc") == 0))
{
if (*p == '_')
*p = '.';
else
*p = '_';
cookie->fp = fopen((char *)NameBuff, READBIN);
}
}
if (cookie->fp == NULL)
{
vim_free(cookie);
return FAIL;
}
#ifdef USE_CRNL
/* If no automatic textmode: Set default to CR-NL. */
if (!p_ta)
cookie->textmode = 1;
else
cookie->textmode = -1;
cookie->error = FALSE;
#endif
/*
* Keep the sourcing name, for recursive calls.
*/
save_sourcing_name = sourcing_name;
save_sourcing_lnum = sourcing_lnum;
sourcing_name = fname;
sourcing_lnum = 0;
#ifdef SLEEP_IN_EMSG
++dont_sleep; /* don't call sleep() in emsg() */
#endif
/*
* Call do_cmdline, which will call getsourceline() to get the lines.
*/
do_cmdline(NULL, getsourceline, (void *)cookie,
DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT);
fclose(cookie->fp);
if (got_int)
emsg(e_interr);
#ifdef SLEEP_IN_EMSG
--dont_sleep;
#endif
sourcing_name = save_sourcing_name;
sourcing_lnum = save_sourcing_lnum;
vim_free(cookie);
return OK;
}
/*
* Get one full line from a sourced file.
* Called by do_source() and do_cmdline().
*
* Return a pointer to the line (IObuff) or NULL for end-of-file.
*/
char_u *
getsourceline(c, cookie, indent)
int c; /* not used */
void *cookie;
int indent; /* not used */
{
struct source_cookie *sp = (struct source_cookie *)cookie;
int len = 0;
#ifdef USE_CRNL
int has_cr; /* CR-LF found */
#endif
/*
* Loop until there is a finished line (or end-of-file).
*/
for (;;)
{
if (fgets((char *)IObuff + len, IOSIZE - len, sp->fp) == NULL ||
got_int)
break;
++sourcing_lnum;
len = STRLEN(IObuff) - 1;
#ifdef USE_CRNL
/* Ignore a trailing CTRL-Z, when in textmode */
if (len == 0 && sp->textmode == 1 && IObuff[0] == Ctrl('Z'))
break;
#endif
if (len >= 0 && IObuff[len] == '\n') /* remove trailing newline */
{
#ifdef USE_CRNL
has_cr = (len > 0 && IObuff[len - 1] == '\r');
if (sp->textmode == -1)
{
if (has_cr)
sp->textmode = 1;
else
sp->textmode = 0;
}
if (sp->textmode)
{
if (has_cr) /* remove trailing CR */
--len;
else /* lines like ":map xx yy^M" will have failed */
{
if (!sp->error)
EMSG("Warning: Wrong line separator, ^M may be missing");
sp->error = TRUE;
sp->textmode = 0;
}
}
#endif
/* escaped newline, read more */
if (len > 0 && len < IOSIZE - 2 && IObuff[len - 1] == Ctrl('V'))
{
IObuff[len - 1] = '\n'; /* remove CTRL-V */
continue;
}
IObuff[len] = NUL;
}
/*
* Check for ^C here now and then, so recursive :so can be broken.
*/
line_breakcheck();
return vim_strsave(IObuff);
}
return NULL;
}
int
ExpandCommands(prog, num_file, file)
vim_regexp *prog;
int *num_file;
char_u ***file;
{
int cmdidx;
int count;
int round;
/*
* round == 1: Count the matches.
* round == 2: Save the matches into the array.
*/
for (round = 1; round <= 2; ++round)
{
count = 0;
for (cmdidx = 0; cmdidx < CMD_SIZE; cmdidx++)
if (vim_regexec(prog, cmdnames[cmdidx].cmd_name, TRUE))
{
if (round == 1)
count++;
else
(*file)[count++] = vim_strsave(cmdnames[cmdidx].cmd_name);
}
if (round == 1)
{
*num_file = count;
if (count == 0 || (*file = (char_u **)
alloc((unsigned)(count * sizeof(char_u *)))) == NULL)
return FAIL;
}
}
return OK;
}
/*
* 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.
*/
static void
not_exiting()
{
exiting = FALSE;
if (!exmode_active)
settmode(TMODE_RAW);
}
/*
* ":quit": quit current window, quit Vim if closed the last window.
*/
static void
do_quit(eap)
EXARG *eap;
{
/*
* If there are more files or windows we won't exit.
*/
if (check_more(FALSE, eap->forceit) == OK && only_one_window())
exiting = TRUE;
if (check_changed(curbuf, FALSE, FALSE, eap->forceit)
|| check_more(TRUE, eap->forceit) == FAIL
|| (only_one_window()
&& !eap->forceit && check_changed_any()))
{
not_exiting();
}
else
{
if (only_one_window()) /* quit last window */
getout(0);
close_window(curwin, TRUE); /* may free buffer */
}
}
/*
* ":stop" and ":suspend": Suspend Vim.
*/
static void
do_suspend(eap)
EXARG *eap;
{
/*
* Disallow suspending for "rvim".
*/
if (!check_restricted()
#ifdef WIN32
/*
* Check if external commands are allowed now.
*/
&& can_end_termcap_mode(TRUE)
#endif
)
{
if (!eap->forceit)
autowrite_all();
windgoto((int)Rows - 1, 0);
outchar('\n');
flushbuf();
stoptermcap();
mch_restore_title(3); /* restore window titles */
ui_suspend(); /* call machine specific function */
maketitle();
starttermcap();
scroll_start(); /* scroll screen before redrawing */
must_redraw = CLEAR;
set_winsize(0, 0, FALSE); /* May have resized window */
}
}
/*
* ":exit", ":xit" and ":wq": Write file and exit Vim.
*/
static void
do_exit(eap)
EXARG *eap;
{
/*
* if more files or windows we won't exit
*/
if (check_more(FALSE, eap->forceit) == OK && only_one_window())
exiting = TRUE;
if ( ((eap->cmdidx == CMD_wq
|| curbuf->b_changed)
&& do_write(eap) == FAIL)
|| check_more(TRUE, eap->forceit) == FAIL
|| (only_one_window()
&& !eap->forceit && check_changed_any()))
{
not_exiting();
}
else
{
if (only_one_window()) /* quit last window, exit Vim */
getout(0);
close_window(curwin, TRUE); /* quit current window, may free buffer */
}
}
/*
* ":wall", ":wqall" and ":xall": Write all changed files (and exit).
*/
static void
do_wqall(eap)
EXARG *eap;
{
BUF *buf;
int error = 0;
for (buf = firstbuf; buf != NULL; buf = buf->b_next)
{
if (buf->b_changed)
{
if (buf->b_ffname == NULL)
{
emsg(e_noname);
++error;
}
else if (!eap->forceit && buf->b_p_ro)
{
EMSG2("\"%s\" is readonly, use ! to write anyway",
buf->b_fname);
++error;
}
else
{
if (buf_write_all(buf) == FAIL)
++error;
#ifdef AUTOCMD
/* an autocommand may have deleted the buffer */
if (!buf_valid(buf))
buf = firstbuf;
#endif
}
}
}
if (exiting)
{
if (!error)
getout(0); /* exit Vim */
not_exiting();
}
}
static void
do_print(eap)
EXARG *eap;
{
for ( ;!got_int; ui_breakcheck())
{
print_line(eap->line1,
(eap->cmdidx == CMD_number || eap->cmdidx == CMD_pound));
if (++eap->line1 > eap->line2)
break;
flushbuf(); /* show one line at a time */
}
setpcmark();
/* put cursor at last line */
curwin->w_cursor.lnum = eap->line2;
ex_no_reprint = TRUE;
}
/*
* Edit file "argn" from the arguments.
*/
static void
do_argfile(eap, argn)
EXARG *eap;
int argn;
{
int other;
char_u *p;
if (argn < 0 || argn >= arg_file_count)
{
if (arg_file_count <= 1)
EMSG("There is only one file to edit");
else if (argn < 0)
EMSG("Cannot go before first file");
else
EMSG("Cannot go beyond last file");
}
else
{
setpcmark();
if (*eap->cmd == 's') /* split window first */
{
if (win_split(0, FALSE) == FAIL)
return;
}
else
{
/*
* if 'hidden' set, only check for changed file when re-editing
* the same buffer
*/
other = TRUE;
if (p_hid)
{
p = fix_fname(arg_files[argn]);
other = otherfile(p);
vim_free(p);
}
if ((!p_hid || !other)
&& check_changed(curbuf, TRUE, !other, eap->forceit))
return;
}
curwin->w_arg_idx = argn;
if (argn == arg_file_count - 1)
arg_had_last = TRUE;
(void)do_ecmd(0, arg_files[curwin->w_arg_idx],
NULL, eap->do_ecmd_cmd, eap->do_ecmd_lnum,
(p_hid ? ECMD_HIDE : 0) +
(eap->forceit ? ECMD_FORCEIT : 0));
}
}
/*
* Do ":next" command, and commands that behave like it.
*/
static void
do_next(eap)
EXARG *eap;
{
int i;
/*
* check for changed buffer now, if this fails the argument list is not
* redefined.
*/
if ( p_hid
|| eap->cmdidx == CMD_snext
|| !check_changed(curbuf, TRUE, FALSE, eap->forceit))
{
if (*eap->arg != NUL) /* redefine file list */
{
if (do_arglist(eap->arg) == FAIL)
return;
i = 0;
}
else
i = curwin->w_arg_idx + (int)eap->line2;
do_argfile(eap, i);
}
}
/*
* Handle ":args" command.
*/
static void
do_args(eap)
EXARG *eap;
{
int i;
/* ":args file": handle like :next */
if (!ends_excmd(*eap->arg))
do_next(eap);
else
{
if (arg_file_count == 0) /* no file name list */
{
if (check_fname() == OK) /* check for no file name */
smsg((char_u *)"[%s]", curbuf->b_ffname);
}
else
{
/*
* Overwrite the command, in most cases there is no scrolling
* required and no wait_return().
*/
gotocmdline(TRUE);
for (i = 0; i < arg_file_count; ++i)
{
if (i == curwin->w_arg_idx)
msg_putchar('[');
msg_outtrans(arg_files[i]);
if (i == curwin->w_arg_idx)
msg_putchar(']');
msg_putchar(' ');
}
}
}
}
/*
* Handle ":resize" command.
* set, increment or decrement current window height
*/
static void
do_resize(eap)
EXARG *eap;
{
int n;
n = atol((char *)eap->arg);
if (*eap->arg == '-' || *eap->arg == '+')
n += curwin->w_height;
else if (n == 0) /* default is very high */
n = 9999;
win_setheight((int)n);
}
static void
do_exedit(eap, old_curwin)
EXARG *eap;
WIN *old_curwin;
{
int n;
/*
* ":vi" command ends Ex mode.
*/
if (eap->cmdidx == CMD_visual || eap->cmdidx == CMD_view)
{
exmode_active = FALSE;
if (*eap->arg == NUL)
return;
}
if ((eap->cmdidx == CMD_new) && *eap->arg == NUL)
{
setpcmark();
(void)do_ecmd(0, NULL, NULL, eap->do_ecmd_cmd, (linenr_t)1,
ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0));
}
else if (eap->cmdidx != CMD_split || *eap->arg != NUL)
{
n = readonlymode;
if (eap->cmdidx == CMD_view || eap->cmdidx == CMD_sview)
readonlymode = TRUE;
setpcmark();
(void)do_ecmd(0, eap->arg, NULL, eap->do_ecmd_cmd, eap->do_ecmd_lnum,
(p_hid ? ECMD_HIDE : 0) +
(eap->forceit ? ECMD_FORCEIT : 0));
readonlymode = n;
}
else
update_screen(NOT_VALID);
/*
* if ":split file" worked, set alternate filename in old window to new
* file
*/
if ( (eap->cmdidx == CMD_new
|| eap->cmdidx == CMD_split)
&& *eap->arg != NUL
&& curwin != old_curwin
&& win_valid(old_curwin)
&& old_curwin->w_buffer != curbuf)
old_curwin->w_alt_fnum = curbuf->b_fnum;
ex_no_reprint = TRUE;
}
#ifdef USE_GUI
/*
* Handle ":gui" or ":gvim" command.
*/
static void
do_gui(eap)
EXARG *eap;
{
/*
* Check for "-f" argument: foreground, don't fork.
*/
if (eap->arg[0] == '-' && eap->arg[1] == 'f' &&
(eap->arg[2] == NUL || vim_iswhite(eap->arg[2])))
{
gui.dofork = FALSE;
eap->arg = skipwhite(eap->arg + 2);
}
else
gui.dofork = TRUE;
if (!gui.in_use)
gui_start();
if (!ends_excmd(*eap->arg))
do_next(eap);
}
#endif
static void
do_read(eap)
EXARG *eap;
{
int i;
if (eap->usefilter) /* :r!cmd */
do_bang(1, eap->line1, eap->line2, FALSE, eap->arg, FALSE, TRUE);
else
{
if (u_save(eap->line2, (linenr_t)(eap->line2 + 1)) == FAIL)
return;
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, MAXLNUM, 0);
}
else
{
if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL)
setaltfname(eap->arg, eap->arg, (linenr_t)1);
i = readfile(eap->arg, NULL, eap->line2, (linenr_t)0, MAXLNUM, 0);
}
if (i == FAIL)
emsg2(e_notopen, eap->arg);
else
update_screen(NOT_VALID);
}
}
static void
do_cd(eap)
EXARG *eap;
{
BUF *buf;
char_u *p;
#ifdef UNIX
/*
* for UNIX ":cd" means: go to home directory
*/
if (*eap->arg == NUL) /* use NameBuff for home directory name */
{
expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
eap->arg = NameBuff;
}
#endif
if (*eap->arg == NUL)
do_pwd();
else
{
if (vim_chdir((char *)eap->arg))
emsg(e_failed);
else
{
/*
* Use full path from now on for files currently being
* edited, both for filename and swap file name. Try
* to shorten the file names a bit if safe to do so.
*/
mch_dirname(IObuff, IOSIZE);
for (buf = firstbuf; buf != NULL; buf = buf->b_next)
{
if (buf->b_fname != NULL)
{
vim_free(buf->b_sfname);
buf->b_sfname = NULL;
p = shorten_fname(buf->b_ffname, IObuff);
if (p != NULL)
{
buf->b_sfname = vim_strsave(p);
buf->b_fname = buf->b_sfname;
}
if (p == NULL || buf->b_fname == NULL)
buf->b_fname = buf->b_ffname;
mf_fullname(buf->b_ml.ml_mfp);
}
}
status_redraw_all();
}
}
}
static void
do_pwd()
{
if (mch_dirname(NameBuff, MAXPATHL) == OK)
msg(NameBuff);
else
emsg(e_unknown);
}
static void
do_sleep(eap)
EXARG *eap;
{
int n;
if (cursor_valid())
{
n = curwin->w_winpos + curwin->w_wrow - msg_scrolled;
if (n >= 0)
{
windgoto((int)n, curwin->w_wcol);
flushbuf();
}
}
ui_delay(eap->line2 * 1000L, TRUE);
}
static void
do_exmap(eap, isabbrev)
EXARG *eap;
int isabbrev;
{
int i;
if (*eap->cmd == 'c') /* cmap, cunmap, cnoremap, etc. */
{
i = CMDLINE;
++eap->cmd;
}
else if (*eap->cmd == 'i') /* imap, iunmap, inoremap, etc. */
{
i = INSERT;
++eap->cmd;
}
/* nmap, nunmap, nnoremap */
else if (*eap->cmd == 'n' && *(eap->cmd + 1) != 'o')
{
i = NORMAL;
++eap->cmd;
}
else if (*eap->cmd == 'v') /* vmap, vunmap, vnoremap */
{
i = VISUAL;
++eap->cmd;
}
else if (eap->forceit || isabbrev) /* map!, unmap!, noremap!, abbrev */
i = INSERT + CMDLINE;
else /* map, unmap, noremap */
i = NORMAL + VISUAL;
switch (do_map((*eap->cmd == 'n') ? 2 : (*eap->cmd == 'u'),
eap->arg, i, isabbrev))
{
case 1: emsg(e_invarg);
break;
case 2: emsg(e_nomap);
break;
case 3: emsg(e_ambmap);
break;
}
}
/*
* Handle command that work like operators: ":delete", ":yank", ":>" and ":<".
*/
static void
do_exops(eap)
EXARG *eap;
{
OPARG 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 = MLINE;
if (eap->cmdidx != CMD_yank) /* position cursor for undo */
{
setpcmark();
curwin->w_cursor.lnum = eap->line1;
beginline(MAYBE);
}
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, FALSE, TRUE);
break;
default: /* CMD_rshift or CMD_lshift */
if ((eap->cmdidx == CMD_rshift)
#ifdef RIGHTLEFT
^ curwin->w_p_rl
#endif
)
oa.op_type = OP_RSHIFT;
else
oa.op_type = OP_LSHIFT;
op_shift(&oa, FALSE, eap->amount);
break;
}
}
/*
* Handle ":copy" and ":move".
*/
static void
do_copymove(eap)
EXARG *eap;
{
long n;
n = get_address(&eap->arg);
if (eap->arg == NULL) /* error detected */
{
eap->nextcomm = NULL;
return;
}
/*
* 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_invaddr);
return;
}
if (eap->cmdidx == CMD_move)
{
if (do_move(eap->line1, eap->line2, n) == FAIL)
return;
}
else
do_copy(eap->line1, eap->line2, n);
u_clearline();
beginline(MAYBE);
update_screen(NOT_VALID);
}
/*
* Handle ":join" command.
*/
static void
do_exjoin(eap)
EXARG *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_do_join(eap->line2 - eap->line1 + 1, !eap->forceit, FALSE);
beginline(TRUE);
}
/*
* Handle ":@" command, execute from register.
*/
static void
do_at(eap)
EXARG *eap;
{
curwin->w_cursor.lnum = eap->line2;
/*
* put the register in mapbuf
*/
if (do_execreg(*eap->arg, TRUE,
vim_strchr(p_cpo, CPO_EXECBUF) != NULL) == FAIL)
beep_flush();
else
{
/*
* execute from the mapbuf
*/
while (vpeekc() == ':')
{
(void)vgetc();
(void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE);
}
}
}
/*
* Handle ":mkexrc" and ":mkvimrc" commands.
*/
static void
do_mkrc(eap)
EXARG *eap;
{
FILE *fd;
if (*eap->arg == NUL)
eap->arg = (char_u *)EXRC_FILE;
#ifdef UNIX
/* with Unix it is possible to open a directory */
if (mch_isdir(eap->arg))
{
EMSG2("\"%s\" is a directory", eap->arg);
return;
}
#endif
if (!eap->forceit && vim_fexists(eap->arg))
{
EMSG2("\"%s\" exists (use ! to override)", eap->arg);
return;
}
if ((fd = fopen((char *)eap->arg, WRITEBIN)) == NULL)
{
EMSG2("Cannot open \"%s\" for writing", eap->arg);
return;
}
/* Write the version command for :mkvimrc */
if (eap->cmdidx == CMD_mkvimrc)
{
#ifdef USE_CRNL
fprintf(fd, "version 4.0\r\n");
#else
fprintf(fd, "version 4.0\n");
#endif
}
if (makemap(fd) == FAIL || makeset(fd) == FAIL || fclose(fd))
emsg(e_write);
}
/*
* Handle ":mark" or ":k" command.
*/
static void
do_setmark(eap)
EXARG *eap;
{
FPOS pos;
pos = curwin->w_cursor; /* save curwin->w_cursor */
curwin->w_cursor.lnum = eap->line2;
beginline(MAYBE);
(void)setmark(*eap->arg); /* set mark */
curwin->w_cursor = pos; /* restore curwin->w_cursor */
}
/*
* Handle ":normal[!] {commands}" - execute normal mode commands
* Mostly used for ":autocmd".
*/
static void
do_normal(eap)
EXARG *eap;
{
OPARG oa;
int len;
/*
* Repeat the :normal command for each line in the range. When no range
* given, execute it just once, without positioning the cursor first.
*/
do
{
clear_oparg(&oa);
if (eap->addr_count != 0)
{
curwin->w_cursor.lnum = eap->line1++;
curwin->w_cursor.col = 0;
}
/*
* Stuff the argument into the typeahead buffer.
* Execute normal_cmd() until there is no more
* typeahead than there was before this command.
*/
len = typelen;
ins_typebuf(eap->arg, eap->forceit ? -1 : 0, 0, TRUE);
while ( (!stuff_empty()
|| (!typebuf_typed()
&& typelen > len))
&& !got_int)
{
adjust_cursor(); /* put cursor on valid line */
normal_cmd(&oa); /* execute a Normal mode cmd */
}
}
while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int);
}
static char_u *
do_findpat(eap, action)
EXARG *eap;
int action;
{
int whole = TRUE;
long n;
char_u *p;
char_u *errormsg = NULL;
n = 1;
if (isdigit(*eap->arg)) /* get count */
{
n = getdigits(&eap->arg);
eap->arg = skipwhite(eap->arg);
}
if (*eap->arg == '/') /* Match regexp, not just whole words */
{
whole = FALSE;
++eap->arg;
for (p = eap->arg; *p && *p != '/'; p++)
if (*p == '\\' && p[1] != NUL)
p++;
if (*p)
{
*p++ = NUL;
p = skipwhite(p);
/* Check for trailing illegal characters */
if (*p && vim_strchr((char_u *)"|\"\n", *p) == NULL)
errormsg = e_trailing;
else
eap->nextcomm = p;
}
}
find_pattern_in_path(eap->arg, (int)STRLEN(eap->arg), whole, !eap->forceit,
*eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
n, action, eap->line1, eap->line2);
return errormsg;
}
static char_u *
do_if(eap, cstack)
EXARG *eap;
struct condstack *cstack;
{
char_u *errormsg = NULL;
int error;
if (cstack->cs_idx == CSTACK_LEN - 1)
errormsg = (char_u *)":if nesting too deep";
else
{
++cstack->cs_idx;
if (cstack->cs_idx == 0 || cstack->cs_active[cstack->cs_idx - 1])
{
cstack->cs_active[cstack->cs_idx] = bool_eval(eap->arg, &error);
if (error)
--cstack->cs_idx;
}
else
cstack->cs_active[cstack->cs_idx] = FALSE;
}
return errormsg;
}
/*
* Handle ":else" and ":elseif" commands.
*/
static char_u *
do_else(eap, cstack)
EXARG *eap;
struct condstack *cstack;
{
char_u *errormsg = NULL;
int error;
if (cstack->cs_idx < 0)
{
if (eap->cmdidx == CMD_else)
errormsg = (char_u *)":else without :if";
else
errormsg = (char_u *)":elseif without :if";
}
else
{
cstack->cs_active[cstack->cs_idx] =
(!cstack->cs_active[cstack->cs_idx] &&
(cstack->cs_idx == 0 ||
cstack->cs_active[cstack->cs_idx - 1]));
if (eap->cmdidx == CMD_elseif &&
cstack->cs_active[cstack->cs_idx])
{
cstack->cs_active[cstack->cs_idx] = bool_eval(eap->arg, &error);
if (error)
--cstack->cs_idx;
}
}
return errormsg;
}
/*
* Evaluate cmdline variables.
*
* change '%' to curbuf->b_ffname
* '#' to curwin->w_altfile
* '<cword>' to word under the cursor
* '<cWORD>' to WORD under the cursor
* '<cfile>' to path name under the cursor
* '<sfile>" to sourced filename
* '<afile>' to file 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.
* Returns 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_u *
eval_vars(src, usedlen, lnump, errormsg)
char_u *src; /* pointer into commandline */
int *usedlen; /* characters after src that are used */
linenr_t *lnump; /* line number for :e command */
char_u **errormsg; /* error message, or NULL */
{
int i;
char_u *s;
char_u *tail;
char_u *result;
int resultlen;
char_u *buf = NULL;
int spec_idx;
static char *(spec_str[]) =
{
"%",
#define SPEC_PERC 0
"#",
#define SPEC_HASH 1
"<cword>", /* cursor word */
#define SPEC_CWORD 2
"<cWORD>", /* cursor WORD */
#define SPEC_CCWORD 3
"<cfile>", /* cursor path name */
#define SPEC_CFILE 4
"<sfile>", /* ":so" file name */
#define SPEC_SFILE 5
#ifdef AUTOCMD
"<afile>" /* autocommand file name */
# define SPEC_AFILE 6
#endif
};
#define SPEC_COUNT (sizeof(spec_str) / sizeof(char *))
*errormsg = NULL;
/*
* Check if there is something to do.
*/
for (spec_idx = 0; spec_idx < SPEC_COUNT; ++spec_idx)
{
*usedlen = strlen(spec_str[spec_idx]);
if (STRNCMP(src, spec_str[spec_idx], *usedlen) == 0)
break;
}
if (spec_idx == SPEC_COUNT) /* no match */
{
*usedlen = 1;
return NULL;
}
/*
* Skip when preceded with a backslash "\%" and "\#".
* Note: In "\\%" the % is also not recognized!
*/
if (*(src - 1) == '\\')
{
*usedlen = 0;
STRCPY(src - 1, src); /* remove backslash */
return NULL;
}
/*
* word or WORD under cursor
*/
if (spec_idx == SPEC_CWORD || spec_idx == SPEC_CCWORD)
{
resultlen = find_ident_under_cursor(&result, spec_idx == SPEC_CWORD ?
(FIND_IDENT|FIND_STRING) : FIND_STRING);
if (resultlen == 0)
{
*errormsg = (char_u *)"";
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_ffname == NULL)
{
*errormsg = (char_u *)"No file name to substitute for '%'";
return NULL;
}
result = curbuf->b_fname;
break;
case SPEC_HASH: /* '#' or "#99": alternate file */
s = src + 1;
i = (int)getdigits(&s);
*usedlen = s - src; /* length of what we expand */
if (buflist_name_nr(i, &result, lnump) == FAIL)
{
*errormsg = (char_u *)"no alternate filename to substitute for '#'";
return NULL;
}
break;
case SPEC_CFILE: /* file name under cursor */
result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L);
if (result == NULL)
{
*errormsg = (char_u *)"";
return NULL;
}
buf = result; /* remember allocated string */
break;
#ifdef AUTOCMD
case SPEC_AFILE: /* file name for autocommand */
result = autocmd_fname;
if (result == NULL)
{
*errormsg = (char_u *)"no autocommand filename to substitute for \"<afile>\"";
return NULL;
}
break;
#endif
case SPEC_SFILE: /* file name for ":so" command */
result = sourcing_name;
if (result == NULL)
{
*errormsg = (char_u *)"no :soure filename to substitute for \"<sfile>\"";
return NULL;
}
break;
}
resultlen = STRLEN(result); /* length of new string */
if (src[*usedlen] == '<') /* remove the file name extension */
{
++*usedlen;
if ((s = vim_strrchr(result, '.')) != NULL && s >= gettail(result))
resultlen = s - result;
}
else
{
/* ":p" - full path/filename */
if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p')
{
*usedlen += 2;
result = FullName_save(result, FALSE);
vim_free(buf); /* free any allocated file name */
if (result == NULL)
{
*errormsg = (char_u *)"";
return NULL;
}
resultlen = STRLEN(result);
buf = result;
}
tail = gettail(result);
/* ":h" - head, remove "/filename" */
/* ":h" can be repeated */
/* Don't remove the first "/" or "c:\" */
while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h')
{
*usedlen += 2;
s = get_past_head(result);
while (tail > s && ispathsep(tail[-1]))
--tail;
resultlen = tail - result;
while (tail > s && !ispathsep(tail[-1]))
--tail;
}
/* ":t" - tail, just the basename */
if (src[*usedlen] == ':' && src[*usedlen + 1] == 't')
{
*usedlen += 2;
resultlen -= tail - result;
result = tail;
}
/* ":e" - extension */
/* ":e" can be repeated */
/* ":r" - root, without extension */
/* ":r" can be repeated */
while (src[*usedlen] == ':' &&
(src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r'))
{
/* find a '.' in the tail:
* - for second :e: before the current fname
* - otherwise: The last '.'
*/
if (src[*usedlen + 1] == 'e' && result > tail)
s = result - 2;
else
s = result + resultlen - 1;
for ( ; s > tail; --s)
if (s[0] == '.')
break;
if (src[*usedlen + 1] == 'e') /* :e */
{
if (s > tail)
{
resultlen += result - (s + 1);
result = s + 1;
}
else if (result <= tail)
resultlen = 0;
}
else /* :r */
{
if (s > tail) /* remove one extension */
resultlen = s - result;
}
*usedlen += 2;
}
}
/* TODO - ":s/pat/foo/" - substitute */
/* if (src[*usedlen] == ':' && src[*usedlen + 1] == 's') */
}
result = vim_strnsave(result, resultlen);
vim_free(buf);
return result;
}
/*
* Expand the <sfile> string in "arg".
*
* Returns an allocated string, or NULL for any error.
*/
char_u *
expand_sfile(arg)
char_u *arg;
{
char_u *errormsg;
int len;
char_u *result;
char_u *newres;
char_u *repl;
int srclen;
char_u *p;
linenr_t dummy;
result = vim_strsave(arg);
if (result == NULL)
return NULL;
for (p = result; *p; )
{
if (STRNCMP(p, "<sfile>", 7))
++p;
else
{
/* replace "<sfile>" with the sourced filename, and do ":" stuff */
repl = eval_vars(p, &srclen, &dummy, &errormsg);
if (errormsg != NULL)
{
if (*errormsg)
emsg(errormsg);
vim_free(result);
return NULL;
}
if (repl == NULL) /* no match (cannot happen) */
{
p += srclen;
continue;
}
len = STRLEN(result) - srclen + STRLEN(repl) + 1;
newres = alloc(len);
if (newres == NULL)
{
vim_free(repl);
vim_free(result);
return NULL;
}
vim_memmove(newres, result, (size_t)(p - result));
STRCPY(newres + (p - result), repl);
len = STRLEN(newres);
STRCAT(newres, p + srclen);
vim_free(result);
result = newres;
p = newres + len; /* continue after the match */
}
}
return result;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.