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.