/*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static char sccsid[] = "@(#)ex.c 8.157 (Berkeley) 8/17/94"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "compat.h" #include #include #include "vi.h" #include "excmd.h" static void badlno __P((SCR *, recno_t)); static __inline EXCMDLIST const * ex_comm_search __P((char *, size_t)); static int ep_line __P((SCR *, EXF *, MARK *, char **, size_t *, int *)); static int ep_range __P((SCR *, EXF *, EXCMDARG *, char **, size_t *)); /* * ex -- * Read an ex command and execute it. */ int ex(sp, ep) SCR *sp; EXF *ep; { enum input irval; TEXT *tp; u_int flags, saved_mode; int eval; if (ex_init(sp, ep)) return (1); if (sp->s_refresh(sp, ep)) return (ex_end(sp)); /* If reading from a file, messages should have line info. */ if (!F_ISSET(sp->gp, G_STDIN_TTY)) { sp->if_lno = 1; sp->if_name = strdup("input"); } /* * !!! * Historically, the beautify option applies to ex command input read * from a file. In addition, the first time a ^H was discarded from * the input, a message "^H discarded" was displayed. We don't bother. */ LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR | TXT_EXSUSPEND); for (eval = 0;; ++sp->if_lno) { /* Set the flags that the user can change. */ if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); else LF_CLR(TXT_BEAUTIFY); if (O_ISSET(sp, O_PROMPT)) LF_SET(TXT_PROMPT); else LF_CLR(TXT_PROMPT); /* * Get the next command. Interrupt flag manipulation is * safe because ex_icmd clears them all. */ CLR_INTERRUPT(sp); F_SET(sp, S_INTERRUPTIBLE); irval = sp->s_get(sp, ep, sp->tiqp, ':', flags); if (INTERRUPTED(sp)) { (void)fputc('\n', stdout); (void)fflush(stdout); goto refresh; } switch (irval) { case INP_OK: break; case INP_EOF: case INP_ERR: F_SET(sp, S_EXIT_FORCE); /* FALLTHROUGH */ case INP_INTR: goto ret; } /* * If the user entered a carriage return, send ex_cmd() * a separator -- it discards single newlines. */ tp = sp->tiqp->cqh_first; if (tp->len == 0) { tp->len = 1; tp->lb[0] = ' '; } saved_mode = F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE); if (ex_icmd(sp, ep, tp->lb, tp->len, 1) && !F_ISSET(sp->gp, G_STDIN_TTY)) F_SET(sp, S_EXIT_FORCE); (void)msg_rpt(sp, 0); if (saved_mode != F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE)) break; refresh: if (sp->s_refresh(sp, ep)) { eval = 1; break; } } ret: if (sp->if_name != NULL) { FREE(sp->if_name, strlen(sp->if_name) + 1); sp->if_name = NULL; } return (ex_end(sp) || eval); } /* * ex_cfile -- * Execute ex commands from a file. */ int ex_cfile(sp, ep, filename, needsep) SCR *sp; EXF *ep; char *filename; int needsep; { struct stat sb; int fd, len, rval; char *bp; bp = NULL; if ((fd = open(filename, O_RDONLY, 0)) < 0 || fstat(fd, &sb)) goto err; /* * XXX * We'd like to test if the file is too big to malloc. Since we don't * know what size or type off_t's or size_t's are, what the largest * unsigned integral type is, or what random insanity the local C * compiler will perpetrate, doing the comparison in a portable way * is flatly impossible. Hope that malloc fails if the file is too * large. */ MALLOC(sp, bp, char *, (size_t)sb.st_size + 1); if (bp == NULL) goto err; len = read(fd, bp, (int)sb.st_size); if (len == -1 || len != sb.st_size) { if (len != sb.st_size) errno = EIO; err: rval = 1; msgq(sp, M_SYSERR, filename); } else { bp[sb.st_size] = '\0'; /* XXX */ /* * Run the command. Messages include file/line information, * but we don't care if we can't get space. */ sp->if_lno = 1; sp->if_name = strdup(filename); F_SET(sp, S_VLITONLY); rval = ex_icmd(sp, ep, bp, len, needsep); F_CLR(sp, S_VLITONLY); free(sp->if_name); sp->if_name = NULL; } /* * !!! * THE UNDERLYING EXF MAY HAVE CHANGED. */ if (bp != NULL) FREE(bp, sb.st_size); if (fd >= 0) (void)close(fd); return (rval); } /* * ex_icmd -- * Call ex_cmd() after turning off interruptible bits. */ int ex_icmd(sp, ep, cmd, len, needsep) SCR *sp; EXF *ep; char *cmd; size_t len; int needsep; { /* * Ex goes through here for each vi :colon command and for each ex * command, however, globally executed commands don't go through * here, instead, they call ex_cmd directly. So, reset all of the * interruptible flags now. * * !!! * Previous versions of nvi cleared mapped characters on error. This * feature was removed when users complained that it wasn't historic * practice. */ CLR_INTERRUPT(sp); return (ex_cmd(sp, ep, cmd, len, needsep)); } /* Special command structure for :s as a repeat substitution command. */ static EXCMDLIST const cmd_subagain = {"s", ex_subagain, E_ADDR2|E_NORC, "s", "[line [,line]] s [cgr] [count] [#lp]", "repeat the last subsitution"}; /* Special command structure for :d[flags]. */ static EXCMDLIST const cmd_del2 = {"delete", ex_delete, E_ADDR2|E_AUTOPRINT|E_NORC, "1bca1", "[line [,line]] d[elete][flags] [buffer] [count] [flags]", "delete lines from the file"}; /* * ex_cmd -- * Parse and execute a string containing ex commands. */ int ex_cmd(sp, ep, cmd, cmdlen, needsep) SCR *sp; EXF *ep; char *cmd; size_t cmdlen; int needsep; { enum { NOTSET, NEEDSEP_N, NEEDSEP_NR, NONE } sep; EX_PRIVATE *exp; EXCMDARG exc; EXCMDLIST const *cp; MARK cur; recno_t lno, num; size_t arg1_len, len, save_cmdlen; long flagoff; u_int saved_mode; int blank, ch, cnt, delim, flags, namelen, nl; int optnum, uselastcmd, tmp, vi_address; char *arg1, *save_cmd, *p, *s, *t; /* Init. */ nl = 0; sep = needsep ? NOTSET : NONE; loop: if (nl) { nl = 0; ++sp->if_lno; } arg1 = NULL; save_cmdlen = 0; /* It's possible that we've been interrupted during a command. */ if (INTERRUPTED(sp)) return (0); /* Skip s, empty lines. */ for (blank = 0; cmdlen > 0; ++cmd, --cmdlen) if ((ch = *cmd) == '\n') ++sp->if_lno; else if (isblank(ch)) blank = 1; else break; /* * !!! * Permit extra colons at the start of the line. Historically, * ex/vi allowed a single extra one. It's simpler not to count. * The stripping is done here because, historically, any command * could have preceding colons, e.g. ":g/pattern/:p" worked. */ if (cmdlen != 0 && ch == ':') { if (sep == NOTSET) sep = NEEDSEP_N; while (--cmdlen > 0 && (ch = *++cmd) == ':'); } /* * Command lines that start with a double-quote are comments. * * !!! * Historically, there was no escape or delimiter for a comment, * e.g. :"foo|set was a single comment and nothing was output. * Since nvi permits users to escape characters into * command lines, we have to check for that case. */ if (cmdlen != 0 && ch == '"') { while (--cmdlen > 0 && *++cmd != '\n'); if (*cmd == '\n') { nl = 1; ++cmd; --cmdlen; } goto loop; } /* Skip whitespace. */ for (; cmdlen > 0; ++cmd, --cmdlen) { ch = *cmd; if (!isblank(ch)) break; } /* * The last point at which an empty line can mean do nothing. * * !!! * Historically, in ex mode, lines containing only characters * were the same as a single , i.e. a default command. * In vi mode, they were ignored. * * In .exrc files this was a serious annoyance, as vi kept trying to * treat them as print commands. We ignore backward compatibility in * this case, and discard lines containing only characters from * .exrc files. */ if (cmdlen == 0 && (!IN_EX_MODE(sp) || ep == NULL || !blank)) return (0); /* Initialize the structure passed to underlying functions. */ memset(&exc, 0, sizeof(EXCMDARG)); exp = EXP(sp); if (argv_init(sp, ep, &exc)) goto err; /* * Check to see if this is a command for which we may want to output * a \r separator instead of a \n. (The command :1 puts out a \n, * but the command : puts out a \r.) If the line is empty except * for s, or , we'll probably want to * output \r. I don't think there's any way to get characters * *after* the command character, but this is the ex parser, and I've * been wrong before. */ if (sep == NOTSET) sep = cmdlen == 0 || cmdlen == 1 && cmd[0] == '\004' ? NEEDSEP_NR : NEEDSEP_N; /* Parse command addresses. */ if (ep_range(sp, ep, &exc, &cmd, &cmdlen)) goto err; /* Skip whitespace. */ for (; cmdlen > 0; ++cmd, --cmdlen) { ch = *cmd; if (!isblank(ch)) break; } /* * If no command, ex does the last specified of p, l, or #, and vi * moves to the line. Otherwise, determine the length of the command * name by looking for the first non-alphabetic character. (There * are a few non-alphabetic characters in command names, but they're * all single character commands.) This isn't a great test, because * it means that, for the command ":e +cut.c file", we'll report that * the command "cut" wasn't known. However, it makes ":e+35 file" work * correctly. * * !!! * Historically, lines with multiple adjacent (or separated) * command separators were very strange. For example, the command * |||, when the cursor was on line 1, displayed * lines 2, 3 and 5 of the file. In addition, the command " | " * would only display the line after the next line, instead of the * next two lines. No ideas why. It worked reasonably when executed * from vi mode, and displayed lines 2, 3, and 4, so we do a default * command for each separator. */ #define SINGLE_CHAR_COMMANDS "\004!#&*<=>@~" if (cmdlen != 0 && cmd[0] != '|' && cmd[0] != '\n') { if (strchr(SINGLE_CHAR_COMMANDS, *cmd)) { p = cmd; ++cmd; --cmdlen; namelen = 1; } else { for (p = cmd; cmdlen > 0; --cmdlen, ++cmd) if (!isalpha(*cmd)) break; if ((namelen = cmd - p) == 0) { msgq(sp, M_ERR, "Unknown command name"); goto err; } } /* * !!! * Historic vi permitted flags to immediately follow any * subset of the 'delete' command, but then did not permit * further arguments (flag, buffer, count). Make it work. * Permit further arguments for the few shreds of dignity * it offers. * * !!! * Note, adding commands that start with 'd', and match * "delete" up to a l, p, +, - or # character can break * this code. */ if (p[0] == 'd') { for (s = p, t = cmds[C_DELETE].name; *s == *t; ++s, ++t); if (s[0] == 'l' || s[0] == 'p' || s[0] == '+' || s[0] == '-' || s[0] == '#') { len = (cmd - p) - (s - p); cmd -= len; cmdlen += len; cp = &cmd_del2; goto skip; } } /* * Search the table for the command. * * !!! * Historic vi permitted the mark to immediately follow the * 'k' in the 'k' command. Make it work. * * !!! * Historic vi permitted pretty much anything to follow the * substitute command, e.g. "s/e/E/|s|sgc3p" was fine. Make * the command "sgc" work. */ if ((cp = ex_comm_search(p, namelen)) == NULL) switch (p[0]) { case 's': cmd -= namelen - 1; cmdlen += namelen - 1; cp = &cmd_subagain; break; case 'k': if (p[1] && !p[2]) { cmd -= namelen - 1; cmdlen += namelen - 1; cp = &cmds[C_K]; break; } /* FALLTHROUGH */ default: msgq(sp, M_ERR, "The %.*s command is unknown", namelen, p); goto err; } /* Some commands are either not implemented or turned off. */ skip: if (F_ISSET(cp, E_NOPERM)) { msgq(sp, M_ERR, "The %s command is not currently supported", cp->name); goto err; } /* Some commands aren't okay in globals. */ if (F_ISSET(sp, S_GLOBAL) && F_ISSET(cp, E_NOGLOBAL)) { msgq(sp, M_ERR, "The %s command can't be used as part of a global command", cp->name); goto err; } /* * Multiple < and > characters; another "feature". Note, * The string passed to the underlying function may not be * nul terminated in this case. */ if ((cp == &cmds[C_SHIFTL] && *p == '<') || (cp == &cmds[C_SHIFTR] && *p == '>')) { for (ch = *p; cmdlen > 0; --cmdlen, ++cmd) if (*cmd != ch) break; if (argv_exp0(sp, ep, &exc, p, cmd - p)) goto err; } /* * The visual command has a different syntax when called * from ex than when called from a vi colon command. FMH. */ if (cp == &cmds[C_VISUAL_EX] && IN_VI_MODE(sp)) cp = &cmds[C_VISUAL_VI]; /* Set the format style flags for the next command. */ if (cp == &cmds[C_HASH]) exp->fdef = E_F_HASH; else if (cp == &cmds[C_LIST]) exp->fdef = E_F_LIST; else if (cp == &cmds[C_PRINT]) exp->fdef = E_F_PRINT; uselastcmd = 0; } else { /* Print is the default command. */ cp = &cmds[C_PRINT]; /* Set the saved format flags. */ F_SET(&exc, exp->fdef); /* * !!! * If no address was specified, and it's not a global command, * we up the address by one. (I have not an idea why global * commands are exempted, but it's (ahem) historic practice. */ if (exc.addrcnt == 0 && !F_ISSET(sp, S_GLOBAL)) { exc.addrcnt = 1; exc.addr1.lno = sp->lno + 1; exc.addr1.cno = sp->cno; } uselastcmd = 1; } /* * !!! * Historically, the number option applied to both ex and vi. One * strangeness was that ex didn't switch display formats until a * command was entered, e.g. 's after the set didn't change to * the new format, but :1p would. */ if (O_ISSET(sp, O_NUMBER)) { optnum = 1; F_SET(&exc, E_F_HASH); } else optnum = 0; /* Initialize local flags to the command flags. */ LF_INIT(cp->flags); /* * File state must be checked throughout this code, because it is * called when reading the .exrc file and similar things. There's * this little chicken and egg problem -- if we read the file first, * we won't know how to display it. If we read/set the exrc stuff * first, we can't allow any command that requires file state. We * don't have a "reading an rc" bit, because we want the commands * to work when the user source's the rc file later. Historic vi * generally took the easy way out and dropped core. */ if (LF_ISSET(E_NORC) && ep == NULL) { msgq(sp, M_ERR, "The %s command requires that a file have already been read in", cp->name); goto err; } /* * There are three normal termination cases for an ex command. They * are the end of the string (cmdlen), or unescaped (by literal next * characters) newline or '|' characters. As we're past any addresses, * we can now determine how long the command is, so we don't have to * look for all the possible terminations. There are three exciting * special cases: * * 1: The bang, global, vglobal and the filter versions of the read and * write commands are delimited by newlines (they can contain shell * pipes). * 2: The ex, edit, next and visual in vi mode commands all take ex * commands as their first arguments. * 3: The substitute command takes an RE as its first argument, and * wants it to be specially delimited. * * Historically, '|' characters in the first argument of the ex, edit, * next, vi visual, and substitute commands didn't delimit the command. * And, in the filter cases for read and write, and the bang, global * and vglobal commands, they did not delimit the command at all. * * For example, the following commands were legal: * * :edit +25|s/abc/ABC/ file.c * :substitute s/|/PIPE/ * :read !spell % | columnate * :global/pattern/p|l * * It's not quite as simple as it sounds, however. The command: * * :substitute s/a/b/|s/c/d|set * * was also legal, i.e. the historic ex parser (using the word loosely, * since "parser" implies some regularity) delimited the RE's based on * its delimiter and not anything so irretrievably vulgar as a command * syntax. * * One thing that makes this easier is that we can ignore most of the * command termination conditions for the commands that want to take * the command up to the next newline. None of them are legal in .exrc * files, so if we're here, we only dealing with a single line, and we * can just eat it. * * Anyhow, the following code makes this all work. First, for the * special cases we move past their special argument(s). Then, we * do normal command processing on whatever is left. Barf-O-Rama. */ arg1_len = 0; save_cmd = cmd; if (cp == &cmds[C_EDIT] || cp == &cmds[C_EX] || cp == &cmds[C_NEXT] || cp == &cmds[C_VISUAL_VI]) { /* * Move to the next non-whitespace character. A '!' * immediately following the command is eaten as a * force flag. */ if (cmdlen > 0 && *cmd == '!') { ++cmd; --cmdlen; F_SET(&exc, E_FORCE); /* Reset, don't reparse. */ save_cmd = cmd; } for (tmp = 0; cmdlen > 0; --cmdlen, ++cmd) if (!isblank(*cmd)) break; /* * QUOTING NOTE: * * The historic implementation ignored all escape characters * so there was no way to put a space or newline into the +cmd * field. We do a simplistic job of fixing it by moving to the * first whitespace character that isn't escaped by a literal * next character. The literal next characters are stripped * as they're no longer useful. */ if (cmdlen > 0 && *cmd == '+') { ++cmd; --cmdlen; for (arg1 = p = cmd; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { --cmdlen; ch = *++cmd; } else if (isblank(ch)) break; *p++ = ch; } arg1_len = cmd - arg1; /* Reset, so the first argument isn't reparsed. */ save_cmd = cmd; } } else if (cp == &cmds[C_BANG] || cp == &cmds[C_GLOBAL] || cp == &cmds[C_VGLOBAL]) { cmd += cmdlen; cmdlen = 0; } else if (cp == &cmds[C_READ] || cp == &cmds[C_WRITE]) { /* * Move to the next character. If it's a '!', it's a filter * command and we want to eat it all, otherwise, we're done. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!isblank(ch)) break; } if (cmdlen > 0 && ch == '!') { cmd += cmdlen; cmdlen = 0; } } else if (cp == &cmds[C_SUBSTITUTE]) { /* * Move to the next non-whitespace character, we'll use it as * the delimiter. If the character isn't an alphanumeric or * a '|', it's the delimiter, so parse it. Otherwise, we're * into something like ":s g", so use the special substitute * command. */ for (; cmdlen > 0; --cmdlen, ++cmd) if (!isblank(cmd[0])) break; if (isalnum(cmd[0]) || cmd[0] == '|') cp = &cmd_subagain; else if (cmdlen > 0) { /* * QUOTING NOTE: * * Backslashes quote delimiter characters for RE's. * The backslashes are NOT removed since they'll be * used by the RE code. Move to the third delimiter * that's not escaped (or the end of the command). */ delim = *cmd; ++cmd; --cmdlen; for (cnt = 2; cmdlen > 0 && cnt; --cmdlen, ++cmd) if (cmd[0] == '\\' && cmdlen > 1) { ++cmd; --cmdlen; } else if (cmd[0] == delim) --cnt; } } /* * Use normal quoting and termination rules to find the end of this * command. * * QUOTING NOTE: * * Historically, vi permitted ^V's to escape 's in the .exrc * file. It was almost certainly a bug, but that's what bug-for-bug * compatibility means, Grasshopper. Also, ^V's escape the command * delimiters. Literal next quote characters in front of the newlines, * '|' characters or literal next characters are stripped as as they're * no longer useful. */ vi_address = cmdlen != 0 && cmd[0] != '\n'; for (p = cmd, cnt = 0; cmdlen > 0; --cmdlen, ++cmd) { ch = cmd[0]; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { tmp = cmd[1]; if (tmp == '\n' || tmp == '|') { if (tmp == '\n') ++sp->if_lno; --cmdlen; ++cmd; ++cnt; ch = tmp; } } else if (ch == '\n' || ch == '|') { if (ch == '\n') nl = 1; --cmdlen; break; } *p++ = ch; } /* * Save off the next command information, go back to the * original start of the command. */ p = cmd + 1; cmd = save_cmd; save_cmd = p; save_cmdlen = cmdlen; cmdlen = ((save_cmd - cmd) - 1) - cnt; /* * !!! * The "set tags" command historically used a backslash, not the * user's literal next character, to escape whitespace. Handle * it here instead of complicating the argv_exp3() code. Note, * this isn't a particularly complex trap, and if backslashes were * legal in set commands, this would have to be much more complicated. */ if (cp == &cmds[C_SET]) for (p = cmd, len = cmdlen; len > 0; --len, ++p) if (*p == '\\') *p = CH_LITERAL; /* * Set the default addresses. It's an error to specify an address for * a command that doesn't take them. If two addresses are specified * for a command that only takes one, lose the first one. Two special * cases here, some commands take 0 or 2 addresses. For most of them * (the E_ADDR2_ALL flag), 0 defaults to the entire file. For one * (the `!' command, the E_ADDR2_NONE flag), 0 defaults to no lines. * * Also, if the file is empty, some commands want to use an address of * 0, i.e. the entire file is 0 to 0, and the default first address is * 0. Otherwise, an entire file is 1 to N and the default line is 1. * Note, we also add the E_ZERO flag to the command flags, for the case * where the 0 address is only valid if it's a default address. * * Also, set a flag if we set the default addresses. Some commands * (ex: z) care if the user specified an address of if we just used * the current cursor. */ switch (LF_ISSET(E_ADDR1|E_ADDR2|E_ADDR2_ALL|E_ADDR2_NONE)) { case E_ADDR1: /* One address: */ switch (exc.addrcnt) { case 0: /* Default cursor/empty file. */ exc.addrcnt = 1; F_SET(&exc, E_ADDRDEF); if (LF_ISSET(E_ZERODEF)) { if (file_lline(sp, ep, &lno)) goto err; if (lno == 0) { exc.addr1.lno = 0; LF_SET(E_ZERO); } else exc.addr1.lno = sp->lno; } else exc.addr1.lno = sp->lno; exc.addr1.cno = sp->cno; break; case 1: break; case 2: /* Lose the first address. */ exc.addrcnt = 1; exc.addr1 = exc.addr2; } break; case E_ADDR2_NONE: /* Zero/two addresses: */ if (exc.addrcnt == 0) /* Default to nothing. */ break; goto two; case E_ADDR2_ALL: /* Zero/two addresses: */ if (exc.addrcnt == 0) { /* Default entire/empty file. */ exc.addrcnt = 2; F_SET(&exc, E_ADDRDEF); if (file_lline(sp, ep, &exc.addr2.lno)) goto err; if (LF_ISSET(E_ZERODEF) && exc.addr2.lno == 0) { exc.addr1.lno = 0; LF_SET(E_ZERO); } else exc.addr1.lno = 1; exc.addr1.cno = exc.addr2.cno = 0; F_SET(&exc, E_ADDR2_ALL); break; } /* FALLTHROUGH */ case E_ADDR2: /* Two addresses: */ two: switch (exc.addrcnt) { case 0: /* Default cursor/empty file. */ exc.addrcnt = 2; F_SET(&exc, E_ADDRDEF); if (LF_ISSET(E_ZERODEF) && sp->lno == 1) { if (file_lline(sp, ep, &lno)) goto err; if (lno == 0) { exc.addr1.lno = exc.addr2.lno = 0; LF_SET(E_ZERO); } else exc.addr1.lno = exc.addr2.lno = sp->lno; } else exc.addr1.lno = exc.addr2.lno = sp->lno; exc.addr1.cno = exc.addr2.cno = sp->cno; break; case 1: /* Default to first address. */ exc.addrcnt = 2; exc.addr2 = exc.addr1; break; case 2: break; } break; default: if (exc.addrcnt) /* Error. */ goto usage; } /* * !!! * The ^D scroll command historically scrolled the value of the scroll * option or to EOF. It was an error if the cursor was already at EOF. * (Leading addresses were permitted, but were then ignored.) */ if (cp == &cmds[C_SCROLL]) { exc.addrcnt = 2; exc.addr1.lno = sp->lno + 1; exc.addr2.lno = sp->lno + O_VAL(sp, O_SCROLL); exc.addr1.cno = exc.addr2.cno = sp->cno; if (file_lline(sp, ep, &lno)) goto err; if (lno != 0 && lno > sp->lno && exc.addr2.lno > lno) exc.addr2.lno = lno; } flagoff = 0; for (p = cp->syntax; *p != '\0'; ++p) { /* * The force flag is sensitive to leading whitespace, i.e. * "next !" is different from "next!". Handle it before * skipping leading s. */ if (*p == '!') { if (cmdlen > 0 && *cmd == '!') { ++cmd; --cmdlen; F_SET(&exc, E_FORCE); } continue; } /* Skip leading s. */ for (; cmdlen > 0; --cmdlen, ++cmd) if (!isblank(*cmd)) break; /* * Quit when reach the end of the command, unless it's a * command that does its own parsing, in which case we want * to build a reasonable argv for it. This code guarantees * that there will be an argv when the function gets called, * so the correct test is for a length of 0, not for the * argc > 0. Since '!' can precede commands that do their * own parsing, we have to have already handled it. */ if (cmdlen == 0 && *p != 'S' && *p != 's') break; switch (*p) { case '1': /* +, -, #, l, p */ /* * !!! * Historically, some flags were ignored depending * on where they occurred in the command line. For * example, in the command, ":3+++p--#", historic vi * acted on the '#' flag, but ignored the '-' flags. * It's unambiguous what the flags mean, so we just * handle them regardless of the stupidity of their * location. */ for (; cmdlen; --cmdlen, ++cmd) switch (*cmd) { case '+': ++flagoff; break; case '-': --flagoff; break; case '#': optnum = 0; F_SET(&exc, E_F_HASH); exp->fdef |= E_F_HASH; break; case 'l': F_SET(&exc, E_F_LIST); exp->fdef |= E_F_LIST; break; case 'p': F_SET(&exc, E_F_PRINT); exp->fdef |= E_F_PRINT; break; default: goto end1; } end1: break; case '2': /* -, ., +, ^ */ case '3': /* -, ., +, ^, = */ for (; cmdlen; --cmdlen, ++cmd) switch (*cmd) { case '-': F_SET(&exc, E_F_DASH); break; case '.': F_SET(&exc, E_F_DOT); break; case '+': F_SET(&exc, E_F_PLUS); break; case '^': F_SET(&exc, E_F_CARAT); break; case '=': if (*p == '3') { F_SET(&exc, E_F_EQUAL); break; } /* FALLTHROUGH */ default: goto end2; } end2: break; case 'b': /* buffer */ /* * !!! * Historically, "d #" was a delete with a flag, not a * delete into the '#' buffer. If the current command * permits a flag, don't use one as a buffer. However, * the 'l' and 'p' flags were legal buffer names in the * historic ex, and were used as buffers, not flags. */ if ((cmd[0] == '+' || cmd[0] == '-' || cmd[0] == '#') && strchr(p, '1') != NULL) break; /* * !!! * Digits can't be buffer names in ex commands, or the * command "d2" would be a delete into buffer '2', and * not a two-line deletion. */ if (!isdigit(cmd[0])) { exc.buffer = *cmd; ++cmd; --cmdlen; F_SET(&exc, E_BUFFER); } break; case 'c': /* count [01+a] */ ++p; /* Validate any signed value. */ if (!isdigit(*cmd) && (*p != '+' || (*cmd != '+' && *cmd != '-'))) break; /* If a signed value, set appropriate flags. */ if (*cmd == '-') F_SET(&exc, E_COUNT_NEG); else if (*cmd == '+') F_SET(&exc, E_COUNT_POS); /* 8-bit XXX */ if ((lno = strtol(cmd, &t, 10)) == 0 && *p != '0') { msgq(sp, M_ERR, "Count may not be zero"); goto err; } cmdlen -= (t - cmd); cmd = t; /* * Count as address offsets occur in commands taking * two addresses. Historic vi practice was to use * the count as an offset from the *second* address. * * Set a count flag; some underlying commands (see * join) do different things with counts than with * line addresses. */ if (*p == 'a') { exc.addr1 = exc.addr2; exc.addr2.lno = exc.addr1.lno + lno - 1; } else exc.count = lno; F_SET(&exc, E_COUNT); break; case 'f': /* file */ if (argv_exp2(sp, ep, &exc, cmd, cmdlen, cp == &cmds[C_BANG])) goto err; goto countchk; case 'l': /* line */ if (ep_line(sp, ep, &cur, &cmd, &cmdlen, &tmp)) goto err; /* Line specifications are always required. */ if (!tmp) { msgq(sp, M_ERR, "%s: bad line specification", cmd); goto err; } /* The line must exist for these commands. */ if (file_lline(sp, ep, &lno)) goto err; if (cur.lno > lno) { badlno(sp, lno); goto err; } exc.lineno = cur.lno; break; case 'S': /* string, file exp. */ if (argv_exp1(sp, ep, &exc, cmd, cmdlen, cp == &cmds[C_BANG])) goto err; goto addr2; case 's': /* string */ if (argv_exp0(sp, ep, &exc, cmd, cmdlen)) goto err; goto addr2; case 'W': /* word string */ /* * QUOTING NOTE: * * Literal next characters escape the following * character. Quoting characters are stripped * here since they are no longer useful. * * First there was the word. */ for (p = t = cmd; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { --cmdlen; *p++ = *++cmd; } else if (isblank(ch)) { ++cmd; --cmdlen; break; } else *p++ = ch; } if (argv_exp0(sp, ep, &exc, t, p - t)) goto err; /* Delete intervening whitespace. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!isblank(ch)) break; } if (cmdlen == 0) goto usage; /* Followed by the string. */ for (p = t = cmd; cmdlen > 0; --cmdlen, ++cmd, ++p) { ch = *cmd; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { --cmdlen; *p = *++cmd; } else *p = ch; } if (argv_exp0(sp, ep, &exc, t, p - t)) goto err; goto addr2; case 'w': /* word */ if (argv_exp3(sp, ep, &exc, cmd, cmdlen)) goto err; countchk: if (*++p != 'N') { /* N */ /* * If a number is specified, must either be * 0 or that number, if optional, and that * number, if required. */ num = *p - '0'; if ((*++p != 'o' || exp->argsoff != 0) && exp->argsoff != num) goto usage; } goto addr2; default: msgq(sp, M_ERR, "Internal syntax table error (%s: %c)", cp->name, *p); } } /* Skip trailing whitespace. */ for (; cmdlen; --cmdlen) { ch = *cmd++; if (!isblank(ch)) break; } /* * There shouldn't be anything left, and no more required * fields, i.e neither 'l' or 'r' in the syntax string. */ if (cmdlen || strpbrk(p, "lr")) { usage: msgq(sp, M_ERR, "Usage: %s", cp->usage); goto err; } /* Verify that the addresses are legal. */ addr2: switch (exc.addrcnt) { case 2: if (file_lline(sp, ep, &lno)) goto err; /* * Historic ex/vi permitted commands with counts to go past * EOF. So, for example, if the file only had 5 lines, the * ex command "1,6>" would fail, but the command ">300" * would succeed. Since we don't want to have to make all * of the underlying commands handle random line numbers, * fix it here. */ if (exc.addr2.lno > lno) if (F_ISSET(&exc, E_COUNT)) exc.addr2.lno = lno; else { badlno(sp, lno); goto err; } /* FALLTHROUGH */ case 1: num = exc.addr1.lno; /* * If it's a "default vi command", zero is okay. Historic * vi allowed this, note, it's also the hack that allows * "vi +100 nonexistent_file" to work. */ if (num == 0 && (IN_EX_MODE(sp) || uselastcmd != 1) && !LF_ISSET(E_ZERO)) { msgq(sp, M_ERR, "The %s command doesn't permit an address of 0", cp->name); goto err; } if (file_lline(sp, ep, &lno)) goto err; if (num > lno) { badlno(sp, lno); goto err; } break; } /* * If doing a default command and there's nothing left on the line, * vi just moves to the line. For example, ":3" and ":'a,'b" just * move to line 3 and line 'b, respectively, but ":3|" prints line 3. * * !!! * This is done before the absolute mark gets set; historically, * "/a/,/b/" did NOT set vi's absolute mark, but "/a/,/b/d" did. */ if (IN_VI_MODE(sp) && uselastcmd && vi_address == 0) { switch (exc.addrcnt) { case 2: sp->lno = exc.addr2.lno ? exc.addr2.lno : 1; sp->cno = exc.addr2.cno; break; case 1: sp->lno = exc.addr1.lno ? exc.addr1.lno : 1; sp->cno = exc.addr1.cno; break; } cmd = save_cmd; cmdlen = save_cmdlen; goto loop; } /* * Set the absolute mark -- we have to set it for vi here, in case * it's a compound command, e.g. ":5p|6" should set the absolute * mark for vi. */ if (F_ISSET(exp, EX_ABSMARK)) { cur.lno = sp->lno; cur.cno = sp->cno; F_CLR(exp, EX_ABSMARK); if (mark_set(sp, ep, ABSMARK1, &cur, 1)) goto err; } /* Final setup for the command. */ exc.cmd = cp; #if defined(DEBUG) && 0 TRACE(sp, "ex_cmd: %s", exc.cmd->name); if (exc.addrcnt > 0) { TRACE(sp, "\taddr1 %d", exc.addr1.lno); if (exc.addrcnt > 1) TRACE(sp, " addr2: %d", exc.addr2.lno); TRACE(sp, "\n"); } if (exc.lineno) TRACE(sp, "\tlineno %d", exc.lineno); if (exc.flags) TRACE(sp, "\tflags %0x", exc.flags); if (F_ISSET(&exc, E_BUFFER)) TRACE(sp, "\tbuffer %c", exc.buffer); TRACE(sp, "\n"); if (exc.argc) { for (cnt = 0; cnt < exc.argc; ++cnt) TRACE(sp, "\targ %d: {%s}", cnt, exc.argv[cnt]); TRACE(sp, "\n"); } #endif /* Clear autoprint flag. */ F_CLR(exp, EX_AUTOPRINT); /* Increment the command count if not called from vi. */ if (IN_EX_MODE(sp)) ++sp->ccnt; /* * If file state available, and not doing a global command, * log the start of an action. */ if (ep != NULL && !F_ISSET(sp, S_GLOBAL)) (void)log_cursor(sp, ep); /* * !!! * There are two special commands for the purposes of this code: the * default command () or the scrolling commands (^D * and ) as the first non- characters in the line. * * If this is the first command in the command line, we received the * command from the ex command loop and we're talking to a tty, and * and there's nothing else on the command line, and it's one of the * special commands, we erase the prompt character with a '\r'. Else, * we put out a newline character to separate the command from the * output from the command. It's OK if vi calls us -- we won't be in * ex mode so we'll do nothing. * * !!! * Historically, ex only put out a \r, so, if the displayed line was * only a single character long, and was represented as ^D, the * output wouldn't overwrite the user's input. Sex currently doesn't * display the character if it's going to be the scroll command, * i.e. if it's the first non- character in the line. If sex * is changed to run in cooked mode, i.e. is displayed, this code * will have to overwrite it. We also don't treat lines with extra * prompt characters as empty -- it's not worth the effort since we'd * have to overwrite some indeterminate number of columns with spaces * to clean up. For now, put out enough spaces to overwrite the prompt. */ if (sep != NONE) { if (ep != NULL && IN_EX_MODE(sp) && F_ISSET(sp->gp, G_STDIN_TTY)) if (sep == NEEDSEP_NR && (uselastcmd || cp == &cmds[C_SCROLL])) { (void)putchar('\r'); for (len = KEY_LEN(sp, PROMPTCHAR); len--;) (void)putchar(' '); (void)putchar('\r'); } else (void)putchar('\n'); sep = NONE; } /* Save the current mode. */ saved_mode = F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE); /* Do the command. */ if (cp->fn(sp, ep, &exc)) goto err; #ifdef DEBUG /* Make sure no function left the temporary space locked. */ if (F_ISSET(sp->gp, G_TMP_INUSE)) { F_CLR(sp->gp, G_TMP_INUSE); msgq(sp, M_ERR, "Error: ex: temporary buffer not released"); goto err; } #endif if (saved_mode != F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE)) { /* * Only here if the mode of the underlying file changed, e.g. * the user switched files or is exiting. Two things that we * might have to save: first, any "+cmd" field set up for an * ex/edit command will have to be saved for later, also, any * part of the current ex command that hasn't been executed * yet. For example: * * :edit +25 file.c|s/abc/ABC/|1 * * !!! * The historic vi just hung, of course; nvi handles it by * pushing the keys onto the tty queue. Since the commands * are intended as ex commands, add additional characters * to make it all work if we're switching modes to vi. Also, * + commands were oriented to the last line in the file, * historically, make the cursor start out there. * * For the fun of it, if you want to see if a vi clone got the * ex argument parsing right, try: * * echo 'foo|bar' > file1; echo 'foo/bar' > file2; * vi * :edit +1|s/|/PIPE/|w file1| e file2|1 | s/\//SLASH/|wq */ if (arg1_len == 0 && save_cmdlen == 0) return (0); if (term_push(sp, "\n", 1, 0)) goto err; if (save_cmdlen != 0) if (term_push(sp, save_cmd, save_cmdlen, 0)) goto err; if (arg1 != NULL) { if (IN_VI_MODE(sp) && save_cmdlen != 0 && term_push(sp, "|", 1, 0)) goto err; if (term_push(sp, arg1, arg1_len, 0)) goto err; if (file_lline(sp, ep, &sp->frp->lno)) goto err; F_SET(sp->frp, FR_CURSORSET); } if (IN_VI_MODE(sp) && term_push(sp, ":", 1, 0)) goto err; return (0); } /* * Integrate any offset parsed by the underlying command, and make * sure the referenced line exists. * * XXX * May not match historic practice (I've never been able to completely * figure it out.) For example, the '=' command from vi mode often * got the offset wrong, and complained it was too large, but didn't * seem to have a problem with the cursor. If anyone complains, ask * them how it's supposed to work, they probably know. */ if (ep != NULL && (flagoff += exc.flagoff)) { if (flagoff < 0) { if (sp->lno <= -flagoff) { msgq(sp, M_ERR, "Flag offset before line 1"); goto err; } } else { if (file_lline(sp, ep, &lno)) goto err; if (sp->lno + flagoff > lno) { msgq(sp, M_ERR, "Flag offset past end-of-file"); goto err; } } sp->lno += flagoff; } /* * If the command was successful and we're in ex command mode, we * may want to display a line. Make sure there's a line to display. */ if (ep != NULL && IN_EX_MODE(sp) && !F_ISSET(sp, S_GLOBAL) && sp->lno != 0) { /* * The print commands have already handled the `print' flags. * If so, clear them. */ if (LF_ISSET(E_F_PRCLEAR)) F_CLR(&exc, E_F_HASH | E_F_LIST | E_F_PRINT); /* If hash only set because of the number option, discard it. */ if (optnum) F_CLR(&exc, E_F_HASH); /* * If there was an explicit flag to display the new cursor * line, or we're in ex mode, autoprint is set, and a change * was made, display the line. If any print flags set use * them, otherwise default to print. */ LF_INIT(F_ISSET(&exc, E_F_HASH | E_F_LIST | E_F_PRINT)); if (!LF_ISSET(E_F_HASH | E_F_LIST | E_F_PRINT) && O_ISSET(sp, O_AUTOPRINT) && (F_ISSET(exp, EX_AUTOPRINT) || F_ISSET(cp, E_AUTOPRINT))) LF_INIT(E_F_PRINT); if (LF_ISSET(E_F_HASH | E_F_LIST | E_F_PRINT)) { memset(&exc, 0, sizeof(EXCMDARG)); exc.addrcnt = 2; exc.addr1.lno = exc.addr2.lno = sp->lno; exc.addr1.cno = exc.addr2.cno = sp->cno; (void)ex_print(sp, ep, &exc.addr1, &exc.addr2, flags); } } cmd = save_cmd; cmdlen = save_cmdlen; goto loop; /* NOTREACHED */ /* * If we haven't put out a separator line, do it now. For more * detailed comments, see above. */ err: if (sep != NONE && ep != NULL && IN_EX_MODE(sp) && F_ISSET(sp->gp, G_STDIN_TTY)) (void)fputc('\n', stdout); /* * On error, we discard any keys we have left, as well as any keys * that were mapped. The test of save_cmdlen isn't necessarily * correct. If we fail early enough we don't know if the entire * string was a single command or not. Try and guess, it's useful * to know if part of the command was discarded. */ if (save_cmdlen == 0) for (; cmdlen; --cmdlen) { ch = *cmd++; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { --cmdlen; ++cmd; } else if (ch == '\n' || ch == '|') { if (cmdlen > 1) save_cmdlen = 1; break; } } if (save_cmdlen != 0) msgq(sp, M_ERR, "Ex command failed: remaining command input discarded"); /* * !!! * Previous versions of nvi cleared mapped characters on error. This * feature was removed when users complained that it wasn't historic * practice. */ return (1); } /* * ep_range -- * Get a line range for ex commands. */ static int ep_range(sp, ep, excp, cmdp, cmdlenp) SCR *sp; EXF *ep; EXCMDARG *excp; char **cmdp; size_t *cmdlenp; { MARK cur, savecursor; size_t cmdlen; int savecursor_set, tmp; char *cmd; /* Percent character is all lines in the file. */ cmd = *cmdp; cmdlen = *cmdlenp; if (*cmd == '%') { excp->addr1.lno = 1; if (file_lline(sp, ep, &excp->addr2.lno)) return (1); /* If an empty file, then the first line is 0, not 1. */ if (excp->addr2.lno == 0) excp->addr1.lno = 0; excp->addr1.cno = excp->addr2.cno = 0; excp->addrcnt = 2; ++*cmdp; --*cmdlenp; return (0); } /* Parse comma or semi-colon delimited line specs. */ for (savecursor_set = 0, excp->addrcnt = 0; cmdlen > 0;) switch (*cmd) { case ';': /* Semi-colon delimiter. */ /* * Comma delimiters delimit; semi-colon delimiters * change the current address for the 2nd address * to be the first address. Trailing or multiple * delimiters are discarded. */ if (excp->addrcnt == 0) goto done; if (!savecursor_set) { savecursor.lno = sp->lno; savecursor.cno = sp->cno; sp->lno = excp->addr1.lno; sp->cno = excp->addr1.cno; savecursor_set = 1; } ++cmd; --cmdlen; break; case ',': /* Comma delimiter. */ /* If no addresses yet, defaults to ".". */ if (excp->addrcnt == 0) { excp->addr1.lno = sp->lno; excp->addr1.cno = sp->cno; excp->addrcnt = 1; } /* FALLTHROUGH */ case ' ': /* Whitespace. */ case '\t': /* Whitespace. */ ++cmd; --cmdlen; break; default: if (ep_line(sp, ep, &cur, &cmd, &cmdlen, &tmp)) return (1); if (!tmp) goto done; /* * Extra addresses are discarded, starting with * the first. */ switch (excp->addrcnt) { case 0: excp->addr1 = cur; excp->addrcnt = 1; break; case 1: excp->addr2 = cur; excp->addrcnt = 2; break; case 2: excp->addr1 = excp->addr2; excp->addr2 = cur; break; } break; } /* * XXX * This is probably not the right behavior for savecursor -- * need to figure out what the historical ex did for ";,;,;5p" * or similar stupidity. */ done: if (savecursor_set) { sp->lno = savecursor.lno; sp->cno = savecursor.cno; } if (excp->addrcnt == 2 && excp->addr2.lno < excp->addr1.lno) { msgq(sp, M_ERR, "The second address is smaller than the first"); return (1); } *cmdp = cmd; *cmdlenp = cmdlen; return (0); } /* * Get a single line address specifier. * * The way the "previous context" mark worked was that any "non-relative" * motion set it. While ex/vi wasn't totally consistent about this, ANY * numeric address, search pattern, '$', or mark reference in an address * was considered non-relative, and set the value. Which should explain * why we're hacking marks down here. The problem was that the mark was * only set if the command was called, i.e. we have to set a flag and test * it later. * * XXX * This is not exactly historic practice, although it's fairly close. */ static int ep_line(sp, ep, cur, cmdp, cmdlenp, addr_found) SCR *sp; EXF *ep; MARK *cur; char **cmdp; size_t *cmdlenp; int *addr_found; { EX_PRIVATE *exp; MARK m; long total; u_int flags; size_t cmdlen; int (*sf) __P((SCR *, EXF *, MARK *, MARK *, char *, char **, u_int *)); char *cmd, *endp; exp = EXP(sp); *addr_found = 0; cmd = *cmdp; cmdlen = *cmdlenp; switch (*cmd) { case '$': /* Last line in the file. */ *addr_found = 1; F_SET(exp, EX_ABSMARK); cur->cno = 0; if (file_lline(sp, ep, &cur->lno)) return (1); ++cmd; --cmdlen; break; /* Absolute line number. */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': *addr_found = 1; F_SET(exp, EX_ABSMARK); cur->cno = 0; /* 8-bit XXX */ cur->lno = strtol(cmd, &endp, 10); cmdlen -= (endp - cmd); cmd = endp; break; case '\'': /* Use a mark. */ *addr_found = 1; F_SET(exp, EX_ABSMARK); if (cmdlen == 1) { msgq(sp, M_ERR, "No mark name supplied"); return (1); } if (mark_get(sp, ep, cmd[1], cur)) return (1); cmd += 2; cmdlen -= 2; break; case '\\': /* Search: forward/backward. */ /* * !!! * I can't find any difference between // and \/ or between * ?? and \?. Mark Horton doesn't remember there being any * difference. C'est la vie. */ if (cmdlen < 2 || cmd[1] != '/' && cmd[1] != '?') { msgq(sp, M_ERR, "\\ not followed by / or ?"); return (1); } ++cmd; --cmdlen; sf = cmd[0] == '/' ? f_search : b_search; goto search; case '/': /* Search forward. */ sf = f_search; goto search; case '?': /* Search backward. */ sf = b_search; search: F_SET(exp, EX_ABSMARK); if (ep == NULL) { msgq(sp, M_ERR, "A search address requires that a file have already been read in"); return (1); } *addr_found = 1; m.lno = sp->lno; m.cno = sp->cno; flags = SEARCH_MSG | SEARCH_PARSE | SEARCH_SET; if (sf(sp, ep, &m, &m, cmd, &endp, &flags)) return (1); cur->lno = m.lno; cur->cno = m.cno; cmdlen -= (endp - cmd); cmd = endp; break; case '.': /* Current position. */ *addr_found = 1; cur->cno = sp->cno; /* If an empty file, then '.' is 0, not 1. */ if (sp->lno == 1) { if (file_lline(sp, ep, &cur->lno)) return (1); if (cur->lno != 0) cur->lno = 1; } else cur->lno = sp->lno; ++cmd; --cmdlen; break; } /* * Evaluate any offset. Offsets are +/- any number, or any number * of +/- signs, or any combination thereof. If no address found * yet, offset is relative to ".". */ for (total = 0; cmdlen > 0 && (cmd[0] == '-' || cmd[0] == '+');) { if (!*addr_found) { cur->lno = sp->lno; cur->cno = sp->cno; *addr_found = 1; } if (cmdlen > 1 && isdigit(cmd[1])) { /* 8-bit XXX */ total += strtol(cmd, &endp, 10); cmdlen -= (endp - cmd); cmd = endp; } else { total += cmd[0] == '-' ? -1 : 1; --cmdlen; ++cmd; } } if (*addr_found) { if (total < 0 && -total > cur->lno) { msgq(sp, M_ERR, "Reference to a line number less than 0"); return (1); } cur->lno += total; *cmdp = cmd; *cmdlenp = cmdlen; } return (0); } /* * ex_is_abbrev - * The vi text input routine needs to know if ex thinks this is * an [un]abbreviate command, so it can turn off abbreviations. * Usual ranting in the vi/v_ntext:txt_abbrev() routine. */ int ex_is_abbrev(name, len) char *name; size_t len; { EXCMDLIST const *cp; return ((cp = ex_comm_search(name, len)) != NULL && (cp == &cmds[C_ABBR] || cp == &cmds[C_UNABBREVIATE])); } /* * ex_is_unmap - * The vi text input routine needs to know if ex thinks this is * an unmap command, so it can turn off input mapping. Usual * ranting in the vi/v_ntext:txt_unmap() routine. */ int ex_is_unmap(name, len) char *name; size_t len; { EXCMDLIST const *cp; /* * The command the vi input routines are really interested in * is "unmap!", not just unmap. */ if (name[len - 1] != '!') return (0); --len; return ((cp = ex_comm_search(name, len)) != NULL && cp == &cmds[C_UNMAP]); } static __inline EXCMDLIST const * ex_comm_search(name, len) char *name; size_t len; { EXCMDLIST const *cp; for (cp = cmds; cp->name != NULL; ++cp) { if (cp->name[0] > name[0]) return (NULL); if (cp->name[0] != name[0]) continue; if (!memcmp(name, cp->name, len)) return (cp); } return (NULL); } static void badlno(sp, lno) SCR *sp; recno_t lno; { if (lno == 0) msgq(sp, M_ERR, "Illegal address: the file is empty"); else msgq(sp, M_ERR, "Illegal address: only %lu line%s in the file", lno, lno > 1 ? "s" : ""); }