diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1996-05-22 11:37:15 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1996-05-22 11:37:15 +0000 |
commit | 0157a77a51c5e35e093ae03581f66dea010edcc8 (patch) | |
tree | 5e8bd32aa4d2b5ed37b7cf3ad26e8bdfc7f20a04 /usr.bin/vi/ex/ex.c | |
parent | 806021be093ad00ce2022a532c0f4cc99b0065ac (diff) |
new vi
Diffstat (limited to 'usr.bin/vi/ex/ex.c')
-rw-r--r-- | usr.bin/vi/ex/ex.c | 2540 |
1 files changed, 1514 insertions, 1026 deletions
diff --git a/usr.bin/vi/ex/ex.c b/usr.bin/vi/ex/ex.c index 77666ca0abe..001fada80a8 100644 --- a/usr.bin/vi/ex/ex.c +++ b/usr.bin/vi/ex/ex.c @@ -1,38 +1,16 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. 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. + * See the LICENSE file for redistribution information. */ +#include "config.h" + #ifndef lint -static char sccsid[] = "@(#)ex.c 8.157 (Berkeley) 8/17/94"; +static const char sccsid[] = "@(#)ex.c 10.46 (Berkeley) 5/15/96"; #endif /* not lint */ #include <sys/types.h> @@ -45,275 +23,243 @@ static char sccsid[] = "@(#)ex.c 8.157 (Berkeley) 8/17/94"; #include <errno.h> #include <fcntl.h> #include <limits.h> -#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <termios.h> #include <unistd.h> -#include "compat.h" -#include <db.h> -#include <regex.h> +#include "../common/common.h" +#include "../vi/vi.h" -#include "vi.h" -#include "excmd.h" - -static void badlno __P((SCR *, recno_t)); -static __inline EXCMDLIST const * +#if defined(DEBUG) && defined(COMLOG) +static void ex_comlog __P((SCR *, EXCMD *)); +#endif +static 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 *)); +static int ex_discard __P((SCR *)); +static int ex_line __P((SCR *, EXCMD *, MARK *, int *, int *)); +static int ex_load __P((SCR *)); +static void ex_unknown __P((SCR *, char *, size_t)); /* * ex -- - * Read an ex command and execute it. + * Main ex loop. + * + * PUBLIC: int ex __P((SCR **)); */ int -ex(sp, ep) - SCR *sp; - EXF *ep; +ex(spp) + SCR **spp; { - enum input irval; + EX_PRIVATE *exp; + GS *gp; + MSGS *mp; + SCR *sp; TEXT *tp; - u_int flags, saved_mode; - int eval; + u_int32_t flags; - if (ex_init(sp, ep)) + sp = *spp; + gp = sp->gp; + exp = EXP(sp); + + /* Start the ex screen. */ + if (ex_init(sp)) return (1); - if (sp->s_refresh(sp, ep)) - return (ex_end(sp)); + /* Flush any saved messages. */ + while ((mp = gp->msgq.lh_first) != NULL) { + gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); + LIST_REMOVE(mp, q); + free(mp->buf); + free(mp); + } - /* 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"); + /* If reading from a file, errors should have name and line info. */ + if (!F_ISSET(gp, G_STDIN_TTY)) { + gp->excmd.if_lno = 1; + gp->excmd.if_name = "script"; } /* * !!! - * 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. + * Initialize the text flags. The beautify edit option historically + * applied to ex command input read from a file. In addition, the + * first time a ^H was discarded from the input, there was a message, + * "^H discarded", that was displayed. We don't bother. */ - LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR | TXT_EXSUSPEND); + LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR); + for (;; ++gp->excmd.if_lno) { + /* Display status line and flush. */ + if (F_ISSET(sp, SC_STATUS)) { + F_CLR(sp, SC_STATUS); + msgq_status(sp, sp->lno, 0); + } + (void)ex_fflush(sp); - for (eval = 0;; ++sp->if_lno) { - /* Set the flags that the user can change. */ + /* Set the flags the user can reset. */ 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. - */ + /* Clear any current interrupts, and get a command. */ CLR_INTERRUPT(sp); - F_SET(sp, S_INTERRUPTIBLE); - irval = sp->s_get(sp, ep, sp->tiqp, ':', flags); + if (ex_txt(sp, &sp->tiq, ':', flags)) + return (1); 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; + (void)ex_puts(sp, "\n"); + (void)ex_fflush(sp); + continue; } + /* Initialize the command structure. */ + CLEAR_EX_PARSER(&gp->excmd); + /* - * If the user entered a carriage return, send ex_cmd() - * a separator -- it discards single newlines. + * If the user entered a single carriage return, send + * ex_cmd() a separator -- it discards single newlines. */ - tp = sp->tiqp->cqh_first; + tp = sp->tiq.cqh_first; if (tp->len == 0) { - tp->len = 1; - tp->lb[0] = ' '; + gp->excmd.cp = " "; /* __TK__ why not |? */ + gp->excmd.clen = 1; + } else { + gp->excmd.cp = tp->lb; + gp->excmd.clen = tp->len; } + F_INIT(&gp->excmd, E_NRSEP); - 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; + if (ex_cmd(sp) && !F_ISSET(gp, G_STDIN_TTY)) + return (1); -refresh: if (sp->s_refresh(sp, ep)) { - eval = 1; + if (INTERRUPTED(sp)) { + CLR_INTERRUPT(sp); + msgq(sp, M_ERR, "170|Interrupted"); + } + + /* + * If the last command caused a restart, or switched screens + * or into vi, return. + */ + if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_SSWITCH | SC_VI)) { + *spp = sp; break; } + + /* If the last command switched files, we don't care. */ + F_CLR(sp, SC_FSWITCH); + + /* + * If we're exiting this screen, move to the next one. By + * definition, this means returning into vi, so return to the + * main editor loop. The ordering is careful, don't discard + * the contents of sp until the end. + */ + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { + if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) + return (1); + *spp = screen_next(sp); + return (screen_end(sp)); + } } -ret: if (sp->if_name != NULL) { - FREE(sp->if_name, strlen(sp->if_name) + 1); - sp->if_name = NULL; - } - return (ex_end(sp) || eval); + return (0); } /* - * ex_cfile -- - * Execute ex commands from a file. + * ex_cmd -- + * The guts of the ex parser: parse and execute a string containing + * ex commands. + * + * !!! + * This code MODIFIES the string that gets passed in, to delete quoting + * characters, etc. The string cannot be readonly/text space, nor should + * you expect to use it again after ex_cmd() returns. + * + * !!! + * 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 + * + * or: vi + * :set|file|append|set|file + * + * For extra credit, try them in a startup .exrc file. + * + * PUBLIC: int ex_cmd __P((SCR *)); */ int -ex_cfile(sp, ep, filename, needsep) +ex_cmd(sp) 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; + enum nresult nret; + EX_PRIVATE *exp; + EXCMD *ecp; + GS *gp; + MARK cur; + recno_t lno; + size_t arg1_len, len; + u_int32_t flags; + long ltmp; + int at_found, gv_found; + int ch, cnt, delim, isaddr, namelen; + int newscreen, notempty, tmp, vi_address; + char *arg1, *p, *s, *t; + + gp = sp->gp; + exp = EXP(sp); /* - * 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. + * We always start running the command on the top of the stack. + * This means that *everything* must be resolved when we leave + * this function for any reason. */ - MALLOC(sp, bp, char *, (size_t)sb.st_size + 1); - if (bp == NULL) - goto err; +loop: ecp = gp->ecq.lh_first; - 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; + /* If we're reading a command from a file, set up error information. */ + if (ecp->if_name != NULL) { + gp->if_lno = ecp->if_lno; + gp->if_name = ecp->if_name; } /* - * !!! - * THE UNDERLYING EXF MAY HAVE CHANGED. + * If a move to the end of the file is scheduled for this command, + * do it now. */ - if (bp != NULL) - FREE(bp, sb.st_size); - if (fd >= 0) - (void)close(fd); - return (rval); -} + if (F_ISSET(ecp, E_MOVETOEND)) { + if (db_last(sp, &sp->lno)) + goto rfail; + sp->cno = 0; + F_CLR(ecp, E_MOVETOEND); + } -/* - * 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)); -} + /* If we found a newline, increment the count now. */ + if (F_ISSET(ecp, E_NEWLINE)) { + ++gp->if_lno; + ++ecp->if_lno; + F_CLR(ecp, E_NEWLINE); + } -/* 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"}; + /* (Re)initialize the EXCMD structure, preserving some flags. */ + CLEAR_EX_CMD(ecp); -/* 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"}; + /* Initialize the argument structures. */ + if (argv_init(sp, ecp)) + goto err; -/* - * 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; - } + /* Initialize +cmd, saved command information. */ arg1 = NULL; - save_cmdlen = 0; - - /* It's possible that we've been interrupted during a command. */ - if (INTERRUPTED(sp)) - return (0); + ecp->save_cmdlen = 0; /* Skip <blank>s, empty lines. */ - for (blank = 0; cmdlen > 0; ++cmd, --cmdlen) - if ((ch = *cmd) == '\n') - ++sp->if_lno; - else if (isblank(ch)) - blank = 1; + for (notempty = 0; ecp->clen > 0; ++ecp->cp, --ecp->clen) + if ((ch = *ecp->cp) == '\n') { + ++gp->if_lno; + ++ecp->if_lno; + } else if (isblank(ch)) + notempty = 1; else break; @@ -324,34 +270,33 @@ loop: if (nl) { * 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) == ':'); + if (ecp->clen != 0 && ch == ':') { + notempty = 1; + while (--ecp->clen > 0 && (ch = *++ecp->cp) == ':'); } /* * 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 <newline> characters into - * command lines, we have to check for that case. + * 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 <newline> 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; + if (ecp->clen != 0 && ch == '"') { + while (--ecp->clen > 0 && *++ecp->cp != '\n'); + if (*ecp->cp == '\n') { + F_SET(ecp, E_NEWLINE); + ++ecp->cp; + --ecp->clen; } goto loop; } /* Skip whitespace. */ - for (; cmdlen > 0; ++cmd, --cmdlen) { - ch = *cmd; + for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { + ch = *ecp->cp; if (!isblank(ch)) break; } @@ -362,43 +307,52 @@ loop: if (nl) { * !!! * Historically, in ex mode, lines containing only <blank> characters * were the same as a single <carriage-return>, i.e. a default command. - * In vi mode, they were ignored. + * 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, discarding lines that + * contain only <blank> characters from .exrc files. * - * 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 <blank> characters from - * .exrc files. + * !!! + * This is where you end up when you're done a command, i.e. clen has + * gone to zero. Continue if there are more commands to run. */ - 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; + if (ecp->clen == 0 && + (!notempty || F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_BLIGNORE))) { + if (ex_load(sp)) + goto rfail; + ecp = gp->ecq.lh_first; + if (ecp->clen == 0) + goto rsuccess; + goto loop; + } /* - * 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<CR> puts out a \n, - * but the command :<CR> puts out a \r.) If the line is empty except - * for <blank>s, <carriage-return> or <eof>, we'll probably want to - * output \r. I don't think there's any way to get <blank> characters - * *after* the command character, but this is the ex parser, and I've - * been wrong before. + * Check to see if this is a command for which we may want to move + * the cursor back up to the previous line. (The command :1<CR> + * wants a <newline> separator, but the command :<CR> wants to erase + * the command line.) If the line is empty except for <blank>s, + * <carriage-return> or <eof>, we'll probably want to move up. I + * don't think there's any way to get <blank> 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; + if (F_ISSET(ecp, E_NRSEP) && + ecp->clen != 0 && (ecp->clen != 1 || ecp->cp[0] != '\004')) + F_CLR(ecp, E_NRSEP); /* Parse command addresses. */ - if (ep_range(sp, ep, &exc, &cmd, &cmdlen)) + if (ex_range(sp, ecp, &tmp)) + goto rfail; + if (tmp) goto err; - /* Skip whitespace. */ - for (; cmdlen > 0; ++cmd, --cmdlen) { - ch = *cmd; - if (!isblank(ch)) + /* + * Skip <blank>s and any more colons (the command :3,5:print + * worked, historically). + */ + for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { + ch = *ecp->cp; + if (!isblank(ch) && ch != ':') break; } @@ -423,18 +377,20 @@ loop: if (nl) { * 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; + newscreen = 0; + if (ecp->clen != 0 && ecp->cp[0] != '|' && ecp->cp[0] != '\n') { + if (strchr(SINGLE_CHAR_COMMANDS, *ecp->cp)) { + p = ecp->cp; + ++ecp->cp; + --ecp->clen; namelen = 1; } else { - for (p = cmd; cmdlen > 0; --cmdlen, ++cmd) - if (!isalpha(*cmd)) + for (p = ecp->cp; + ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (!isalpha(*ecp->cp)) break; - if ((namelen = cmd - p) == 0) { - msgq(sp, M_ERR, "Unknown command name"); + if ((namelen = ecp->cp - p) == 0) { + msgq(sp, M_ERR, "080|Unknown command name"); goto err; } } @@ -447,22 +403,33 @@ loop: if (nl) { * Permit further arguments for the few shreds of dignity * it offers. * + * Adding commands that start with 'd', and match "delete" + * up to a l, p, +, - or # character can break this code. + * * !!! - * Note, adding commands that start with 'd', and match - * "delete" up to a l, p, +, - or # character can break - * this code. + * Capital letters beginning the command names ex, edit, + * next, previous, tag and visual (in vi mode) indicate the + * command should happen in a new screen. */ - if (p[0] == 'd') { + switch (p[0]) { + case '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; + if (s[0] == 'l' || s[0] == 'p' || s[0] == '+' || + s[0] == '-' || s[0] == '^' || s[0] == '#') { + len = (ecp->cp - p) - (s - p); + ecp->cp -= len; + ecp->clen += len; + ecp->rcmd = cmds[C_DELETE]; + ecp->rcmd.syntax = "1bca1"; + ecp->cmd = &ecp->rcmd; + goto skip_srch; } + break; + case 'E': case 'N': case 'P': case 'T': case 'V': + newscreen = 1; + p[0] = tolower(p[0]); + break; } /* @@ -473,44 +440,76 @@ loop: if (nl) { * '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. + * Historic vi permitted any flag to follow the s command, e.g. + * "s/e/E/|s|sgc3p" was legal. Make the command "sgc" work. + * Since the following characters all have to be flags, i.e. + * alphabetics, we can let the s command routine return errors + * if it was some illegal command string. This code will break + * if an "sg" or similar command is ever added. The substitute + * code doesn't care if it's a "cgr" flag or a "#lp" flag that + * follows the 's', but we limit the choices here to "cgr" so + * that we get unknown command messages for wrong combinations. */ - if ((cp = ex_comm_search(p, namelen)) == NULL) + if ((ecp->cmd = 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]; + ecp->cp -= namelen - 1; + ecp->clen += namelen - 1; + ecp->cmd = &cmds[C_K]; + break; + } + goto unknown; + case 's': + for (s = p + 1, cnt = namelen; --cnt; ++s) + if (s[0] != 'c' && + s[0] != 'g' && s[0] != 'r') + break; + if (cnt == 0) { + ecp->cp -= namelen - 1; + ecp->clen += namelen - 1; + ecp->rcmd = cmds[C_SUBSTITUTE]; + ecp->rcmd.fn = ex_subagain; + ecp->cmd = &ecp->rcmd; break; } /* FALLTHROUGH */ default: - msgq(sp, M_ERR, - "The %.*s command is unknown", namelen, p); +unknown: if (newscreen) + p[0] = toupper(p[0]); + ex_unknown(sp, p, namelen); 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; - } + /* + * The visual command has a different syntax when called + * from ex than when called from a vi colon command. FMH. + * Make the change now, before we test for the newscreen + * semantic, so that we're testing the right one. + */ +skip_srch: if (ecp->cmd == &cmds[C_VISUAL_EX] && F_ISSET(sp, SC_VI)) + ecp->cmd = &cmds[C_VISUAL_VI]; - /* 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); + /* + * !!! + * Historic vi permitted a capital 'P' at the beginning of + * any command that started with 'p'. Probably wanted the + * P[rint] command for backward compatibility, and the code + * just made Preserve and Put work by accident. Nvi uses + * Previous to mean previous-in-a-new-screen, so be careful. + */ + if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN) && + (ecp->cmd == &cmds[C_PRINT] || + ecp->cmd == &cmds[C_PRESERVE])) + newscreen = 0; + + /* Test for a newscreen associated with this command. */ + if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN)) + goto unknown; + + /* Secure means no shell access. */ + if (F_ISSET(ecp->cmd, E_SECURE) && O_ISSET(sp, O_SECURE)) { + ex_emsg(sp, ecp->cmd->name, EXM_SECURE); goto err; } @@ -519,50 +518,44 @@ skip: if (F_ISSET(cp, E_NOPERM)) { * 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) + if ((ecp->cmd == &cmds[C_SHIFTL] && *p == '<') || + (ecp->cmd == &cmds[C_SHIFTR] && *p == '>')) { + for (ch = *p; + ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (*ecp->cp != ch) break; - if (argv_exp0(sp, ep, &exc, p, cmd - p)) + if (argv_exp0(sp, ecp, p, ecp->cp - 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; + if (ecp->cmd == &cmds[C_HASH]) + exp->fdef = E_C_HASH; + else if (ecp->cmd == &cmds[C_LIST]) + exp->fdef = E_C_LIST; + else if (ecp->cmd == &cmds[C_PRINT]) + exp->fdef = E_C_PRINT; + F_CLR(ecp, E_USELASTCMD); } else { /* Print is the default command. */ - cp = &cmds[C_PRINT]; + ecp->cmd = &cmds[C_PRINT]; /* Set the saved format flags. */ - F_SET(&exc, exp->fdef); + F_SET(ecp, 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. + * we up the address by one. (I have no idea why globals 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; + if (ecp->addrcnt == 0 && !F_ISSET(sp, SC_EX_GLOBAL)) { + ecp->addrcnt = 1; + ecp->addr1.lno = sp->lno + 1; + ecp->addr1.cno = sp->cno; } - uselastcmd = 1; + F_SET(ecp, E_USELASTCMD); } /* @@ -573,62 +566,54 @@ skip: if (F_ISSET(cp, E_NOPERM)) { * the new format, but :1p would. */ if (O_ISSET(sp, O_NUMBER)) { - optnum = 1; - F_SET(&exc, E_F_HASH); + F_SET(ecp, E_OPTNUM); + FL_SET(ecp->iflags, E_C_HASH); } else - optnum = 0; - - /* Initialize local flags to the command flags. */ - LF_INIT(cp->flags); + F_CLR(ecp, E_OPTNUM); - /* - * 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) { + /* Check for ex mode legality. */ + if (F_ISSET(sp, SC_EX) && (F_ISSET(ecp->cmd, E_VIONLY) || newscreen)) { msgq(sp, M_ERR, - "The %s command requires that a file have already been read in", - cp->name); + "082|%s: command not available in ex mode", ecp->cmd->name); goto err; } + /* Add standard command flags. */ + F_SET(ecp, ecp->cmd->flags); + if (!newscreen) + F_CLR(ecp, E_NEWSCREEN); + /* * 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: + * are the end of the string (ecp->clen), or unescaped (by <literal + * next> characters) <newline> or '|' characters. As we're now past + * possible addresses, we can determine how long the command is, so we + * don't have to look for all the possible terminations. Naturally, + * there are some 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). + * 1: The bang, global, v and the filter versions of the read and + * write commands are delimited by <newline>s (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. + * 3: The s 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. + * next, vi visual, and s commands didn't delimit the command. And, + * in the filter cases for read and write, and the bang, global and v + * 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/ + * :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 + * :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 @@ -646,24 +631,24 @@ skip: if (F_ISSET(cp, E_NOPERM)) { * 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]) { + ecp->save_cmd = ecp->cp; + if (ecp->cmd == &cmds[C_EDIT] || ecp->cmd == &cmds[C_EX] || + ecp->cmd == &cmds[C_NEXT] || ecp->cmd == &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); + if (ecp->clen > 0 && *ecp->cp == '!') { + ++ecp->cp; + --ecp->clen; + FL_SET(ecp->iflags, E_C_FORCE); /* Reset, don't reparse. */ - save_cmd = cmd; + ecp->save_cmd = ecp->cp; } - for (tmp = 0; cmdlen > 0; --cmdlen, ++cmd) - if (!isblank(*cmd)) + for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (!isblank(*ecp->cp)) break; /* * QUOTING NOTE: @@ -675,56 +660,59 @@ skip: if (F_ISSET(cp, E_NOPERM)) { * 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; + if (ecp->clen > 0 && *ecp->cp == '+') { + ++ecp->cp; + --ecp->clen; + for (arg1 = p = ecp->cp; + ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; + if (IS_ESCAPE(sp, + ecp, ch) && ecp->clen > 1) { + --ecp->clen; + ch = *++ecp->cp; } else if (isblank(ch)) break; *p++ = ch; } - arg1_len = cmd - arg1; + arg1_len = ecp->cp - arg1; /* Reset, so the first argument isn't reparsed. */ - save_cmd = cmd; + ecp->save_cmd = ecp->cp; } - } 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]) { + } else if (ecp->cmd == &cmds[C_BANG] || + ecp->cmd == &cmds[C_GLOBAL] || ecp->cmd == &cmds[C_V]) { + ecp->cp += ecp->clen; + ecp->clen = 0; + } else if (ecp->cmd == &cmds[C_READ] || ecp->cmd == &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; + for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; if (!isblank(ch)) break; } - if (cmdlen > 0 && ch == '!') { - cmd += cmdlen; - cmdlen = 0; + if (ecp->clen > 0 && ch == '!') { + ecp->cp += ecp->clen; + ecp->clen = 0; } - } else if (cp == &cmds[C_SUBSTITUTE]) { + } else if (ecp->cmd == &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. + * into something like ":s g", so use the special s command. */ - for (; cmdlen > 0; --cmdlen, ++cmd) - if (!isblank(cmd[0])) + for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (!isblank(ecp->cp[0])) break; - if (isalnum(cmd[0]) || cmd[0] == '|') - cp = &cmd_subagain; - else if (cmdlen > 0) { + if (isalnum(ecp->cp[0]) || ecp->cp[0] == '|') { + ecp->rcmd = cmds[C_SUBSTITUTE]; + ecp->rcmd.fn = ex_subagain; + ecp->cmd = &ecp->rcmd; + } else if (ecp->clen > 0) { /* * QUOTING NOTE: * @@ -733,17 +721,20 @@ skip: if (F_ISSET(cp, E_NOPERM)) { * 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) + delim = *ecp->cp; + ++ecp->cp; + --ecp->clen; + for (cnt = 2; ecp->clen > 0 && + cnt != 0; --ecp->clen, ++ecp->cp) + if (ecp->cp[0] == '\\' && + ecp->clen > 1) { + ++ecp->cp; + --ecp->clen; + } else if (ecp->cp[0] == delim) --cnt; } } + /* * Use normal quoting and termination rules to find the end of this * command. @@ -754,26 +745,28 @@ skip: if (F_ISSET(cp, E_NOPERM)) { * 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 + * '|' characters or literal next characters are stripped 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]; + vi_address = ecp->clen != 0 && ecp->cp[0] != '\n'; + for (cnt = 0, p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = ecp->cp[0]; + if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { + tmp = ecp->cp[1]; if (tmp == '\n' || tmp == '|') { - if (tmp == '\n') - ++sp->if_lno; - --cmdlen; - ++cmd; + if (tmp == '\n') { + ++gp->if_lno; + ++ecp->if_lno; + } + --ecp->clen; + ++ecp->cp; ++cnt; ch = tmp; } } else if (ch == '\n' || ch == '|') { if (ch == '\n') - nl = 1; - --cmdlen; + F_SET(ecp, E_NEWLINE); + --ecp->clen; break; } *p++ = ch; @@ -783,22 +776,23 @@ skip: if (F_ISSET(cp, E_NOPERM)) { * 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; + p = ecp->cp + 1; + ecp->cp = ecp->save_cmd; + ecp->save_cmd = p; + ecp->save_cmdlen = ecp->clen; + ecp->clen = ((ecp->save_cmd - ecp->cp) - 1) - cnt; /* - * !!! + * QUOTING NOTE: + * * 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 (ecp->cmd == &cmds[C_SET]) + for (p = ecp->cp, len = ecp->clen; len > 0; --len, ++p) if (*p == '\\') *p = CH_LITERAL; @@ -813,85 +807,90 @@ skip: if (F_ISSET(cp, E_NOPERM)) { * 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. + * Note, we also add the E_ADDR_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)) { + switch (F_ISSET(ecp, E_ADDR1 | E_ADDR2 | E_ADDR2_ALL | E_ADDR2_NONE)) { case E_ADDR1: /* One address: */ - switch (exc.addrcnt) { + switch (ecp->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)) + ecp->addrcnt = 1; + F_SET(ecp, E_ADDR_DEF); + if (F_ISSET(ecp, E_ADDR_ZERODEF)) { + if (db_last(sp, &lno)) goto err; if (lno == 0) { - exc.addr1.lno = 0; - LF_SET(E_ZERO); + ecp->addr1.lno = 0; + F_SET(ecp, E_ADDR_ZERO); } else - exc.addr1.lno = sp->lno; + ecp->addr1.lno = sp->lno; } else - exc.addr1.lno = sp->lno; - exc.addr1.cno = sp->cno; + ecp->addr1.lno = sp->lno; + ecp->addr1.cno = sp->cno; break; case 1: break; case 2: /* Lose the first address. */ - exc.addrcnt = 1; - exc.addr1 = exc.addr2; + ecp->addrcnt = 1; + ecp->addr1 = ecp->addr2; } break; case E_ADDR2_NONE: /* Zero/two addresses: */ - if (exc.addrcnt == 0) /* Default to nothing. */ + if (ecp->addrcnt == 0) /* Default to nothing. */ break; - goto two; + goto two_addr; 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)) + if (ecp->addrcnt == 0) { /* Default entire/empty file. */ + F_SET(ecp, E_ADDR_DEF); + ecp->addrcnt = 2; + if (sp->ep == NULL) + ecp->addr2.lno = 0; + else if (db_last(sp, &ecp->addr2.lno)) goto err; - if (LF_ISSET(E_ZERODEF) && exc.addr2.lno == 0) { - exc.addr1.lno = 0; - LF_SET(E_ZERO); + if (F_ISSET(ecp, E_ADDR_ZERODEF) && + ecp->addr2.lno == 0) { + ecp->addr1.lno = 0; + F_SET(ecp, E_ADDR_ZERO); } else - exc.addr1.lno = 1; - exc.addr1.cno = exc.addr2.cno = 0; - F_SET(&exc, E_ADDR2_ALL); + ecp->addr1.lno = 1; + ecp->addr1.cno = ecp->addr2.cno = 0; + F_SET(ecp, E_ADDR2_ALL); break; } /* FALLTHROUGH */ case E_ADDR2: /* Two addresses: */ -two: switch (exc.addrcnt) { +two_addr: switch (ecp->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)) + ecp->addrcnt = 2; + F_SET(ecp, E_ADDR_DEF); + if (sp->lno == 1 && + F_ISSET(ecp, E_ADDR_ZERODEF)) { + if (db_last(sp, &lno)) goto err; if (lno == 0) { - exc.addr1.lno = exc.addr2.lno = 0; - LF_SET(E_ZERO); + ecp->addr1.lno = ecp->addr2.lno = 0; + F_SET(ecp, E_ADDR_ZERO); } else - exc.addr1.lno = exc.addr2.lno = sp->lno; + ecp->addr1.lno = + ecp->addr2.lno = sp->lno; } else - exc.addr1.lno = exc.addr2.lno = sp->lno; - exc.addr1.cno = exc.addr2.cno = sp->cno; + ecp->addr1.lno = ecp->addr2.lno = sp->lno; + ecp->addr1.cno = ecp->addr2.cno = sp->cno; break; case 1: /* Default to first address. */ - exc.addrcnt = 2; - exc.addr2 = exc.addr1; + ecp->addrcnt = 2; + ecp->addr2 = ecp->addr1; break; case 2: break; } break; default: - if (exc.addrcnt) /* Error. */ + if (ecp->addrcnt) /* Error. */ goto usage; } @@ -901,48 +900,38 @@ two: switch (exc.addrcnt) { * 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)) + if (ecp->cmd == &cmds[C_SCROLL]) { + ecp->addrcnt = 2; + ecp->addr1.lno = sp->lno + 1; + ecp->addr2.lno = sp->lno + O_VAL(sp, O_SCROLL); + ecp->addr1.cno = ecp->addr2.cno = sp->cno; + if (db_last(sp, &lno)) goto err; - if (lno != 0 && lno > sp->lno && exc.addr2.lno > lno) - exc.addr2.lno = lno; + if (lno != 0 && lno > sp->lno && ecp->addr2.lno > lno) + ecp->addr2.lno = lno; } - flagoff = 0; - for (p = cp->syntax; *p != '\0'; ++p) { + ecp->flagoff = 0; + for (p = ecp->cmd->syntax; *p != '\0'; ++p) { /* * The force flag is sensitive to leading whitespace, i.e. * "next !" is different from "next!". Handle it before * skipping leading <blank>s. */ if (*p == '!') { - if (cmdlen > 0 && *cmd == '!') { - ++cmd; - --cmdlen; - F_SET(&exc, E_FORCE); + if (ecp->clen > 0 && *ecp->cp == '!') { + ++ecp->cp; + --ecp->clen; + FL_SET(ecp->iflags, E_C_FORCE); } continue; } /* Skip leading <blank>s. */ - for (; cmdlen > 0; --cmdlen, ++cmd) - if (!isblank(*cmd)) + for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (!isblank(*ecp->cp)) 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') + if (ecp->clen == 0) break; switch (*p) { @@ -957,57 +946,58 @@ two: switch (exc.addrcnt) { * handle them regardless of the stupidity of their * location. */ - for (; cmdlen; --cmdlen, ++cmd) - switch (*cmd) { + for (; ecp->clen; --ecp->clen, ++ecp->cp) + switch (*ecp->cp) { case '+': - ++flagoff; + ++ecp->flagoff; break; case '-': - --flagoff; + case '^': + --ecp->flagoff; break; case '#': - optnum = 0; - F_SET(&exc, E_F_HASH); - exp->fdef |= E_F_HASH; + F_CLR(ecp, E_OPTNUM); + FL_SET(ecp->iflags, E_C_HASH); + exp->fdef |= E_C_HASH; break; case 'l': - F_SET(&exc, E_F_LIST); - exp->fdef |= E_F_LIST; + FL_SET(ecp->iflags, E_C_LIST); + exp->fdef |= E_C_LIST; break; case 'p': - F_SET(&exc, E_F_PRINT); - exp->fdef |= E_F_PRINT; + FL_SET(ecp->iflags, E_C_PRINT); + exp->fdef |= E_C_PRINT; break; default: - goto end1; + goto end_case1; } -end1: break; +end_case1: break; case '2': /* -, ., +, ^ */ case '3': /* -, ., +, ^, = */ - for (; cmdlen; --cmdlen, ++cmd) - switch (*cmd) { + for (; ecp->clen; --ecp->clen, ++ecp->cp) + switch (*ecp->cp) { case '-': - F_SET(&exc, E_F_DASH); + FL_SET(ecp->iflags, E_C_DASH); break; case '.': - F_SET(&exc, E_F_DOT); + FL_SET(ecp->iflags, E_C_DOT); break; case '+': - F_SET(&exc, E_F_PLUS); + FL_SET(ecp->iflags, E_C_PLUS); break; case '^': - F_SET(&exc, E_F_CARAT); + FL_SET(ecp->iflags, E_C_CARAT); break; case '=': if (*p == '3') { - F_SET(&exc, E_F_EQUAL); + FL_SET(ecp->iflags, E_C_EQUAL); break; } /* FALLTHROUGH */ default: - goto end2; + goto end_case23; } -end2: break; +end_case23: break; case 'b': /* buffer */ /* * !!! @@ -1017,7 +1007,8 @@ end2: break; * 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] == '#') && + if ((ecp->cp[0] == '+' || ecp->cp[0] == '-' || + ecp->cp[0] == '^' || ecp->cp[0] == '#') && strchr(p, '1') != NULL) break; /* @@ -1026,32 +1017,38 @@ end2: break; * 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); + if (!isdigit(ecp->cp[0])) { + ecp->buffer = *ecp->cp; + ++ecp->cp; + --ecp->clen; + FL_SET(ecp->iflags, E_C_BUFFER); } break; case 'c': /* count [01+a] */ ++p; /* Validate any signed value. */ - if (!isdigit(*cmd) && - (*p != '+' || (*cmd != '+' && *cmd != '-'))) + if (!isdigit(*ecp->cp) && (*p != '+' || + (*ecp->cp != '+' && *ecp->cp != '-'))) 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"); + if (*ecp->cp == '-') + FL_SET(ecp->iflags, E_C_COUNT_NEG); + else if (*ecp->cp == '+') + FL_SET(ecp->iflags, E_C_COUNT_POS); + if ((nret = + nget_slong(<mp, ecp->cp, &t, 10)) != NUM_OK) { + ex_badaddr(sp, NULL, A_NOTSET, nret); + goto err; + } + if (ltmp == 0 && *p != '0') { + msgq(sp, M_ERR, "083|Count may not be zero"); goto err; } - cmdlen -= (t - cmd); - cmd = t; + ecp->clen -= (t - ecp->cp); + ecp->cp = t; + /* - * Count as address offsets occur in commands taking + * Counts as address offsets occur in commands taking * two addresses. Historic vi practice was to use * the count as an offset from the *second* address. * @@ -1060,133 +1057,159 @@ end2: break; * line addresses. */ if (*p == 'a') { - exc.addr1 = exc.addr2; - exc.addr2.lno = exc.addr1.lno + lno - 1; + ecp->addr1 = ecp->addr2; + ecp->addr2.lno = ecp->addr1.lno + ltmp - 1; } else - exc.count = lno; - F_SET(&exc, E_COUNT); + ecp->count = ltmp; + FL_SET(ecp->iflags, E_C_COUNT); break; case 'f': /* file */ - if (argv_exp2(sp, ep, - &exc, cmd, cmdlen, cp == &cmds[C_BANG])) + if (argv_exp2(sp, ecp, ecp->cp, ecp->clen)) goto err; - goto countchk; + goto arg_cnt_chk; case 'l': /* line */ - if (ep_line(sp, ep, &cur, &cmd, &cmdlen, &tmp)) + /* + * Get a line specification. + * + * If the line was a search expression, we may have + * changed state during the call, and we're now + * searching the file. Push ourselves onto the state + * stack. + */ + if (ex_line(sp, ecp, &cur, &isaddr, &tmp)) + goto rfail; + if (tmp) goto err; + /* Line specifications are always required. */ - if (!tmp) { - msgq(sp, M_ERR, - "%s: bad line specification", cmd); + if (!isaddr) { + msgq_str(sp, M_ERR, ecp->cp, + "084|%s: bad line specification"); goto err; } - /* The line must exist for these commands. */ - if (file_lline(sp, ep, &lno)) - goto err; - if (cur.lno > lno) { - badlno(sp, lno); + /* + * The target line should exist for these commands, + * but 0 is legal for them as well. + */ + if (cur.lno != 0 && !db_exist(sp, cur.lno)) { + ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } - exc.lineno = cur.lno; + ecp->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; + if (ecp->clen != 0) { + if (argv_exp1(sp, ecp, ecp->cp, + ecp->clen, ecp->cmd == &cmds[C_BANG])) + goto err; + goto addr_verify; + } + /* FALLTHROUGH */ case 's': /* string */ - if (argv_exp0(sp, ep, &exc, cmd, cmdlen)) + if (argv_exp0(sp, ecp, ecp->cp, ecp->clen)) goto err; - goto addr2; + goto addr_verify; case 'W': /* word string */ /* * QUOTING NOTE: * * Literal next characters escape the following - * character. Quoting characters are stripped - * here since they are no longer useful. + * 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; + for (p = t = ecp->cp; + ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; + if (IS_ESCAPE(sp, + ecp, ch) && ecp->clen > 1) { + --ecp->clen; + *p++ = *++ecp->cp; } else if (isblank(ch)) { - ++cmd; - --cmdlen; + ++ecp->cp; + --ecp->clen; break; } else *p++ = ch; } - if (argv_exp0(sp, ep, &exc, t, p - t)) + if (argv_exp0(sp, ecp, t, p - t)) goto err; /* Delete intervening whitespace. */ - for (; cmdlen > 0; --cmdlen, ++cmd) { - ch = *cmd; + for (; ecp->clen > 0; + --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; if (!isblank(ch)) break; } - if (cmdlen == 0) + if (ecp->clen == 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; + for (p = t = ecp->cp; ecp->clen > 0; + --ecp->clen, ++ecp->cp, ++p) { + ch = *ecp->cp; + if (IS_ESCAPE(sp, + ecp, ch) && ecp->clen > 1) { + --ecp->clen; + *p = *++ecp->cp; } else *p = ch; } - if (argv_exp0(sp, ep, &exc, t, p - t)) + if (argv_exp0(sp, ecp, t, p - t)) goto err; - goto addr2; + goto addr_verify; case 'w': /* word */ - if (argv_exp3(sp, ep, &exc, cmd, cmdlen)) + if (argv_exp3(sp, ecp, ecp->cp, ecp->clen)) goto err; -countchk: if (*++p != 'N') { /* N */ +arg_cnt_chk: 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'; + tmp = *p - '0'; if ((*++p != 'o' || exp->argsoff != 0) && - exp->argsoff != num) + exp->argsoff != tmp) goto usage; } - goto addr2; + goto addr_verify; default: msgq(sp, M_ERR, - "Internal syntax table error (%s: %c)", - cp->name, *p); + "085|Internal syntax table error (%s: %s)", + ecp->cmd->name, KEY_NAME(sp, *p)); } } /* Skip trailing whitespace. */ - for (; cmdlen; --cmdlen) { - ch = *cmd++; + for (; ecp->clen > 0; --ecp->clen) { + ch = *ecp->cp++; 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. + * 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); + if (ecp->clen != 0 || strpbrk(p, "lr")) { +usage: msgq(sp, M_ERR, "086|Usage: %s", ecp->cmd->usage); goto err; } - /* Verify that the addresses are legal. */ -addr2: switch (exc.addrcnt) { + /* + * Verify that the addresses are legal. Check the addresses here, + * because this is a place where all ex addresses pass through. + * (They don't all pass through ep_line(), for instance.) We're + * assuming that any non-existent line doesn't exist because it's + * past the end-of-file. That's a pretty good guess. + * + * If it's a "default vi command", an address of zero is okay. + */ +addr_verify: + switch (ecp->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 @@ -1195,32 +1218,33 @@ addr2: switch (exc.addrcnt) { * 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); + if (ecp->addr2.lno == 0) { + if (!F_ISSET(ecp, E_ADDR_ZERO) && + (F_ISSET(sp, SC_EX) || + !F_ISSET(ecp, E_USELASTCMD))) { + ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); + goto err; + } + } else if (!db_exist(sp, ecp->addr2.lno)) + if (FL_ISSET(ecp->iflags, E_C_COUNT)) { + if (db_last(sp, &lno)) + goto err; + ecp->addr2.lno = lno; + } else { + ex_badaddr(sp, NULL, A_EOF, NUM_OK); 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); + if (ecp->addr1.lno == 0) { + if (!F_ISSET(ecp, E_ADDR_ZERO) && + (F_ISSET(sp, SC_EX) || + !F_ISSET(ecp, E_USELASTCMD))) { + ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); + goto err; + } + } else if (!db_exist(sp, ecp->addr1.lno)) { + ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } break; @@ -1232,22 +1256,37 @@ addr2: switch (exc.addrcnt) { * move to line 3 and line 'b, respectively, but ":3|" prints line 3. * * !!! + * In addition, IF THE LINE CHANGES, move to the first nonblank of + * the line. + * + * !!! * 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) { + if ((F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_NOPRDEF)) && + F_ISSET(ecp, E_USELASTCMD) && vi_address == 0) { + switch (ecp->addrcnt) { case 2: - sp->lno = exc.addr2.lno ? exc.addr2.lno : 1; - sp->cno = exc.addr2.cno; + if (sp->lno != + (ecp->addr2.lno ? ecp->addr2.lno : 1)) { + sp->lno = + ecp->addr2.lno ? ecp->addr2.lno : 1; + sp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + } break; case 1: - sp->lno = exc.addr1.lno ? exc.addr1.lno : 1; - sp->cno = exc.addr1.cno; + if (sp->lno != + (ecp->addr1.lno ? ecp->addr1.lno : 1)) { + sp->lno = + ecp->addr1.lno ? ecp->addr1.lno : 1; + sp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + } break; } - cmd = save_cmd; - cmdlen = save_cmdlen; + ecp->cp = ecp->save_cmd; + ecp->clen = ecp->save_cmdlen; goto loop; } @@ -1256,51 +1295,27 @@ addr2: switch (exc.addrcnt) { * it's a compound command, e.g. ":5p|6" should set the absolute * mark for vi. */ - if (F_ISSET(exp, EX_ABSMARK)) { + if (F_ISSET(ecp, E_ABSMARK)) { cur.lno = sp->lno; cur.cno = sp->cno; - F_CLR(exp, EX_ABSMARK); - if (mark_set(sp, ep, ABSMARK1, &cur, 1)) + F_CLR(ecp, E_ABSMARK); + if (mark_set(sp, 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"); - } +#if defined(DEBUG) && defined(COMLOG) + ex_comlog(sp, ecp); #endif - /* Clear autoprint flag. */ - F_CLR(exp, EX_AUTOPRINT); - /* Increment the command count if not called from vi. */ - if (IN_EX_MODE(sp)) + if (F_ISSET(sp, SC_EX)) ++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); + if (sp->ep != NULL && !F_ISSET(sp, SC_EX_GLOBAL)) + (void)log_cursor(sp); /* * !!! @@ -1311,329 +1326,459 @@ addr2: switch (exc.addrcnt) { * 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 <eof> was represented as ^D, the - * output wouldn't overwrite the user's input. Sex currently doesn't - * display the <eof> character if it's going to be the scroll command, - * i.e. if it's the first non-<blank> character in the line. If sex - * is changed to run in cooked mode, i.e. <eof> 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. + * special commands, we move back up to the previous line, and erase + * the prompt character with the output. Since ex runs in canonical + * mode, we don't have to do anything else, a <newline> has already + * been echoed by the tty driver. It's OK if vi calls us -- we won't + * be in ex mode so we'll do nothing. */ - 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; + if (F_ISSET(ecp, E_NRSEP)) { + if (sp->ep != NULL && + F_ISSET(sp, SC_EX) && F_ISSET(gp, G_STDIN_TTY) && + (F_ISSET(ecp, E_USELASTCMD) || ecp->cmd == &cmds[C_SCROLL])) + gp->scr_ex_adjust(sp, EX_TERM_SCROLL); + F_CLR(ecp, E_NRSEP); } - /* Save the current mode. */ - saved_mode = F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE); - - /* Do the command. */ - if (cp->fn(sp, ep, &exc)) + /* + * Call the underlying function for the ex command. + * + * XXX + * Interrupts behave like errors, for now. + */ + if (ecp->cmd->fn(sp, ecp) || INTERRUPTED(sp)) { + if (!F_ISSET(gp, G_STDIN_TTY)) + F_SET(sp, SC_EXIT_FORCE); 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; + /* Make sure no function left global temporary space locked. */ + if (F_ISSET(gp, G_TMP_INUSE)) { + F_CLR(gp, G_TMP_INUSE); + msgq(sp, M_ERR, "087|%s: temporary buffer not released", + ecp->cmd->name); } #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); - } + /* + * Ex displayed the number of lines modified immediately after each + * command, so the command "1,10d|1,10d" would display: + * + * 10 lines deleted + * 10 lines deleted + * <autoprint line> + * + * Executing ex commands from vi only reported the final modified + * lines message -- that's wrong enough that we don't match it. + */ + if (F_ISSET(sp, SC_EX)) + msgq_rpt(sp); /* * 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. + * May not match historic practice (which I've never been able to + * completely figure 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 might know. */ - if (ep != NULL && (flagoff += exc.flagoff)) { - if (flagoff < 0) { - if (sp->lno <= -flagoff) { - msgq(sp, M_ERR, "Flag offset before line 1"); + if (sp->ep != NULL && ecp->flagoff) { + if (ecp->flagoff < 0) { + if (sp->lno <= -ecp->flagoff) { + msgq(sp, M_ERR, + "088|Flag offset to before line 1"); goto err; } } else { - if (file_lline(sp, ep, &lno)) + if (!NPFITS(MAX_REC_NUMBER, sp->lno, ecp->flagoff)) { + ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); goto err; - if (sp->lno + flagoff > lno) { - msgq(sp, M_ERR, "Flag offset past end-of-file"); + } + if (!db_exist(sp, sp->lno + ecp->flagoff)) { + msgq(sp, M_ERR, + "089|Flag offset past end-of-file"); goto err; } } - sp->lno += flagoff; + sp->lno += ecp->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 the command was successful may want to display a line based on + * the autoprint option or an explicit print flag. (Make sure that + * there's a line to display.) Also, the autoprint edit option is + * turned off for the duration of global commands. */ - if (ep != NULL && - IN_EX_MODE(sp) && !F_ISSET(sp, S_GLOBAL) && sp->lno != 0) { + if (F_ISSET(sp, SC_EX) && sp->ep != NULL && 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 (FL_ISSET(ecp->iflags, E_CLRFLAG)) + FL_CLR(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT); - /* If hash only set because of the number option, discard it. */ - if (optnum) - F_CLR(&exc, E_F_HASH); + /* If hash set only because of the number option, discard it. */ + if (F_ISSET(ecp, E_OPTNUM)) + FL_CLR(ecp->iflags, E_C_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. + * If there was an explicit flag to display the new cursor line, + * or autoprint is set and a change was made, display the line. + * If any print flags were set use them, else 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); + LF_INIT(FL_ISSET(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)); + if (!LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT | E_NOAUTO) && + !F_ISSET(sp, SC_EX_GLOBAL) && + O_ISSET(sp, O_AUTOPRINT) && F_ISSET(ecp, E_AUTOPRINT)) + LF_INIT(E_C_PRINT); + + if (LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT)) { + cur.lno = sp->lno; + cur.cno = 0; + (void)ex_print(sp, ecp, &cur, &cur, 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. + * If the command had an associated "+cmd", it has to be executed + * before we finish executing any more of this ex command. For + * example, consider a .exrc file that contains the following lines: + * + * :set all + * :edit +25 file.c|s/abc/ABC/|1 + * :3,5 print + * + * This can happen more than once -- the historic vi simply hung or + * dropped core, of course. Prepend the + command back into the + * current command and continue. We may have to add up to two more + * characters, a <literal next> and a command separator. We know + * that it will still fit because we discarded at least one space + * and the + character. */ -err: if (sep != NONE && - ep != NULL && IN_EX_MODE(sp) && F_ISSET(sp->gp, G_STDIN_TTY)) - (void)fputc('\n', stdout); + if (arg1_len != 0) { + /* Add in a command separator. */ + *--ecp->save_cmd = '\n'; + ++ecp->save_cmdlen; + + /* + * If the last character of the + command was a <literal next> + * character, it would be treated differently because of the + * append. Quote it, if necessary. + */ + if (IS_ESCAPE(sp, ecp, arg1[arg1_len - 1])) { + *--ecp->save_cmd = CH_LITERAL; + ++ecp->save_cmdlen; + } + + ecp->save_cmd -= arg1_len; + ecp->save_cmdlen += arg1_len; + memmove(ecp->save_cmd, arg1, arg1_len); + + /* + * Any commands executed from a +cmd are executed starting at + * the last line, first column of the file -- NOT the first + * nonblank.) The main file startup code doesn't know that a + * +cmd was set, however, so it may have put us at the top of + * the file. (Note, this is safe because we must have switched + * files to get here.) + */ + F_SET(ecp, E_MOVETOEND); + } + + /* Update the current command. */ + ecp->cp = ecp->save_cmd; + ecp->clen = ecp->save_cmdlen; + /* - * 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 we've changed screens or underlying files, any pending global or + * v command, or @ buffer that has associated addresses, has to be + * discarded. This is historic practice for globals, and necessary for + * @ buffers that had associated addresses. + * + * Otherwise, if we've changed underlying files, it's not a problem, + * we continue with the rest of the ex command(s), operating on the + * new file. However, if we switch screens (either by exiting or by + * an explicit command), we have no way of knowing where to put output + * messages, and, since we don't control screens here, we could screw + * up the upper layers, (e.g. we could exit/reenter a screen multiple + * times). So, return and continue after we've got a new screen. */ - if (save_cmdlen == 0) - for (; cmdlen; --cmdlen) { - ch = *cmd++; - if (IS_ESCAPE(sp, ch) && cmdlen > 1) { - --cmdlen; - ++cmd; + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_FSWITCH | SC_SSWITCH)) { + at_found = gv_found = 0; + for (ecp = sp->gp->ecq.lh_first; + ecp != NULL; ecp = ecp->q.le_next) + switch (ecp->agv_flags) { + case 0: + case AGV_AT_NORANGE: + break; + case AGV_AT: + if (!at_found) { + at_found = 1; + msgq(sp, M_ERR, + "090|@ with range running when the file/screen changed"); + } + break; + case AGV_GLOBAL: + case AGV_V: + if (!gv_found) { + gv_found = 1; + msgq(sp, M_ERR, + "091|Global/v command running when the file/screen changed"); + } + break; + default: + abort(); + } + if (at_found || gv_found) + goto discard; + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_SSWITCH)) + goto rsuccess; + } + + goto loop; + /* NOTREACHED */ + +err: /* + * On command failure, we discard keys and pending commands remaining, + * as well as any keys that were mapped and waiting. The save_cmdlen + * test is not necessarily correct. If we fail early enough we don't + * know if the entire string was a single command or not. Guess, as + * it's useful to know if commands other than the current one are being + * discarded. + */ + if (ecp->save_cmdlen == 0) + for (; ecp->clen; --ecp->clen) { + ch = *ecp->cp++; + if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { + --ecp->clen; + ++ecp->cp; } else if (ch == '\n' || ch == '|') { - if (cmdlen > 1) - save_cmdlen = 1; + if (ecp->clen > 1) + ecp->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); + if (ecp->save_cmdlen != 0 || gp->ecq.lh_first != &gp->excmd) { +discard: msgq(sp, M_BERR, + "092|Ex command failed: pending commands discarded"); + ex_discard(sp); + } + if (v_event_flush(sp, CH_MAPPED)) + msgq(sp, M_BERR, + "093|Ex command failed: mapped keys discarded"); + +rfail: tmp = 1; + if (0) +rsuccess: tmp = 0; + + /* Turn off any file name error information. */ + gp->if_name = NULL; + + /* Turn off the global bit. */ + F_CLR(sp, SC_EX_GLOBAL); + + return (tmp); } /* - * ep_range -- - * Get a line range for ex commands. + * ex_range -- + * Get a line range for ex commands, or perform a vi ex address search. + * + * PUBLIC: int ex_range __P((SCR *, EXCMD *, int *)); */ -static int -ep_range(sp, ep, excp, cmdp, cmdlenp) +int +ex_range(sp, ecp, errp) SCR *sp; - EXF *ep; - EXCMDARG *excp; - char **cmdp; - size_t *cmdlenp; + EXCMD *ecp; + int *errp; { - 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; + enum { ADDR_FOUND, ADDR_NEED, ADDR_NONE } addr; + GS *gp; + EX_PRIVATE *exp; + MARK m; + int isaddr; - ++*cmdp; - --*cmdlenp; - return (0); - } + *errp = 0; - /* Parse comma or semi-colon delimited line specs. */ - for (savecursor_set = 0, excp->addrcnt = 0; cmdlen > 0;) - switch (*cmd) { - case ';': /* Semi-colon delimiter. */ + /* + * Parse comma or semi-colon delimited line specs. + * + * Semi-colon delimiters update the current address to be the last + * address. For example, the command + * + * :3;/pattern/ecp->cp + * + * will search for pattern from line 3. In addition, if ecp->cp + * is not a valid command, the current line will be left at 3, not + * at the original address. + * + * Extra addresses are discarded, starting with the first. + * + * !!! + * If any addresses are missing, they default to the current line. + * This was historically true for both leading and trailing comma + * delimited addresses as well as for trailing semicolon delimited + * addresses. For consistency, we make it true for leading semicolon + * addresses as well. + */ + gp = sp->gp; + exp = EXP(sp); + for (addr = ADDR_NONE, ecp->addrcnt = 0; ecp->clen > 0;) + switch (*ecp->cp) { + case '%': /* Entire file. */ + /* Vi ex address searches didn't permit % signs. */ + if (F_ISSET(ecp, E_VISEARCH)) + goto ret; + + /* It's an error if the file is empty. */ + if (sp->ep == NULL) { + ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); + *errp = 1; + return (0); + } /* - * 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. + * !!! + * A percent character addresses all of the lines in + * the file. Historically, it couldn't be followed by + * any other address. We do it as a text substitution + * for simplicity. POSIX 1003.2 is expected to follow + * this practice. + * + * If it's an empty file, the first line is 0, not 1. */ - 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; + if (addr == ADDR_FOUND) { + ex_badaddr(sp, NULL, A_COMBO, NUM_OK); + *errp = 1; + return (0); } - ++cmd; - --cmdlen; + if (db_last(sp, &ecp->addr2.lno)) + return (1); + ecp->addr1.lno = ecp->addr2.lno == 0 ? 0 : 1; + ecp->addr1.cno = ecp->addr2.cno = 0; + ecp->addrcnt = 2; + addr = ADDR_FOUND; + ++ecp->cp; + --ecp->clen; 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; + case ',': /* Comma delimiter. */ + /* Vi ex address searches didn't permit commas. */ + if (F_ISSET(ecp, E_VISEARCH)) + goto ret; + /* FALLTHROUGH */ + case ';': /* Semi-colon delimiter. */ + if (sp->ep == NULL) { + ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); + *errp = 1; + return (0); } + if (addr != ADDR_FOUND) + switch (ecp->addrcnt) { + case 0: + ecp->addr1.lno = sp->lno; + ecp->addr1.cno = sp->cno; + ecp->addrcnt = 1; + break; + case 2: + ecp->addr1 = ecp->addr2; + /* FALLTHROUGH */ + case 1: + ecp->addr2.lno = sp->lno; + ecp->addr2.cno = sp->cno; + ecp->addrcnt = 2; + break; + } + if (*ecp->cp == ';') + switch (ecp->addrcnt) { + case 0: + abort(); + /* NOTREACHED */ + case 1: + sp->lno = ecp->addr1.lno; + sp->cno = ecp->addr1.cno; + break; + case 2: + sp->lno = ecp->addr2.lno; + sp->cno = ecp->addr2.cno; + break; + } + addr = ADDR_NEED; /* FALLTHROUGH */ case ' ': /* Whitespace. */ case '\t': /* Whitespace. */ - ++cmd; - --cmdlen; + ++ecp->cp; + --ecp->clen; break; default: - if (ep_line(sp, ep, &cur, &cmd, &cmdlen, &tmp)) + /* Get a line specification. */ + if (ex_line(sp, ecp, &m, &isaddr, errp)) return (1); - if (!tmp) - goto done; - - /* - * Extra addresses are discarded, starting with - * the first. - */ - switch (excp->addrcnt) { + if (*errp) + return (0); + if (!isaddr) + goto ret; + if (addr == ADDR_FOUND) { + ex_badaddr(sp, NULL, A_COMBO, NUM_OK); + *errp = 1; + return (0); + } + switch (ecp->addrcnt) { case 0: - excp->addr1 = cur; - excp->addrcnt = 1; + ecp->addr1 = m; + ecp->addrcnt = 1; break; case 1: - excp->addr2 = cur; - excp->addrcnt = 2; + ecp->addr2 = m; + ecp->addrcnt = 2; break; case 2: - excp->addr1 = excp->addr2; - excp->addr2 = cur; + ecp->addr1 = ecp->addr2; + ecp->addr2 = m; break; } + addr = ADDR_FOUND; 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. + * !!! + * Vi ex address searches are indifferent to order or trailing + * semi-colons. */ -done: if (savecursor_set) { - sp->lno = savecursor.lno; - sp->cno = savecursor.cno; - } - if (excp->addrcnt == 2 && excp->addr2.lno < excp->addr1.lno) { +ret: if (F_ISSET(ecp, E_VISEARCH)) + return (0); + + if (addr == ADDR_NEED) + switch (ecp->addrcnt) { + case 0: + ecp->addr1.lno = sp->lno; + ecp->addr1.cno = sp->cno; + ecp->addrcnt = 1; + break; + case 2: + ecp->addr1 = ecp->addr2; + /* FALLTHROUGH */ + case 1: + ecp->addr2.lno = sp->lno; + ecp->addr2.cno = sp->cno; + ecp->addrcnt = 2; + break; + } + + if (ecp->addrcnt == 2 && ecp->addr2.lno < ecp->addr1.lno) { msgq(sp, M_ERR, - "The second address is smaller than the first"); - return (1); + "094|The second address is smaller than the first"); + *errp = 1; } - *cmdp = cmd; - *cmdlenp = cmdlen; return (0); } /* - * Get a single line address specifier. + * ex_line -- + * 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 @@ -1644,63 +1789,83 @@ done: if (savecursor_set) { * it later. * * XXX - * This is not exactly historic practice, although it's fairly close. + * This is probably still not exactly historic practice, although I think + * it's fairly close. */ static int -ep_line(sp, ep, cur, cmdp, cmdlenp, addr_found) +ex_line(sp, ecp, mp, isaddrp, errp) SCR *sp; - EXF *ep; - MARK *cur; - char **cmdp; - size_t *cmdlenp; - int *addr_found; + EXCMD *ecp; + MARK *mp; + int *isaddrp, *errp; { + enum nresult nret; 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; + GS *gp; + long total, val; + int isneg; + int (*sf) __P((SCR *, MARK *, MARK *, char *, char **, u_int)); + char *endp; + gp = sp->gp; exp = EXP(sp); - *addr_found = 0; - cmd = *cmdp; - cmdlen = *cmdlenp; - switch (*cmd) { + *isaddrp = *errp = 0; + F_CLR(ecp, E_DELTA); + + /* No addresses permitted until a file has been read in. */ + if (sp->ep == NULL && strchr("$0123456789'\\/?.+-^", *ecp->cp)) { + ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); + *errp = 1; + return (0); + } + + switch (*ecp->cp) { case '$': /* Last line in the file. */ - *addr_found = 1; - F_SET(exp, EX_ABSMARK); + *isaddrp = 1; + F_SET(ecp, E_ABSMARK); - cur->cno = 0; - if (file_lline(sp, ep, &cur->lno)) + mp->cno = 0; + if (db_last(sp, &mp->lno)) return (1); - ++cmd; - --cmdlen; + ++ecp->cp; + --ecp->clen; 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); + *isaddrp = 1; + F_SET(ecp, E_ABSMARK); - cur->cno = 0; -/* 8-bit XXX */ cur->lno = strtol(cmd, &endp, 10); - cmdlen -= (endp - cmd); - cmd = endp; + if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK) { + ex_badaddr(sp, NULL, A_NOTSET, nret); + *errp = 1; + return (0); + } + if (!NPFITS(MAX_REC_NUMBER, 0, val)) { + ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); + *errp = 1; + return (0); + } + mp->lno = val; + mp->cno = 0; + ecp->clen -= (endp - ecp->cp); + ecp->cp = endp; break; case '\'': /* Use a mark. */ - *addr_found = 1; - F_SET(exp, EX_ABSMARK); + *isaddrp = 1; + F_SET(ecp, E_ABSMARK); - if (cmdlen == 1) { - msgq(sp, M_ERR, "No mark name supplied"); - return (1); + if (ecp->clen == 1) { + msgq(sp, M_ERR, "095|No mark name supplied"); + *errp = 1; + return (0); } - if (mark_get(sp, ep, cmd[1], cur)) - return (1); - cmd += 2; - cmdlen -= 2; + if (mark_get(sp, ecp->cp[1], mp, M_ERR)) { + *errp = 1; + return (0); + } + ecp->cp += 2; + ecp->clen -= 2; break; case '\\': /* Search: forward/backward. */ /* @@ -1709,96 +1874,328 @@ ep_line(sp, ep, cur, cmdp, cmdlenp, addr_found) * ?? 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); + if (ecp->clen < 2 || + ecp->cp[1] != '/' && ecp->cp[1] != '?') { + msgq(sp, M_ERR, "096|\\ not followed by / or ?"); + *errp = 1; + return (0); } - ++cmd; - --cmdlen; - sf = cmd[0] == '/' ? f_search : b_search; + ++ecp->cp; + --ecp->clen; + sf = ecp->cp[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); +search: mp->lno = sp->lno; + mp->cno = sp->cno; + if (sf(sp, mp, mp, + ecp->cp, &endp, SEARCH_MSG | SEARCH_PARSE | SEARCH_SET)) { + *errp = 1; + return (0); } - *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; + + /* Fix up the command pointers. */ + ecp->clen -= (endp - ecp->cp); + ecp->cp = endp; + + *isaddrp = 1; + F_SET(ecp, E_ABSMARK); break; case '.': /* Current position. */ - *addr_found = 1; - cur->cno = sp->cno; + *isaddrp = 1; + mp->cno = sp->cno; /* If an empty file, then '.' is 0, not 1. */ if (sp->lno == 1) { - if (file_lline(sp, ep, &cur->lno)) + if (db_last(sp, &mp->lno)) return (1); - if (cur->lno != 0) - cur->lno = 1; + if (mp->lno != 0) + mp->lno = 1; } else - cur->lno = sp->lno; - ++cmd; - --cmdlen; + mp->lno = sp->lno; + + /* + * !!! + * Historically, .<number> was the same as .+<number>, i.e. + * the '+' could be omitted. (This feature is found in ed + * as well.) + */ + if (ecp->clen > 1 && isdigit(ecp->cp[1])) + *ecp->cp = '+'; + else { + ++ecp->cp; + --ecp->clen; + } break; } + /* Skip trailing <blank>s. */ + for (; ecp->clen > 0 && + isblank(ecp->cp[0]); ++ecp->cp, --ecp->clen); + /* - * 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 ".". + * Evaluate any offset. If no address yet found, the 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; + total = 0; + if (ecp->clen != 0 && (isdigit(ecp->cp[0]) || + ecp->cp[0] == '+' || ecp->cp[0] == '-' || + ecp->cp[0] == '^')) { + if (!*isaddrp) { + *isaddrp = 1; + mp->lno = sp->lno; + mp->cno = sp->cno; } - - 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; + /* + * Evaluate an offset, defined as: + * + * [+-^<blank>]*[<blank>]*[0-9]* + * + * The rough translation is any number of signs, optionally + * followed by numbers, or a number by itself, all <blank> + * separated. + * + * !!! + * All address offsets were additive, e.g. "2 2 3p" was the + * same as "7p", or, "/ZZZ/ 2" was the same as "/ZZZ/+2". + * Note, however, "2 /ZZZ/" was an error. It was also legal + * to insert signs without numbers, so "3 - 2" was legal, and + * equal to 4. + * + * !!! + * Offsets were historically permitted for any line address, + * e.g. the command "1,2 copy 2 2 2 2" copied lines 1,2 after + * line 8. + * + * !!! + * Offsets were historically permitted for search commands, + * and handled as addresses: "/pattern/2 2 2" was legal, and + * referenced the 6th line after pattern. + */ + F_SET(ecp, E_DELTA); + for (;;) { + for (; ecp->clen > 0 && isblank(ecp->cp[0]); + ++ecp->cp, --ecp->clen); + if (ecp->clen == 0 || !isdigit(ecp->cp[0]) && + ecp->cp[0] != '+' && ecp->cp[0] != '-' && + ecp->cp[0] != '^') + break; + if (!isdigit(ecp->cp[0]) && + !isdigit(ecp->cp[1])) { + total += ecp->cp[0] == '+' ? 1 : -1; + --ecp->clen; + ++ecp->cp; + } else { + if (ecp->cp[0] == '-' || + ecp->cp[0] == '^') { + ++ecp->cp; + --ecp->clen; + isneg = 1; + } else + isneg = 0; + + /* Get a signed long, add it to the total. */ + if ((nret = nget_slong(&val, + ecp->cp, &endp, 10)) != NUM_OK || + (nret = NADD_SLONG(sp, + total, val)) != NUM_OK) { + ex_badaddr(sp, NULL, A_NOTSET, nret); + *errp = 1; + return (0); + } + total += isneg ? -val : val; + ecp->clen -= (endp - ecp->cp); + ecp->cp = endp; + } } } - if (*addr_found) { - if (total < 0 && -total > cur->lno) { - msgq(sp, M_ERR, - "Reference to a line number less than 0"); - return (1); + /* + * Any value less than 0 is an error. Make sure that the new value + * will fit into a recno_t. + */ + if (*isaddrp && total != 0) { + if (total < 0) { + if (-total > mp->lno) { + msgq(sp, M_ERR, + "097|Reference to a line number less than 0"); + *errp = 1; + return (0); + } + } else + if (!NPFITS(MAX_REC_NUMBER, mp->lno, total)) { + ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); + *errp = 1; + return (0); + } + mp->lno += total; + } + return (0); +} + + +/* + * ex_load -- + * Load up the next command, which may be an @ buffer or global command. + */ +static int +ex_load(sp) + SCR *sp; +{ + GS *gp; + EXCMD *ecp; + RANGE *rp; + + F_CLR(sp, SC_EX_GLOBAL); + + /* + * Lose any exhausted commands. We know that the first command + * can't be an AGV command, which makes things a bit easier. + */ + for (gp = sp->gp;;) { + /* + * If we're back to the original structure, leave it around, + * but discard any allocated source name, we've returned to + * the beginning of the command stack. + */ + if ((ecp = gp->ecq.lh_first) == &gp->excmd) { + if (F_ISSET(ecp, E_NAMEDISCARD)) { + free(ecp->if_name); + ecp->if_name = NULL; + } + return (0); + } + + /* + * ecp->clen will be 0 for the first discarded command, but + * may not be 0 for subsequent ones, e.g. if the original + * command was ":g/xx/@a|s/b/c/", then when we discard the + * command pushed on the stack by the @a, we have to resume + * the global command which included the substitute command. + */ + if (ecp->clen != 0) + return (0); + + /* + * If it's an @, global or v command, we may need to continue + * the command on a different line. + */ + if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { + /* Discard any exhausted ranges. */ + while ((rp = ecp->rq.cqh_first) != (void *)&ecp->rq) + if (rp->start > rp->stop) { + CIRCLEQ_REMOVE(&ecp->rq, rp, q); + free(rp); + } else + break; + + /* If there's another range, continue with it. */ + if (rp != (void *)&ecp->rq) + break; + + /* If it's a global/v command, fix up the last line. */ + if (FL_ISSET(ecp->agv_flags, + AGV_GLOBAL | AGV_V) && ecp->range_lno != OOBLNO) + if (db_exist(sp, ecp->range_lno)) + sp->lno = ecp->range_lno; + else { + if (db_last(sp, &sp->lno)) + return (1); + if (sp->lno == 0) + sp->lno = 1; + } + free(ecp->o_cp); } - cur->lno += total; - *cmdp = cmd; - *cmdlenp = cmdlen; + /* Discard the EXCMD. */ + LIST_REMOVE(ecp, q); + free(ecp); } + + /* + * We only get here if it's an active @, global or v command. Set + * the current line number, and get a new copy of the command for + * the parser. Note, the original pointer almost certainly moved, + * so we have play games. + * + * See ex.h for a discussion of SEARCH_TERMINATION. + */ + ecp->cp = ecp->o_cp; + memmove(ecp->cp, + ecp->cp + ecp->o_clen + SEARCH_TERMINATION, ecp->o_clen); + ecp->clen = ecp->o_clen; + ecp->range_lno = sp->lno = rp->start++; + + if (FL_ISSET(ecp->agv_flags, AGV_GLOBAL | AGV_V)) + F_SET(sp, SC_EX_GLOBAL); return (0); } /* + * ex_discard -- + * Discard any pending ex commands. + */ +static int +ex_discard(sp) + SCR *sp; +{ + GS *gp; + EXCMD *ecp; + RANGE *rp; + + /* + * We know the first command can't be an AGV command, so we don't + * process it specially. We do, however, nail the command itself. + */ + for (gp = sp->gp; (ecp = gp->ecq.lh_first) != &gp->excmd;) { + if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { + while ((rp = ecp->rq.cqh_first) != (void *)&ecp->rq) { + CIRCLEQ_REMOVE(&ecp->rq, rp, q); + free(rp); + } + free(ecp->o_cp); + } + LIST_REMOVE(ecp, q); + free(ecp); + } + gp->ecq.lh_first->clen = 0; + return (0); +} + +/* + * ex_unknown -- + * Display an unknown command name. + */ +static void +ex_unknown(sp, cmd, len) + SCR *sp; + char *cmd; + size_t len; +{ + size_t blen; + char *bp; + + GET_SPACE_GOTO(sp, bp, blen, len + 1); + bp[len] = '\0'; + memmove(bp, cmd, len); + msgq_str(sp, M_ERR, bp, "098|The %s command is unknown"); + FREE_SPACE(sp, bp, blen); + +alloc_err: + return; +} + +/* * 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. + * The vi text input routine needs to know if ex thinks this is an + * [un]abbreviate command, so it can turn off abbreviations. See + * the usual ranting in the vi/v_txt_ev.c:txt_abbrev() routine. + * + * PUBLIC: int ex_is_abbrev __P((char *, size_t)); */ int ex_is_abbrev(name, len) @@ -1813,9 +2210,11 @@ ex_is_abbrev(name, len) /* * 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. + * The vi text input routine needs to know if ex thinks this is an + * unmap command, so it can turn off input mapping. See the usual + * ranting in the vi/v_txt_ev.c:txt_unmap() routine. + * + * PUBLIC: int ex_is_unmap __P((char *, size_t)); */ int ex_is_unmap(name, len) @@ -1835,7 +2234,11 @@ ex_is_unmap(name, len) cp == &cmds[C_UNMAP]); } -static __inline EXCMDLIST const * +/* + * ex_comm_search -- + * Search for a command name. + */ +static EXCMDLIST const * ex_comm_search(name, len) char *name; size_t len; @@ -1853,14 +2256,99 @@ ex_comm_search(name, len) return (NULL); } -static void -badlno(sp, lno) +/* + * ex_badaddr -- + * Display a bad address message. + * + * PUBLIC: void ex_badaddr + * PUBLIC: __P((SCR *, EXCMDLIST const *, enum badaddr, enum nresult)); + */ +void +ex_badaddr(sp, cp, ba, nret) SCR *sp; + EXCMDLIST const *cp; + enum badaddr ba; + enum nresult nret; +{ recno_t lno; + + switch (nret) { + case NUM_OK: + break; + case NUM_ERR: + msgq(sp, M_SYSERR, NULL); + return; + case NUM_OVER: + msgq(sp, M_ERR, "099|Address value overflow"); + return; + case NUM_UNDER: + msgq(sp, M_ERR, "100|Address value underflow"); + return; + } + + /* + * When encountering an address error, tell the user if there's no + * underlying file, that's the real problem. + */ + if (sp->ep == NULL) { + ex_emsg(sp, cp->name, EXM_NOFILEYET); + return; + } + + switch (ba) { + case A_COMBO: + msgq(sp, M_ERR, "101|Illegal address combination"); + break; + case A_EOF: + if (db_last(sp, &lno)) + return; + if (lno != 0) { + msgq(sp, M_ERR, + "102|Illegal address: only %lu lines in the file", + lno); + break; + } + /* FALLTHROUGH */ + case A_EMPTY: + msgq(sp, M_ERR, "103|Illegal address: the file is empty"); + break; + case A_NOTSET: + abort(); + /* NOTREACHED */ + case A_ZERO: + msgq(sp, M_ERR, + "104|The %s command doesn't permit an address of 0", + cp->name); + break; + } + return; +} + +#if defined(DEBUG) && defined(COMLOG) +/* + * ex_comlog -- + * Log ex commands. + */ +static void +ex_comlog(sp, ecp) + SCR *sp; + EXCMD *ecp; { - 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" : ""); + TRACE(sp, "ecmd: %s", ecp->cmd->name); + if (ecp->addrcnt > 0) { + TRACE(sp, " a1 %d", ecp->addr1.lno); + if (ecp->addrcnt > 1) + TRACE(sp, " a2: %d", ecp->addr2.lno); + } + if (ecp->lineno) + TRACE(sp, " line %d", ecp->lineno); + if (ecp->flags) + TRACE(sp, " flags 0x%x", ecp->flags); + if (F_ISSET(&exc, E_BUFFER)) + TRACE(sp, " buffer %c", ecp->buffer); + if (ecp->argc) + for (cnt = 0; cnt < ecp->argc; ++cnt) + TRACE(sp, " arg %d: {%s}", cnt, ecp->argv[cnt]->bp); + TRACE(sp, "\n"); } +#endif |