This is ex.c in view mode; [Download] [Up]
/* ex.c */ /* Copyright 1995 by Steve Kirkendall */ char id_ex[] = "$Id: ex.c,v 2.95 1996/09/21 01:21:36 steve Exp $"; #include "elvis.h" #if USE_PROTOTYPES static void skipwhitespace(CHAR **refp); static BOOLEAN parsewindowid(CHAR **refp, EXINFO *xinf); static BOOLEAN parsebuffername(CHAR **refp, EXINFO *xinf); static BOOLEAN parseregexp(CHAR **refp, EXINFO *xinf, long flags); static BOOLEAN parsecommandname(CHAR **refp, EXINFO *xinf); static void parsemulti(CHAR **refp, EXINFO *xinf); static void parsebang(CHAR **refp, EXINFO *xinf, long flags); static void parseplus(CHAR **refp, EXINFO *xinf, long flags); static void parseprintflag(CHAR **refp, EXINFO *xinf, long flags); static void parselhs(CHAR **refp, EXINFO *xinf, long flags); static void parserhs(CHAR **refp, EXINFO *xinf, long flags); static void parsecutbuffer(CHAR **refp, EXINFO *xinf, long flags); static void parsecount(CHAR **refp, EXINFO *xinf, long flags); static BOOLEAN parsecmds(CHAR **refp, EXINFO *xinf, long flags); static BOOLEAN parsefileargs(CHAR **refp, EXINFO *xinf, long flags); static RESULT parse(WINDOW win, CHAR **refp, EXINFO *xinf); static RESULT execute(EXINFO *xinf); #endif /* Minimum number of filename pointers to allocate at a time. */ #define FILEGRAN 8 /* These are the possible arguments for a command. These may be combined * via the bitwise OR operator to describe arbitrary combinations of these. */ #define a_Line 0x00000001L /* single line specifier */ #define a_Range 0x00000002L /* range of lines */ #define a_Multi 0x00000004L /* command character may be stuttered */ #define a_Bang 0x00000008L /* '!' after the command name */ #define a_Target 0x00000010L /* extra line specifier after the command name */ #define a_Lhs 0x00000020L /* single word after the command */ #define a_Rhs 0x00000040L /* extra words (after Lhs, if Lhs allowed) */ #define a_Buffer 0x00000080L /* cut buffer name */ #define a_Count 0x00000100L /* number of lines affected */ #define a_Pflag 0x00000200L /* print flag (p, l, or #) */ #define a_Plus 0x00000400L /* line offset (as for ":e +20 foo") */ #define a_Append 0x00000800L /* ">>" indicating append mode */ #define a_Filter 0x00001000L /* "!cmd", where cmd may include '|' characters */ #define a_File 0x00002000L /* a single file name */ #define a_Files 0x00004000L /* Zero or more filenames */ #define a_Cmds 0x00008000L /* text which may contain '|' characters */ #define a_RegExp 0x00010000L /* regular expression */ #define a_RegSub 0x00020000L /* substitution text (requires RegExp) */ #define a_Text 0x00040000L /* If no Rhs, following lines are part of cmd */ /* The following values indicate the default values of arguments */ #define d_All 0x00080000L /* default range is all lines */ #define d_None 0x00100000L /* default range is no lines (else current line) */ #define d_File 0x00200000L /* default file is current file (else no default) */ /* The following indicate other quirks of commands */ #define q_Unsafe 0x00400000L /* command can't be executed in .exrc script */ #define q_Autop 0x00800000L /* autoprint current line */ #define q_Zero 0x01000000L /* allow Target or Line to be 0 */ #define q_Exrc 0x02000000L /* command may be run in a .exrc file */ #define q_Undo 0x04000000L /* save an "undo" version before this command */ #define q_Custom 0x08000000L /* save options/maps after command */ #define q_Ex 0x10000000L /* only allowed in ex mode, not vi's <:> cmd */ #define q_CtrlV 0x20000000L /* use ^V for quote char, instead of \ */ #define q_MayQuit 0x40000000L /* may cause window to disappear */ /* This array maps ex command names to command codes. The order in which * command names are listed below is significant -- ambiguous abbreviations * are always resolved to be the first possible match. (e.g. "r" is taken * to mean "read", not "rewind", because "read" comes before "rewind") * * Also, commands which share the same first letter are grouped together. * Similarly, within each one-letter group, the commands are ordered so that * the commands with the same two letters are grouped together, and those * groups are then divided into 3-letter groups, and so on. This allows * the command list to be searched faster. * * The comment at the start of each line below gives the shortest abbreviation. * HOWEVER, YOU CAN'T SIMPLY SORT THOSE ABBREVIATIONS to produce the correct * order for the commands. Consider the change/chdir/calculate commands, for * an example of why that wouldn't work. */ static struct { char *name; /* name of the command */ EXCMD code; /* enum code of the command */ RESULT (*fn) P_((EXINFO *)); /* function which executes the command */ long flags; /* command line arguments permitted/needed/used */ } cmdnames[] = { /* cmd name cmd code function arguments */ /*! */{"!", EX_BANG, ex_bang, a_Range | a_Cmds | d_None | q_Exrc | q_Unsafe | q_Undo }, /*# */{"#", EX_NUMBER, ex_print, a_Range | a_Count | a_Pflag }, /*& */{"&", EX_SUBAGAIN, ex_substitute, a_Range | q_Undo }, /*< */{"<", EX_SHIFTL, ex_shift, a_Range | a_Multi | a_Bang | a_Count | q_Autop | q_Undo }, /*= */{"=", EX_EQUAL, ex_file, a_Range }, /*> */{">", EX_SHIFTR, ex_shift, a_Range | a_Multi | a_Bang | a_Count | q_Autop | q_Undo }, /*@ */{"@", EX_AT, ex_at, a_Buffer }, /*N */{"Next", EX_PREVIOUS, ex_next, a_Bang | q_Unsafe }, /*" */{"\"", EX_COMMENT, ex_comment, a_Cmds | q_Exrc }, /*a */{"append", EX_APPEND, ex_append, a_Line | a_Bang | a_Cmds | a_Text | q_Zero | q_Undo | q_Ex }, /*ab */{"abbreviate", EX_ABBR, ex_map, a_Bang | a_Lhs | a_Rhs | q_Exrc | q_Custom | q_Unsafe | q_CtrlV }, /*al */{"all", EX_ALL, ex_all, a_Bang | a_Cmds }, /*ar */{"args", EX_ARGS, ex_args, a_Files | q_Exrc | q_Unsafe }, /*b */{"buffer", EX_BUFFER, ex_buffer, a_Bang | a_Rhs }, /*br */{"break", EX_BREAK, ex_map, a_Bang | a_Lhs | q_CtrlV }, /*c */{"change", EX_CHANGE, ex_append, a_Range | a_Bang | a_Count | a_Text | q_Undo | q_Ex }, /*cha */{"chdir", EX_CD, ex_cd, a_Bang | a_File | q_Exrc | q_Unsafe }, /*ca */{"calculate", EX_CALC, ex_comment, a_Cmds | q_Exrc }, /*cc */{"cc", EX_CC, ex_make, a_Bang | a_Rhs | q_Unsafe }, /*cd */{"cd", EX_CD, ex_cd, a_Bang | a_File | q_Exrc | q_Unsafe }, /*cl */{"close", EX_CLOSE, ex_xit, a_Bang | q_MayQuit }, /*co */{"copy", EX_COPY, ex_move, a_Range | a_Target | a_Pflag | q_Autop | q_Undo }, /*col */{"color", EX_COLOR, ex_color, a_Rhs | q_Exrc | q_Custom }, /*d */{"delete", EX_DELETE, ex_delete, a_Range | a_Buffer | a_Count | a_Pflag | q_Undo | q_Autop }, /*di */{"display", EX_DISPLAY, ex_display, a_Rhs }, /*dis */{"digraph", EX_DIGRAPH, ex_digraph, a_Bang | a_Rhs | q_Exrc | q_Custom }, /*e */{"edit", EX_EDIT, ex_edit, a_Bang | a_Plus | a_File | d_File }, /*ec */{"echo", EX_ECHO, ex_comment, a_Rhs | q_Exrc }, /*el */{"else", EX_ELSE, ex_then, a_Cmds | q_Exrc }, /*er */{"errlist", EX_ERRLIST, ex_errlist, a_Bang | a_File | a_Filter }, /*ev */{"eval", EX_EVAL, ex_if, a_Cmds | q_Exrc }, /*ex */{"ex", EX_EDIT, ex_edit, a_Bang | a_Plus | a_File | d_File | q_Unsafe }, /*f */{"file", EX_FILE, ex_file, a_File | q_Unsafe }, /*g */{"global", EX_GLOBAL, ex_global, a_Range | a_Bang | a_RegExp | a_Cmds | d_All | q_Undo }, /*go */{"goto", EX_GOTO, ex_comment, a_Line | q_Zero | q_Autop }, /*gu */{"gui", EX_GUI, ex_gui, a_Rhs | q_Exrc }, /*h */{"help", EX_HELP, ex_help, a_Lhs | a_Rhs }, /*i */{"insert", EX_INSERT, ex_append, a_Line | a_Bang | a_Cmds | a_Text | q_Undo | q_Ex }, /*if */{"if", EX_IF, ex_if, a_Cmds | q_Exrc }, /*j */{"join", EX_INSERT, ex_join, a_Range | a_Bang | q_Autop | q_Undo }, /*k */{"k", EX_MARK, ex_mark, a_Line | a_Lhs }, /*l */{"list", EX_LIST, ex_print, a_Range | a_Count | a_Pflag }, /*la */{"last", EX_LAST, ex_next, q_Unsafe }, /*le */{"let", EX_LET, ex_set, a_Bang | a_Cmds | q_Exrc | q_Custom }, /*lp */{"lpr", EX_LPR, ex_lpr, a_Range | a_Bang | a_File | a_Filter | d_All | q_Unsafe }, /*m */{"move", EX_MOVE, ex_move, a_Range | a_Target | q_Autop | q_Undo }, /*ma */{"mark", EX_MARK, ex_mark, a_Line | a_Lhs }, /*mak */{"make", EX_MAKE, ex_make, a_Bang | a_Rhs | q_Unsafe }, /*map */{"map", EX_MAP, ex_map, a_Bang | a_Lhs | a_Rhs | q_Exrc | q_Custom | q_Unsafe | q_CtrlV }, /*mk */{"mkexrc", EX_MKEXRC, ex_mkexrc, a_Bang | a_File | q_Unsafe }, /*n */{"next", EX_NEXT, ex_next, a_Bang | a_Files | q_Unsafe }, /*ne */{"new", EX_SNEW, ex_split, q_Unsafe }, /*no */{"normal", EX_NORMAL, ex_display }, /*nu */{"number", EX_NUMBER, ex_print, a_Range | a_Count | a_Pflag }, /*o */{"open", EX_OPEN, ex_edit, a_Bang | a_Plus | a_File }, /*p */{"print", EX_PRINT, ex_print, a_Range | a_Count | a_Pflag }, /*pre */{"previous", EX_PREVIOUS, ex_next, a_Bang | q_Unsafe }, /*pres*/{"preserve", EX_PRESERVE, ex_qall, d_None | q_Unsafe | q_MayQuit }, /*po */{"pop", EX_POP, ex_pop, a_Bang | q_Unsafe }, /*pu */{"put", EX_PUT, ex_put, a_Line | q_Zero | a_Buffer | q_Undo }, /*q */{"quit", EX_QUIT, ex_xit, a_Bang | q_MayQuit }, /*qa */{"qall", EX_QALL, ex_qall, a_Bang | q_MayQuit }, /*r */{"read", EX_READ, ex_read, a_Line | a_File | a_Filter | q_Zero | q_Undo }, /*red */{"redo", EX_REDO, ex_undo, a_Lhs | q_Autop }, /*rew */{"rewind", EX_REWIND, ex_next, a_Bang | q_Unsafe }, /*s */{"substitute", EX_SUBSTITUTE, ex_substitute, a_Range|a_RegExp|a_RegSub|a_Rhs|a_Count|a_Pflag|q_Autop|q_Undo|q_Exrc }, /*sN */{"sNext", EX_SPREVIOUS, ex_next, q_Unsafe }, /*sa */{"sall", EX_SALL, ex_sall, q_Unsafe }, /*saf */{"safer", EX_SAFER, ex_source, a_Bang | a_File | q_Exrc }, /*se */{"set", EX_SET, ex_set, a_Bang | a_Rhs | q_Exrc | q_Custom }, /*sh */{"shell", EX_SHELL, ex_suspend, q_Unsafe }, /*sl */{"slast", EX_SLAST, ex_next, q_Unsafe }, /*sn */{"snext", EX_SNEXT, ex_next, a_Files }, /*snew*/{"snew", EX_SNEW, ex_split, q_Unsafe }, /*so */{"source", EX_SOURCE, ex_source, a_Bang | a_File | q_Exrc }, /*sp */{"split", EX_SPLIT, ex_split, a_Line | a_File }, /*sr */{"srewind", EX_SREWIND, ex_next, a_Bang | q_Unsafe }, /*st */{"stop", EX_STOP, ex_suspend, a_Bang | q_Unsafe }, /*sta */{"stag", EX_STAG, ex_tag, a_Lhs }, /*stac*/{"stack", EX_STACK, ex_stack, d_None }, /*su */{"suspend", EX_SUSPEND, ex_suspend, a_Bang | q_Unsafe }, /*t */{"to", EX_COPY, ex_move, a_Range | a_Target | a_Pflag | q_Autop | q_Undo }, /*ta */{"tag", EX_TAG, ex_tag, a_Bang | a_Lhs | q_Unsafe }, /*th */{"then", EX_THEN, ex_then, a_Cmds | q_Exrc }, /*u */{"undo", EX_UNDO, ex_undo, a_Lhs | q_Autop }, /*una */{"unabbreviate",EX_UNABBR, ex_map, a_Bang | a_Lhs | q_Exrc | q_Custom | q_CtrlV }, /*unb */{"unbreak", EX_UNBREAK, ex_map, a_Bang | a_Lhs | q_CtrlV }, /*unm */{"unmap", EX_UNMAP, ex_map, a_Bang | a_Lhs | q_Exrc | q_Custom | q_CtrlV }, /*v */{"vglobal", EX_VGLOBAL, ex_global, a_Range | a_Bang | a_RegExp | a_Cmds | d_All | q_Undo }, /*ve */{"version", EX_VERSION, ex_version, q_Exrc }, /*vi */{"visual", EX_VISUAL, ex_edit, a_Bang | a_Plus | a_File }, /*w */{"write", EX_WRITE, ex_write, a_Range | a_Bang | a_Append | a_File | a_Filter | d_All | q_Unsafe }, /*wi */{"window", EX_WINDOW, ex_window, a_Lhs }, /*wn */{"wnext", EX_WNEXT, ex_next, a_Bang | q_Unsafe }, /*wq */{"wquit", EX_WQUIT, ex_xit, a_Bang | a_File | d_File | q_MayQuit }, /*x */{"xit", EX_XIT, ex_xit, a_Bang | a_File | q_MayQuit }, /*y */{"yank", EX_YANK, ex_delete, a_Range | a_Buffer | a_Count }, /*z */{"z", EX_Z, ex_z, a_Line | a_Rhs }, /*~ */{"~", EX_SUBAGAIN, ex_substitute, a_Range | q_Undo }, }; /* This variable is used for detecting nested global statements */ static int globaldepth; /* This function discards info from an EXINFO struct. The struct itself * is not freed, since it is usually just a local variable in some function. */ void exfree(xinf) EXINFO *xinf; /* the command to be freed */ { int i; if (xinf->fromaddr) markfree(xinf->fromaddr); if (xinf->toaddr) markfree(xinf->toaddr); if (xinf->destaddr) markfree(xinf->destaddr); if (xinf->re) safefree(xinf->re); if (xinf->lhs) safefree(xinf->lhs); if (xinf->rhs) safefree(xinf->rhs); for (i = 0; i < xinf->nfiles; i++) { assert(xinf->file && xinf->file[i]); safefree(xinf->file[i]); } if (xinf->file) safefree(xinf->file); } /* This function skips over blanks and tabs */ static void skipwhitespace(refp) CHAR **refp; /* pointer to scan variable */ { while (*refp && (**refp == ' ' || **refp == '\t')) { scannext(refp); } } /* This function attempts to parse a window ID. If there is no window ID, * then it leaves xinf->win unchanged; else it sets xinf->win to the given * window. Returns True unless there was an error. * * This function also sets the default buffer to the default window's state * buffer, or the specified window's main buffer. It is assumed that * xinf->window has already been initialized to the default window. */ static BOOLEAN parsewindowid(refp, xinf) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ { long num; MARK start; WINDOW win; /* set default buffer, assuming default window. */ if (xinf->window->state->pop) xinf->defaddr = *xinf->window->state->pop->cursor; else xinf->defaddr = *xinf->window->cursor; if (markbuffer(&xinf->defaddr) != bufdefault) { xinf->defaddr.buffer = bufdefault; xinf->defaddr.offset = 0L; } /* if doesn't start with a digit, ignore it */ if (!*refp || !isdigit(**refp)) { return True; } /* convert the number */ start = scanmark(refp); for (num = 0; *refp && isdigit(**refp); scannext(refp)) { num = num * 10 + **refp - '0'; } /* if doesn't end with a ':', then it's not meant to be a window id */ if (!*refp || **refp != ':' || num <= 0) { scanseek(refp, start); return True; } /* eat the ':' character */ scannext(refp); /* convert the number to a WINDOW */ for (win = winofbuf((WINDOW)0, (BUFFER)0); win && o_windowid(win) != num; win = winofbuf(win, (BUFFER)0)) { } if (!win) { msg(MSG_ERROR, "bad window"); return False; } /* use the named window */ xinf->window = win; xinf->defaddr = *win->cursor; return True; } /* This function attempts to parse a single buffer name. It returns True * unless an invalid buffer is specified. * * If an explicit buffer is named, then it sets the default address to that * buffer's undo point. (Else it is left set window's cursor, as set by * the parsewindowid() function.) */ static BOOLEAN parsebuffername(refp, xinf) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ { CHAR bufname[100];/* buffer name, as a string */ int len; BUFFER buf; /* if the first character isn't '(' then this isn't a buffer name */ if (!*refp || **refp != '(') { return True; } /* collect the characters into buffer */ for (len = 0; scannext(refp) && **refp != ')' && **refp != '\n' && **refp != '|' && len < QTY(bufname) - 1; ) { bufname[len++] = **refp; } bufname[len] = '\0'; /* consume the closing ')', if any */ while (*refp && **refp != ')' && **refp != '\n' && **refp != '|') { scannext(refp); } if (*refp && **refp == ')') { scannext(refp); } /* try to find the buffer */ buf = buffind(bufname); if (!buf) { msg(MSG_ERROR, "no such buffer"); return False; } xinf->defaddr.buffer = buf; marksetoffset(&xinf->defaddr, buf->changepos); return True; } /* If the command supports a regular expression, then this function parses * that regular expression from the command line and compiles it. If the * command also supports replacement text, then that is parsed too. */ static BOOLEAN parseregexp(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { CHAR delim; /* delimiter character */ CHAR *retext;/* source code of the regular expression */ /* If this command doesn't use regular expressions, then do nothing. * (By the way, not use regexps implies that it doesn't use replacement * text either.) */ if ((flags & a_RegExp) == 0) { return True; } /* get the delimiter character. Can be any punctuation character * except '|'. If no delimiter is given, then use an empty string. */ empty[0] = (CHAR)0; if (*refp && ispunct(**refp) && **refp != '|') { /* remember the delimiter */ delim = **refp; scannext(refp); /* collect the characters of the regexp source code */ for (retext = (CHAR *)0; *refp && **refp != '\n' && **refp != delim; scannext(refp)) { /* if backslash followed by delimiter, then just add * delimiter. Else add both the backslash and whatever * other character followed it. */ if (**refp == '\\') { if (!scannext(refp)) { buildCHAR(&retext, '\\'); } else if (**refp == delim) { buildCHAR(&retext, delim); } else { buildCHAR(&retext, '\\'); buildCHAR(&retext, **refp); } } else { buildCHAR(&retext, **refp); } } if (!retext) { retext = empty; } } else { retext = empty; delim = '\n'; /* illegal delimiter, affects a_RegSub text below */ } /* consume the ending delimiter (if any) */ if (*refp && **refp == delim) { scannext(refp); } /* compile the regular expression */ xinf->re = regcomp(retext, xinf->window ? xinf->window->state->cursor : NULL); if (!xinf->re) { /* error message already written out by regcomp() */ return False; } /* We don't need to source to the regexp anymore. If the source was * anything other than the empty string, then free its memory. */ if (retext != empty) { safefree(retext); } /* if no substitution text is needed, then we're done. */ if ((flags & a_RegSub) == 0) { return True; } /* Collect characters up to the next delimiter to be the replacement * text. Same rules as the regular expression. The first delimiter * has already been comsumed. */ if (delim == '\n') { /* no delimiter implies that there is no replacement text */ xinf->lhs = (CHAR *)safealloc(1, sizeof(CHAR)); } else { for (; *refp && **refp != '\n' && **refp != delim; scannext(refp)) { /* if backslash followed by delimiter, then just add * delimiter. Else add both the backslash and whatever * other character followed it. */ if (**refp == '\\') { if (!scannext(refp)) { buildCHAR(&xinf->lhs, '\\'); } else if (**refp == delim) { buildCHAR(&xinf->lhs, delim); } else if (**refp == ELVCTRL('M')) { buildCHAR(&xinf->lhs, (CHAR)ELVCTRL('M')); } else { buildCHAR(&xinf->lhs, '\\'); buildCHAR(&xinf->lhs, **refp); } } else if (**refp == ELVCTRL('M')) { buildCHAR(&xinf->lhs, '\n'); } else { buildCHAR(&xinf->lhs, **refp); } } if (!xinf->lhs) { /* zero-length string, if nothing else */ xinf->lhs = (CHAR *)safealloc(1, sizeof(CHAR)); } /* consume the closing delimiter */ if (*refp && **refp == delim) { scannext(refp); } } return True; } /* This function parses an address, and places the results in xinf->to. * Returns True unless there is an error. */ BOOLEAN exparseaddress(refp, xinf) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ { BLKNO bi; /* bufinfo block of the buffer being addressed */ long lnum; /* line number */ long delta; /* movement amount */ CHAR sign; /* '+' or '-' for delta */ long start; /* where search stared, so we can detect wrap */ long buflines;/* number of lines in buffer */ MARKBUF m; /* start of a line */ CHAR ch; /* if nothing, do nothing */ if (!*refp) return True; /* find the default line number */ bi = bufbufinfo(markbuffer(&xinf->defaddr)); lnum = markline(&xinf->defaddr); xinf->fromoffset = 0; /* parse the address */ switch (**refp) { case '.': /* use the line containing the default address */ scannext(refp); break; case '/': case '?': /* parse & compile the regular expression */ delta = (**refp == '?') ? -1 : 1; if (!parseregexp(refp, xinf, a_RegExp)) { return False; } /* allow the visual 'n' and 'N' commands to search for the * same regexp. */ if (searchre) { safefree(searchre); } searchre = xinf->re; searchforward = (BOOLEAN)(delta > 0); searchhasdelta = False; xinf->re = NULL; /* search for the regular expression */ start = lnum; buflines = o_buflines(markbuffer(&xinf->defaddr)); do { /* find next line */ lnum += delta; if (o_wrapscan) { if (lnum == 0) lnum = buflines; else if (lnum > buflines) lnum = 1; } else if (lnum == 0) { msg(MSG_ERROR, "no match above"); return False; } else if (lnum > buflines) { msg(MSG_ERROR, "no match below"); return False; } /* see if this line contains the regexp */ if (regexec(searchre, marktmp(m, markbuffer(&xinf->defaddr), lowline(bi, lnum)), True)) { delta = 0; } /* if we've wrapped back to our starting point, * then we've failed. */ if (lnum == start) { msg(MSG_ERROR, "no match"); return False; } /* did the user cancel the search? */ if (guipoll(False)) { return False; } } while (delta != 0); /* If we get here, then we've found a match, and lnum is set * to its line number. Good! */ xinf->fromoffset = (searchre->leavep >= 0 ? searchre->leavep : searchre->startp[0]); break; case '\'': case '`': ch = **refp; if (!scannext(refp) || **refp < 'a' || **refp > 'z') { msg(MSG_ERROR, "bad mark name"); return False; } else if (!namedmark[**refp - 'a']) { msg(MSG_ERROR, "[C]'$1 unset", **refp); return False; } else if (markbuffer(&xinf->defaddr) != markbuffer(namedmark[**refp - 'a'])) { if (xinf->anyaddr) { msg(MSG_ERROR, "would span buffers"); return False; } xinf->defaddr = *namedmark[**refp - 'a']; } lnum = markline(namedmark[**refp - 'a']); if (ch == '`') xinf->fromoffset = markoffset(namedmark[**refp - 'a']); scannext(refp); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': for (lnum = 0; *refp && isdigit(**refp); scannext(refp)) { lnum = lnum * 10 + **refp - '0'; } if (lnum < 0 || lnum > o_buflines(markbuffer(&xinf->defaddr))) { msg(MSG_ERROR, "bad line number"); return False; } break; case '$': scannext(refp); lnum = o_buflines(markbuffer(&xinf->defaddr)); break; default: /* use the default address, but don't consume any chars */ lnum = markline(&xinf->defaddr); } /* followed by a delta? */ skipwhitespace(refp); if (*refp && (**refp == '+' || **refp == '-')) { /* delta cancels implies whole-line addressing */ xinf->fromoffset = 0; /* find the sign & magnitude of the delta */ sign = **refp; for (delta = 1; scannext(refp) && **refp == sign; delta++) { } if (delta == 1 && *refp && isdigit(**refp)) { for (delta = **refp - '0'; scannext(refp) && isdigit(**refp); delta = delta * 10 + **refp - '0') { } } /* add the delta to the line number */ if (sign == '+') lnum += delta; else lnum -= delta; /* if sum is invalid, complain */ if (lnum < 1 || lnum > o_buflines(markbuffer(&xinf->defaddr))) { msg(MSG_ERROR, "bad delta"); } } xinf->to = lnum; return True; } /* This parses a command name, and sets xinf->command accordingly. Returns * True if everything is okay, or False if the command is unrecognized or * was given addresses but doesn't allow addresses. */ static BOOLEAN parsecommandname(refp, xinf) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ { BOOLEAN anymatch; int matches; /* number of commands that match so far */ int firstmatch; /* first command that matched */ char cmdname[20]; /* command name */ int len; /* number of characters in name so far */ MARK start; /* where the command name started */ int i; /* if no command, then assume either "goto" or comment */ if (!*refp || **refp == '\n' || **refp == '|') { strcpy(cmdname, (xinf->anyaddr || (xinf->window && markbuffer(xinf->window->cursor) != xinf->defaddr.buffer)) ? "goto" : "\""); for (firstmatch = QTY(cmdnames) - 1; firstmatch >= 0 && strcmp(cmdnames[firstmatch].name, cmdname); firstmatch--) { } goto Found; } /* start with shortest possible command name, and extend the command * name as much as possible without eliminating all commands from * matching. When we get it as long as possible, then the first * matching name is the one we'll use. */ len = 0; start = scanmark(refp); firstmatch = 0; anymatch = False; do { /* copy the first (or next) character of the name into a buffer */ cmdname[len++] = **refp; /* see how many commands match this command so far */ for (matches = 0, i = firstmatch; i < QTY(cmdnames); i++) { /* In the cmdnames[] array, commands with the same * initial letters are grouped together. Have we * reached the end of the group that interests us? */ if (len > 1 && (cmdnames[i].name[0] != cmdname[0] || strncmp(cmdnames[i].name, cmdname, (size_t)(len - 1)))) { /* no more matches are possible -- we reached * the end of this (len-1) letter group. */ break; } if ((CHAR)cmdnames[i].name[len - 1] == **refp) { /* Partial match, but keep looking. If this * was the first match with this length then * remember it. */ matches++; if (matches == 1) { firstmatch = i; anymatch = True; } } } } while (matches > 0 && scannext(refp) && isalpha(**refp)); /* at this point, if "matches" is zero and "firstmatch" has been set, * then we may have read one too many characters for the command name, * so we need to adjust the position of the *refp. */ if (matches == 0 && anymatch && (strlen(cmdnames[firstmatch].name) == (unsigned)(len - 1) || !*refp || !isalpha(**refp))) { len--; markaddoffset(start, len); scanseek(refp, start); matches = 1; } /* If we still haven't found anything, give up! */ if (matches == 0) { cmdname[len] = '\0'; msg(MSG_ERROR, "[s]bad command name $1", cmdname); return False; } /* so I guess we found a match. */ Found: assert(firstmatch >= 0); xinf->cmdidx = firstmatch; xinf->command = cmdnames[firstmatch].code; xinf->cmdname = cmdnames[firstmatch].name; return True; } /* This function parses multiplied command names, as in :<<<<. This can't * possibly fail, so this isn't a boolean function. */ static void parsemulti(refp, xinf) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ { /* the default for all commands is 1. */ xinf->multi = 1; /* if this command can be multiplied, then see how many we have */ if (cmdnames[xinf->cmdidx].flags & a_Multi) { while (*refp && **refp == (CHAR)xinf->cmdname[0]) { xinf->multi++; scannext(refp); } } } /* This function parses an optional '!' character for some commands. This * can't possibly fail, so this isn't a boolean function. */ static void parsebang(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { /* if this supports '!', and a '!' character really is given, then * consume the '!' character and set the "bang" flag. */ if ((flags & a_Bang) != 0 && *refp && **refp == '!') { scannext(refp); xinf->bang = True; } } /* This function parses an optional "+line" argument, for commands which * support it. */ static void parseplus(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { /* if the command doesn't support "+line" or the next character isn't * '+' then do nothing. */ if ((flags & a_Plus) == 0 || !*refp || **refp != '+') { return; } /* nothing else should have used the xinf->lhs field yet */ assert(!xinf->lhs); /* skip the '+' */ scannext(refp); /* collect following characters up to next whitespace or '|' */ while (*refp && !isspace(**refp) && **refp != '|') { buildCHAR(&xinf->lhs, **refp); scannext(refp); } } /* This function parses an optional print flag and delta, if the command * supports them. */ static void parseprintflag(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { CHAR sign; static PFLAG pflag = PF_NONE; /* if no print flag is possible, skip parsing this */ if (!*refp || (flags & a_Pflag) == 0) { goto NoFlag; } /* try to parse a print flag here */ switch (**refp) { case 'p': scannext(refp); xinf->pflag = PF_PRINT; break; case 'l': scannext(refp); if (*refp && **refp == '#') { scannext(refp); xinf->pflag = PF_NUMLIST; } else { xinf->pflag = PF_LIST; } break; case '#': scannext(refp); if (*refp && **refp == 'l') { scannext(refp); xinf->pflag = PF_NUMLIST; } else { xinf->pflag = PF_NUMBER; } break; default: goto NoFlag; } /* we have a print flag -- now see if we have a delta */ if (refp && (**refp == '+' || **refp == '-')) { sign = **refp; while (scannext(refp) && isdigit(**refp)) { xinf->delta = xinf->delta * 10 + **refp - '0'; } if (sign == '-') { xinf->delta = -xinf->delta; } } /* remember this print flag as the default for next time */ pflag = xinf->pflag; return; NoFlag: /* see if we're supposed to autoprint this command. If so, then * assume the previous print flag is the default to use here. */ if ((flags & q_Autop) != 0 && o_autoprint && globaldepth == 0 && xinf->window && xinf->window->state && (xinf->window->state->flags & (ELVIS_POP|ELVIS_1LINE)) == 0 && xinf->window->state->enter && markbuffer(scanmark(refp)) == buffind(toCHAR(EX_BUF))) { if (pflag == PF_NONE) pflag = PF_PRINT; xinf->pflag = pflag; } } /* This function parses single word which doesn't contain any unquoted * whitespace (if the command supports this). */ static void parselhs(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { CHAR quote; /* quote character -- either ^V or \ */ /* if the command doesn't use lhs, then do nothing */ if ((flags & a_Lhs) == 0) return; /* choose the proper quote character */ quote = (flags & q_CtrlV) ? ELVCTRL('V') : '\\'; /* collect characters up to next whitespace or end of command */ while (*refp && **refp != ' ' && **refp != '\t' && **refp != '\n' && **refp != '|') { /* backslash followed by whitespace becomes just whitespace */ if (**refp == quote) { scannext(refp); if (!*refp) { buildCHAR(&xinf->lhs, quote); } else if (**refp == ' ' || **refp == '\t' || **refp == '|' || quote == ELVCTRL('V')) { buildCHAR(&xinf->lhs, **refp); } else { buildCHAR(&xinf->lhs, quote); buildCHAR(&xinf->lhs, **refp); } } else { /* unquoted character */ buildCHAR(&xinf->lhs, **refp); } scannext(refp); } } /* This function parses an optional print flag and delta, if the command * supports them. */ static void parserhs(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { CHAR quote; /* quote character -- either ^V or \ */ /* if the command doesn't use rhs, then do nothing */ if ((flags & a_Rhs) == 0) return; /* choose the proper quote character */ quote = (flags & q_CtrlV) ? ELVCTRL('V') : '\\'; /* collect characters up to end of command */ while (*refp && **refp != '\n' && **refp != '|') { /* backslash followed by whitespace becomes just whitespace */ if (**refp == quote) { scannext(refp); if (!*refp) { buildCHAR(&xinf->rhs, quote); } else if (**refp == '|' || quote == ELVCTRL('V')) { buildCHAR(&xinf->rhs, **refp); } else { buildCHAR(&xinf->rhs, quote); buildCHAR(&xinf->rhs, **refp); } } else { /* unquoted character */ buildCHAR(&xinf->rhs, **refp); } scannext(refp); } } /* This function parses an optional cut buffer name, if the command * supports them. A cut buffer name is a single letter, or a " followed * a letter, digit, or another ". */ static void parsecutbuffer(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { /* if the command doesn't support cut buffer names, do nothing */ if ((flags & a_Buffer) == 0) return; /* if double-quote, then following character is buffer name */ if (*refp && (isalpha(**refp) || **refp == '<' || **refp == '>' || (**refp == '"' && scannext(refp)))) { xinf->cutbuf = **refp; scannext(refp); } } /* This function parses an optional count, if the command supports them. * A count is a series of digits. If no count is given, then the count * field is set to -1. */ static void parsecount(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { if ((flags & a_Count) == 0 || !*refp || !isdigit(**refp)) { /* no count given */ xinf->count = -1; } else { /* count given -- convert it to binary */ do { xinf->count = xinf->count * 10 + **refp - '0'; scannext(refp); } while (*refp && isdigit(**refp)); } } /* This function parses an optional command list, if the command supports them. * A command list is a string which may contain any characters except newline. * (In particular, '|' is allowed.) */ static BOOLEAN parsecmds(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { BOOLEAN shell; /* is this command for a shell? */ BOOLEAN bol; /* at front of a line? */ CHAR *scan; CHAR ch; MARKBUF end; MARK start; int i; /* if the command doesn't use cmds, then do nothing */ if ((flags & a_Cmds) == 0) return True; /* determine whether this command is for a shell or for ex */ shell = (BOOLEAN)(xinf->command == EX_BANG || (flags & a_Filter) != 0); /* skip whitespace */ skipwhitespace(refp); /* ex commands allow a { ... } syntax */ if (!shell && *refp && **refp == '{') { /* collect characters up to next } character which appears * at the front of a line. Ignore blank lines. */ for (bol = True; scannext(refp) && !(bol && **refp == '}'); ) { if (!bol || **refp != '\n') buildCHAR(&xinf->rhs, **refp); if (**refp == '\n') bol = True; if (!isspace(**refp)) bol = False; } /* if we hit end without finding } then complain. */ if (!*refp) { msg(MSG_ERROR, "missing }"); return False; } /* eat the closing } */ scannext(refp); return True; } /* collect characters up to end of command */ while (*refp && **refp && **refp != '\n') { if (!shell) { /* copy all characters literally */ buildCHAR(&xinf->rhs, **refp); scannext(refp); } else { /* copy most characters literally, but perform * substitutions for !, %, # */ ch = **refp; switch (ch) { case '%': scan = o_filename(markbuffer(&xinf->defaddr)); break; case '#': scan = o_previousfile; break; case '!': scan = o_previouscommand; break; case '\\': scannext(refp); if (*refp && !CHARchr(toCHAR("%#!@\\"), **refp)) { buildCHAR(&xinf->rhs, (_CHAR_)'\\'); ch = **refp; } else if (*refp && **refp == '@') { if (!xinf->window) { msg(MSG_ERROR, "can't use \\@ during initialization"); } end = *xinf->window->cursor; start = wordatcursor(&end); if (!start) { msg(MSG_ERROR, "cursor not on word"); return False; } for (i = markoffset(&end) - markoffset(start), scanalloc(&scan, start); scan && i > 0; scannext(&scan), i--) { buildCHAR(&xinf->rhs, *scan); } scanfree(&scan); /* don't add anything else */ scan = xinf->rhs; break; } /* fall through for all but \@ ... */ default: buildCHAR(&xinf->rhs, **refp); scan = xinf->rhs; } /* are we trying to add a string? */ if (scan != xinf->rhs) { /* we want to... but do we have the string? */ if (scan) { for ( ; *scan; scan++) buildCHAR(&xinf->rhs, *scan); } else { msg(MSG_ERROR, "[C]no value to substitute for $1", ch); return False; } } /* Move on to the next character. Note that we need to * check (*refp) because it may have become NULL during * the processing of a backslash. */ if (*refp) scannext(refp); } } /* if shell command, then remember it for later ! substitutions */ if (shell) { if (!xinf->rhs) return False; /* blank commands are illegal */ if (o_previouscommand) safefree(o_previouscommand); o_previouscommand = CHARkdup(xinf->rhs); } return True; } /* This is a "helper" function for parsefileargs(). This function adds a * single item to the list of filename arguments. Optionally, it can * attempt to expand wildcards and recursively add each matching filename. * Returns True if successful, False if error. */ BOOLEAN exaddfilearg(file, nfiles, filename, wild) char ***file; /* ptr to a dynamic array of char* pointers */ int *nfiles; /* number of files in file array */ char *filename; /* the filename (or wildcard expression) to add */ BOOLEAN wild; /* if True, expand wildcards */ { char *match; /* a name matching a wildcard */ char **old; /* previous value of xinf->file pointer */ int start; /* index into xinf->file of wildcard's expansion */ BOOLEAN mustfree=False; /* if True, then free "match" before returning*/ int i; /* are we supposed to match wildcards? */ if (wild) { /* expand any environment variables */ filename = tochar8(calculate(toCHAR(filename), NULL, True)); if (!filename) return False; /* If this operating system uses backslashes between names, * then replace any forward slashes with backslashes. This is * intended to allow MS-DOS users to type forward slashes in * their filenames, if they so prefer. */ if (dirpath("a", "b")[1] == '\\') { for (i = 0; filename[i]; i++) { if (filename[i] == '/') { filename[i] = '\\'; } } } /* replace ~ with the name of the home directory */ if (filename[0] == '~' && !isalpha(filename[1])) { filename = strdup(filename[1] ? dirpath(tochar8(o_home), &filename[2]) : tochar8(o_home)); mustfree = True; } /* If there are any matches... */ if ((match = dirfirst(filename, False)) != NULL) { /* for each match... */ start = *nfiles; do { /* add the matching name */ exaddfilearg(file, nfiles, match, False); /* ripple the new name back, to keep things sorted */ for (i = *nfiles - 1; i > start && strcmp((*file)[i], (*file)[i - 1]) < 0; i--) { match = (*file)[i]; (*file)[i] = (*file)[i - 1]; (*file)[i - 1] = match; } } while ((match = dirnext()) != (char *)0); } /* if necessary, free the filename */ if (mustfree) { safefree(filename); } } else /* literal filename */ { /* [re-]allocate the *files array if necessary. Note that * we've arranged it so there will always be at least one * NULL pointer at the end of the list. */ if (*nfiles == 0 || (*nfiles + 1) % FILEGRAN == 0) { old = *file; *file = (char **)safealloc(*nfiles + FILEGRAN, sizeof(char *)); if (old) { memcpy(*file, old, *nfiles * sizeof(char *)); safefree(old); } } /* append the new filename */ (*file)[(*nfiles)++] = safedup(filename); } return True; } /* This function parses filenames, if the command supports them. */ static BOOLEAN parsefileargs(refp, xinf, flags) CHAR **refp; /* pointer to the (CHAR *) used for scanning command */ EXINFO *xinf; /* info about the command being parsed */ long flags; /* bitmap of command attributes */ { CHAR *filename; CHAR *scan; /* if no filenames expected, or hit end of cmd, then do nothing */ if ((flags & (a_File|a_Files|a_Filter|a_Append)) == 0 || !*refp) { return True; } /* If filter is allowed, and next character is a '!' the we have * a filter command. */ if ((flags & a_Filter) != 0 && **refp == '!') { /* skip the initial '!' */ if (!scannext(refp)) return True; /* missing filter will be detected later */ /* begin the rhs argument with a bang */ buildCHAR(&xinf->rhs, (_CHAR_)'!'); /* collect characters up to next newline */ return parsecmds(refp, xinf, flags | a_Cmds); } /* An initial backslash could have been used to quote a ! or > at * the start of a filename. If the first character is backslash, * and the second is either ! or > then skip the backslash. Also, * if the second is another backslash and backslash isn't used as * the directory separator on this operating system, then skip the * backslash; this should allow "\\foo" to be "\foo" under UNIX, * while still allowing "\\machine\dir\file" to be parsed as a UNC * name under Win32. */ if (*refp && **refp == '\\') { scannext(refp); if (!*refp) { msg(MSG_ERROR, "oops"); return False; } if (**refp != '!' && **refp != '>' && (**refp != '\\' || dirpath("a", "b")[1] == '\\')) { scanprev(refp); } } /* collect each whitespace-delimited filename argument */ for (filename = NULL; *refp && **refp != '|' && **refp != '\n'; scannext(refp)) { /* if whitespace, then process filename (if any) and then skip */ switch (**refp) { case ' ': case '\t': /* do we have a filename? */ if (filename) { /* store the name */ if (!exaddfilearg(&xinf->file, &xinf->nfiles, tochar8(filename), True)) goto Error; /* free the buildCHAR copy of the name */ safefree(filename); filename = NULL; } break; case '\\': /* backslash can be used to quote some characters. */ scannext(refp); if (*refp && **refp == '*' && dirpath("a", "b")[1] == '\\') { /* This operating system seems use backslashes * in filenames. So an expression such as * "foo\*.c" should NOT become "foo*.c" */ scanprev(refp); } else if (*refp && !CHARchr(toCHAR(" \t\\#!*"), **refp)) { scanprev(refp); } buildCHAR(&filename, **refp); break; case '#': if (o_previousfile) { for (scan = o_previousfile; *scan; scan++) { buildCHAR(&filename, *scan); } } else { msg(MSG_ERROR, "no previous file"); goto Error; } break; case '%': scan = o_filename(markbuffer(&xinf->defaddr)); if (scan) { for ( ; *scan; scan++) { buildCHAR(&filename, *scan); } } else { msg(MSG_ERROR, "no file name"); goto Error; } break; case '!': if (o_previouscommand) { for (scan = o_previouscommand; *scan; scan++) { buildCHAR(&filename, *scan); } } else { msg(MSG_ERROR, "no previous command"); goto Error; } break; case '\0': msg(MSG_ERROR, "NUL not allowed in file name"); goto Error; default: /* Append the character to the name. If this results * in a partial name of ">>" and either this isn't the * first file, or this command doesn't support appending * then complain. */ if (buildCHAR(&filename, **refp) == 2 && !CHARcmp(filename, ">>") && (xinf->nfiles > 0 || (flags & a_Append) == 0)) { msg(MSG_ERROR, "bad >>", xinf->cmdname); goto Error; } } } /* if we were working on a filename when we ended the loop, add it */ if (filename) { if (!exaddfilearg(&xinf->file, &xinf->nfiles, tochar8(filename), True)) goto Error; safefree(filename); filename = (CHAR *)0; } /* complain if we have multiple filenames and only support one */ if (xinf->nfiles > 1 && (flags & a_Files) == 0) { msg(MSG_ERROR, "too many files"); goto Error; } /* If no files named, maybe go for a default name */ if (xinf->nfiles == 0 && (flags & d_File) != 0) { if (o_filename(markbuffer(&xinf->defaddr)) == NULL) { /* nishi */ msg(MSG_ERROR, "no file name"); goto Error; } exaddfilearg(&xinf->file, &xinf->nfiles, tochar8(o_filename(markbuffer(&xinf->defaddr))), False); } return True; Error: /* If we were in the middle of a filename, free the filename. * Other stuff will be freed by the calling function. */ if (filename) { safefree(filename); } return False; } /* Parse a single command, and leave *refp pointing past the last character of * the command. Return RESULT_COMPLETE normally, RESULT_MORE if the command is * incomplete, or RESULT_ERROR for an error (after outputting an error message). */ static RESULT parse(win, refp, xinf) WINDOW win; /* window that the command applies to */ CHAR **refp; /* pointer to (CHAR *) used for scanning the command */ EXINFO *xinf; /* where to place the results of the parse */ { CHAR sep; /* separator character, from scanning */ MARKBUF orig; /* original mark of "refp", so we can seek back later */ MARKBUF rngdef; /* default range */ long rngfrom;/* "from" line number of range */ long rngto; /* "to" line number of range */ long flags; /* bitmap of command attributes */ BOOLEAN sel; /* did address come from selection? */ CHAR *p2; /* a second scanning variable */ RESULT result; /* result of parsing */ int i; /* set defaults */ memset((char *)xinf, 0, sizeof *xinf); xinf->window = win; /* remember where this command started */ orig = *scanmark(refp); /* skip leading ':' characters */ while (*refp && **refp == ':') { scannext(refp); } /* If no command was entered, then assume the command line should be * "+p", so the user can simply hit <Enter> to step through the file. * Only do this for interactively entered commands, though! */ skipwhitespace(refp); if ((!*refp || **refp == '\n') && markbuffer(&orig) == buffind(toCHAR(EX_BUF))) { scanstring(&p2, toCHAR("+p")); result = parse(win, &p2, xinf); scanfree(&p2); return result; } /* parse the window id */ if (win) { skipwhitespace(refp); if (!parsewindowid(refp, xinf)) { return RESULT_ERROR; } } else { xinf->defaddr.buffer = bufdefault; } /* parse the buffer name (unless window has visible selection) */ skipwhitespace(refp); if ((!xinf->window || !xinf->window->seltop) && !parsebuffername(refp, xinf)) { return RESULT_ERROR; } /* parse addresses */ sel = False; if (xinf->defaddr.buffer) { skipwhitespace(refp); if (xinf->window && xinf->window->seltop) { xinf->defaddr = *xinf->window->seltop; sel = True; } xinf->to = markline(&xinf->defaddr); xinf->from = xinf->to; if (*refp && **refp == '%') { scannext(refp); xinf->from = 1; xinf->to = o_buflines(markbuffer(&xinf->defaddr)); xinf->anyaddr = True; sel = False; } else if ((xinf->window && xinf->window->seltop) || (*refp && strchr("./?'0123456789$+-,;", (char)**refp))) { do { /* Shift addresses, so we always remember the * last two addresses. */ xinf->from = xinf->to; /* Parse the address */ if (!exparseaddress(refp, xinf)) { return RESULT_ERROR; } /* We now have at least one address. If this * is the first address we've encountered, * then copy it into "from" as well. */ if (!xinf->anyaddr) { xinf->from = xinf->to; if (xinf->window && xinf->window->selbottom) xinf->to = markline(xinf->window->selbottom); xinf->anyaddr = True; } /* if followed by a semicolon, then this * address becomes default. */ skipwhitespace(refp); if (*refp) { sep = **refp; } else { sep = '\0'; } if (sep == ';') { marksetoffset(&xinf->defaddr, lowline(bufbufinfo(markbuffer(&xinf->defaddr)), xinf->to)); } else if (xinf->window && xinf->window->selbottom) { xinf->defaddr = *xinf->window->selbottom; } } while (*refp && (**refp == ',' || **refp == ';') && scannext(refp)); /* "from" can't come after "to" */ if (xinf->from > xinf->to) { msg(MSG_ERROR, "bad range"); return RESULT_ERROR; } } /* if we used visible selection, then unmark it now */ if (xinf->window && xinf->window->seltop) { markfree(xinf->window->seltop); markfree(xinf->window->selbottom); xinf->window->seltop = xinf->window->selbottom = NULL; } } /* parse command name */ skipwhitespace(refp); if (!parsecommandname(refp, xinf)) { return RESULT_ERROR; } flags = cmdnames[xinf->cmdidx].flags; /* is the command legal in this context? */ if (!win && 0 == (flags & q_Exrc)) { msg(MSG_ERROR, "[s]$1 is illegal during initialization", xinf->cmdname); return RESULT_ERROR; } if (o_safer && 0 != (flags & q_Unsafe)) { msg(MSG_ERROR, "[s]$1 is unsafe", xinf->cmdname); return RESULT_ERROR; } /* if given addresses for a command which doesn't support addresses, * then complain. If the addresses were due to a visible selection, * then ignore them silently. */ if (xinf->anyaddr && 0 == (flags & (a_Line|a_Range))) { if (sel) { /* visible selection; pretend no addresses were given */ xinf->anyaddr = False; } else { /* explicit addresses; complain */ msg(MSG_ERROR, "[s]$1 doesn't use addresses", xinf->cmdname); return RESULT_ERROR; } } /* if "from" is 0 for a command which doesn't allow 0, then * complain. */ if (xinf->anyaddr && xinf->from == 0 && 0 == (flags & q_Zero)) { msg(MSG_ERROR, "[s]$1 doesn't allow address 0", xinf->cmdname); return RESULT_ERROR; } /* parse multiplied command names */ parsemulti(refp, xinf); /* If a command allows both a print flag and a buffer name * then parsing them can be tricky because this can lead * to ambiguous situations. According to POSIX docs, * if there is no space after the command name (:dp) then * the print flag is next; in all other situations, the * cut buffer name comes first. THEREFORE, if this command * allows both a print flag and a buffer, and we appear to * have a print flag appended to the command name, then * we should parse the print flag now. */ if ((flags & (a_Pflag|a_Buffer)) == (a_Pflag|a_Buffer) && *refp && (**refp == 'p' || **refp == 'l' || **refp == '#')) { parseprintflag(refp, xinf, flags); } /* parse an optional '!' appended to the name */ parsebang(refp, xinf, flags); /* maybe parse a regular expression & replacement text */ skipwhitespace(refp); if (!parseregexp(refp, xinf, flags)) { return RESULT_ERROR; } /* maybe parse a target buffer and address */ if (flags & a_Target) { /* The target allows a window, a buffer, and an * address all to be specified. Parsing these * could clobber the source range fields, so we'll * copy them into local variables while we parse. */ rngdef = xinf->defaddr; rngfrom = xinf->from; rngto = xinf->to; /* parse the window id */ skipwhitespace(refp); if (!parsewindowid(refp, xinf)) { return RESULT_ERROR; } /* parse the buffer name */ skipwhitespace(refp); if (!parsebuffername(refp, xinf)) { return RESULT_ERROR; } /* parse an address. Note that we don't allow * looping here, and the address is mandatory * for commands that allow it. */ skipwhitespace(refp); if (*refp && (**refp == '\n' || **refp == '|')) { msg(MSG_ERROR, "[s]$1 requires destination", xinf->cmdname); return RESULT_ERROR; } if (!exparseaddress(refp, xinf)) { return RESULT_ERROR; } /* create the "destaddr" mark */ xinf->destaddr = markalloc(markbuffer(&xinf->defaddr), lowline(bufbufinfo(markbuffer(&xinf->defaddr)), xinf->to + 1)); /* move the range info back where it belongs */ xinf->defaddr = rngdef; xinf->from = rngfrom; xinf->to = rngto; } /* for some commands, parse an LHS and RHS */ parselhs(refp, xinf, flags); skipwhitespace(refp); parserhs(refp, xinf, flags); /* for some commands, parse an optional cut buffer name, * optional count, optional "+arg", and optional print flag. */ skipwhitespace(refp); parsecutbuffer(refp, xinf, flags); skipwhitespace(refp); parsecount(refp, xinf, flags); skipwhitespace(refp); parseplus(refp, xinf, flags); skipwhitespace(refp); parseprintflag(refp, xinf, flags); /* for some commands, parse file arguments. This includes * filenames, "!cmd" filter commands, ">>" append tokens, * and wildcard expansion. */ skipwhitespace(refp); if (!parsefileargs(refp, xinf, flags)) { return RESULT_ERROR; } /* for some commands, parse a long argument string which * may include the '|' character but not newline */ skipwhitespace(refp); if (!parsecmds(refp, xinf, flags)) { return RESULT_ERROR; } /* By this point, there should be no more arguments on this line. */ skipwhitespace(refp); if (*refp && **refp != '|' && **refp != '\n') { msg(MSG_ERROR, "[s]too many arguments for $1", xinf->cmdname); return RESULT_ERROR; } /* beware of EX-only commands */ if ((!win || 0 != (win->state->flags & (ELVIS_POP|ELVIS_ONCE|ELVIS_1LINE))) && 0 != (flags & q_Ex) && !xinf->rhs) { msg(MSG_ERROR, "[s]$1 is illegal in vi mode", xinf->cmdname); return RESULT_ERROR; } /* Maybe parse extra text lines which are arguments to this command */ if (!xinf->rhs && 0 != (flags & a_Text) && *refp) { /* skip past the newline on the command line */ assert(**refp == '\n'); scannext(refp); /* collect characters up to the next line containing only "." */ for (i = 0; *refp && (**refp != '\n' || (i >= 1 && xinf->rhs[i-1] != '.') || (i >= 2 && xinf->rhs[i-2] != '\n')); i++) { buildCHAR(&xinf->rhs, **refp); scannext(refp); } /* if all went well, then strip the "." from the end */ if (*refp) { xinf->rhs[i>2 ? i-2 : 0] = '\0'; } else /* end not found, need more text */ { scanseek(refp, &orig); drawopencomplete(win); return RESULT_MORE; } assert(!*refp || **refp == '\n'); } /* convert line numbers to marks (if there are any) */ if ((flags & (a_Line|a_Range)) != 0 && (xinf->anyaddr || (flags & d_None) == 0)) { /* if no lines, set the default */ if (!xinf->anyaddr && (flags & d_All) != 0) { xinf->from = 1; xinf->to = o_buflines(markbuffer(&xinf->defaddr)); } /* if there was a count, add it to "from" to make "to" */ if (xinf->count > 0) { xinf->to = xinf->from + xinf->count - 1; } /* create the "fromaddr" mark -- start of "from" line */ xinf->fromaddr = markalloc(markbuffer(&xinf->defaddr), lowline(bufbufinfo(markbuffer(&xinf->defaddr)), xinf->from)); /* create the "toaddr" mark -- end of "to" line. If that's * the last line, then the computation can be tricky. */ if (xinf->to == o_buflines(markbuffer(&xinf->defaddr))) xinf->toaddr = markalloc(markbuffer(&xinf->defaddr), o_bufchars(markbuffer(&xinf->defaddr))); else xinf->toaddr = markalloc(markbuffer(&xinf->defaddr), lowline(bufbufinfo(markbuffer(&xinf->defaddr)), xinf->to + 1)); #if 0 /* the cursor should be left on the last line */ xinf->newcurs = markalloc(markbuffer(&xinf->defaddr), lowline(bufbufinfo(markbuffer(&xinf->defaddr)), xinf->to)); #endif } else { /* the cursor won't move */ xinf->newcurs = (MARK)0; } /* move the scan point past the command separator */ if (*refp) scannext(refp); return RESULT_COMPLETE; } /* Execute an ex command, after it has been parsed by the parse() function. * Return RESULT_COMPLETE normally, or RESULT_ERROR for errors (after outputting * an error message). */ static RESULT execute(xinf) EXINFO *xinf; /* the parsed command to execute */ { MARK pline; /* line to autoprint */ BUFFER custom; /* the CUSTOM_BUF buffer */ MARKBUF top, bottom; RESULT ret; BUFFER origdef;/* original value of bufdefault */ struct state_s *state; /* If we need to save an "undo" version of the buffer, then * remember that fact. */ if (globaldepth == 0 && (cmdnames[xinf->cmdidx].flags & q_Undo)) { if (xinf->destaddr) bufwilldo(xinf->destaddr); else if (xinf->window) bufwilldo(xinf->window->cursor); else if (xinf->toaddr) bufwilldo(xinf->toaddr); } /* if global command, then increment globaldepth */ if (xinf->command == EX_GLOBAL || xinf->command == EX_VGLOBAL) { globaldepth++; } /* if quit command, then cause "wrote..." messages to be displayed * as INFO instead of the normal STATUS. This is so they'll be queued * and can be printed someplace else after the window is closed. */ if (cmdnames[xinf->cmdidx].flags & q_MayQuit) { bufmsgtype = MSG_INFO; } /* if the command's default buffer isn't the window's default buffer, * then make the command's default buffer be the one used by :set. */ origdef = bufdefault; if (xinf->window && markbuffer(&xinf->defaddr) != markbuffer(xinf->window->state->pop ? xinf->window->state->pop->cursor : xinf->window->cursor)) { bufoptions(markbuffer(&xinf->defaddr)); } /* Hooray! Now all we need to do is execute the damn thing */ ret = (*cmdnames[xinf->cmdidx].fn)(xinf); /* restore the options buffer to what it was before */ bufoptions(origdef); /* if global command, then decrement globaldepth */ if (xinf->command == EX_GLOBAL || xinf->command == EX_VGLOBAL) { globaldepth--; } /* if command failed, we're done. */ if (ret != RESULT_COMPLETE) { return RESULT_ERROR; } /* move the cursor to where it wants to be */ if (xinf->newcurs) { /* find the window's main state */ for (state = xinf->window->state; state->acton; state = state->acton) { } /* if we're switching buffers, then we need to * switch names, too. */ if (markbuffer(xinf->window->cursor) != markbuffer(xinf->newcurs)) { optprevfile(o_filename(markbuffer(xinf->window->cursor)), markline(xinf->window->cursor)); if (gui->retitle) { (*gui->retitle)(xinf->window->gw, tochar8(o_bufname(markbuffer(xinf->newcurs)))); } marksetbuffer(state->cursor, markbuffer(xinf->newcurs)); marksetbuffer(state->top, markbuffer(xinf->newcurs)); marksetbuffer(state->bottom, markbuffer(xinf->newcurs)); dispset(xinf->window, tochar8(o_bufdisplay(markbuffer(xinf->newcurs)))); } /* other stuff is easy */ marksetoffset(state->cursor, markoffset(xinf->newcurs)); marksetoffset(state->top, markoffset(xinf->newcurs)); marksetoffset(state->bottom, markoffset(xinf->newcurs)); markfree(xinf->newcurs); xinf->window->wantcol = state->wantcol = (*xinf->window->md->mark2col)(xinf->window, xinf->window->cursor, True); assert(markbuffer(state->cursor) == markbuffer(state->top)); assert(markbuffer(state->cursor) == markbuffer(state->bottom)); } /* if the new cursor position is off the end of the buffer, * then move it to the start of the last line, instead. */ if (xinf->window && markoffset(xinf->window->cursor) >= o_bufchars(markbuffer(xinf->window->cursor))) { marksetoffset(xinf->window->cursor, o_bufchars(markbuffer(xinf->window->cursor))); if (markoffset(xinf->window->cursor) > 0L) { markaddoffset(xinf->window->cursor, -1L); marksetoffset(xinf->window->cursor, markoffset((*xinf->window->md->move) (xinf->window, xinf->window->cursor, 0L, 0L, False))); } } /* print flags and autoprinting happen here */ if (xinf->pflag != PF_NONE) { /* Find the start of the line which contains the cursor. * Also add delta. */ pline = (*xinf->window->md->move)(xinf->window, xinf->window->cursor, xinf->delta, 0L, False); /* print the line */ exprintlines(xinf->window, pline, 1, xinf->pflag); } /* if we're supposed to regenerate CUSTOM_BUF, then do so now */ if (cmdnames[xinf->cmdidx].flags & q_Custom) { /* find/create the buffer */ custom = bufalloc(toCHAR(CUSTOM_BUF), 0); o_internal(custom) = True; if (o_bufchars(custom) != 0) { bufreplace(marktmp(top, custom, 0), marktmp(bottom, custom, o_bufchars(custom)), NULL, 0); } /* add stuff into it */ optsave(custom); mapsave(custom); digsave(custom); colorsave(custom); /* abbrsave(custom); */ if (gui && gui->save) { (*gui->save)(custom, xinf->window->gw); } } /* free the data associated with that command */ exfree(xinf); return RESULT_COMPLETE; } /* This function parses one or more ex commands, and executes them. It * returns RESULT_COMPLETE if successful, RESULT_ERROR (after printing an * error message) if unsuccessful, or RESULT_MORE if the command is * incomplete as entered. As a side-effect, the offset of the "top" mark * is moved passed any commands which have been completely parsed and * executed. * * If the "win" argument is NULL, then the parsing uses the parsing style * of a ".exrc" file: no window or line numbers are allowed, only commands * which have q_Exrc set and q_Unsafe cleared are allowed, the | character * is quoted via ^V instead of backslash, and blank lines are ignored (instead * of being interpretted as "+p"). */ RESULT experform(win, top, bottom) WINDOW win; /* default window (implies default buffer) */ MARK top; /* start of commands */ MARK bottom; /* end of commands */ { EXINFO xinfb; /* buffer, holds info about command being parsed */ CHAR *p; /* pointer used for scanning command line */ long next; /* where the next command starts */ /* start reading commands */ scanalloc(&p, top); /* for each command... */ while (p && markoffset(top) < markoffset(bottom)) { /* parse an ex command */ switch (parse(win, &p, &xinfb)) { case RESULT_ERROR: goto Fail; case RESULT_MORE: goto More; case RESULT_COMPLETE: ; /* continue processing */ } /* Suspend the scanning while we execute this command. * We aren't allowed to change text while scanning. */ next = (p ? markoffset(scanmark(&p)) : markoffset(bottom)); scanfree(&p); /* execute the command */ if (execute(&xinfb) != RESULT_COMPLETE) { goto Fail2; } /* adjust "top" to point after the command, and resume scan */ marksetoffset(top, next); scanalloc(&p, top); } scanfree(&p); return RESULT_COMPLETE; Fail: scanfree(&p); Fail2: exfree(&xinfb); return RESULT_ERROR; More: scanfree(&p); exfree(&xinfb); return RESULT_MORE; } /* This function resembles experform(), except that this function parses * from a string instead of from a buffer. */ RESULT exstring(win, str) WINDOW win; /* default window (implies default buffer) */ CHAR *str; /* the string containing ex commands */ { EXINFO xinfb; /* buffer, holds info about command being parsed */ CHAR *p; /* pointer used for scanning command line */ /* start reading commands */ scanstring(&p, str); /* for each command... */ while (p && *p) { /* parse and execute one ex command */ if (parse(win, &p, &xinfb) != RESULT_COMPLETE || execute(&xinfb) != RESULT_COMPLETE) { goto Fail; } } scanfree(&p); return RESULT_COMPLETE; Fail: scanfree(&p); exfree(&xinfb); return RESULT_ERROR; } /* This function checks to see whether a given string is an acceptable * abbreviation for a command name. If so, it returns the full command name; * if not, it returns NULL */ CHAR *exname(name) CHAR *name; /* possible name of an ex command */ { int i, len; /* non-alphabetic names get special treatment */ len = CHARlen(name); if (len == 1 && !isalpha(*name)) { switch (*name) { case '!': return toCHAR("BANG"); case '"': return toCHAR("QUOTE"); case '#': return toCHAR("HASH"); case '<': return toCHAR("LT"); case '=': return toCHAR("EQ"); case '>': return toCHAR("GT"); case '&': return toCHAR("AMP"); case '~': return toCHAR("TILDE"); case '@': return toCHAR("AT"); case '(': return toCHAR("OPEN"); /* not a real command */ case '{': return toCHAR("OCUR"); /* not a real command */ } } /* else look up the name in the cmdnames[] array */ for (i = 0; i < QTY(cmdnames) && strncmp(cmdnames[i].name, tochar8(name), (size_t)len); i++) { } if (i < QTY(cmdnames)) { return toCHAR(cmdnames[i].name); } return NULL; } /* This function is called when the user hits <Enter> after entering an * ex command line. It returns RESULT_COMPLETE if successful, RESULT_ERROR * (after printing an error message) if unsuccessful, or RESULT_MORE if * the command is incomplete as entered. As a side-effect, the offset of * the "top" mark is moved passed any commands which have been completely * parsed and executed. */ RESULT exenter(win) WINDOW win; /* window where an ex command has been entered */ { STATE *state = win->state; #if 0 assert(markoffset(state->top) <= markoffset(state->cursor)); assert(markoffset(state->cursor) <= markoffset(state->bottom)); #endif return experform(win, state->top, state->bottom); } /* This function prints single line as ex output text. If "number" is true, * it will precede each line with a line number. If "list" is true, it will * make all characters visible (including tab) and show a '$' at the end of * each line. Returns the offset of the last line output. */ long exprintlines(win, mark, qty, pflag) WINDOW win; /* window to write to */ MARK mark; /* start of line to output */ long qty; /* number of lines to print */ PFLAG pflag; /* controls how the line is printed */ { CHAR tmp[24];/* temp strings */ CHAR *scan; /* used for scanning */ long last; /* offset of start of last line */ long lnum; /* line number */ long col; /* output column number */ long tabstop;/* size of tabs */ BOOLEAN number; /* show line number? */ BOOLEAN list; /* show all characters? */ long i; /* initialize "last" just to silence a compiler warning */ last = 0; /* figure out how we'll show the lines */ if (pflag == PF_NONE) { return markoffset(mark); } number = (BOOLEAN)(pflag == PF_NUMBER || pflag == PF_NUMLIST); list = (BOOLEAN)(pflag == PF_LIST || pflag == PF_NUMLIST); /* If we'll be showing line numbers, then find the first one now */ if (number) { (void)lowoffset(bufbufinfo(markbuffer(mark)), markoffset(mark), (COUNT *)0, (COUNT *)0, (LBLKNO *)0, &lnum); } /* compute tab size */ tabstop = o_tabstop(markbuffer(mark)); /* for each line... */ for (scanalloc(&scan, mark); scan && qty > 0 && !guipoll(False); scannext(&scan), qty--) { /* remember where this line started */ last = markoffset(scanmark(&scan)); /* output the line number, if we're supposed to */ if (number) { memset(tmp, ' ', QTY(tmp)); long2CHAR(tmp + 8, lnum); CHARcat(tmp, toCHAR(" ")); drawextext(win, tmp + CHARlen(tmp) - 8, 8); lnum++; } /* scan the line and output each character */ col = 0; for (; scan && *scan != '\n'; scannext(&scan)) { if (*scan == '\t' && !list) { /* expand tab into a bunch of spaces */ i = tabstop - (col % tabstop); col += i; memset(tmp, ' ', QTY(tmp)); while (i > QTY(tmp)) { drawextext(win, tmp, QTY(tmp)); i -= QTY(tmp); } if (i > 0) { drawextext(win, tmp, (int)i); } } else if (*scan < ' ' || *scan == '\177') { tmp[0] = '^'; tmp[1] = ELVCTRL(*scan); drawextext(win, tmp, 2); col += 2; } else if (*scan > '\177' && list) { sprintf((char *)tmp, "\\x%02x", *scan); i = CHARlen(tmp); drawextext(win, tmp, (int)i); col += i; } else { /* count consecutive normal chars */ for (i = 1; i < scanright(&scan) && scan[i] >= ' ' && scan[i] < '\177'; i++) { } drawextext(win, scan, (int)i); col += i; scan += i - 1; /* plus one more @ top of loop */ } } /* for "list", append a '$' */ if (list) { tmp[0] = '$'; tmp[1] = '\n'; drawextext(win, tmp, 2); } else { tmp[0] = '\n'; drawextext(win, tmp, 1); } } scanfree(&scan); return last; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.