summaryrefslogtreecommitdiffstats
path: root/contrib/nvi/ex
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>1996-11-01 06:45:43 +0000
committerpeter <peter@FreeBSD.org>1996-11-01 06:45:43 +0000
commit59cc89c2c2e686da3bdab2d5cfac4f33462d29fe (patch)
tree88f923c9c0be2e2a225a9b21716fd582de668b42 /contrib/nvi/ex
downloadFreeBSD-src-59cc89c2c2e686da3bdab2d5cfac4f33462d29fe.zip
FreeBSD-src-59cc89c2c2e686da3bdab2d5cfac4f33462d29fe.tar.gz
Import of nvi-1.79, minus a few bits that we dont need (eg: postscript
files, curses, db, regex etc that we already have). The other glue will follow shortly. Obtained from: Keith Bostic <bostic@bostic.com>
Diffstat (limited to 'contrib/nvi/ex')
-rw-r--r--contrib/nvi/ex/ex.awk6
-rw-r--r--contrib/nvi/ex/ex.c2370
-rw-r--r--contrib/nvi/ex/ex.h228
-rw-r--r--contrib/nvi/ex/ex_abbrev.c117
-rw-r--r--contrib/nvi/ex/ex_append.c277
-rw-r--r--contrib/nvi/ex/ex_args.c327
-rw-r--r--contrib/nvi/ex/ex_argv.c756
-rw-r--r--contrib/nvi/ex/ex_at.c126
-rw-r--r--contrib/nvi/ex/ex_bang.c186
-rw-r--r--contrib/nvi/ex/ex_cd.c129
-rw-r--r--contrib/nvi/ex/ex_cmd.c457
-rw-r--r--contrib/nvi/ex/ex_cscope.c1057
-rw-r--r--contrib/nvi/ex/ex_delete.c65
-rw-r--r--contrib/nvi/ex/ex_display.c145
-rw-r--r--contrib/nvi/ex/ex_edit.c153
-rw-r--r--contrib/nvi/ex/ex_equal.c59
-rw-r--r--contrib/nvi/ex/ex_file.c80
-rw-r--r--contrib/nvi/ex/ex_filter.c316
-rw-r--r--contrib/nvi/ex/ex_global.c328
-rw-r--r--contrib/nvi/ex/ex_init.c417
-rw-r--r--contrib/nvi/ex/ex_join.c177
-rw-r--r--contrib/nvi/ex/ex_map.c121
-rw-r--r--contrib/nvi/ex/ex_mark.c45
-rw-r--r--contrib/nvi/ex/ex_mkexrc.c101
-rw-r--r--contrib/nvi/ex/ex_move.c198
-rw-r--r--contrib/nvi/ex/ex_open.c46
-rw-r--r--contrib/nvi/ex/ex_perl.c69
-rw-r--r--contrib/nvi/ex/ex_preserve.c103
-rw-r--r--contrib/nvi/ex/ex_print.c352
-rw-r--r--contrib/nvi/ex/ex_put.c51
-rw-r--r--contrib/nvi/ex/ex_quit.c46
-rw-r--r--contrib/nvi/ex/ex_read.c360
-rw-r--r--contrib/nvi/ex/ex_screen.c138
-rw-r--r--contrib/nvi/ex/ex_script.c798
-rw-r--r--contrib/nvi/ex/ex_set.c46
-rw-r--r--contrib/nvi/ex/ex_shell.c378
-rw-r--r--contrib/nvi/ex/ex_shift.c191
-rw-r--r--contrib/nvi/ex/ex_source.c85
-rw-r--r--contrib/nvi/ex/ex_stop.c51
-rw-r--r--contrib/nvi/ex/ex_subst.c1459
-rw-r--r--contrib/nvi/ex/ex_tag.c1324
-rw-r--r--contrib/nvi/ex/ex_tcl.c80
-rw-r--r--contrib/nvi/ex/ex_txt.c430
-rw-r--r--contrib/nvi/ex/ex_undo.c77
-rw-r--r--contrib/nvi/ex/ex_usage.c196
-rw-r--r--contrib/nvi/ex/ex_util.c234
-rw-r--r--contrib/nvi/ex/ex_version.c39
-rw-r--r--contrib/nvi/ex/ex_visual.c161
-rw-r--r--contrib/nvi/ex/ex_write.c375
-rw-r--r--contrib/nvi/ex/ex_yank.c46
-rw-r--r--contrib/nvi/ex/ex_z.c150
-rw-r--r--contrib/nvi/ex/script.h23
-rw-r--r--contrib/nvi/ex/tag.h107
-rw-r--r--contrib/nvi/ex/version.h2
54 files changed, 15658 insertions, 0 deletions
diff --git a/contrib/nvi/ex/ex.awk b/contrib/nvi/ex/ex.awk
new file mode 100644
index 0000000..3ee372e
--- /dev/null
+++ b/contrib/nvi/ex/ex.awk
@@ -0,0 +1,6 @@
+# @(#)ex.awk 10.1 (Berkeley) 6/8/95
+
+/^\/\* C_[0-9A-Z_]* \*\/$/ {
+ printf("#define %s %d\n", $2, cnt++);
+ next;
+}
diff --git a/contrib/nvi/ex/ex.c b/contrib/nvi/ex/ex.c
new file mode 100644
index 0000000..f92d8f7
--- /dev/null
+++ b/contrib/nvi/ex/ex.c
@@ -0,0 +1,2370 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex.c 10.57 (Berkeley) 10/10/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+#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 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 --
+ * Main ex loop.
+ *
+ * PUBLIC: int ex __P((SCR **));
+ */
+int
+ex(spp)
+ SCR **spp;
+{
+ EX_PRIVATE *exp;
+ GS *gp;
+ MSGS *mp;
+ SCR *sp;
+ TEXT *tp;
+ u_int32_t flags;
+
+ sp = *spp;
+ gp = sp->gp;
+ exp = EXP(sp);
+
+ /* Start the ex screen. */
+ if (ex_init(sp))
+ return (1);
+
+ /* 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, errors should have name and line info. */
+ if (F_ISSET(gp, G_SCRIPTED)) {
+ gp->excmd.if_lno = 1;
+ gp->excmd.if_name = "script";
+ }
+
+ /*
+ * !!!
+ * 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);
+ for (;; ++gp->excmd.if_lno) {
+ /* Display status line and flush. */
+ if (F_ISSET(sp, SC_STATUS)) {
+ if (!F_ISSET(sp, SC_EX_SILENT))
+ msgq_status(sp, sp->lno, 0);
+ F_CLR(sp, SC_STATUS);
+ }
+ (void)ex_fflush(sp);
+
+ /* Set the flags the user can reset. */
+ if (O_ISSET(sp, O_BEAUTIFY))
+ LF_SET(TXT_BEAUTIFY);
+ if (O_ISSET(sp, O_PROMPT))
+ LF_SET(TXT_PROMPT);
+
+ /* Clear any current interrupts, and get a command. */
+ CLR_INTERRUPT(sp);
+ if (ex_txt(sp, &sp->tiq, ':', flags))
+ return (1);
+ if (INTERRUPTED(sp)) {
+ (void)ex_puts(sp, "\n");
+ (void)ex_fflush(sp);
+ continue;
+ }
+
+ /* Initialize the command structure. */
+ CLEAR_EX_PARSER(&gp->excmd);
+
+ /*
+ * If the user entered a single carriage return, send
+ * ex_cmd() a separator -- it discards single newlines.
+ */
+ tp = sp->tiq.cqh_first;
+ if (tp->len == 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);
+
+ if (ex_cmd(sp) && F_ISSET(gp, G_SCRIPTED))
+ return (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));
+ }
+ }
+ return (0);
+}
+
+/*
+ * 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_cmd(sp)
+ SCR *sp;
+{
+ enum nresult nret;
+ EX_PRIVATE *exp;
+ EXCMD *ecp;
+ GS *gp;
+ MARK cur;
+ recno_t lno;
+ size_t arg1_len, discard, 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);
+
+ /*
+ * 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.
+ */
+loop: ecp = gp->ecq.lh_first;
+
+ /* 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;
+ }
+
+ /*
+ * If a move to the end of the file is scheduled for this command,
+ * do it now.
+ */
+ if (F_ISSET(ecp, E_MOVETOEND)) {
+ if (db_last(sp, &sp->lno))
+ goto rfail;
+ sp->cno = 0;
+ F_CLR(ecp, E_MOVETOEND);
+ }
+
+ /* 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);
+ }
+
+ /* (Re)initialize the EXCMD structure, preserving some flags. */
+ CLEAR_EX_CMD(ecp);
+
+ /* Initialize the argument structures. */
+ if (argv_init(sp, ecp))
+ goto err;
+
+ /* Initialize +cmd, saved command information. */
+ arg1 = NULL;
+ ecp->save_cmdlen = 0;
+
+ /* Skip <blank>s, empty lines. */
+ 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;
+
+ /*
+ * !!!
+ * 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 (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.
+ */
+ 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 (; ecp->clen > 0; ++ecp->cp, --ecp->clen) {
+ ch = *ecp->cp;
+ if (!isblank(ch))
+ break;
+ }
+
+ /*
+ * The last point at which an empty line can mean do nothing.
+ *
+ * !!!
+ * 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 .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.
+ *
+ * !!!
+ * 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 (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 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 (F_ISSET(ecp, E_NRSEP) &&
+ ecp->clen != 0 && (ecp->clen != 1 || ecp->cp[0] != '\004'))
+ F_CLR(ecp, E_NRSEP);
+
+ /* Parse command addresses. */
+ if (ex_range(sp, ecp, &tmp))
+ goto rfail;
+ if (tmp)
+ goto err;
+
+ /*
+ * 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;
+ }
+
+ /*
+ * 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 <blank> separated)
+ * command separators were very strange. For example, the command
+ * |||<carriage-return>, 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!#&*<=>@~"
+ 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 = ecp->cp;
+ ecp->clen > 0; --ecp->clen, ++ecp->cp)
+ if (!isalpha(*ecp->cp))
+ break;
+ if ((namelen = ecp->cp - p) == 0) {
+ msgq(sp, M_ERR, "080|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.
+ *
+ * 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.
+ */
+ 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] == '^' || 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 'F': case 'N': case 'P': case 'T': case 'V':
+ newscreen = 1;
+ p[0] = tolower(p[0]);
+ break;
+ }
+
+ /*
+ * 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 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 ((ecp->cmd = ex_comm_search(p, namelen)) == NULL)
+ switch (p[0]) {
+ case 'k':
+ if (namelen == 2) {
+ 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:
+unknown: if (newscreen)
+ p[0] = toupper(p[0]);
+ ex_unknown(sp, p, namelen);
+ 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];
+
+ /*
+ * !!!
+ * 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;
+ }
+
+ /*
+ * Multiple < and > characters; another "feature". Note,
+ * The string passed to the underlying function may not be
+ * nul terminated in this case.
+ */
+ 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, ecp, p, ecp->cp - p))
+ goto err;
+ }
+
+ /* Set the format style flags for the next command. */
+ 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. */
+ ecp->cmd = &cmds[C_PRINT];
+
+ /* Set the saved format flags. */
+ 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 no idea why globals are
+ * exempted, but it's (ahem) historic practice.)
+ */
+ if (ecp->addrcnt == 0 && !F_ISSET(sp, SC_EX_GLOBAL)) {
+ ecp->addrcnt = 1;
+ ecp->addr1.lno = sp->lno + 1;
+ ecp->addr1.cno = sp->cno;
+ }
+
+ F_SET(ecp, E_USELASTCMD);
+ }
+
+ /*
+ * !!!
+ * 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. <CR>'s after the set didn't change to
+ * the new format, but :1p would.
+ */
+ if (O_ISSET(sp, O_NUMBER)) {
+ F_SET(ecp, E_OPTNUM);
+ FL_SET(ecp->iflags, E_C_HASH);
+ } else
+ F_CLR(ecp, E_OPTNUM);
+
+ /* Check for ex mode legality. */
+ if (F_ISSET(sp, SC_EX) && (F_ISSET(ecp->cmd, E_VIONLY) || newscreen)) {
+ msgq(sp, M_ERR,
+ "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 (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, 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 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 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
+ * :s/|/PIPE/
+ * :read !spell % | columnate
+ * :global/pattern/p|l
+ *
+ * It's not quite as simple as it sounds, however. The command:
+ *
+ * :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 of syntax) delimited the RE's
+ * based on its delimiter and not anything so irretrievably vulgar as a
+ * command syntax.
+ *
+ * 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.
+ */
+ discard = 0; /* Characters discarded from the command. */
+ arg1_len = 0;
+ 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 (ecp->clen > 0 && *ecp->cp == '!') {
+ ++ecp->cp;
+ --ecp->clen;
+ FL_SET(ecp->iflags, E_C_FORCE);
+
+ /* Reset, don't reparse. */
+ ecp->save_cmd = ecp->cp;
+ }
+ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp)
+ if (!isblank(*ecp->cp))
+ 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. The escaping
+ * characters are stripped as no longer useful.
+ */
+ 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) {
+ ++discard;
+ --ecp->clen;
+ ch = *++ecp->cp;
+ } else if (isblank(ch))
+ break;
+ *p++ = ch;
+ }
+ arg1_len = ecp->cp - arg1;
+
+ /* Reset, so the first argument isn't reparsed. */
+ ecp->save_cmd = ecp->cp;
+ }
+ } else if (ecp->cmd == &cmds[C_BANG] ||
+ ecp->cmd == &cmds[C_GLOBAL] || ecp->cmd == &cmds[C_V]) {
+ /*
+ * QUOTING NOTE:
+ *
+ * We use backslashes to escape <newline> characters, although
+ * this wasn't historic practice for the bang command. It was
+ * for the global and v commands, and it's common usage when
+ * doing text insert during the command. Escaping characters
+ * are stripped as no longer useful.
+ */
+ for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) {
+ ch = *ecp->cp;
+ if (ch == '\\' && ecp->clen > 1 && ecp->cp[1] == '\n') {
+ ++discard;
+ --ecp->clen;
+ ch = *++ecp->cp;
+
+ ++gp->if_lno;
+ ++ecp->if_lno;
+ } else if (ch == '\n')
+ break;
+ *p++ = ch;
+ }
+ } else if (ecp->cmd == &cmds[C_READ] || ecp->cmd == &cmds[C_WRITE]) {
+ /*
+ * For write commands, if the next character is a <blank>, and
+ * the next non-blank character is a '!', it's a filter command
+ * and we want to eat everything up to the <newline>. For read
+ * commands, if the next non-blank character is a '!', it's a
+ * filter command and we want to eat everything up to the next
+ * <newline>. Otherwise, we're done.
+ */
+ for (tmp = 0; ecp->clen > 0; --ecp->clen, ++ecp->cp) {
+ ch = *ecp->cp;
+ if (isblank(ch))
+ tmp = 1;
+ else
+ break;
+ }
+ if (ecp->clen > 0 && ch == '!' &&
+ (ecp->cmd == &cmds[C_READ] || tmp))
+ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp)
+ if (ecp->cp[0] == '\n')
+ break;
+ } 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 s command.
+ */
+ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp)
+ if (!isblank(ecp->cp[0]))
+ break;
+
+ 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:
+ *
+ * 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 = *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.
+ *
+ * QUOTING NOTE:
+ *
+ * Historically, vi permitted ^V's to escape <newline>'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 they're
+ * no longer useful.
+ */
+ vi_address = ecp->clen != 0 && ecp->cp[0] != '\n';
+ for (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') {
+ ++gp->if_lno;
+ ++ecp->if_lno;
+ }
+ ++discard;
+ --ecp->clen;
+ ++ecp->cp;
+ ch = tmp;
+ }
+ } else if (ch == '\n' || ch == '|') {
+ if (ch == '\n')
+ F_SET(ecp, E_NEWLINE);
+ --ecp->clen;
+ break;
+ }
+ *p++ = ch;
+ }
+
+ /*
+ * Save off the next command information, go back to the
+ * original start of the command.
+ */
+ 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) - discard;
+
+ /*
+ * 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 (ecp->cmd == &cmds[C_SET])
+ for (p = ecp->cp, len = ecp->clen; 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_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 or if we just used
+ * the current cursor.
+ */
+ switch (F_ISSET(ecp, E_ADDR1 | E_ADDR2 | E_ADDR2_ALL | E_ADDR2_NONE)) {
+ case E_ADDR1: /* One address: */
+ switch (ecp->addrcnt) {
+ case 0: /* Default cursor/empty file. */
+ 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) {
+ ecp->addr1.lno = 0;
+ F_SET(ecp, E_ADDR_ZERO);
+ } else
+ ecp->addr1.lno = sp->lno;
+ } else
+ ecp->addr1.lno = sp->lno;
+ ecp->addr1.cno = sp->cno;
+ break;
+ case 1:
+ break;
+ case 2: /* Lose the first address. */
+ ecp->addrcnt = 1;
+ ecp->addr1 = ecp->addr2;
+ }
+ break;
+ case E_ADDR2_NONE: /* Zero/two addresses: */
+ if (ecp->addrcnt == 0) /* Default to nothing. */
+ break;
+ goto two_addr;
+ case E_ADDR2_ALL: /* Zero/two addresses: */
+ 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 (F_ISSET(ecp, E_ADDR_ZERODEF) &&
+ ecp->addr2.lno == 0) {
+ ecp->addr1.lno = 0;
+ F_SET(ecp, E_ADDR_ZERO);
+ } else
+ 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_addr: switch (ecp->addrcnt) {
+ case 0: /* Default cursor/empty file. */
+ 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) {
+ ecp->addr1.lno = ecp->addr2.lno = 0;
+ F_SET(ecp, E_ADDR_ZERO);
+ } else
+ ecp->addr1.lno =
+ ecp->addr2.lno = sp->lno;
+ } else
+ ecp->addr1.lno = ecp->addr2.lno = sp->lno;
+ ecp->addr1.cno = ecp->addr2.cno = sp->cno;
+ break;
+ case 1: /* Default to first address. */
+ ecp->addrcnt = 2;
+ ecp->addr2 = ecp->addr1;
+ break;
+ case 2:
+ break;
+ }
+ break;
+ default:
+ if (ecp->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 (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 && ecp->addr2.lno > lno)
+ ecp->addr2.lno = lno;
+ }
+
+ 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 (ecp->clen > 0 && *ecp->cp == '!') {
+ ++ecp->cp;
+ --ecp->clen;
+ FL_SET(ecp->iflags, E_C_FORCE);
+ }
+ continue;
+ }
+
+ /* Skip leading <blank>s. */
+ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp)
+ if (!isblank(*ecp->cp))
+ break;
+ if (ecp->clen == 0)
+ 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 (; ecp->clen; --ecp->clen, ++ecp->cp)
+ switch (*ecp->cp) {
+ case '+':
+ ++ecp->flagoff;
+ break;
+ case '-':
+ case '^':
+ --ecp->flagoff;
+ break;
+ case '#':
+ F_CLR(ecp, E_OPTNUM);
+ FL_SET(ecp->iflags, E_C_HASH);
+ exp->fdef |= E_C_HASH;
+ break;
+ case 'l':
+ FL_SET(ecp->iflags, E_C_LIST);
+ exp->fdef |= E_C_LIST;
+ break;
+ case 'p':
+ FL_SET(ecp->iflags, E_C_PRINT);
+ exp->fdef |= E_C_PRINT;
+ break;
+ default:
+ goto end_case1;
+ }
+end_case1: break;
+ case '2': /* -, ., +, ^ */
+ case '3': /* -, ., +, ^, = */
+ for (; ecp->clen; --ecp->clen, ++ecp->cp)
+ switch (*ecp->cp) {
+ case '-':
+ FL_SET(ecp->iflags, E_C_DASH);
+ break;
+ case '.':
+ FL_SET(ecp->iflags, E_C_DOT);
+ break;
+ case '+':
+ FL_SET(ecp->iflags, E_C_PLUS);
+ break;
+ case '^':
+ FL_SET(ecp->iflags, E_C_CARAT);
+ break;
+ case '=':
+ if (*p == '3') {
+ FL_SET(ecp->iflags, E_C_EQUAL);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ goto end_case23;
+ }
+end_case23: 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 ((ecp->cp[0] == '+' || ecp->cp[0] == '-' ||
+ ecp->cp[0] == '^' || ecp->cp[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(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(*ecp->cp) && (*p != '+' ||
+ (*ecp->cp != '+' && *ecp->cp != '-')))
+ break;
+ /* If a signed value, set appropriate flags. */
+ 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(&ltmp, 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;
+ }
+ ecp->clen -= (t - ecp->cp);
+ ecp->cp = t;
+
+ /*
+ * 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.
+ *
+ * Set a count flag; some underlying commands (see
+ * join) do different things with counts than with
+ * line addresses.
+ */
+ if (*p == 'a') {
+ ecp->addr1 = ecp->addr2;
+ ecp->addr2.lno = ecp->addr1.lno + ltmp - 1;
+ } else
+ ecp->count = ltmp;
+ FL_SET(ecp->iflags, E_C_COUNT);
+ break;
+ case 'f': /* file */
+ if (argv_exp2(sp, ecp, ecp->cp, ecp->clen))
+ goto err;
+ goto arg_cnt_chk;
+ case 'l': /* line */
+ /*
+ * 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 (!isaddr) {
+ msgq_str(sp, M_ERR, ecp->cp,
+ "084|%s: bad line specification");
+ goto err;
+ }
+ /*
+ * 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;
+ }
+ ecp->lineno = cur.lno;
+ break;
+ case 'S': /* string, file exp. */
+ 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, ecp, ecp->cp, ecp->clen))
+ goto err;
+ 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.
+ *
+ * First there was the word.
+ */
+ 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)) {
+ ++ecp->cp;
+ --ecp->clen;
+ break;
+ } else
+ *p++ = ch;
+ }
+ if (argv_exp0(sp, ecp, t, p - t))
+ goto err;
+
+ /* Delete intervening whitespace. */
+ for (; ecp->clen > 0;
+ --ecp->clen, ++ecp->cp) {
+ ch = *ecp->cp;
+ if (!isblank(ch))
+ break;
+ }
+ if (ecp->clen == 0)
+ goto usage;
+
+ /* Followed by the string. */
+ 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, ecp, t, p - t))
+ goto err;
+ goto addr_verify;
+ case 'w': /* word */
+ if (argv_exp3(sp, ecp, ecp->cp, ecp->clen))
+ goto err;
+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.
+ */
+ tmp = *p - '0';
+ if ((*++p != 'o' || exp->argsoff != 0) &&
+ exp->argsoff != tmp)
+ goto usage;
+ }
+ goto addr_verify;
+ default:
+ msgq(sp, M_ERR,
+ "085|Internal syntax table error (%s: %s)",
+ ecp->cmd->name, KEY_NAME(sp, *p));
+ }
+ }
+
+ /* Skip trailing whitespace. */
+ 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.
+ */
+ 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. Check the addresses here,
+ * because this is a place where all ex addresses pass through.
+ * (They don't all pass through ex_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:
+ /*
+ * 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 (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:
+ 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;
+ }
+
+ /*
+ * 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.
+ *
+ * !!!
+ * 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 ((F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_NOPRDEF)) &&
+ F_ISSET(ecp, E_USELASTCMD) && vi_address == 0) {
+ switch (ecp->addrcnt) {
+ case 2:
+ 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:
+ 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;
+ }
+ ecp->cp = ecp->save_cmd;
+ ecp->clen = ecp->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(ecp, E_ABSMARK)) {
+ cur.lno = sp->lno;
+ cur.cno = sp->cno;
+ F_CLR(ecp, E_ABSMARK);
+ if (mark_set(sp, ABSMARK1, &cur, 1))
+ goto err;
+ }
+
+#if defined(DEBUG) && defined(COMLOG)
+ ex_comlog(sp, ecp);
+#endif
+ /* Increment the command count if not called from vi. */
+ 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 (sp->ep != NULL && !F_ISSET(sp, SC_EX_GLOBAL))
+ (void)log_cursor(sp);
+
+ /*
+ * !!!
+ * There are two special commands for the purposes of this code: the
+ * default command (<carriage-return>) or the scrolling commands (^D
+ * and <EOF>) as the first non-<blank> 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 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 (F_ISSET(ecp, E_NRSEP)) {
+ if (sp->ep != NULL &&
+ F_ISSET(sp, SC_EX) && !F_ISSET(gp, G_SCRIPTED) &&
+ (F_ISSET(ecp, E_USELASTCMD) || ecp->cmd == &cmds[C_SCROLL]))
+ gp->scr_ex_adjust(sp, EX_TERM_SCROLL);
+ F_CLR(ecp, E_NRSEP);
+ }
+
+ /*
+ * 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_SCRIPTED))
+ F_SET(sp, SC_EXIT_FORCE);
+ goto err;
+ }
+
+#ifdef DEBUG
+ /* 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
+ /*
+ * 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))
+ mod_rpt(sp);
+
+ /*
+ * Integrate any offset parsed by the underlying command, and make
+ * sure the referenced line exists.
+ *
+ * XXX
+ * 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 (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 (!NPFITS(MAX_REC_NUMBER, sp->lno, ecp->flagoff)) {
+ ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER);
+ goto err;
+ }
+ if (!db_exist(sp, sp->lno + ecp->flagoff)) {
+ msgq(sp, M_ERR,
+ "089|Flag offset past end-of-file");
+ goto err;
+ }
+ }
+ sp->lno += ecp->flagoff;
+ }
+
+ /*
+ * If the command executed successfully, we 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 (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 (FL_ISSET(ecp->iflags, E_CLRFLAG))
+ FL_CLR(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT);
+
+ /* 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 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(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);
+ }
+ }
+
+ /*
+ * 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 an additional
+ * <literal next> character. We know that it will fit because we
+ * discarded at least one space and the + character.
+ */
+ if (arg1_len != 0) {
+ /*
+ * 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;
+ memcpy(ecp->save_cmd, arg1, arg1_len);
+
+ /*
+ * Any commands executed from a +cmd are executed starting at
+ * the first column of the last line 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;
+
+ /*
+ * !!!
+ * 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 (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 (ecp->clen > 1)
+ ecp->save_cmdlen = 1;
+ break;
+ }
+ }
+ 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);
+}
+
+/*
+ * ex_range --
+ * Get a line range for ex commands, or perform a vi ex address search.
+ *
+ * PUBLIC: int ex_range __P((SCR *, EXCMD *, int *));
+ */
+int
+ex_range(sp, ecp, errp)
+ SCR *sp;
+ EXCMD *ecp;
+ int *errp;
+{
+ enum { ADDR_FOUND, ADDR_NEED, ADDR_NONE } addr;
+ GS *gp;
+ EX_PRIVATE *exp;
+ MARK m;
+ int isaddr;
+
+ *errp = 0;
+
+ /*
+ * 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);
+ }
+ /*
+ * !!!
+ * 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 (addr == ADDR_FOUND) {
+ ex_badaddr(sp, NULL, A_COMBO, NUM_OK);
+ *errp = 1;
+ return (0);
+ }
+ 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. */
+ /* 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. */
+ ++ecp->cp;
+ --ecp->clen;
+ break;
+ default:
+ /* Get a line specification. */
+ if (ex_line(sp, ecp, &m, &isaddr, errp))
+ return (1);
+ 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:
+ ecp->addr1 = m;
+ ecp->addrcnt = 1;
+ break;
+ case 1:
+ ecp->addr2 = m;
+ ecp->addrcnt = 2;
+ break;
+ case 2:
+ ecp->addr1 = ecp->addr2;
+ ecp->addr2 = m;
+ break;
+ }
+ addr = ADDR_FOUND;
+ break;
+ }
+
+ /*
+ * !!!
+ * Vi ex address searches are indifferent to order or trailing
+ * semi-colons.
+ */
+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,
+ "094|The second address is smaller than the first");
+ *errp = 1;
+ }
+ return (0);
+}
+
+/*
+ * 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
+ * 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 probably still not exactly historic practice, although I think
+ * it's fairly close.
+ */
+static int
+ex_line(sp, ecp, mp, isaddrp, errp)
+ SCR *sp;
+ EXCMD *ecp;
+ MARK *mp;
+ int *isaddrp, *errp;
+{
+ enum nresult nret;
+ EX_PRIVATE *exp;
+ GS *gp;
+ long total, val;
+ int isneg;
+ int (*sf) __P((SCR *, MARK *, MARK *, char *, size_t, char **, u_int));
+ char *endp;
+
+ gp = sp->gp;
+ exp = EXP(sp);
+
+ *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. */
+ *isaddrp = 1;
+ F_SET(ecp, E_ABSMARK);
+
+ mp->cno = 0;
+ if (db_last(sp, &mp->lno))
+ return (1);
+ ++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':
+ *isaddrp = 1;
+ F_SET(ecp, E_ABSMARK);
+
+ 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. */
+ *isaddrp = 1;
+ F_SET(ecp, E_ABSMARK);
+
+ if (ecp->clen == 1) {
+ msgq(sp, M_ERR, "095|No mark name supplied");
+ *errp = 1;
+ return (0);
+ }
+ 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. */
+ /*
+ * !!!
+ * 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 (ecp->clen < 2 ||
+ ecp->cp[1] != '/' && ecp->cp[1] != '?') {
+ msgq(sp, M_ERR, "096|\\ not followed by / or ?");
+ *errp = 1;
+ return (0);
+ }
+ ++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: mp->lno = sp->lno;
+ mp->cno = sp->cno;
+ if (sf(sp, mp, mp, ecp->cp, ecp->clen, &endp,
+ SEARCH_MSG | SEARCH_PARSE | SEARCH_SET |
+ (F_ISSET(ecp, E_SEARCH_WMSG) ? SEARCH_WMSG : 0))) {
+ *errp = 1;
+ return (0);
+ }
+
+ /* Fix up the command pointers. */
+ ecp->clen -= (endp - ecp->cp);
+ ecp->cp = endp;
+
+ *isaddrp = 1;
+ F_SET(ecp, E_ABSMARK);
+ break;
+ case '.': /* Current position. */
+ *isaddrp = 1;
+ mp->cno = sp->cno;
+
+ /* If an empty file, then '.' is 0, not 1. */
+ if (sp->lno == 1) {
+ if (db_last(sp, &mp->lno))
+ return (1);
+ if (mp->lno != 0)
+ mp->lno = 1;
+ } else
+ 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. If no address yet found, the offset
+ * is relative to ".".
+ */
+ 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;
+ }
+ /*
+ * 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;
+ }
+ }
+ }
+
+ /*
+ * 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);
+ }
+
+ /* 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.
+ */
+ ecp->cp = ecp->o_cp;
+ memcpy(ecp->cp, ecp->cp + ecp->o_clen, 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';
+ memcpy(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. 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)
+ 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. 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)
+ 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]);
+}
+
+/*
+ * ex_comm_search --
+ * Search for a command name.
+ */
+static 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);
+}
+
+/*
+ * 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;
+{
+ 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
diff --git a/contrib/nvi/ex/ex.h b/contrib/nvi/ex/ex.h
new file mode 100644
index 0000000..5870990
--- /dev/null
+++ b/contrib/nvi/ex/ex.h
@@ -0,0 +1,228 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ *
+ * @(#)ex.h 10.24 (Berkeley) 8/12/96
+ */
+
+#define PROMPTCHAR ':' /* Prompt using a colon. */
+
+typedef struct _excmdlist { /* Ex command table structure. */
+ char *name; /* Command name, underlying function. */
+ int (*fn) __P((SCR *, EXCMD *));
+
+#define E_ADDR1 0x00000001 /* One address. */
+#define E_ADDR2 0x00000002 /* Two addresses. */
+#define E_ADDR2_ALL 0x00000004 /* Zero/two addresses; zero == all. */
+#define E_ADDR2_NONE 0x00000008 /* Zero/two addresses; zero == none. */
+#define E_ADDR_ZERO 0x00000010 /* 0 is a legal addr1. */
+#define E_ADDR_ZERODEF 0x00000020 /* 0 is default addr1 of empty files. */
+#define E_AUTOPRINT 0x00000040 /* Command always sets autoprint. */
+#define E_CLRFLAG 0x00000080 /* Clear the print (#, l, p) flags. */
+#define E_NEWSCREEN 0x00000100 /* Create a new screen. */
+#define E_SECURE 0x00000200 /* Permission denied if O_SECURE set. */
+#define E_VIONLY 0x00000400 /* Meaningful only in vi. */
+#define __INUSE1 0xfffff800 /* Same name space as EX_PRIVATE. */
+ u_int16_t flags;
+
+ char *syntax; /* Syntax script. */
+ char *usage; /* Usage line. */
+ char *help; /* Help line. */
+} EXCMDLIST;
+
+#define MAXCMDNAMELEN 12 /* Longest command name. */
+extern EXCMDLIST const cmds[]; /* Table of ex commands. */
+
+/*
+ * !!!
+ * QUOTING NOTE:
+ *
+ * Historically, .exrc files and EXINIT variables could only use ^V as an
+ * escape character, neither ^Q or a user specified character worked. We
+ * enforce that here, just in case someone depends on it.
+ */
+#define IS_ESCAPE(sp, cmdp, ch) \
+ (F_ISSET(cmdp, E_VLITONLY) ? \
+ (ch) == CH_LITERAL : KEY_VAL(sp, ch) == K_VLNEXT)
+
+/*
+ * File state must be checked for each command -- any ex command may be entered
+ * at any time, and most of them won't work well if a file hasn't yet been read
+ * in. Historic vi generally took the easy way out and dropped core.
+ */
+#define NEEDFILE(sp, cmdp) { \
+ if ((sp)->ep == NULL) { \
+ ex_emsg(sp, (cmdp)->cmd->name, EXM_NOFILEYET); \
+ return (1); \
+ } \
+}
+
+/* Range structures for global and @ commands. */
+typedef struct _range RANGE;
+struct _range { /* Global command range. */
+ CIRCLEQ_ENTRY(_range) q; /* Linked list of ranges. */
+ recno_t start, stop; /* Start/stop of the range. */
+};
+
+/* Ex command structure. */
+struct _excmd {
+ LIST_ENTRY(_excmd) q; /* Linked list of commands. */
+
+ char *if_name; /* Associated file. */
+ recno_t if_lno; /* Associated line number. */
+
+ /* Clear the structure for the ex parser. */
+#define CLEAR_EX_PARSER(cmdp) \
+ memset(&((cmdp)->cp), 0, ((char *)&(cmdp)->flags - \
+ (char *)&((cmdp)->cp)) + sizeof((cmdp)->flags))
+
+ char *cp; /* Current command text. */
+ size_t clen; /* Current command length. */
+
+ char *save_cmd; /* Remaining command. */
+ size_t save_cmdlen; /* Remaining command length. */
+
+ EXCMDLIST const *cmd; /* Command: entry in command table. */
+ EXCMDLIST rcmd; /* Command: table entry/replacement. */
+
+ CIRCLEQ_HEAD(_rh, _range) rq; /* @/global range: linked list. */
+ recno_t range_lno; /* @/global range: set line number. */
+ char *o_cp; /* Original @/global command. */
+ size_t o_clen; /* Original @/global command length. */
+#define AGV_AT 0x01 /* @ buffer execution. */
+#define AGV_AT_NORANGE 0x02 /* @ buffer execution without range. */
+#define AGV_GLOBAL 0x04 /* global command. */
+#define AGV_V 0x08 /* v command. */
+#define AGV_ALL (AGV_AT | AGV_AT_NORANGE | AGV_GLOBAL | AGV_V)
+ u_int8_t agv_flags;
+
+ /* Clear the structure before each ex command. */
+#define CLEAR_EX_CMD(cmdp) { \
+ u_int32_t L__f = F_ISSET(cmdp, E_PRESERVE); \
+ memset(&((cmdp)->buffer), 0, ((char *)&(cmdp)->flags - \
+ (char *)&((cmdp)->buffer)) + sizeof((cmdp)->flags)); \
+ F_SET(cmdp, L__f); \
+}
+
+ CHAR_T buffer; /* Command: named buffer. */
+ recno_t lineno; /* Command: line number. */
+ long count; /* Command: signed count. */
+ long flagoff; /* Command: signed flag offset. */
+ int addrcnt; /* Command: addresses (0, 1 or 2). */
+ MARK addr1; /* Command: 1st address. */
+ MARK addr2; /* Command: 2nd address. */
+ ARGS **argv; /* Command: array of arguments. */
+ int argc; /* Command: count of arguments. */
+
+#define E_C_BUFFER 0x00001 /* Buffer name specified. */
+#define E_C_CARAT 0x00002 /* ^ flag. */
+#define E_C_COUNT 0x00004 /* Count specified. */
+#define E_C_COUNT_NEG 0x00008 /* Count was signed negative. */
+#define E_C_COUNT_POS 0x00010 /* Count was signed positive. */
+#define E_C_DASH 0x00020 /* - flag. */
+#define E_C_DOT 0x00040 /* . flag. */
+#define E_C_EQUAL 0x00080 /* = flag. */
+#define E_C_FORCE 0x00100 /* ! flag. */
+#define E_C_HASH 0x00200 /* # flag. */
+#define E_C_LIST 0x00400 /* l flag. */
+#define E_C_PLUS 0x00800 /* + flag. */
+#define E_C_PRINT 0x01000 /* p flag. */
+ u_int16_t iflags; /* User input information. */
+
+#define __INUSE2 0x000004ff /* Same name space as EXCMDLIST. */
+#define E_BLIGNORE 0x00000800 /* Ignore blank lines. */
+#define E_NAMEDISCARD 0x00001000 /* Free/discard the name. */
+#define E_NOAUTO 0x00002000 /* Don't do autoprint output. */
+#define E_NOPRDEF 0x00004000 /* Don't print as default. */
+#define E_NRSEP 0x00008000 /* Need to line adjust ex output. */
+#define E_OPTNUM 0x00010000 /* Number edit option affected. */
+#define E_VLITONLY 0x00020000 /* Use ^V quoting only. */
+#define E_PRESERVE 0x0003f800 /* Bits to preserve across commands. */
+
+#define E_ABSMARK 0x00040000 /* Set the absolute mark. */
+#define E_ADDR_DEF 0x00080000 /* Default addresses used. */
+#define E_DELTA 0x00100000 /* Search address with delta. */
+#define E_MODIFY 0x00200000 /* File name expansion modified arg. */
+#define E_MOVETOEND 0x00400000 /* Move to the end of the file first. */
+#define E_NEWLINE 0x00800000 /* Found ending <newline>. */
+#define E_SEARCH_WMSG 0x01000000 /* Display search-wrapped message. */
+#define E_USELASTCMD 0x02000000 /* Use the last command. */
+#define E_VISEARCH 0x04000000 /* It's really a vi search command. */
+ u_int32_t flags; /* Current flags. */
+};
+
+/* Ex private, per-screen memory. */
+typedef struct _ex_private {
+ CIRCLEQ_HEAD(_tqh, _tagq) tq; /* Tag queue. */
+ TAILQ_HEAD(_tagfh, _tagf) tagfq;/* Tag file list. */
+ LIST_HEAD(_csch, _csc) cscq; /* Cscope connection list. */
+ char *tag_last; /* Saved last tag string. */
+
+ CHAR_T *lastbcomm; /* Last bang command. */
+
+ ARGS **args; /* Command: argument list. */
+ int argscnt; /* Command: argument list count. */
+ int argsoff; /* Command: offset into arguments. */
+
+ u_int32_t fdef; /* Saved E_C_* default command flags. */
+
+ char *ibp; /* File line input buffer. */
+ size_t ibp_len; /* File line input buffer length. */
+
+ /*
+ * Buffers for the ex output. The screen/vi support doesn't do any
+ * character buffering of any kind. We do it here so that we're not
+ * calling the screen output routines on every character.
+ *
+ * XXX
+ * Change to grow dynamically.
+ */
+ char obp[1024]; /* Ex output buffer. */
+ size_t obp_len; /* Ex output buffer length. */
+
+#define EXP_CSCINIT 0x01 /* Cscope initialized. */
+ u_int8_t flags;
+} EX_PRIVATE;
+#define EXP(sp) ((EX_PRIVATE *)((sp)->ex_private))
+
+/*
+ * Filter actions:
+ *
+ * FILTER_BANG !: filter text through the utility.
+ * FILTER_RBANG !: read from the utility (without stdin).
+ * FILTER_READ read: read from the utility (with stdin).
+ * FILTER_WRITE write: write to the utility, display its output.
+ */
+enum filtertype { FILTER_BANG, FILTER_RBANG, FILTER_READ, FILTER_WRITE };
+
+/* Ex common error messages. */
+typedef enum {
+ EXM_EMPTYBUF, /* Empty buffer. */
+ EXM_FILECOUNT, /* Too many file names. */
+ EXM_NOCANON, /* No terminal interface. */
+ EXM_NOCANON_F, /* EXM_NOCANO: filter version. */
+ EXM_NOFILEYET, /* Illegal until a file read in. */
+ EXM_NOPREVBUF, /* No previous buffer specified. */
+ EXM_NOPREVRE, /* No previous RE specified. */
+ EXM_NOSUSPEND, /* No suspension. */
+ EXM_SECURE, /* Illegal if secure edit option set. */
+ EXM_SECURE_F, /* EXM_SECURE: filter version */
+ EXM_USAGE /* Standard usage message. */
+} exm_t;
+
+/* Ex address error types. */
+enum badaddr { A_COMBO, A_EMPTY, A_EOF, A_NOTSET, A_ZERO };
+
+/* Ex common tag error messages. */
+typedef enum {
+ TAG_BADLNO, /* Tag line doesn't exist. */
+ TAG_EMPTY, /* Tags stack is empty. */
+ TAG_SEARCH /* Tags search pattern wasn't found. */
+} tagmsg_t;
+
+#include "ex_def.h"
+#include "ex_extern.h"
diff --git a/contrib/nvi/ex/ex_abbrev.c b/contrib/nvi/ex/ex_abbrev.c
new file mode 100644
index 0000000..231098c
--- /dev/null
+++ b/contrib/nvi/ex/ex_abbrev.c
@@ -0,0 +1,117 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_abbrev.c 10.7 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+/*
+ * ex_abbr -- :abbreviate [key replacement]
+ * Create an abbreviation or display abbreviations.
+ *
+ * PUBLIC: int ex_abbr __P((SCR *, EXCMD *));
+ */
+int
+ex_abbr(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ CHAR_T *p;
+ size_t len;
+
+ switch (cmdp->argc) {
+ case 0:
+ if (seq_dump(sp, SEQ_ABBREV, 0) == 0)
+ msgq(sp, M_INFO, "105|No abbreviations to display");
+ return (0);
+ case 2:
+ break;
+ default:
+ abort();
+ }
+
+ /*
+ * Check for illegal characters.
+ *
+ * !!!
+ * Another fun one, historically. See vi/v_ntext.c:txt_abbrev() for
+ * details. The bottom line is that all abbreviations have to end
+ * with a "word" character, because it's the transition from word to
+ * non-word characters that triggers the test for an abbreviation. In
+ * addition, because of the way the test is done, there can't be any
+ * transitions from word to non-word character (or vice-versa) other
+ * than between the next-to-last and last characters of the string,
+ * and there can't be any <blank> characters. Warn the user.
+ */
+ if (!inword(cmdp->argv[0]->bp[cmdp->argv[0]->len - 1])) {
+ msgq(sp, M_ERR,
+ "106|Abbreviations must end with a \"word\" character");
+ return (1);
+ }
+ for (p = cmdp->argv[0]->bp; *p != '\0'; ++p)
+ if (isblank(p[0])) {
+ msgq(sp, M_ERR,
+ "107|Abbreviations may not contain tabs or spaces");
+ return (1);
+ }
+ if (cmdp->argv[0]->len > 2)
+ for (p = cmdp->argv[0]->bp,
+ len = cmdp->argv[0]->len - 2; len; --len, ++p)
+ if (inword(p[0]) != inword(p[1])) {
+ msgq(sp, M_ERR,
+"108|Abbreviations may not mix word/non-word characters, except at the end");
+ return (1);
+ }
+
+ if (seq_set(sp, NULL, 0, cmdp->argv[0]->bp, cmdp->argv[0]->len,
+ cmdp->argv[1]->bp, cmdp->argv[1]->len, SEQ_ABBREV, SEQ_USERDEF))
+ return (1);
+
+ F_SET(sp->gp, G_ABBREV);
+ return (0);
+}
+
+/*
+ * ex_unabbr -- :unabbreviate key
+ * Delete an abbreviation.
+ *
+ * PUBLIC: int ex_unabbr __P((SCR *, EXCMD *));
+ */
+int
+ex_unabbr(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ ARGS *ap;
+
+ ap = cmdp->argv[0];
+ if (!F_ISSET(sp->gp, G_ABBREV) ||
+ seq_delete(sp, ap->bp, ap->len, SEQ_ABBREV)) {
+ msgq_str(sp, M_ERR, ap->bp,
+ "109|\"%s\" is not an abbreviation");
+ return (1);
+ }
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_append.c b/contrib/nvi/ex/ex_append.c
new file mode 100644
index 0000000..8d89e12
--- /dev/null
+++ b/contrib/nvi/ex/ex_append.c
@@ -0,0 +1,277 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_append.c 10.30 (Berkeley) 10/23/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+enum which {APPEND, CHANGE, INSERT};
+
+static int ex_aci __P((SCR *, EXCMD *, enum which));
+
+/*
+ * ex_append -- :[line] a[ppend][!]
+ * Append one or more lines of new text after the specified line,
+ * or the current line if no address is specified.
+ *
+ * PUBLIC: int ex_append __P((SCR *, EXCMD *));
+ */
+int
+ex_append(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (ex_aci(sp, cmdp, APPEND));
+}
+
+/*
+ * ex_change -- :[line[,line]] c[hange][!] [count]
+ * Change one or more lines to the input text.
+ *
+ * PUBLIC: int ex_change __P((SCR *, EXCMD *));
+ */
+int
+ex_change(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (ex_aci(sp, cmdp, CHANGE));
+}
+
+/*
+ * ex_insert -- :[line] i[nsert][!]
+ * Insert one or more lines of new text before the specified line,
+ * or the current line if no address is specified.
+ *
+ * PUBLIC: int ex_insert __P((SCR *, EXCMD *));
+ */
+int
+ex_insert(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (ex_aci(sp, cmdp, INSERT));
+}
+
+/*
+ * ex_aci --
+ * Append, change, insert in ex.
+ */
+static int
+ex_aci(sp, cmdp, cmd)
+ SCR *sp;
+ EXCMD *cmdp;
+ enum which cmd;
+{
+ CHAR_T *p, *t;
+ GS *gp;
+ TEXT *tp;
+ TEXTH tiq;
+ recno_t cnt, lno;
+ size_t len;
+ u_int32_t flags;
+ int need_newline;
+
+ gp = sp->gp;
+ NEEDFILE(sp, cmdp);
+
+ /*
+ * If doing a change, replace lines for as long as possible. Then,
+ * append more lines or delete remaining lines. Changes to an empty
+ * file are appends, inserts are the same as appends to the previous
+ * line.
+ *
+ * !!!
+ * Set the address to which we'll append. We set sp->lno to this
+ * address as well so that autoindent works correctly when get text
+ * from the user.
+ */
+ lno = cmdp->addr1.lno;
+ sp->lno = lno;
+ if ((cmd == CHANGE || cmd == INSERT) && lno != 0)
+ --lno;
+
+ /*
+ * !!!
+ * If the file isn't empty, cut changes into the unnamed buffer.
+ */
+ if (cmd == CHANGE && cmdp->addr1.lno != 0 &&
+ (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE) ||
+ del(sp, &cmdp->addr1, &cmdp->addr2, 1)))
+ return (1);
+
+ /*
+ * !!!
+ * Anything that was left after the command separator becomes part
+ * of the inserted text. Apparently, it was common usage to enter:
+ *
+ * :g/pattern/append|stuff1
+ *
+ * and append the line of text "stuff1" to the lines containing the
+ * pattern. It was also historically legal to enter:
+ *
+ * :append|stuff1
+ * stuff2
+ * .
+ *
+ * and the text on the ex command line would be appended as well as
+ * the text inserted after it. There was an historic bug however,
+ * that the user had to enter *two* terminating lines (the '.' lines)
+ * to terminate text input mode, in this case. This whole thing
+ * could be taken too far, however. Entering:
+ *
+ * :append|stuff1\
+ * stuff2
+ * stuff3
+ * .
+ *
+ * i.e. mixing and matching the forms confused the historic vi, and,
+ * not only did it take two terminating lines to terminate text input
+ * mode, but the trailing backslashes were retained on the input. We
+ * match historic practice except that we discard the backslashes.
+ *
+ * Input lines specified on the ex command line lines are separated by
+ * <newline>s. If there is a trailing delimiter an empty line was
+ * inserted. There may also be a leading delimiter, which is ignored
+ * unless it's also a trailing delimiter. It is possible to encounter
+ * a termination line, i.e. a single '.', in a global command, but not
+ * necessary if the text insert command was the last of the global
+ * commands.
+ */
+ if (cmdp->save_cmdlen != 0) {
+ for (p = cmdp->save_cmd,
+ len = cmdp->save_cmdlen; len > 0; p = t) {
+ for (t = p; len > 0 && t[0] != '\n'; ++t, --len);
+ if (t != p || len == 0) {
+ if (F_ISSET(sp, SC_EX_GLOBAL) &&
+ t - p == 1 && p[0] == '.') {
+ ++t;
+ if (len > 0)
+ --len;
+ break;
+ }
+ if (db_append(sp, 1, lno++, p, t - p))
+ return (1);
+ }
+ if (len != 0) {
+ ++t;
+ if (--len == 0 &&
+ db_append(sp, 1, lno++, "", 0))
+ return (1);
+ }
+ }
+ /*
+ * If there's any remaining text, we're in a global, and
+ * there's more command to parse.
+ *
+ * !!!
+ * We depend on the fact that non-global commands will eat the
+ * rest of the command line as text input, and before getting
+ * any text input from the user. Otherwise, we'd have to save
+ * off the command text before or during the call to the text
+ * input function below.
+ */
+ if (len != 0)
+ cmdp->save_cmd = t;
+ cmdp->save_cmdlen = len;
+ }
+
+ if (F_ISSET(sp, SC_EX_GLOBAL)) {
+ if ((sp->lno = lno) == 0 && db_exist(sp, 1))
+ sp->lno = 1;
+ return (0);
+ }
+
+ /*
+ * If not in a global command, read from the terminal.
+ *
+ * If this code is called by vi, we want to reset the terminal and use
+ * ex's line get routine. It actually works fine if we use vi's get
+ * routine, but it doesn't look as nice. Maybe if we had a separate
+ * window or something, but getting a line at a time looks awkward.
+ * However, depending on the screen that we're using, that may not
+ * be possible.
+ */
+ if (F_ISSET(sp, SC_VI)) {
+ if (gp->scr_screen(sp, SC_EX)) {
+ ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON);
+ return (1);
+ }
+
+ /* If we're still in the vi screen, move out explicitly. */
+ need_newline = !F_ISSET(sp, SC_SCR_EXWROTE);
+ F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);
+ if (need_newline)
+ (void)ex_puts(sp, "\n");
+
+ /*
+ * !!!
+ * Users of historical versions of vi sometimes get confused
+ * when they enter append mode, and can't seem to get out of
+ * it. Give them an informational message.
+ */
+ (void)ex_puts(sp,
+ msg_cat(sp, "273|Entering ex input mode.", NULL));
+ (void)ex_puts(sp, "\n");
+ (void)ex_fflush(sp);
+ }
+
+ /*
+ * Set input flags; the ! flag turns off autoindent for append,
+ * change and insert.
+ */
+ LF_INIT(TXT_DOTTERM | TXT_NUMBER);
+ if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && O_ISSET(sp, O_AUTOINDENT))
+ LF_SET(TXT_AUTOINDENT);
+ if (O_ISSET(sp, O_BEAUTIFY))
+ LF_SET(TXT_BEAUTIFY);
+
+ /*
+ * This code can't use the common screen TEXTH structure (sp->tiq),
+ * as it may already be in use, e.g. ":append|s/abc/ABC/" would fail
+ * as we are only halfway through the text when the append code fires.
+ * Use a local structure instead. (The ex code would have to use a
+ * local structure except that we're guaranteed to finish remaining
+ * characters in the common TEXTH structure when they were inserted
+ * into the file, above.)
+ */
+ memset(&tiq, 0, sizeof(TEXTH));
+ CIRCLEQ_INIT(&tiq);
+
+ if (ex_txt(sp, &tiq, 0, flags))
+ return (1);
+
+ for (cnt = 0, tp = tiq.cqh_first;
+ tp != (TEXT *)&tiq; ++cnt, tp = tp->q.cqe_next)
+ if (db_append(sp, 1, lno++, tp->lb, tp->len))
+ return (1);
+
+ /*
+ * Set sp->lno to the final line number value (correcting for a
+ * possible 0 value) as that's historically correct for the final
+ * line value, whether or not the user entered any text.
+ */
+ if ((sp->lno = lno) == 0 && db_exist(sp, 1))
+ sp->lno = 1;
+
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_args.c b/contrib/nvi/ex/ex_args.c
new file mode 100644
index 0000000..bc37109
--- /dev/null
+++ b/contrib/nvi/ex/ex_args.c
@@ -0,0 +1,327 @@
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1991, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_args.c 10.16 (Berkeley) 7/13/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+static int ex_N_next __P((SCR *, EXCMD *));
+
+/*
+ * ex_next -- :next [+cmd] [files]
+ * Edit the next file, optionally setting the list of files.
+ *
+ * !!!
+ * The :next command behaved differently from the :rewind command in
+ * historic vi. See nvi/docs/autowrite for details, but the basic
+ * idea was that it ignored the force flag if the autowrite flag was
+ * set. This implementation handles them all identically.
+ *
+ * PUBLIC: int ex_next __P((SCR *, EXCMD *));
+ */
+int
+ex_next(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ ARGS **argv;
+ FREF *frp;
+ int noargs;
+ char **ap;
+
+ /* Check for file to move to. */
+ if (cmdp->argc == 0 && (sp->cargv == NULL || sp->cargv[1] == NULL)) {
+ msgq(sp, M_ERR, "111|No more files to edit");
+ return (1);
+ }
+
+ if (F_ISSET(cmdp, E_NEWSCREEN)) {
+ /* By default, edit the next file in the old argument list. */
+ if (cmdp->argc == 0) {
+ if (argv_exp0(sp,
+ cmdp, sp->cargv[1], strlen(sp->cargv[1])))
+ return (1);
+ return (ex_edit(sp, cmdp));
+ }
+ return (ex_N_next(sp, cmdp));
+ }
+
+ /* Check modification. */
+ if (file_m1(sp,
+ FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE))
+ return (1);
+
+ /* Any arguments are a replacement file list. */
+ if (cmdp->argc) {
+ /* Free the current list. */
+ if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) {
+ for (ap = sp->argv; *ap != NULL; ++ap)
+ free(*ap);
+ free(sp->argv);
+ }
+ F_CLR(sp, SC_ARGNOFREE | SC_ARGRECOVER);
+ sp->cargv = NULL;
+
+ /* Create a new list. */
+ CALLOC_RET(sp,
+ sp->argv, char **, cmdp->argc + 1, sizeof(char *));
+ for (ap = sp->argv,
+ argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv)
+ if ((*ap =
+ v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL)
+ return (1);
+ *ap = NULL;
+
+ /* Switch to the first file. */
+ sp->cargv = sp->argv;
+ if ((frp = file_add(sp, *sp->cargv)) == NULL)
+ return (1);
+ noargs = 0;
+
+ /* Display a file count with the welcome message. */
+ F_SET(sp, SC_STATUS_CNT);
+ } else {
+ if ((frp = file_add(sp, sp->cargv[1])) == NULL)
+ return (1);
+ if (F_ISSET(sp, SC_ARGRECOVER))
+ F_SET(frp, FR_RECOVER);
+ noargs = 1;
+ }
+
+ if (file_init(sp, frp, NULL, FS_SETALT |
+ (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))
+ return (1);
+ if (noargs)
+ ++sp->cargv;
+
+ F_SET(sp, SC_FSWITCH);
+ return (0);
+}
+
+/*
+ * ex_N_next --
+ * New screen version of ex_next.
+ */
+static int
+ex_N_next(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ SCR *new;
+ FREF *frp;
+
+ /* Get a new screen. */
+ if (screen_init(sp->gp, sp, &new))
+ return (1);
+ if (vs_split(sp, new, 0)) {
+ (void)screen_end(new);
+ return (1);
+ }
+
+ /* Get a backing file. */
+ if ((frp = file_add(new, cmdp->argv[0]->bp)) == NULL ||
+ file_init(new, frp, NULL,
+ (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) {
+ (void)vs_discard(new, NULL);
+ (void)screen_end(new);
+ return (1);
+ }
+
+ /* The arguments are a replacement file list. */
+ new->cargv = new->argv = ex_buildargv(sp, cmdp, NULL);
+
+ /* Display a file count with the welcome message. */
+ F_SET(new, SC_STATUS_CNT);
+
+ /* Set up the switch. */
+ sp->nextdisp = new;
+ F_SET(sp, SC_SSWITCH);
+
+ return (0);
+}
+
+/*
+ * ex_prev -- :prev
+ * Edit the previous file.
+ *
+ * PUBLIC: int ex_prev __P((SCR *, EXCMD *));
+ */
+int
+ex_prev(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ FREF *frp;
+
+ if (sp->cargv == sp->argv) {
+ msgq(sp, M_ERR, "112|No previous files to edit");
+ return (1);
+ }
+
+ if (F_ISSET(cmdp, E_NEWSCREEN)) {
+ if (argv_exp0(sp, cmdp, sp->cargv[-1], strlen(sp->cargv[-1])))
+ return (1);
+ return (ex_edit(sp, cmdp));
+ }
+
+ if (file_m1(sp,
+ FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE))
+ return (1);
+
+ if ((frp = file_add(sp, sp->cargv[-1])) == NULL)
+ return (1);
+
+ if (file_init(sp, frp, NULL, FS_SETALT |
+ (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))
+ return (1);
+ --sp->cargv;
+
+ F_SET(sp, SC_FSWITCH);
+ return (0);
+}
+
+/*
+ * ex_rew -- :rew
+ * Re-edit the list of files.
+ *
+ * !!!
+ * Historic practice was that all files would start editing at the beginning
+ * of the file. We don't get this right because we may have multiple screens
+ * and we can't clear the FR_CURSORSET bit for a single screen. I don't see
+ * anyone noticing, but if they do, we'll have to put information into the SCR
+ * structure so we can keep track of it.
+ *
+ * PUBLIC: int ex_rew __P((SCR *, EXCMD *));
+ */
+int
+ex_rew(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ FREF *frp;
+
+ /*
+ * !!!
+ * Historic practice -- you can rewind to the current file.
+ */
+ if (sp->argv == NULL) {
+ msgq(sp, M_ERR, "113|No previous files to rewind");
+ return (1);
+ }
+
+ if (file_m1(sp,
+ FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE))
+ return (1);
+
+ /* Switch to the first one. */
+ sp->cargv = sp->argv;
+ if ((frp = file_add(sp, *sp->cargv)) == NULL)
+ return (1);
+ if (file_init(sp, frp, NULL, FS_SETALT |
+ (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))
+ return (1);
+
+ /* Switch and display a file count with the welcome message. */
+ F_SET(sp, SC_FSWITCH | SC_STATUS_CNT);
+
+ return (0);
+}
+
+/*
+ * ex_args -- :args
+ * Display the list of files.
+ *
+ * PUBLIC: int ex_args __P((SCR *, EXCMD *));
+ */
+int
+ex_args(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ GS *gp;
+ int cnt, col, len, sep;
+ char **ap;
+
+ if (sp->argv == NULL) {
+ (void)msgq(sp, M_ERR, "114|No file list to display");
+ return (0);
+ }
+
+ gp = sp->gp;
+ col = len = sep = 0;
+ for (cnt = 1, ap = sp->argv; *ap != NULL; ++ap) {
+ col += len = strlen(*ap) + sep + (ap == sp->cargv ? 2 : 0);
+ if (col >= sp->cols - 1) {
+ col = len;
+ sep = 0;
+ (void)ex_puts(sp, "\n");
+ } else if (cnt != 1) {
+ sep = 1;
+ (void)ex_puts(sp, " ");
+ }
+ ++cnt;
+
+ (void)ex_printf(sp, "%s%s%s", ap == sp->cargv ? "[" : "",
+ *ap, ap == sp->cargv ? "]" : "");
+ if (INTERRUPTED(sp))
+ break;
+ }
+ (void)ex_puts(sp, "\n");
+ return (0);
+}
+
+/*
+ * ex_buildargv --
+ * Build a new file argument list.
+ *
+ * PUBLIC: char **ex_buildargv __P((SCR *, EXCMD *, char *));
+ */
+char **
+ex_buildargv(sp, cmdp, name)
+ SCR *sp;
+ EXCMD *cmdp;
+ char *name;
+{
+ ARGS **argv;
+ int argc;
+ char **ap, **s_argv;
+
+ argc = cmdp == NULL ? 1 : cmdp->argc;
+ CALLOC(sp, s_argv, char **, argc + 1, sizeof(char *));
+ if ((ap = s_argv) == NULL)
+ return (NULL);
+
+ if (cmdp == NULL) {
+ if ((*ap = v_strdup(sp, name, strlen(name))) == NULL)
+ return (NULL);
+ ++ap;
+ } else
+ for (argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv)
+ if ((*ap =
+ v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL)
+ return (NULL);
+ *ap = NULL;
+ return (s_argv);
+}
diff --git a/contrib/nvi/ex/ex_argv.c b/contrib/nvi/ex/ex_argv.c
new file mode 100644
index 0000000..cc5a201
--- /dev/null
+++ b/contrib/nvi/ex/ex_argv.c
@@ -0,0 +1,756 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_argv.c 10.26 (Berkeley) 9/20/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+static int argv_alloc __P((SCR *, size_t));
+static int argv_comp __P((const void *, const void *));
+static int argv_fexp __P((SCR *, EXCMD *,
+ char *, size_t, char *, size_t *, char **, size_t *, int));
+static int argv_lexp __P((SCR *, EXCMD *, char *));
+static int argv_sexp __P((SCR *, char **, size_t *, size_t *));
+
+/*
+ * argv_init --
+ * Build a prototype arguments list.
+ *
+ * PUBLIC: int argv_init __P((SCR *, EXCMD *));
+ */
+int
+argv_init(sp, excp)
+ SCR *sp;
+ EXCMD *excp;
+{
+ EX_PRIVATE *exp;
+
+ exp = EXP(sp);
+ exp->argsoff = 0;
+ argv_alloc(sp, 1);
+
+ excp->argv = exp->args;
+ excp->argc = exp->argsoff;
+ return (0);
+}
+
+/*
+ * argv_exp0 --
+ * Append a string to the argument list.
+ *
+ * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, char *, size_t));
+ */
+int
+argv_exp0(sp, excp, cmd, cmdlen)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd;
+ size_t cmdlen;
+{
+ EX_PRIVATE *exp;
+
+ exp = EXP(sp);
+ argv_alloc(sp, cmdlen);
+ memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen);
+ exp->args[exp->argsoff]->bp[cmdlen] = '\0';
+ exp->args[exp->argsoff]->len = cmdlen;
+ ++exp->argsoff;
+ excp->argv = exp->args;
+ excp->argc = exp->argsoff;
+ return (0);
+}
+
+/*
+ * argv_exp1 --
+ * Do file name expansion on a string, and append it to the
+ * argument list.
+ *
+ * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, char *, size_t, int));
+ */
+int
+argv_exp1(sp, excp, cmd, cmdlen, is_bang)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd;
+ size_t cmdlen;
+ int is_bang;
+{
+ EX_PRIVATE *exp;
+ size_t blen, len;
+ char *bp, *p, *t;
+
+ GET_SPACE_RET(sp, bp, blen, 512);
+
+ len = 0;
+ exp = EXP(sp);
+ if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
+ FREE_SPACE(sp, bp, blen);
+ return (1);
+ }
+
+ /* If it's empty, we're done. */
+ if (len != 0) {
+ for (p = bp, t = bp + len; p < t; ++p)
+ if (!isblank(*p))
+ break;
+ if (p == t)
+ goto ret;
+ } else
+ goto ret;
+
+ (void)argv_exp0(sp, excp, bp, len);
+
+ret: FREE_SPACE(sp, bp, blen);
+ return (0);
+}
+
+/*
+ * argv_exp2 --
+ * Do file name and shell expansion on a string, and append it to
+ * the argument list.
+ *
+ * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, char *, size_t));
+ */
+int
+argv_exp2(sp, excp, cmd, cmdlen)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd;
+ size_t cmdlen;
+{
+ size_t blen, len, n;
+ int rval;
+ char *bp, *mp, *p;
+
+ GET_SPACE_RET(sp, bp, blen, 512);
+
+#define SHELLECHO "echo "
+#define SHELLOFFSET (sizeof(SHELLECHO) - 1)
+ memcpy(bp, SHELLECHO, SHELLOFFSET);
+ p = bp + SHELLOFFSET;
+ len = SHELLOFFSET;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
+#endif
+
+ if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
+ rval = 1;
+ goto err;
+ }
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "before shell: %d: {%s}\n", len, bp);
+#endif
+
+ /*
+ * Do shell word expansion -- it's very, very hard to figure out what
+ * magic characters the user's shell expects. Historically, it was a
+ * union of v7 shell and csh meta characters. We match that practice
+ * by default, so ":read \%" tries to read a file named '%'. It would
+ * make more sense to pass any special characters through the shell,
+ * but then, if your shell was csh, the above example will behave
+ * differently in nvi than in vi. If you want to get other characters
+ * passed through to your shell, change the "meta" option.
+ *
+ * To avoid a function call per character, we do a first pass through
+ * the meta characters looking for characters that aren't expected
+ * to be there, and then we can ignore them in the user's argument.
+ */
+ if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
+ n = 0;
+ else {
+ for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p)
+ if (isblank(*p) || isalnum(*p))
+ break;
+ p = bp + SHELLOFFSET;
+ n = len - SHELLOFFSET;
+ if (*p != '\0') {
+ for (; n > 0; --n, ++p)
+ if (strchr(mp, *p) != NULL)
+ break;
+ } else
+ for (; n > 0; --n, ++p)
+ if (!isblank(*p) &&
+ !isalnum(*p) && strchr(mp, *p) != NULL)
+ break;
+ }
+
+ /*
+ * If we found a meta character in the string, fork a shell to expand
+ * it. Unfortunately, this is comparatively slow. Historically, it
+ * didn't matter much, since users don't enter meta characters as part
+ * of pathnames that frequently. The addition of filename completion
+ * broke that assumption because it's easy to use. As a result, lots
+ * folks have complained that the expansion code is too slow. So, we
+ * detect filename completion as a special case, and do it internally.
+ * Note that this code assumes that the <asterisk> character is the
+ * match-anything meta character. That feels safe -- if anyone writes
+ * a shell that doesn't follow that convention, I'd suggest giving them
+ * a festive hot-lead enema.
+ */
+ switch (n) {
+ case 0:
+ p = bp + SHELLOFFSET;
+ len -= SHELLOFFSET;
+ rval = argv_exp3(sp, excp, p, len);
+ break;
+ case 1:
+ if (*p == '*') {
+ *p = '\0';
+ rval = argv_lexp(sp, excp, bp + SHELLOFFSET);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ if (argv_sexp(sp, &bp, &blen, &len)) {
+ rval = 1;
+ goto err;
+ }
+ p = bp;
+ rval = argv_exp3(sp, excp, p, len);
+ break;
+ }
+
+err: FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
+
+/*
+ * argv_exp3 --
+ * Take a string and break it up into an argv, which is appended
+ * to the argument list.
+ *
+ * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, char *, size_t));
+ */
+int
+argv_exp3(sp, excp, cmd, cmdlen)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd;
+ size_t cmdlen;
+{
+ EX_PRIVATE *exp;
+ size_t len;
+ int ch, off;
+ char *ap, *p;
+
+ for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
+ /* Skip any leading whitespace. */
+ for (; cmdlen > 0; --cmdlen, ++cmd) {
+ ch = *cmd;
+ if (!isblank(ch))
+ break;
+ }
+ if (cmdlen == 0)
+ break;
+
+ /*
+ * Determine the length of this whitespace delimited
+ * argument.
+ *
+ * QUOTING NOTE:
+ *
+ * Skip any character preceded by the user's quoting
+ * character.
+ */
+ for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
+ ch = *cmd;
+ if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
+ ++cmd;
+ --cmdlen;
+ } else if (isblank(ch))
+ break;
+ }
+
+ /*
+ * Copy the argument into place.
+ *
+ * QUOTING NOTE:
+ *
+ * Lose quote chars.
+ */
+ argv_alloc(sp, len);
+ off = exp->argsoff;
+ exp->args[off]->len = len;
+ for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
+ if (IS_ESCAPE(sp, excp, *ap))
+ ++ap;
+ *p = '\0';
+ }
+ excp->argv = exp->args;
+ excp->argc = exp->argsoff;
+
+#if defined(DEBUG) && 0
+ for (cnt = 0; cnt < exp->argsoff; ++cnt)
+ TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
+#endif
+ return (0);
+}
+
+/*
+ * argv_fexp --
+ * Do file name and bang command expansion.
+ */
+static int
+argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd, *p, **bpp;
+ size_t cmdlen, *lenp, *blenp;
+ int is_bang;
+{
+ EX_PRIVATE *exp;
+ char *bp, *t;
+ size_t blen, len, off, tlen;
+
+ /* Replace file name characters. */
+ for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
+ switch (*cmd) {
+ case '!':
+ if (!is_bang)
+ goto ins_ch;
+ exp = EXP(sp);
+ if (exp->lastbcomm == NULL) {
+ msgq(sp, M_ERR,
+ "115|No previous command to replace \"!\"");
+ return (1);
+ }
+ len += tlen = strlen(exp->lastbcomm);
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ memcpy(p, exp->lastbcomm, tlen);
+ p += tlen;
+ F_SET(excp, E_MODIFY);
+ break;
+ case '%':
+ if ((t = sp->frp->name) == NULL) {
+ msgq(sp, M_ERR,
+ "116|No filename to substitute for %%");
+ return (1);
+ }
+ tlen = strlen(t);
+ len += tlen;
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ memcpy(p, t, tlen);
+ p += tlen;
+ F_SET(excp, E_MODIFY);
+ break;
+ case '#':
+ if ((t = sp->alt_name) == NULL) {
+ msgq(sp, M_ERR,
+ "117|No filename to substitute for #");
+ return (1);
+ }
+ len += tlen = strlen(t);
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ memcpy(p, t, tlen);
+ p += tlen;
+ F_SET(excp, E_MODIFY);
+ break;
+ case '\\':
+ /*
+ * QUOTING NOTE:
+ *
+ * Strip any backslashes that protected the file
+ * expansion characters.
+ */
+ if (cmdlen > 1 &&
+ (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
+ ++cmd;
+ --cmdlen;
+ }
+ /* FALLTHROUGH */
+ default:
+ins_ch: ++len;
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ *p++ = *cmd;
+ }
+
+ /* Nul termination. */
+ ++len;
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ *p = '\0';
+
+ /* Return the new string length, buffer, buffer length. */
+ *lenp = len - 1;
+ *bpp = bp;
+ *blenp = blen;
+ return (0);
+}
+
+/*
+ * argv_alloc --
+ * Make more space for arguments.
+ */
+static int
+argv_alloc(sp, len)
+ SCR *sp;
+ size_t len;
+{
+ ARGS *ap;
+ EX_PRIVATE *exp;
+ int cnt, off;
+
+ /*
+ * Allocate room for another argument, always leaving
+ * enough room for an ARGS structure with a length of 0.
+ */
+#define INCREMENT 20
+ exp = EXP(sp);
+ off = exp->argsoff;
+ if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
+ cnt = exp->argscnt + INCREMENT;
+ REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
+ if (exp->args == NULL) {
+ (void)argv_free(sp);
+ goto mem;
+ }
+ memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
+ exp->argscnt = cnt;
+ }
+
+ /* First argument. */
+ if (exp->args[off] == NULL) {
+ CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
+ if (exp->args[off] == NULL)
+ goto mem;
+ }
+
+ /* First argument buffer. */
+ ap = exp->args[off];
+ ap->len = 0;
+ if (ap->blen < len + 1) {
+ ap->blen = len + 1;
+ REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
+ if (ap->bp == NULL) {
+ ap->bp = NULL;
+ ap->blen = 0;
+ F_CLR(ap, A_ALLOCATED);
+mem: msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+ F_SET(ap, A_ALLOCATED);
+ }
+
+ /* Second argument. */
+ if (exp->args[++off] == NULL) {
+ CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
+ if (exp->args[off] == NULL)
+ goto mem;
+ }
+ /* 0 length serves as end-of-argument marker. */
+ exp->args[off]->len = 0;
+ return (0);
+}
+
+/*
+ * argv_free --
+ * Free up argument structures.
+ *
+ * PUBLIC: int argv_free __P((SCR *));
+ */
+int
+argv_free(sp)
+ SCR *sp;
+{
+ EX_PRIVATE *exp;
+ int off;
+
+ exp = EXP(sp);
+ if (exp->args != NULL) {
+ for (off = 0; off < exp->argscnt; ++off) {
+ if (exp->args[off] == NULL)
+ continue;
+ if (F_ISSET(exp->args[off], A_ALLOCATED))
+ free(exp->args[off]->bp);
+ free(exp->args[off]);
+ }
+ free(exp->args);
+ }
+ exp->args = NULL;
+ exp->argscnt = 0;
+ exp->argsoff = 0;
+ return (0);
+}
+
+/*
+ * argv_lexp --
+ * Find all file names matching the prefix and append them to the
+ * buffer.
+ */
+static int
+argv_lexp(sp, excp, path)
+ SCR *sp;
+ EXCMD *excp;
+ char *path;
+{
+ struct dirent *dp;
+ DIR *dirp;
+ EX_PRIVATE *exp;
+ int off;
+ size_t dlen, len, nlen;
+ char *dname, *name, *p;
+
+ exp = EXP(sp);
+
+ /* Set up the name and length for comparison. */
+ if ((p = strrchr(path, '/')) == NULL) {
+ dname = ".";
+ dlen = 0;
+ name = path;
+ } else {
+ if (p == path) {
+ dname = "/";
+ dlen = 1;
+ } else {
+ *p = '\0';
+ dname = path;
+ dlen = strlen(path);
+ }
+ name = p + 1;
+ }
+ nlen = strlen(name);
+
+ /*
+ * XXX
+ * We don't use the d_namlen field, it's not portable enough; we
+ * assume that d_name is nul terminated, instead.
+ */
+ if ((dirp = opendir(dname)) == NULL) {
+ msgq_str(sp, M_SYSERR, dname, "%s");
+ return (1);
+ }
+ for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
+ if (nlen == 0) {
+ if (dp->d_name[0] == '.')
+ continue;
+ len = strlen(dp->d_name);
+ } else {
+ len = strlen(dp->d_name);
+ if (len < nlen || memcmp(dp->d_name, name, nlen))
+ continue;
+ }
+
+ /* Directory + name + slash + null. */
+ argv_alloc(sp, dlen + len + 2);
+ p = exp->args[exp->argsoff]->bp;
+ if (dlen != 0) {
+ memcpy(p, dname, dlen);
+ p += dlen;
+ if (dlen > 1 || dname[0] != '/')
+ *p++ = '/';
+ }
+ memcpy(p, dp->d_name, len + 1);
+ exp->args[exp->argsoff]->len = dlen + len + 1;
+ ++exp->argsoff;
+ excp->argv = exp->args;
+ excp->argc = exp->argsoff;
+ }
+ closedir(dirp);
+
+ if (off == exp->argsoff) {
+ /*
+ * If we didn't find a match, complain that the expansion
+ * failed. We can't know for certain that's the error, but
+ * it's a good guess, and it matches historic practice.
+ */
+ msgq(sp, M_ERR, "304|Shell expansion failed");
+ return (1);
+ }
+ qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
+ return (0);
+}
+
+/*
+ * argv_comp --
+ * Alphabetic comparison.
+ */
+static int
+argv_comp(a, b)
+ const void *a, *b;
+{
+ return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp));
+}
+
+/*
+ * argv_sexp --
+ * Fork a shell, pipe a command through it, and read the output into
+ * a buffer.
+ */
+static int
+argv_sexp(sp, bpp, blenp, lenp)
+ SCR *sp;
+ char **bpp;
+ size_t *blenp, *lenp;
+{
+ enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
+ FILE *ifp;
+ pid_t pid;
+ size_t blen, len;
+ int ch, std_output[2];
+ char *bp, *p, *sh, *sh_path;
+
+ /* Secure means no shell access. */
+ if (O_ISSET(sp, O_SECURE)) {
+ msgq(sp, M_ERR,
+"289|Shell expansions not supported when the secure edit option is set");
+ return (1);
+ }
+
+ sh_path = O_STR(sp, O_SHELL);
+ if ((sh = strrchr(sh_path, '/')) == NULL)
+ sh = sh_path;
+ else
+ ++sh;
+
+ /* Local copies of the buffer variables. */
+ bp = *bpp;
+ blen = *blenp;
+
+ /*
+ * There are two different processes running through this code, named
+ * the utility (the shell) and the parent. The utility reads standard
+ * input and writes standard output and standard error output. The
+ * parent writes to the utility, reads its standard output and ignores
+ * its standard error output. Historically, the standard error output
+ * was discarded by vi, as it produces a lot of noise when file patterns
+ * don't match.
+ *
+ * The parent reads std_output[0], and the utility writes std_output[1].
+ */
+ ifp = NULL;
+ std_output[0] = std_output[1] = -1;
+ if (pipe(std_output) < 0) {
+ msgq(sp, M_SYSERR, "pipe");
+ return (1);
+ }
+ if ((ifp = fdopen(std_output[0], "r")) == NULL) {
+ msgq(sp, M_SYSERR, "fdopen");
+ goto err;
+ }
+
+ /*
+ * Do the minimal amount of work possible, the shell is going to run
+ * briefly and then exit. We sincerely hope.
+ */
+ switch (pid = vfork()) {
+ case -1: /* Error. */
+ msgq(sp, M_SYSERR, "vfork");
+err: if (ifp != NULL)
+ (void)fclose(ifp);
+ else if (std_output[0] != -1)
+ close(std_output[0]);
+ if (std_output[1] != -1)
+ close(std_output[0]);
+ return (1);
+ case 0: /* Utility. */
+ /* Redirect stdout to the write end of the pipe. */
+ (void)dup2(std_output[1], STDOUT_FILENO);
+
+ /* Close the utility's file descriptors. */
+ (void)close(std_output[0]);
+ (void)close(std_output[1]);
+ (void)close(STDERR_FILENO);
+
+ /*
+ * XXX
+ * Assume that all shells have -c.
+ */
+ execl(sh_path, sh, "-c", bp, NULL);
+ msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
+ _exit(127);
+ default: /* Parent. */
+ /* Close the pipe ends the parent won't use. */
+ (void)close(std_output[1]);
+ break;
+ }
+
+ /*
+ * Copy process standard output into a buffer.
+ *
+ * !!!
+ * Historic vi apparently discarded leading \n and \r's from
+ * the shell output stream. We don't on the grounds that any
+ * shell that does that is broken.
+ */
+ for (p = bp, len = 0, ch = EOF;
+ (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len)
+ if (blen < 5) {
+ ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2);
+ p = bp + len;
+ blen = *blenp - len;
+ }
+
+ /* Delete the final newline, nul terminate the string. */
+ if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
+ --p;
+ --len;
+ }
+ *p = '\0';
+ *lenp = len;
+ *bpp = bp; /* *blenp is already updated. */
+
+ if (ferror(ifp))
+ goto ioerr;
+ if (fclose(ifp)) {
+ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
+alloc_err: rval = SEXP_ERR;
+ } else
+ rval = SEXP_OK;
+
+ /*
+ * Wait for the process. If the shell process fails (e.g., "echo $q"
+ * where q wasn't a defined variable) or if the returned string has
+ * no characters or only blank characters, (e.g., "echo $5"), complain
+ * that the shell expansion failed. We can't know for certain that's
+ * the error, but it's a good guess, and it matches historic practice.
+ * This won't catch "echo foo_$5", but that's not a common error and
+ * historic vi didn't catch it either.
+ */
+ if (proc_wait(sp, (long)pid, sh, 1, 0))
+ rval = SEXP_EXPANSION_ERR;
+
+ for (p = bp; len; ++p, --len)
+ if (!isblank(*p))
+ break;
+ if (len == 0)
+ rval = SEXP_EXPANSION_ERR;
+
+ if (rval == SEXP_EXPANSION_ERR)
+ msgq(sp, M_ERR, "304|Shell expansion failed");
+
+ return (rval == SEXP_OK ? 0 : 1);
+}
diff --git a/contrib/nvi/ex/ex_at.c b/contrib/nvi/ex/ex_at.c
new file mode 100644
index 0000000..e9c6c59
--- /dev/null
+++ b/contrib/nvi/ex/ex_at.c
@@ -0,0 +1,126 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_at.c 10.12 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_at -- :@[@ | buffer]
+ * :*[* | buffer]
+ *
+ * Execute the contents of the buffer.
+ *
+ * PUBLIC: int ex_at __P((SCR *, EXCMD *));
+ */
+int
+ex_at(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ CB *cbp;
+ CHAR_T name;
+ EXCMD *ecp;
+ RANGE *rp;
+ TEXT *tp;
+ size_t len;
+ char *p;
+
+ /*
+ * !!!
+ * Historically, [@*]<carriage-return> and [@*][@*] executed the most
+ * recently executed buffer in ex mode.
+ */
+ name = FL_ISSET(cmdp->iflags, E_C_BUFFER) ? cmdp->buffer : '@';
+ if (name == '@' || name == '*') {
+ if (!F_ISSET(sp, SC_AT_SET)) {
+ ex_emsg(sp, NULL, EXM_NOPREVBUF);
+ return (1);
+ }
+ name = sp->at_lbuf;
+ }
+ sp->at_lbuf = name;
+ F_SET(sp, SC_AT_SET);
+
+ CBNAME(sp, cbp, name);
+ if (cbp == NULL) {
+ ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF);
+ return (1);
+ }
+
+ /*
+ * !!!
+ * Historically the @ command took a range of lines, and the @ buffer
+ * was executed once per line. The historic vi could be trashed by
+ * this because it didn't notice if the underlying file changed, or,
+ * for that matter, if there were no more lines on which to operate.
+ * For example, take a 10 line file, load "%delete" into a buffer,
+ * and enter :8,10@<buffer>.
+ *
+ * The solution is a bit tricky. If the user specifies a range, take
+ * the same approach as for global commands, and discard the command
+ * if exit or switch to a new file/screen. If the user doesn't specify
+ * the range, continue to execute after a file/screen switch, which
+ * means @ buffers are still useful in a multi-screen environment.
+ */
+ CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD));
+ CIRCLEQ_INIT(&ecp->rq);
+ CALLOC_RET(sp, rp, RANGE *, 1, sizeof(RANGE));
+ rp->start = cmdp->addr1.lno;
+ if (F_ISSET(cmdp, E_ADDR_DEF)) {
+ rp->stop = rp->start;
+ FL_SET(ecp->agv_flags, AGV_AT_NORANGE);
+ } else {
+ rp->stop = cmdp->addr2.lno;
+ FL_SET(ecp->agv_flags, AGV_AT);
+ }
+ CIRCLEQ_INSERT_HEAD(&ecp->rq, rp, q);
+
+ /*
+ * Buffers executed in ex mode or from the colon command line in vi
+ * were ex commands. We can't push it on the terminal queue, since
+ * it has to be executed immediately, and we may be in the middle of
+ * an ex command already. Push the command on the ex command stack.
+ * Build two copies of the command. We need two copies because the
+ * ex parser may step on the command string when it's parsing it.
+ */
+ for (len = 0, tp = cbp->textq.cqh_last;
+ tp != (void *)&cbp->textq; tp = tp->q.cqe_prev)
+ len += tp->len + 1;
+
+ MALLOC_RET(sp, ecp->cp, char *, len * 2);
+ ecp->o_cp = ecp->cp;
+ ecp->o_clen = len;
+ ecp->cp[len] = '\0';
+
+ /* Copy the buffer into the command space. */
+ for (p = ecp->cp + len, tp = cbp->textq.cqh_last;
+ tp != (void *)&cbp->textq; tp = tp->q.cqe_prev) {
+ memcpy(p, tp->lb, tp->len);
+ p += tp->len;
+ *p++ = '\n';
+ }
+
+ LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q);
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_bang.c b/contrib/nvi/ex/ex_bang.c
new file mode 100644
index 0000000..25f3f77
--- /dev/null
+++ b/contrib/nvi/ex/ex_bang.c
@@ -0,0 +1,186 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_bang.c 10.33 (Berkeley) 9/23/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+/*
+ * ex_bang -- :[line [,line]] ! command
+ *
+ * Pass the rest of the line after the ! character to the program named by
+ * the O_SHELL option.
+ *
+ * Historical vi did NOT do shell expansion on the arguments before passing
+ * them, only file name expansion. This means that the O_SHELL program got
+ * "$t" as an argument if that is what the user entered. Also, there's a
+ * special expansion done for the bang command. Any exclamation points in
+ * the user's argument are replaced by the last, expanded ! command.
+ *
+ * There's some fairly amazing slop in this routine to make the different
+ * ways of getting here display the right things. It took a long time to
+ * get it right (wrong?), so be careful.
+ *
+ * PUBLIC: int ex_bang __P((SCR *, EXCMD *));
+ */
+int
+ex_bang(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ enum filtertype ftype;
+ ARGS *ap;
+ EX_PRIVATE *exp;
+ MARK rm;
+ recno_t lno;
+ int rval;
+ const char *msg;
+
+ ap = cmdp->argv[0];
+ if (ap->len == 0) {
+ ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+
+ /* Set the "last bang command" remembered value. */
+ exp = EXP(sp);
+ if (exp->lastbcomm != NULL)
+ free(exp->lastbcomm);
+ if ((exp->lastbcomm = strdup(ap->bp)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+
+ /*
+ * If the command was modified by the expansion, it was historically
+ * redisplayed.
+ */
+ if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, SC_EX_SILENT)) {
+ /*
+ * Display the command if modified. Historic ex/vi displayed
+ * the command if it was modified due to file name and/or bang
+ * expansion. If piping lines in vi, it would be immediately
+ * overwritten by any error or line change reporting.
+ */
+ if (F_ISSET(sp, SC_VI))
+ vs_update(sp, "!", ap->bp);
+ else {
+ (void)ex_printf(sp, "!%s\n", ap->bp);
+ (void)ex_fflush(sp);
+ }
+ }
+
+ /*
+ * If no addresses were specified, run the command. If there's an
+ * underlying file, it's been modified and autowrite is set, write
+ * the file back. If the file has been modified, autowrite is not
+ * set and the warn option is set, tell the user about the file.
+ */
+ if (cmdp->addrcnt == 0) {
+ msg = NULL;
+ if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED))
+ if (O_ISSET(sp, O_AUTOWRITE)) {
+ if (file_aw(sp, FS_ALL))
+ return (0);
+ } else if (O_ISSET(sp, O_WARN) &&
+ !F_ISSET(sp, SC_EX_SILENT))
+ msg = msg_cat(sp,
+ "303|File modified since last write.",
+ NULL);
+
+ /* If we're still in a vi screen, move out explicitly. */
+ (void)ex_exec_proc(sp,
+ cmdp, ap->bp, msg, !F_ISSET(sp, SC_EX | SC_SCR_EXWROTE));
+ }
+
+ /*
+ * If addresses were specified, pipe lines from the file through the
+ * command.
+ *
+ * Historically, vi lines were replaced by both the stdout and stderr
+ * lines of the command, but ex lines by only the stdout lines. This
+ * makes no sense to me, so nvi makes it consistent for both, and
+ * matches vi's historic behavior.
+ */
+ else {
+ NEEDFILE(sp, cmdp);
+
+ /* Autoprint is set historically, even if the command fails. */
+ F_SET(cmdp, E_AUTOPRINT);
+
+ /*
+ * !!!
+ * Historical vi permitted "!!" in an empty file. When this
+ * happens, we arrive here with two addresses of 1,1 and a
+ * bad attitude. The simple solution is to turn it into a
+ * FILTER_READ operation, with the exception that stdin isn't
+ * opened for the utility, and the cursor position isn't the
+ * same. The only historic glitch (I think) is that we don't
+ * put an empty line into the default cut buffer, as historic
+ * vi did. Imagine, if you can, my disappointment.
+ */
+ ftype = FILTER_BANG;
+ if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) {
+ if (db_last(sp, &lno))
+ return (1);
+ if (lno == 0) {
+ cmdp->addr1.lno = cmdp->addr2.lno = 0;
+ ftype = FILTER_RBANG;
+ }
+ }
+ rval = ex_filter(sp, cmdp,
+ &cmdp->addr1, &cmdp->addr2, &rm, ap->bp, ftype);
+
+ /*
+ * If in vi mode, move to the first nonblank.
+ *
+ * !!!
+ * Historic vi wasn't consistent in this area -- if you used
+ * a forward motion it moved to the first nonblank, but if you
+ * did a backward motion it didn't. And, if you followed a
+ * backward motion with a forward motion, it wouldn't move to
+ * the nonblank for either. Going to the nonblank generally
+ * seems more useful and consistent, so we do it.
+ */
+ sp->lno = rm.lno;
+ if (F_ISSET(sp, SC_VI)) {
+ sp->cno = 0;
+ (void)nonblank(sp, sp->lno, &sp->cno);
+ } else
+ sp->cno = rm.cno;
+ }
+
+ /* Ex terminates with a bang, even if the command fails. */
+ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT))
+ (void)ex_puts(sp, "!\n");
+
+ /*
+ * XXX
+ * The ! commands never return an error, so that autoprint always
+ * happens in the ex parser.
+ */
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_cd.c b/contrib/nvi/ex/ex_cd.c
new file mode 100644
index 0000000..3307c7b
--- /dev/null
+++ b/contrib/nvi/ex/ex_cd.c
@@ -0,0 +1,129 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_cd.c 10.10 (Berkeley) 8/12/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_cd -- :cd[!] [directory]
+ * Change directories.
+ *
+ * PUBLIC: int ex_cd __P((SCR *, EXCMD *));
+ */
+int
+ex_cd(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ struct passwd *pw;
+ ARGS *ap;
+ CHAR_T savech;
+ char *dir, *p, *t; /* XXX: END OF THE STACK, DON'T TRUST GETCWD. */
+ char buf[MAXPATHLEN * 2];
+
+ /*
+ * !!!
+ * Historic practice is that the cd isn't attempted if the file has
+ * been modified, unless its name begins with a leading '/' or the
+ * force flag is set.
+ */
+ if (F_ISSET(sp->ep, F_MODIFIED) &&
+ !FL_ISSET(cmdp->iflags, E_C_FORCE) && sp->frp->name[0] != '/') {
+ msgq(sp, M_ERR,
+ "120|File modified since last complete write; write or use ! to override");
+ return (1);
+ }
+
+ switch (cmdp->argc) {
+ case 0:
+ /* If no argument, change to the user's home directory. */
+ if ((dir = getenv("HOME")) == NULL) {
+ if ((pw = getpwuid(getuid())) == NULL ||
+ pw->pw_dir == NULL || pw->pw_dir[0] == '\0') {
+ msgq(sp, M_ERR,
+ "121|Unable to find home directory location");
+ return (1);
+ }
+ dir = pw->pw_dir;
+ }
+ break;
+ case 1:
+ dir = cmdp->argv[0]->bp;
+ break;
+ default:
+ abort();
+ }
+
+ /*
+ * Try the current directory first. If this succeeds, don't display
+ * a message, vi didn't historically, and it should be obvious to the
+ * user where they are.
+ */
+ if (!chdir(dir))
+ return (0);
+
+ /*
+ * If moving to the user's home directory, or, the path begins with
+ * "/", "./" or "../", it's the only place we try.
+ */
+ if (cmdp->argc == 0 ||
+ (ap = cmdp->argv[0])->bp[0] == '/' ||
+ ap->len == 1 && ap->bp[0] == '.' ||
+ ap->len >= 2 && ap->bp[0] == '.' && ap->bp[1] == '.' &&
+ (ap->bp[2] == '/' || ap->bp[2] == '\0'))
+ goto err;
+
+ /* Try the O_CDPATH option values. */
+ for (p = t = O_STR(sp, O_CDPATH);; ++p)
+ if (*p == '\0' || *p == ':') {
+ /*
+ * Empty strings specify ".". The only way to get an
+ * empty string is a leading colon, colons in a row,
+ * or a trailing colon. Or, to put it the other way,
+ * if the length is 1 or less, then we're dealing with
+ * ":XXX", "XXX::XXXX" , "XXX:", or "". Since we've
+ * already tried dot, we ignore tham all.
+ */
+ if (t < p - 1) {
+ savech = *p;
+ *p = '\0';
+ (void)snprintf(buf,
+ sizeof(buf), "%s/%s", t, dir);
+ *p = savech;
+ if (!chdir(buf)) {
+ if (getcwd(buf, sizeof(buf)) != NULL)
+ msgq_str(sp, M_INFO, buf, "122|New current directory: %s");
+ return (0);
+ }
+ }
+ t = p + 1;
+ if (*p == '\0')
+ break;
+ }
+
+err: msgq_str(sp, M_SYSERR, dir, "%s");
+ return (1);
+}
diff --git a/contrib/nvi/ex/ex_cmd.c b/contrib/nvi/ex/ex_cmd.c
new file mode 100644
index 0000000..8f7fc8d
--- /dev/null
+++ b/contrib/nvi/ex/ex_cmd.c
@@ -0,0 +1,457 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_cmd.c 10.20 (Berkeley) 10/10/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+
+/*
+ * This array maps ex command names to command functions.
+ *
+ * The order in which command names are listed below is important --
+ * ambiguous abbreviations are resolved to be the first possible match,
+ * e.g. "r" means "read", not "rewind", because "read" is listed before
+ * "rewind".
+ *
+ * The syntax of the ex commands is unbelievably irregular, and a special
+ * case from beginning to end. Each command has an associated "syntax
+ * script" which describes the "arguments" that are possible. The script
+ * syntax is as follows:
+ *
+ * ! -- ! flag
+ * 1 -- flags: [+-]*[pl#][+-]*
+ * 2 -- flags: [-.+^]
+ * 3 -- flags: [-.+^=]
+ * b -- buffer
+ * c[01+a] -- count (0-N, 1-N, signed 1-N, address offset)
+ * f[N#][or] -- file (a number or N, optional or required)
+ * l -- line
+ * S -- string with file name expansion
+ * s -- string
+ * W -- word string
+ * w[N#][or] -- word (a number or N, optional or required)
+ */
+EXCMDLIST const cmds[] = {
+/* C_SCROLL */
+ {"\004", ex_pr, E_ADDR2,
+ "",
+ "^D",
+ "scroll lines"},
+/* C_BANG */
+ {"!", ex_bang, E_ADDR2_NONE | E_SECURE,
+ "S",
+ "[line [,line]] ! command",
+ "filter lines through commands or run commands"},
+/* C_HASH */
+ {"#", ex_number, E_ADDR2|E_CLRFLAG,
+ "ca1",
+ "[line [,line]] # [count] [l]",
+ "display numbered lines"},
+/* C_SUBAGAIN */
+ {"&", ex_subagain, E_ADDR2,
+ "s",
+ "[line [,line]] & [cgr] [count] [#lp]",
+ "repeat the last subsitution"},
+/* C_STAR */
+ {"*", ex_at, 0,
+ "b",
+ "* [buffer]",
+ "execute a buffer"},
+/* C_SHIFTL */
+ {"<", ex_shiftl, E_ADDR2|E_AUTOPRINT,
+ "ca1",
+ "[line [,line]] <[<...] [count] [flags]",
+ "shift lines left"},
+/* C_EQUAL */
+ {"=", ex_equal, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF,
+ "1",
+ "[line] = [flags]",
+ "display line number"},
+/* C_SHIFTR */
+ {">", ex_shiftr, E_ADDR2|E_AUTOPRINT,
+ "ca1",
+ "[line [,line]] >[>...] [count] [flags]",
+ "shift lines right"},
+/* C_AT */
+ {"@", ex_at, E_ADDR2,
+ "b",
+ "@ [buffer]",
+ "execute a buffer"},
+/* C_APPEND */
+ {"append", ex_append, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF,
+ "!",
+ "[line] a[ppend][!]",
+ "append input to a line"},
+/* C_ABBR */
+ {"abbreviate", ex_abbr, 0,
+ "W",
+ "ab[brev] [word replace]",
+ "specify an input abbreviation"},
+/* C_ARGS */
+ {"args", ex_args, 0,
+ "",
+ "ar[gs]",
+ "display file argument list"},
+/* C_BG */
+ {"bg", ex_bg, E_VIONLY,
+ "",
+ "bg",
+ "put a foreground screen into the background"},
+/* C_CHANGE */
+ {"change", ex_change, E_ADDR2|E_ADDR_ZERODEF,
+ "!ca",
+ "[line [,line]] c[hange][!] [count]",
+ "change lines to input"},
+/* C_CD */
+ {"cd", ex_cd, 0,
+ "!f1o",
+ "cd[!] [directory]",
+ "change the current directory"},
+/* C_CHDIR */
+ {"chdir", ex_cd, 0,
+ "!f1o",
+ "chd[ir][!] [directory]",
+ "change the current directory"},
+/* C_COPY */
+ {"copy", ex_copy, E_ADDR2|E_AUTOPRINT,
+ "l1",
+ "[line [,line]] co[py] line [flags]",
+ "copy lines elsewhere in the file"},
+/* C_CSCOPE */
+ {"cscope", ex_cscope, 0,
+ "!s",
+ "cs[cope] command [args]",
+ "create a set of tags using a cscope command"},
+/*
+ * !!!
+ * Adding new commands starting with 'd' may break the delete command code
+ * in ex_cmd() (the ex parser). Read through the comments there, first.
+ */
+/* C_DELETE */
+ {"delete", ex_delete, E_ADDR2|E_AUTOPRINT,
+ "bca1",
+ "[line [,line]] d[elete][flags] [buffer] [count] [flags]",
+ "delete lines from the file"},
+/* C_DISPLAY */
+ {"display", ex_display, 0,
+ "w1r",
+ "display b[uffers] | c[onnections] | s[creens] | t[ags]",
+ "display buffers, connections, screens or tags"},
+/* C_EDIT */
+ {"edit", ex_edit, E_NEWSCREEN,
+ "f1o",
+ "[Ee][dit][!] [+cmd] [file]",
+ "begin editing another file"},
+/* C_EX */
+ {"ex", ex_edit, E_NEWSCREEN,
+ "f1o",
+ "[Ee]x[!] [+cmd] [file]",
+ "begin editing another file"},
+/* C_EXUSAGE */
+ {"exusage", ex_usage, 0,
+ "w1o",
+ "[exu]sage [command]",
+ "display ex command usage statement"},
+/* C_FILE */
+ {"file", ex_file, 0,
+ "f1o",
+ "f[ile] [name]",
+ "display (and optionally set) file name"},
+/* C_FG */
+ {"fg", ex_fg, E_NEWSCREEN|E_VIONLY,
+ "f1o",
+ "[Ff]g [file]",
+ "bring a backgrounded screen into the foreground"},
+/* C_GLOBAL */
+ {"global", ex_global, E_ADDR2_ALL,
+ "!s",
+ "[line [,line]] g[lobal][!] [;/]RE[;/] [commands]",
+ "execute a global command on lines matching an RE"},
+/* C_HELP */
+ {"help", ex_help, 0,
+ "",
+ "he[lp]",
+ "display help statement"},
+/* C_INSERT */
+ {"insert", ex_insert, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF,
+ "!",
+ "[line] i[nsert][!]",
+ "insert input before a line"},
+/* C_JOIN */
+ {"join", ex_join, E_ADDR2|E_AUTOPRINT,
+ "!ca1",
+ "[line [,line]] j[oin][!] [count] [flags]",
+ "join lines into a single line"},
+/* C_K */
+ {"k", ex_mark, E_ADDR1,
+ "w1r",
+ "[line] k key",
+ "mark a line position"},
+/* C_LIST */
+ {"list", ex_list, E_ADDR2|E_CLRFLAG,
+ "ca1",
+ "[line [,line]] l[ist] [count] [#]",
+ "display lines in an unambiguous form"},
+/* C_MOVE */
+ {"move", ex_move, E_ADDR2|E_AUTOPRINT,
+ "l",
+ "[line [,line]] m[ove] line",
+ "move lines elsewhere in the file"},
+/* C_MARK */
+ {"mark", ex_mark, E_ADDR1,
+ "w1r",
+ "[line] ma[rk] key",
+ "mark a line position"},
+/* C_MAP */
+ {"map", ex_map, 0,
+ "!W",
+ "map[!] [keys replace]",
+ "map input or commands to one or more keys"},
+/* C_MKEXRC */
+ {"mkexrc", ex_mkexrc, 0,
+ "!f1r",
+ "mkexrc[!] file",
+ "write a .exrc file"},
+/* C_NEXT */
+ {"next", ex_next, E_NEWSCREEN,
+ "!fN",
+ "[Nn][ext][!] [+cmd] [file ...]",
+ "edit (and optionally specify) the next file"},
+/* C_NUMBER */
+ {"number", ex_number, E_ADDR2|E_CLRFLAG,
+ "ca1",
+ "[line [,line]] nu[mber] [count] [l]",
+ "change display to number lines"},
+/* C_OPEN */
+ {"open", ex_open, E_ADDR1,
+ "s",
+ "[line] o[pen] [/RE/] [flags]",
+ "enter \"open\" mode (not implemented)"},
+/* C_PRINT */
+ {"print", ex_pr, E_ADDR2|E_CLRFLAG,
+ "ca1",
+ "[line [,line]] p[rint] [count] [#l]",
+ "display lines"},
+/* C_PERLCMD */
+ {"perl", ex_perl, E_ADDR2_ALL|E_ADDR_ZERO|
+ E_ADDR_ZERODEF|E_SECURE,
+ "s",
+ "pe[rl] cmd",
+ "run the perl interpreter with the command"},
+/* C_PERLDOCMD */
+ {"perldo", ex_perl, E_ADDR2_ALL|E_ADDR_ZERO|
+ E_ADDR_ZERODEF|E_SECURE,
+ "s",
+ "perld[o] cmd",
+ "run the perl interpreter with the command, on each line"},
+/* C_PRESERVE */
+ {"preserve", ex_preserve, 0,
+ "",
+ "pre[serve]",
+ "preserve an edit session for recovery"},
+/* C_PREVIOUS */
+ {"previous", ex_prev, E_NEWSCREEN,
+ "!",
+ "[Pp]rev[ious][!]",
+ "edit the previous file in the file argument list"},
+/* C_PUT */
+ {"put", ex_put,
+ E_ADDR1|E_AUTOPRINT|E_ADDR_ZERO|E_ADDR_ZERODEF,
+ "b",
+ "[line] pu[t] [buffer]",
+ "append a cut buffer to the line"},
+/* C_QUIT */
+ {"quit", ex_quit, 0,
+ "!",
+ "q[uit][!]",
+ "exit ex/vi"},
+/* C_READ */
+ {"read", ex_read, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF,
+ "s",
+ "[line] r[ead] [!cmd | [file]]",
+ "append input from a command or file to the line"},
+/* C_RECOVER */
+ {"recover", ex_recover, 0,
+ "!f1r",
+ "recover[!] file",
+ "recover a saved file"},
+/* C_RESIZE */
+ {"resize", ex_resize, E_VIONLY,
+ "c+",
+ "resize [+-]rows",
+ "grow or shrink the current screen"},
+/* C_REWIND */
+ {"rewind", ex_rew, 0,
+ "!",
+ "rew[ind][!]",
+ "re-edit all the files in the file argument list"},
+/*
+ * !!!
+ * Adding new commands starting with 's' may break the substitute command code
+ * in ex_cmd() (the ex parser). Read through the comments there, first.
+ */
+/* C_SUBSTITUTE */
+ {"s", ex_s, E_ADDR2,
+ "s",
+ "[line [,line]] s [[/;]RE[/;]repl[/;] [cgr] [count] [#lp]]",
+ "substitute on lines matching an RE"},
+/* C_SCRIPT */
+ {"script", ex_script, E_SECURE,
+ "!f1o",
+ "sc[ript][!] [file]",
+ "run a shell in a screen"},
+/* C_SET */
+ {"set", ex_set, 0,
+ "wN",
+ "se[t] [option[=[value]]...] [nooption ...] [option? ...] [all]",
+ "set options (use \":set all\" to see all options)"},
+/* C_SHELL */
+ {"shell", ex_shell, E_SECURE,
+ "",
+ "sh[ell]",
+ "suspend editing and run a shell"},
+/* C_SOURCE */
+ {"source", ex_source, 0,
+ "f1r",
+ "so[urce] file",
+ "read a file of ex commands"},
+/* C_STOP */
+ {"stop", ex_stop, E_SECURE,
+ "!",
+ "st[op][!]",
+ "suspend the edit session"},
+/* C_SUSPEND */
+ {"suspend", ex_stop, E_SECURE,
+ "!",
+ "su[spend][!]",
+ "suspend the edit session"},
+/* C_T */
+ {"t", ex_copy, E_ADDR2|E_AUTOPRINT,
+ "l1",
+ "[line [,line]] t line [flags]",
+ "copy lines elsewhere in the file"},
+/* C_TAG */
+ {"tag", ex_tag_push, E_NEWSCREEN,
+ "!w1o",
+ "[Tt]a[g][!] [string]",
+ "edit the file containing the tag"},
+/* C_TAGNEXT */
+ {"tagnext", ex_tag_next, 0,
+ "!",
+ "tagn[ext][!]",
+ "move to the next tag"},
+/* C_TAGPOP */
+ {"tagpop", ex_tag_pop, 0,
+ "!w1o",
+ "tagp[op][!] [number | file]",
+ "return to the previous group of tags"},
+/* C_TAGPREV */
+ {"tagprev", ex_tag_prev, 0,
+ "!",
+ "tagpr[ev][!]",
+ "move to the previous tag"},
+/* C_TAGTOP */
+ {"tagtop", ex_tag_top, 0,
+ "!",
+ "tagt[op][!]",
+ "discard all tags"},
+/* C_TCLCMD */
+ {"tcl", ex_tcl, E_ADDR2_ALL|E_ADDR_ZERO|
+ E_ADDR_ZERODEF|E_SECURE,
+ "s",
+ "tc[l] cmd",
+ "run the tcl interpreter with the command"},
+/* C_UNDO */
+ {"undo", ex_undo, E_AUTOPRINT,
+ "",
+ "u[ndo]",
+ "undo the most recent change"},
+/* C_UNABBREVIATE */
+ {"unabbreviate",ex_unabbr, 0,
+ "w1r",
+ "una[bbrev] word",
+ "delete an abbreviation"},
+/* C_UNMAP */
+ {"unmap", ex_unmap, 0,
+ "!w1r",
+ "unm[ap][!] word",
+ "delete an input or command map"},
+/* C_V */
+ {"v", ex_v, E_ADDR2_ALL,
+ "s",
+ "[line [,line]] v [;/]RE[;/] [commands]",
+ "execute a global command on lines NOT matching an RE"},
+/* C_VERSION */
+ {"version", ex_version, 0,
+ "",
+ "version",
+ "display the program version information"},
+/* C_VISUAL_EX */
+ {"visual", ex_visual, E_ADDR1|E_ADDR_ZERODEF,
+ "2c11",
+ "[line] vi[sual] [-|.|+|^] [window_size] [flags]",
+ "enter visual (vi) mode from ex mode"},
+/* C_VISUAL_VI */
+ {"visual", ex_edit, E_NEWSCREEN,
+ "f1o",
+ "[Vv]i[sual][!] [+cmd] [file]",
+ "edit another file (from vi mode only)"},
+/* C_VIUSAGE */
+ {"viusage", ex_viusage, 0,
+ "w1o",
+ "[viu]sage [key]",
+ "display vi key usage statement"},
+/* C_WRITE */
+ {"write", ex_write, E_ADDR2_ALL|E_ADDR_ZERODEF,
+ "!s",
+ "[line [,line]] w[rite][!] [ !cmd | [>>] [file]]",
+ "write the file"},
+/* C_WN */
+ {"wn", ex_wn, E_ADDR2_ALL|E_ADDR_ZERODEF,
+ "!s",
+ "[line [,line]] wn[!] [>>] [file]",
+ "write the file and switch to the next file"},
+/* C_WQ */
+ {"wq", ex_wq, E_ADDR2_ALL|E_ADDR_ZERODEF,
+ "!s",
+ "[line [,line]] wq[!] [>>] [file]",
+ "write the file and exit"},
+/* C_XIT */
+ {"xit", ex_xit, E_ADDR2_ALL|E_ADDR_ZERODEF,
+ "!f1o",
+ "[line [,line]] x[it][!] [file]",
+ "exit"},
+/* C_YANK */
+ {"yank", ex_yank, E_ADDR2,
+ "bca",
+ "[line [,line]] ya[nk] [buffer] [count]",
+ "copy lines to a cut buffer"},
+/* C_Z */
+ {"z", ex_z, E_ADDR1,
+ "3c01",
+ "[line] z [-|.|+|^|=] [count] [flags]",
+ "display different screens of the file"},
+/* C_SUBTILDE */
+ {"~", ex_subtilde, E_ADDR2,
+ "s",
+ "[line [,line]] ~ [cgr] [count] [#lp]",
+ "replace previous RE with previous replacement string,"},
+ {NULL},
+};
diff --git a/contrib/nvi/ex/ex_cscope.c b/contrib/nvi/ex/ex_cscope.c
new file mode 100644
index 0000000..c2fa0a5
--- /dev/null
+++ b/contrib/nvi/ex/ex_cscope.c
@@ -0,0 +1,1057 @@
+/*-
+ * Copyright (c) 1994, 1996
+ * Rob Mayoff. All rights reserved.
+ * Copyright (c) 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_cscope.c 10.13 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h> /* XXX: param.h may not have included types.h */
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "pathnames.h"
+#include "tag.h"
+
+#define CSCOPE_DBFILE "cscope.out"
+#define CSCOPE_PATHS "cscope.tpath"
+
+/*
+ * 0name find all uses of name
+ * 1name find definition of name
+ * 2name find all function calls made from name
+ * 3name find callers of name
+ * 4string find text string (cscope 12.9)
+ * 4name find assignments to name (cscope 13.3)
+ * 5pattern change pattern -- NOT USED
+ * 6pattern find pattern
+ * 7name find files with name as substring
+ * 8name find files #including name
+ */
+#define FINDHELP "\
+find c|d|e|f|g|i|s|t buffer|pattern\n\
+ c: find callers of name\n\
+ d: find all function calls made from name\n\
+ e: find pattern\n\
+ f: find files with name as substring\n\
+ g: find definition of name\n\
+ i: find files #including name\n\
+ s: find all uses of name\n\
+ t: find assignments to name"
+
+static int cscope_add __P((SCR *, EXCMD *, char *));
+static int cscope_find __P((SCR *, EXCMD*, char *));
+static int cscope_help __P((SCR *, EXCMD *, char *));
+static int cscope_kill __P((SCR *, EXCMD *, char *));
+static int cscope_reset __P((SCR *, EXCMD *, char *));
+
+typedef struct _cc {
+ char *name;
+ int (*function) __P((SCR *, EXCMD *, char *));
+ char *help_msg;
+ char *usage_msg;
+} CC;
+
+static CC const cscope_cmds[] = {
+ { "add", cscope_add,
+ "Add a new cscope database", "add file | directory" },
+ { "find", cscope_find,
+ "Query the databases for a pattern", FINDHELP },
+ { "help", cscope_help,
+ "Show help for cscope commands", "help [command]" },
+ { "kill", cscope_kill,
+ "Kill a cscope connection", "kill number" },
+ { "reset", cscope_reset,
+ "Discard all current cscope connections", "reset" },
+ { NULL }
+};
+
+static TAGQ *create_cs_cmd __P((SCR *, char *, size_t *));
+static int csc_help __P((SCR *, char *));
+static void csc_file __P((SCR *,
+ CSC *, char *, char **, size_t *, int *));
+static int get_paths __P((SCR *, CSC *));
+static CC const *lookup_ccmd __P((char *));
+static int parse __P((SCR *, CSC *, TAGQ *, int *));
+static int read_prompt __P((SCR *, CSC *));
+static int run_cscope __P((SCR *, CSC *, char *));
+static int start_cscopes __P((SCR *, EXCMD *));
+static int terminate __P((SCR *, CSC *, int));
+
+/*
+ * ex_cscope --
+ * Perform an ex cscope.
+ *
+ * PUBLIC: int ex_cscope __P((SCR *, EXCMD *));
+ */
+int
+ex_cscope(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ CC const *ccp;
+ EX_PRIVATE *exp;
+ int i;
+ char *cmd, *p;
+
+ /* Initialize the default cscope directories. */
+ exp = EXP(sp);
+ if (!F_ISSET(exp, EXP_CSCINIT) && start_cscopes(sp, cmdp))
+ return (1);
+ F_SET(exp, EXP_CSCINIT);
+
+ /* Skip leading whitespace. */
+ for (p = cmdp->argv[0]->bp, i = cmdp->argv[0]->len; i > 0; --i, ++p)
+ if (!isspace(*p))
+ break;
+ if (i == 0)
+ goto usage;
+
+ /* Skip the command to any arguments. */
+ for (cmd = p; i > 0; --i, ++p)
+ if (isspace(*p))
+ break;
+ if (*p != '\0') {
+ *p++ = '\0';
+ for (; *p && isspace(*p); ++p);
+ }
+
+ if ((ccp = lookup_ccmd(cmd)) == NULL) {
+usage: msgq(sp, M_ERR, "309|Use \"cscope help\" for help");
+ return (1);
+ }
+
+ /* Call the underlying function. */
+ return (ccp->function(sp, cmdp, p));
+}
+
+/*
+ * start_cscopes --
+ * Initialize the cscope package.
+ */
+static int
+start_cscopes(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ size_t blen, len;
+ char *bp, *cscopes, *p, *t;
+
+ /*
+ * EXTENSION #1:
+ *
+ * If the CSCOPE_DIRS environment variable is set, we treat it as a
+ * list of cscope directories that we're using, similar to the tags
+ * edit option.
+ *
+ * XXX
+ * This should probably be an edit option, although that implies that
+ * we start/stop cscope processes periodically, instead of once when
+ * the editor starts.
+ */
+ if ((cscopes = getenv("CSCOPE_DIRS")) == NULL)
+ return (0);
+ len = strlen(cscopes);
+ GET_SPACE_RET(sp, bp, blen, len);
+ memcpy(bp, cscopes, len + 1);
+
+ for (cscopes = t = bp; (p = strsep(&t, "\t :")) != NULL;)
+ if (*p != '\0')
+ (void)cscope_add(sp, cmdp, p);
+
+ FREE_SPACE(sp, bp, blen);
+ return (0);
+}
+
+/*
+ * cscope_add --
+ * The cscope add command.
+ */
+static int
+cscope_add(sp, cmdp, dname)
+ SCR *sp;
+ EXCMD *cmdp;
+ char *dname;
+{
+ struct stat sb;
+ EX_PRIVATE *exp;
+ CSC *csc;
+ size_t len;
+ int cur_argc;
+ char *dbname, path[MAXPATHLEN];
+
+ exp = EXP(sp);
+
+ /*
+ * 0 additional args: usage.
+ * 1 additional args: matched a file.
+ * >1 additional args: object, too many args.
+ */
+ cur_argc = cmdp->argc;
+ if (argv_exp2(sp, cmdp, dname, strlen(dname)))
+ return (1);
+ if (cmdp->argc == cur_argc) {
+ (void)csc_help(sp, "add");
+ return (1);
+ }
+ if (cmdp->argc == cur_argc + 1)
+ dname = cmdp->argv[cur_argc]->bp;
+ else {
+ ex_emsg(sp, dname, EXM_FILECOUNT);
+ return (1);
+ }
+
+ /*
+ * The user can specify a specific file (so they can have multiple
+ * Cscope databases in a single directory) or a directory. If the
+ * file doesn't exist, we're done. If it's a directory, append the
+ * standard database file name and try again. Store the directory
+ * name regardless so that we can use it as a base for searches.
+ */
+ if (stat(dname, &sb)) {
+ msgq(sp, M_SYSERR, dname);
+ return (1);
+ }
+ if (S_ISDIR(sb.st_mode)) {
+ (void)snprintf(path, sizeof(path),
+ "%s/%s", dname, CSCOPE_DBFILE);
+ if (stat(path, &sb)) {
+ msgq(sp, M_SYSERR, path);
+ return (1);
+ }
+ dbname = CSCOPE_DBFILE;
+ } else if ((dbname = strrchr(dname, '/')) != NULL)
+ *dbname++ = '\0';
+
+ /* Allocate a cscope connection structure and initialize its fields. */
+ len = strlen(dname);
+ CALLOC_RET(sp, csc, CSC *, 1, sizeof(CSC) + len);
+ csc->dname = csc->buf;
+ csc->dlen = len;
+ memcpy(csc->dname, dname, len);
+ csc->mtime = sb.st_mtime;
+
+ /* Get the search paths for the cscope. */
+ if (get_paths(sp, csc))
+ goto err;
+
+ /* Start the cscope process. */
+ if (run_cscope(sp, csc, dbname))
+ goto err;
+
+ /*
+ * Add the cscope connection to the screen's list. From now on,
+ * on error, we have to call terminate, which expects the csc to
+ * be on the chain.
+ */
+ LIST_INSERT_HEAD(&exp->cscq, csc, q);
+
+ /* Read the initial prompt from the cscope to make sure it's okay. */
+ if (read_prompt(sp, csc)) {
+ terminate(sp, csc, 0);
+ return (1);
+ }
+
+ return (0);
+
+err: free(csc);
+ return (1);
+}
+
+/*
+ * get_paths --
+ * Get the directories to search for the files associated with this
+ * cscope database.
+ */
+static int
+get_paths(sp, csc)
+ SCR *sp;
+ CSC *csc;
+{
+ struct stat sb;
+ int fd, nentries;
+ size_t len;
+ char *p, **pathp, buf[MAXPATHLEN * 2];
+
+ /*
+ * EXTENSION #2:
+ *
+ * If there's a cscope directory with a file named CSCOPE_PATHS, it
+ * contains a colon-separated list of paths in which to search for
+ * files returned by cscope.
+ *
+ * XXX
+ * These paths are absolute paths, and not relative to the cscope
+ * directory. To fix this, rewrite the each path using the cscope
+ * directory as a prefix.
+ */
+ (void)snprintf(buf, sizeof(buf), "%s/%s", csc->dname, CSCOPE_PATHS);
+ if (stat(buf, &sb) == 0) {
+ /* Read in the CSCOPE_PATHS file. */
+ len = sb.st_size;
+ MALLOC_RET(sp, csc->pbuf, char *, len + 1);
+ if ((fd = open(buf, O_RDONLY, 0)) < 0 ||
+ read(fd, csc->pbuf, len) != len) {
+ msgq_str(sp, M_SYSERR, buf, "%s");
+ if (fd >= 0)
+ (void)close(fd);
+ return (1);
+ }
+ (void)close(fd);
+ csc->pbuf[len] = '\0';
+
+ /* Count up the entries. */
+ for (nentries = 0, p = csc->pbuf; *p != '\0'; ++p)
+ if (p[0] == ':' && p[1] != '\0')
+ ++nentries;
+
+ /* Build an array of pointers to the paths. */
+ CALLOC_GOTO(sp,
+ csc->paths, char **, nentries + 1, sizeof(char **));
+ for (pathp = csc->paths, p = strtok(csc->pbuf, ":");
+ p != NULL; p = strtok(NULL, ":"))
+ *pathp++ = p;
+ return (0);
+ }
+
+ /*
+ * If the CSCOPE_PATHS file doesn't exist, we look for files
+ * relative to the cscope directory.
+ */
+ if ((csc->pbuf = strdup(csc->dname)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+ CALLOC_GOTO(sp, csc->paths, char **, 2, sizeof(char *));
+ csc->paths[0] = csc->pbuf;
+ return (0);
+
+alloc_err:
+ if (csc->pbuf != NULL) {
+ free(csc->pbuf);
+ csc->pbuf = NULL;
+ }
+ return (1);
+}
+
+/*
+ * run_cscope --
+ * Fork off the cscope process.
+ */
+static int
+run_cscope(sp, csc, dbname)
+ SCR *sp;
+ CSC *csc;
+ char *dbname;
+{
+ int to_cs[2], from_cs[2];
+ char cmd[MAXPATHLEN * 2];
+
+ /*
+ * Cscope reads from to_cs[0] and writes to from_cs[1]; vi reads from
+ * from_cs[0] and writes to to_cs[1].
+ */
+ to_cs[0] = to_cs[1] = from_cs[0] = from_cs[0] = -1;
+ if (pipe(to_cs) < 0 || pipe(from_cs) < 0) {
+ msgq(sp, M_SYSERR, "pipe");
+ goto err;
+ }
+ switch (csc->pid = vfork()) {
+ case -1:
+ msgq(sp, M_SYSERR, "vfork");
+err: if (to_cs[0] != -1)
+ (void)close(to_cs[0]);
+ if (to_cs[1] != -1)
+ (void)close(to_cs[1]);
+ if (from_cs[0] != -1)
+ (void)close(from_cs[0]);
+ if (from_cs[1] != -1)
+ (void)close(from_cs[1]);
+ return (1);
+ case 0: /* child: run cscope. */
+ (void)dup2(to_cs[0], STDIN_FILENO);
+ (void)dup2(from_cs[1], STDOUT_FILENO);
+ (void)dup2(from_cs[1], STDERR_FILENO);
+
+ /* Close unused file descriptors. */
+ (void)close(to_cs[1]);
+ (void)close(from_cs[0]);
+
+ /* Run the cscope command. */
+#define CSCOPE_CMD_FMT "cd '%s' && exec cscope -dl -f %s"
+ (void)snprintf(cmd, sizeof(cmd),
+ CSCOPE_CMD_FMT, csc->dname, dbname);
+ (void)execl(_PATH_BSHELL, "sh", "-c", cmd, NULL);
+ msgq_str(sp, M_SYSERR, cmd, "execl: %s");
+ _exit (127);
+ /* NOTREACHED */
+ default: /* parent. */
+ /* Close unused file descriptors. */
+ (void)close(to_cs[0]);
+ (void)close(from_cs[1]);
+
+ /*
+ * Save the file descriptors for later duplication, and
+ * reopen as streams.
+ */
+ csc->to_fd = to_cs[1];
+ csc->to_fp = fdopen(to_cs[1], "w");
+ csc->from_fd = from_cs[0];
+ csc->from_fp = fdopen(from_cs[0], "r");
+ break;
+ }
+ return (0);
+}
+
+/*
+ * cscope_find --
+ * The cscope find command.
+ */
+static int
+cscope_find(sp, cmdp, pattern)
+ SCR *sp;
+ EXCMD *cmdp;
+ char *pattern;
+{
+ CSC *csc, *csc_next;
+ EX_PRIVATE *exp;
+ FREF *frp;
+ TAGQ *rtqp, *tqp;
+ TAG *rtp;
+ recno_t lno;
+ size_t cno, search;
+ int force, istmp, matches;
+
+ exp = EXP(sp);
+
+ /* Check for connections. */
+ if (exp->cscq.lh_first == NULL) {
+ msgq(sp, M_ERR, "310|No cscope connections running");
+ return (1);
+ }
+
+ /*
+ * Allocate all necessary memory before doing anything hard. If the
+ * tags stack is empty, we'll need the `local context' TAGQ structure
+ * later.
+ */
+ rtp = NULL;
+ rtqp = NULL;
+ if (exp->tq.cqh_first == (void *)&exp->tq) {
+ /* Initialize the `local context' tag queue structure. */
+ CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ));
+ CIRCLEQ_INIT(&rtqp->tagq);
+
+ /* Initialize and link in its tag structure. */
+ CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG));
+ CIRCLEQ_INSERT_HEAD(&rtqp->tagq, rtp, q);
+ rtqp->current = rtp;
+ }
+
+ /* Create the cscope command. */
+ if ((tqp = create_cs_cmd(sp, pattern, &search)) == NULL)
+ goto err;
+
+ /*
+ * Stick the current context in a convenient place, we'll lose it
+ * when we switch files.
+ */
+ frp = sp->frp;
+ lno = sp->lno;
+ cno = sp->cno;
+ istmp = F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN);
+
+ /* Search all open connections for a match. */
+ matches = 0;
+ for (csc = exp->cscq.lh_first; csc != NULL; csc = csc_next) {
+ /* Copy csc->q.lh_next here in case csc is killed. */
+ csc_next = csc->q.le_next;
+
+ /*
+ * Send the command to the cscope program. (We skip the
+ * first two bytes of the command, because we stored the
+ * search cscope command character and a leading space
+ * there.)
+ */
+ (void)fprintf(csc->to_fp, "%d%s\n", search, tqp->tag + 2);
+ (void)fflush(csc->to_fp);
+
+ /* Read the output. */
+ if (parse(sp, csc, tqp, &matches)) {
+ if (rtqp != NULL)
+ free(rtqp);
+ tagq_free(sp, tqp);
+ return (1);
+ }
+ }
+
+ if (matches == 0) {
+ msgq(sp, M_INFO, "278|No matches for query");
+ return (0);
+ }
+
+ tqp->current = tqp->tagq.cqh_first;
+
+ /* Try to switch to the first tag. */
+ force = FL_ISSET(cmdp->iflags, E_C_FORCE);
+ if (F_ISSET(cmdp, E_NEWSCREEN)) {
+ if (ex_tag_Nswitch(sp, tqp->current, force))
+ goto err;
+
+ /* Everything else gets done in the new screen. */
+ sp = sp->nextdisp;
+ exp = EXP(sp);
+ } else
+ if (ex_tag_nswitch(sp, tqp->current, force))
+ goto err;
+
+ /*
+ * If this is the first tag, put a `current location' queue entry
+ * in place, so we can pop all the way back to the current mark.
+ * Note, it doesn't point to much of anything, it's a placeholder.
+ */
+ if (exp->tq.cqh_first == (void *)&exp->tq) {
+ CIRCLEQ_INSERT_HEAD(&exp->tq, rtqp, q);
+ } else
+ rtqp = exp->tq.cqh_first;
+
+ /* Link the current TAGQ structure into place. */
+ CIRCLEQ_INSERT_HEAD(&exp->tq, tqp, q);
+
+ (void)cscope_search(sp, tqp, tqp->current);
+
+ /*
+ * Move the current context from the temporary save area into the
+ * right structure.
+ *
+ * If we were in a temporary file, we don't have a context to which
+ * we can return, so just make it be the same as what we're moving
+ * to. It will be a little odd that ^T doesn't change anything, but
+ * I don't think it's a big deal.
+ */
+ if (istmp) {
+ rtqp->current->frp = sp->frp;
+ rtqp->current->lno = sp->lno;
+ rtqp->current->cno = sp->cno;
+ } else {
+ rtqp->current->frp = frp;
+ rtqp->current->lno = lno;
+ rtqp->current->cno = cno;
+ }
+
+ return (0);
+
+err:
+alloc_err:
+ if (rtqp != NULL)
+ free(rtqp);
+ if (rtp != NULL)
+ free(rtp);
+ return (1);
+}
+
+/*
+ * create_cs_cmd --
+ * Build a cscope command, creating and initializing the base TAGQ.
+ */
+static TAGQ *
+create_cs_cmd(sp, pattern, searchp)
+ SCR *sp;
+ char *pattern;
+ size_t *searchp;
+{
+ CB *cbp;
+ TAGQ *tqp;
+ size_t tlen;
+ char *p;
+
+ /*
+ * Cscope supports a "change pattern" command which we never use,
+ * cscope command 5. Set CSCOPE_QUERIES[5] to " " since the user
+ * can't pass " " as the first character of pattern. That way the
+ * user can't ask for pattern 5 so we don't need any special-case
+ * code.
+ */
+#define CSCOPE_QUERIES "sgdct efi"
+
+ if (pattern == NULL)
+ goto usage;
+
+ /* Skip leading blanks, check for command character. */
+ for (; isblank(pattern[0]); ++pattern);
+ if (pattern[0] == '\0' || !isblank(pattern[1]))
+ goto usage;
+ for (*searchp = 0, p = CSCOPE_QUERIES;
+ *p != '\0' && *p != pattern[0]; ++*searchp, ++p);
+ if (*p == '\0') {
+ msgq(sp, M_ERR,
+ "311|%s: unknown search type: use one of %s",
+ KEY_NAME(sp, pattern[0]), CSCOPE_QUERIES);
+ return (NULL);
+ }
+
+ /* Skip <blank> characters to the pattern. */
+ for (p = pattern + 1; *p != '\0' && isblank(*p); ++p);
+ if (*p == '\0') {
+usage: (void)csc_help(sp, "find");
+ return (NULL);
+ }
+
+ /* The user can specify the contents of a buffer as the pattern. */
+ cbp = NULL;
+ if (p[0] == '"' && p[1] != '\0' && p[2] == '\0')
+ CBNAME(sp, cbp, p[1]);
+ if (cbp != NULL) {
+ p = cbp->textq.cqh_first->lb;
+ tlen = cbp->textq.cqh_first->len;
+ } else
+ tlen = strlen(p);
+
+ /* Allocate and initialize the TAGQ structure. */
+ CALLOC(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + tlen + 3);
+ if (tqp == NULL)
+ return (NULL);
+ CIRCLEQ_INIT(&tqp->tagq);
+ tqp->tag = tqp->buf;
+ tqp->tag[0] = pattern[0];
+ tqp->tag[1] = ' ';
+ tqp->tlen = tlen + 2;
+ memcpy(tqp->tag + 2, p, tlen);
+ tqp->tag[tlen + 2] = '\0';
+ F_SET(tqp, TAG_CSCOPE);
+
+ return (tqp);
+}
+
+/*
+ * parse --
+ * Parse the cscope output.
+ */
+static int
+parse(sp, csc, tqp, matchesp)
+ SCR *sp;
+ CSC *csc;
+ TAGQ *tqp;
+ int *matchesp;
+{
+ TAG *tp;
+ recno_t slno;
+ size_t dlen, nlen, slen;
+ int ch, i, isolder, nlines;
+ char *dname, *name, *search, *p, *t, dummy[2], buf[2048];
+
+ for (;;) {
+ if (!fgets(buf, sizeof(buf), csc->from_fp))
+ goto io_err;
+
+ /*
+ * If the database is out of date, or there's some other
+ * problem, cscope will output error messages before the
+ * number-of-lines output. Display/discard any output
+ * that doesn't match what we want.
+ */
+#define CSCOPE_NLINES_FMT "cscope: %d lines%1[\n]"
+ if (sscanf(buf, CSCOPE_NLINES_FMT, &nlines, dummy) == 2)
+ break;
+ if ((p = strchr(buf, '\n')) != NULL)
+ *p = '\0';
+ msgq(sp, M_ERR, "%s: \"%s\"", csc->dname, buf);
+ }
+
+ while (nlines--) {
+ if (fgets(buf, sizeof(buf), csc->from_fp) == NULL)
+ goto io_err;
+
+ /* If the line's too long for the buffer, discard it. */
+ if ((p = strchr(buf, '\n')) == NULL) {
+ while ((ch = getc(csc->from_fp)) != EOF && ch != '\n');
+ continue;
+ }
+ *p = '\0';
+
+ /*
+ * The cscope output is in the following format:
+ *
+ * <filename> <context> <line number> <pattern>
+ *
+ * Figure out how long everything is so we can allocate in one
+ * swell foop, but discard anything that looks wrong.
+ */
+ for (p = buf, i = 0;
+ i < 3 && (t = strsep(&p, "\t ")) != NULL; ++i)
+ switch (i) {
+ case 0: /* Filename. */
+ name = t;
+ nlen = strlen(name);
+ break;
+ case 1: /* Context. */
+ break;
+ case 2: /* Line number. */
+ slno = (recno_t)atol(t);
+ break;
+ }
+ if (i != 3 || p == NULL || t == NULL)
+ continue;
+
+ /* The rest of the string is the search pattern. */
+ search = p;
+ slen = strlen(p);
+
+ /* Resolve the file name. */
+ csc_file(sp, csc, name, &dname, &dlen, &isolder);
+
+ /*
+ * If the file is older than the cscope database, that is,
+ * the database was built since the file was last modified,
+ * or there wasn't a search string, use the line number.
+ */
+ if (isolder || strcmp(search, "<unknown>") == 0) {
+ search = NULL;
+ slen = 0;
+ }
+
+ /*
+ * Allocate and initialize a tag structure plus the variable
+ * length cscope information that follows it.
+ */
+ CALLOC_RET(sp, tp,
+ TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1);
+ tp->fname = tp->buf;
+ if (dlen != 0) {
+ memcpy(tp->fname, dname, dlen);
+ tp->fname[dlen] = '/';
+ ++dlen;
+ }
+ memcpy(tp->fname + dlen, name, nlen + 1);
+ tp->fnlen = dlen + nlen;
+ tp->slno = slno;
+ if (slen != 0) {
+ tp->search = tp->fname + tp->fnlen + 1;
+ memcpy(tp->search, search, (tp->slen = slen) + 1);
+ }
+ CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
+
+ ++*matchesp;
+ }
+
+ (void)read_prompt(sp, csc);
+ return (0);
+
+io_err: if (feof(csc->from_fp))
+ errno = EIO;
+ msgq_str(sp, M_SYSERR, "%s", csc->dname);
+ terminate(sp, csc, 0);
+ return (1);
+}
+
+/*
+ * csc_file --
+ * Search for the right path to this file.
+ */
+static void
+csc_file(sp, csc, name, dirp, dlenp, isolderp)
+ SCR *sp;
+ CSC *csc;
+ char *name, **dirp;
+ size_t *dlenp;
+ int *isolderp;
+{
+ struct stat sb;
+ char **pp, buf[MAXPATHLEN];
+
+ /*
+ * Check for the file in all of the listed paths. If we don't
+ * find it, we simply return it unchanged. We have to do this
+ * now, even though it's expensive, because if the user changes
+ * directories, we can't change our minds as to where the file
+ * lives.
+ */
+ for (pp = csc->paths; *pp != NULL; ++pp) {
+ (void)snprintf(buf, sizeof(buf), "%s/%s", *pp, name);
+ if (stat(buf, &sb) == 0) {
+ *dirp = *pp;
+ *dlenp = strlen(*pp);
+ *isolderp = sb.st_mtime < csc->mtime;
+ return;
+ }
+ }
+ *dlenp = 0;
+}
+
+/*
+ * cscope_help --
+ * The cscope help command.
+ */
+static int
+cscope_help(sp, cmdp, subcmd)
+ SCR *sp;
+ EXCMD *cmdp;
+ char *subcmd;
+{
+ return (csc_help(sp, subcmd));
+}
+
+/*
+ * csc_help --
+ * Display help/usage messages.
+ */
+static int
+csc_help(sp, cmd)
+ SCR *sp;
+ char *cmd;
+{
+ CC const *ccp;
+
+ if (cmd != NULL && *cmd != '\0')
+ if ((ccp = lookup_ccmd(cmd)) == NULL) {
+ ex_printf(sp,
+ "%s doesn't match any cscope command\n", cmd);
+ return (1);
+ } else {
+ ex_printf(sp,
+ "Command: %s (%s)\n", ccp->name, ccp->help_msg);
+ ex_printf(sp, " Usage: %s\n", ccp->usage_msg);
+ return (0);
+ }
+
+ ex_printf(sp, "cscope commands:\n");
+ for (ccp = cscope_cmds; ccp->name != NULL; ++ccp)
+ ex_printf(sp, " %*s: %s\n", 5, ccp->name, ccp->help_msg);
+ return (0);
+}
+
+/*
+ * cscope_kill --
+ * The cscope kill command.
+ */
+static int
+cscope_kill(sp, cmdp, cn)
+ SCR *sp;
+ EXCMD *cmdp;
+ char *cn;
+{
+ return (terminate(sp, NULL, atoi(cn)));
+}
+
+/*
+ * terminate --
+ * Detach from a cscope process.
+ */
+static int
+terminate(sp, csc, n)
+ SCR *sp;
+ CSC *csc;
+ int n;
+{
+ EX_PRIVATE *exp;
+ int i, pstat;
+
+ exp = EXP(sp);
+
+ /*
+ * We either get a csc structure or a number. If not provided a
+ * csc structure, find the right one.
+ */
+ if (csc == NULL) {
+ if (n < 1)
+ goto badno;
+ for (i = 1, csc = exp->cscq.lh_first;
+ csc != NULL; csc = csc->q.le_next, i++)
+ if (i == n)
+ break;
+ if (csc == NULL) {
+badno: msgq(sp, M_ERR, "312|%d: no such cscope session", n);
+ return (1);
+ }
+ }
+
+ /*
+ * XXX
+ * Theoretically, we have the only file descriptors to the process,
+ * so closing them should let it exit gracefully, deleting temporary
+ * files, etc. The original vi cscope integration sent the cscope
+ * connection a SIGTERM signal, so I'm not sure if closing the file
+ * descriptors is sufficient.
+ */
+ if (csc->from_fp != NULL)
+ (void)fclose(csc->from_fp);
+ if (csc->to_fp != NULL)
+ (void)fclose(csc->to_fp);
+ (void)waitpid(csc->pid, &pstat, 0);
+
+ /* Discard cscope connection information. */
+ LIST_REMOVE(csc, q);
+ if (csc->pbuf != NULL)
+ free(csc->pbuf);
+ if (csc->paths != NULL)
+ free(csc->paths);
+ free(csc);
+ return (0);
+}
+
+/*
+ * cscope_reset --
+ * The cscope reset command.
+ */
+static int
+cscope_reset(sp, cmdp, notusedp)
+ SCR *sp;
+ EXCMD *cmdp;
+ char *notusedp;
+{
+ EX_PRIVATE *exp;
+
+ for (exp = EXP(sp); exp->cscq.lh_first != NULL;)
+ if (cscope_kill(sp, cmdp, "1"))
+ return (1);
+ return (0);
+}
+
+/*
+ * cscope_display --
+ * Display current connections.
+ *
+ * PUBLIC: int cscope_display __P((SCR *));
+ */
+int
+cscope_display(sp)
+ SCR *sp;
+{
+ EX_PRIVATE *exp;
+ CSC *csc;
+ int i;
+
+ exp = EXP(sp);
+ if (exp->cscq.lh_first == NULL) {
+ ex_printf(sp, "No cscope connections.\n");
+ return (0);
+ }
+ for (i = 1,
+ csc = exp->cscq.lh_first; csc != NULL; ++i, csc = csc->q.le_next)
+ ex_printf(sp,
+ "%2d %s (process %lu)\n", i, csc->dname, (u_long)csc->pid);
+ return (0);
+}
+
+/*
+ * cscope_search --
+ * Search a file for a cscope entry.
+ *
+ * PUBLIC: int cscope_search __P((SCR *, TAGQ *, TAG *));
+ */
+int
+cscope_search(sp, tqp, tp)
+ SCR *sp;
+ TAGQ *tqp;
+ TAG *tp;
+{
+ MARK m;
+
+ /* If we don't have a search pattern, use the line number. */
+ if (tp->search == NULL) {
+ if (!db_exist(sp, tp->slno)) {
+ tag_msg(sp, TAG_BADLNO, tqp->tag);
+ return (1);
+ }
+ m.lno = tp->slno;
+ } else {
+ /*
+ * Search for the tag; cheap fallback for C functions
+ * if the name is the same but the arguments have changed.
+ */
+ m.lno = 1;
+ m.cno = 0;
+ if (f_search(sp, &m, &m,
+ tp->search, tp->slen, NULL, SEARCH_CSCOPE | SEARCH_FILE)) {
+ tag_msg(sp, TAG_SEARCH, tqp->tag);
+ return (1);
+ }
+
+ /*
+ * !!!
+ * Historically, tags set the search direction if it wasn't
+ * already set.
+ */
+ if (sp->searchdir == NOTSET)
+ sp->searchdir = FORWARD;
+ }
+
+ /*
+ * !!!
+ * Tags move to the first non-blank, NOT the search pattern start.
+ */
+ sp->lno = m.lno;
+ sp->cno = 0;
+ (void)nonblank(sp, sp->lno, &sp->cno);
+ return (0);
+}
+
+
+/*
+ * lookup_ccmd --
+ * Return a pointer to the command structure.
+ */
+static CC const *
+lookup_ccmd(name)
+ char *name;
+{
+ CC const *ccp;
+ size_t len;
+
+ len = strlen(name);
+ for (ccp = cscope_cmds; ccp->name != NULL; ++ccp)
+ if (strncmp(name, ccp->name, len) == 0)
+ return (ccp);
+ return (NULL);
+}
+
+/*
+ * read_prompt --
+ * Read a prompt from cscope.
+ */
+static int
+read_prompt(sp, csc)
+ SCR *sp;
+ CSC *csc;
+{
+ int ch;
+
+#define CSCOPE_PROMPT ">> "
+ for (;;) {
+ while ((ch =
+ getc(csc->from_fp)) != EOF && ch != CSCOPE_PROMPT[0]);
+ if (ch == EOF) {
+ terminate(sp, csc, 0);
+ return (1);
+ }
+ if (getc(csc->from_fp) != CSCOPE_PROMPT[1])
+ continue;
+ if (getc(csc->from_fp) != CSCOPE_PROMPT[2])
+ continue;
+ break;
+ }
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_delete.c b/contrib/nvi/ex/ex_delete.c
new file mode 100644
index 0000000..58734bb
--- /dev/null
+++ b/contrib/nvi/ex/ex_delete.c
@@ -0,0 +1,65 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_delete.c 10.9 (Berkeley) 10/23/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_delete: [line [,line]] d[elete] [buffer] [count] [flags]
+ *
+ * Delete lines from the file.
+ *
+ * PUBLIC: int ex_delete __P((SCR *, EXCMD *));
+ */
+int
+ex_delete(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ recno_t lno;
+
+ NEEDFILE(sp, cmdp);
+
+ /*
+ * !!!
+ * Historically, lines deleted in ex were not placed in the numeric
+ * buffers. We follow historic practice so that we don't overwrite
+ * vi buffers accidentally.
+ */
+ if (cut(sp,
+ FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL,
+ &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE))
+ return (1);
+
+ /* Delete the lines. */
+ if (del(sp, &cmdp->addr1, &cmdp->addr2, 1))
+ return (1);
+
+ /* Set the cursor to the line after the last line deleted. */
+ sp->lno = cmdp->addr1.lno;
+
+ /* Or the last line in the file if deleted to the end of the file. */
+ if (db_last(sp, &lno))
+ return (1);
+ if (sp->lno > lno)
+ sp->lno = lno;
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_display.c b/contrib/nvi/ex/ex_display.c
new file mode 100644
index 0000000..7531517
--- /dev/null
+++ b/contrib/nvi/ex/ex_display.c
@@ -0,0 +1,145 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_display.c 10.12 (Berkeley) 4/10/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "tag.h"
+
+static int bdisplay __P((SCR *));
+static void db __P((SCR *, CB *, CHAR_T *));
+
+/*
+ * ex_display -- :display b[uffers] | c[onnections] | s[creens] | t[ags]
+ *
+ * Display cscope connections, buffers, tags or screens.
+ *
+ * PUBLIC: int ex_display __P((SCR *, EXCMD *));
+ */
+int
+ex_display(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ switch (cmdp->argv[0]->bp[0]) {
+ case 'b':
+#undef ARG
+#define ARG "buffers"
+ if (cmdp->argv[0]->len >= sizeof(ARG) ||
+ memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len))
+ break;
+ return (bdisplay(sp));
+ case 'c':
+#undef ARG
+#define ARG "connections"
+ if (cmdp->argv[0]->len >= sizeof(ARG) ||
+ memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len))
+ break;
+ return (cscope_display(sp));
+ case 's':
+#undef ARG
+#define ARG "screens"
+ if (cmdp->argv[0]->len >= sizeof(ARG) ||
+ memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len))
+ break;
+ return (ex_sdisplay(sp));
+ case 't':
+#undef ARG
+#define ARG "tags"
+ if (cmdp->argv[0]->len >= sizeof(ARG) ||
+ memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len))
+ break;
+ return (ex_tag_display(sp));
+ }
+ ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+}
+
+/*
+ * bdisplay --
+ *
+ * Display buffers.
+ */
+static int
+bdisplay(sp)
+ SCR *sp;
+{
+ CB *cbp;
+
+ if (sp->gp->cutq.lh_first == NULL && sp->gp->dcbp == NULL) {
+ msgq(sp, M_INFO, "123|No cut buffers to display");
+ return (0);
+ }
+
+ /* Display regular cut buffers. */
+ for (cbp = sp->gp->cutq.lh_first; cbp != NULL; cbp = cbp->q.le_next) {
+ if (isdigit(cbp->name))
+ continue;
+ if (cbp->textq.cqh_first != (void *)&cbp->textq)
+ db(sp, cbp, NULL);
+ if (INTERRUPTED(sp))
+ return (0);
+ }
+ /* Display numbered buffers. */
+ for (cbp = sp->gp->cutq.lh_first; cbp != NULL; cbp = cbp->q.le_next) {
+ if (!isdigit(cbp->name))
+ continue;
+ if (cbp->textq.cqh_first != (void *)&cbp->textq)
+ db(sp, cbp, NULL);
+ if (INTERRUPTED(sp))
+ return (0);
+ }
+ /* Display default buffer. */
+ if ((cbp = sp->gp->dcbp) != NULL)
+ db(sp, cbp, "default buffer");
+ return (0);
+}
+
+/*
+ * db --
+ * Display a buffer.
+ */
+static void
+db(sp, cbp, name)
+ SCR *sp;
+ CB *cbp;
+ CHAR_T *name;
+{
+ CHAR_T *p;
+ GS *gp;
+ TEXT *tp;
+ size_t len;
+
+ gp = sp->gp;
+ (void)ex_printf(sp, "********** %s%s\n",
+ name == NULL ? KEY_NAME(sp, cbp->name) : name,
+ F_ISSET(cbp, CB_LMODE) ? " (line mode)" : " (character mode)");
+ for (tp = cbp->textq.cqh_first;
+ tp != (void *)&cbp->textq; tp = tp->q.cqe_next) {
+ for (len = tp->len, p = tp->lb; len--; ++p) {
+ (void)ex_puts(sp, KEY_NAME(sp, *p));
+ if (INTERRUPTED(sp))
+ return;
+ }
+ (void)ex_puts(sp, "\n");
+ }
+}
diff --git a/contrib/nvi/ex/ex_edit.c b/contrib/nvi/ex/ex_edit.c
new file mode 100644
index 0000000..8b18e0f
--- /dev/null
+++ b/contrib/nvi/ex/ex_edit.c
@@ -0,0 +1,153 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_edit.c 10.10 (Berkeley) 4/27/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+static int ex_N_edit __P((SCR *, EXCMD *, FREF *, int));
+
+/*
+ * ex_edit -- :e[dit][!] [+cmd] [file]
+ * :ex[!] [+cmd] [file]
+ * :vi[sual][!] [+cmd] [file]
+ *
+ * Edit a file; if none specified, re-edit the current file. The third
+ * form of the command can only be executed while in vi mode. See the
+ * hack in ex.c:ex_cmd().
+ *
+ * !!!
+ * Historic vi didn't permit the '+' command form without specifying
+ * a file name as well. This seems unreasonable, so we support it
+ * regardless.
+ *
+ * PUBLIC: int ex_edit __P((SCR *, EXCMD *));
+ */
+int
+ex_edit(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ FREF *frp;
+ int attach, setalt;
+
+ switch (cmdp->argc) {
+ case 0:
+ /*
+ * If the name has been changed, we edit that file, not the
+ * original name. If the user was editing a temporary file
+ * (or wasn't editing any file), create another one. The
+ * reason for not reusing temporary files is that there is
+ * special exit processing of them, and reuse is tricky.
+ */
+ frp = sp->frp;
+ if (sp->ep == NULL || F_ISSET(frp, FR_TMPFILE)) {
+ if ((frp = file_add(sp, NULL)) == NULL)
+ return (1);
+ attach = 0;
+ } else
+ attach = 1;
+ setalt = 0;
+ break;
+ case 1:
+ if ((frp = file_add(sp, cmdp->argv[0]->bp)) == NULL)
+ return (1);
+ attach = 0;
+ setalt = 1;
+ set_alt_name(sp, cmdp->argv[0]->bp);
+ break;
+ default:
+ abort();
+ }
+
+ if (F_ISSET(cmdp, E_NEWSCREEN))
+ return (ex_N_edit(sp, cmdp, frp, attach));
+
+ /*
+ * Check for modifications.
+ *
+ * !!!
+ * Contrary to POSIX 1003.2-1992, autowrite did not affect :edit.
+ */
+ if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
+ return (1);
+
+ /* Switch files. */
+ if (file_init(sp, frp, NULL, (setalt ? FS_SETALT : 0) |
+ (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))
+ return (1);
+
+ F_SET(sp, SC_FSWITCH);
+ return (0);
+}
+
+/*
+ * ex_N_edit --
+ * New screen version of ex_edit.
+ */
+static int
+ex_N_edit(sp, cmdp, frp, attach)
+ SCR *sp;
+ EXCMD *cmdp;
+ FREF *frp;
+ int attach;
+{
+ SCR *new;
+
+ /* Get a new screen. */
+ if (screen_init(sp->gp, sp, &new))
+ return (1);
+ if (vs_split(sp, new, 0)) {
+ (void)screen_end(new);
+ return (1);
+ }
+
+ /* Get a backing file. */
+ if (attach) {
+ /* Copy file state, keep the screen and cursor the same. */
+ new->ep = sp->ep;
+ ++new->ep->refcnt;
+
+ new->frp = frp;
+ new->frp->flags = sp->frp->flags;
+
+ new->lno = sp->lno;
+ new->cno = sp->cno;
+ } else if (file_init(new, frp, NULL,
+ (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) {
+ (void)vs_discard(new, NULL);
+ (void)screen_end(new);
+ return (1);
+ }
+
+ /* Create the argument list. */
+ new->cargv = new->argv = ex_buildargv(sp, NULL, frp->name);
+
+ /* Set up the switch. */
+ sp->nextdisp = new;
+ F_SET(sp, SC_SSWITCH);
+
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_equal.c b/contrib/nvi/ex/ex_equal.c
new file mode 100644
index 0000000..565df66
--- /dev/null
+++ b/contrib/nvi/ex/ex_equal.c
@@ -0,0 +1,59 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_equal.c 10.10 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_equal -- :address =
+ *
+ * PUBLIC: int ex_equal __P((SCR *, EXCMD *));
+ */
+int
+ex_equal(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ recno_t lno;
+
+ NEEDFILE(sp, cmdp);
+
+ /*
+ * Print out the line number matching the specified address,
+ * or the number of the last line in the file if no address
+ * specified.
+ *
+ * !!!
+ * Historically, ":0=" displayed 0, and ":=" or ":1=" in an
+ * empty file displayed 1. Until somebody complains loudly,
+ * we're going to do it right. The tables in excmd.c permit
+ * lno to get away with any address from 0 to the end of the
+ * file, which, in an empty file, is 0.
+ */
+ if (F_ISSET(cmdp, E_ADDR_DEF)) {
+ if (db_last(sp, &lno))
+ return (1);
+ } else
+ lno = cmdp->addr1.lno;
+
+ (void)ex_printf(sp, "%ld\n", lno);
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_file.c b/contrib/nvi/ex/ex_file.c
new file mode 100644
index 0000000..3492f9c
--- /dev/null
+++ b/contrib/nvi/ex/ex_file.c
@@ -0,0 +1,80 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_file.c 10.12 (Berkeley) 7/12/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_file -- :f[ile] [name]
+ * Change the file's name and display the status line.
+ *
+ * PUBLIC: int ex_file __P((SCR *, EXCMD *));
+ */
+int
+ex_file(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ CHAR_T *p;
+ FREF *frp;
+
+ NEEDFILE(sp, cmdp);
+
+ switch (cmdp->argc) {
+ case 0:
+ break;
+ case 1:
+ frp = sp->frp;
+
+ /* Make sure can allocate enough space. */
+ if ((p = v_strdup(sp,
+ cmdp->argv[0]->bp, cmdp->argv[0]->len)) == NULL)
+ return (1);
+
+ /* If already have a file name, it becomes the alternate. */
+ if (!F_ISSET(frp, FR_TMPFILE))
+ set_alt_name(sp, frp->name);
+
+ /* Free the previous name. */
+ free(frp->name);
+ frp->name = p;
+
+ /*
+ * The file has a real name, it's no longer a temporary,
+ * clear the temporary file flags.
+ */
+ F_CLR(frp, FR_TMPEXIT | FR_TMPFILE);
+
+ /* Have to force a write if the file exists, next time. */
+ F_SET(frp, FR_NAMECHANGE);
+
+ /* Notify the screen. */
+ (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
+ break;
+ default:
+ abort();
+ }
+ msgq_status(sp, sp->lno, MSTAT_SHOWLAST);
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_filter.c b/contrib/nvi/ex/ex_filter.c
new file mode 100644
index 0000000..2e86e58
--- /dev/null
+++ b/contrib/nvi/ex/ex_filter.c
@@ -0,0 +1,316 @@
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1991, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_filter.c 10.34 (Berkeley) 10/23/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+static int filter_ldisplay __P((SCR *, FILE *));
+
+/*
+ * ex_filter --
+ * Run a range of lines through a filter utility and optionally
+ * replace the original text with the stdout/stderr output of
+ * the utility.
+ *
+ * PUBLIC: int ex_filter __P((SCR *,
+ * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype));
+ */
+int
+ex_filter(sp, cmdp, fm, tm, rp, cmd, ftype)
+ SCR *sp;
+ EXCMD *cmdp;
+ MARK *fm, *tm, *rp;
+ char *cmd;
+ enum filtertype ftype;
+{
+ FILE *ifp, *ofp;
+ pid_t parent_writer_pid, utility_pid;
+ recno_t nread;
+ int input[2], output[2], rval;
+ char *name;
+
+ rval = 0;
+
+ /* Set return cursor position, which is never less than line 1. */
+ *rp = *fm;
+ if (rp->lno == 0)
+ rp->lno = 1;
+
+ /* We're going to need a shell. */
+ if (opts_empty(sp, O_SHELL, 0))
+ return (1);
+
+ /*
+ * There are three different processes running through this code.
+ * They are the utility, the parent-writer and the parent-reader.
+ * The parent-writer is the process that writes from the file to
+ * the utility, the parent reader is the process that reads from
+ * the utility.
+ *
+ * Input and output are named from the utility's point of view.
+ * The utility reads from input[0] and the parent(s) write to
+ * input[1]. The parent(s) read from output[0] and the utility
+ * writes to output[1].
+ *
+ * !!!
+ * Historically, in the FILTER_READ case, the utility reads from
+ * the terminal (e.g. :r! cat works). Otherwise open up utility
+ * input pipe.
+ */
+ ofp = NULL;
+ input[0] = input[1] = output[0] = output[1] = -1;
+ if (ftype != FILTER_READ && pipe(input) < 0) {
+ msgq(sp, M_SYSERR, "pipe");
+ goto err;
+ }
+
+ /* Open up utility output pipe. */
+ if (pipe(output) < 0) {
+ msgq(sp, M_SYSERR, "pipe");
+ goto err;
+ }
+ if ((ofp = fdopen(output[0], "r")) == NULL) {
+ msgq(sp, M_SYSERR, "fdopen");
+ goto err;
+ }
+
+ /* Fork off the utility process. */
+ switch (utility_pid = vfork()) {
+ case -1: /* Error. */
+ msgq(sp, M_SYSERR, "vfork");
+err: if (input[0] != -1)
+ (void)close(input[0]);
+ if (input[1] != -1)
+ (void)close(input[1]);
+ if (ofp != NULL)
+ (void)fclose(ofp);
+ else if (output[0] != -1)
+ (void)close(output[0]);
+ if (output[1] != -1)
+ (void)close(output[1]);
+ return (1);
+ case 0: /* Utility. */
+ /*
+ * Redirect stdin from the read end of the input pipe, and
+ * redirect stdout/stderr to the write end of the output pipe.
+ *
+ * !!!
+ * Historically, ex only directed stdout into the input pipe,
+ * letting stderr come out on the terminal as usual. Vi did
+ * not, directing both stdout and stderr into the input pipe.
+ * We match that practice in both ex and vi for consistency.
+ */
+ if (input[0] != -1)
+ (void)dup2(input[0], STDIN_FILENO);
+ (void)dup2(output[1], STDOUT_FILENO);
+ (void)dup2(output[1], STDERR_FILENO);
+
+ /* Close the utility's file descriptors. */
+ if (input[0] != -1)
+ (void)close(input[0]);
+ if (input[1] != -1)
+ (void)close(input[1]);
+ (void)close(output[0]);
+ (void)close(output[1]);
+
+ if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)
+ name = O_STR(sp, O_SHELL);
+ else
+ ++name;
+
+ execl(O_STR(sp, O_SHELL), name, "-c", cmd, NULL);
+ msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s");
+ _exit (127);
+ /* NOTREACHED */
+ default: /* Parent-reader, parent-writer. */
+ /* Close the pipe ends neither parent will use. */
+ if (input[0] != -1)
+ (void)close(input[0]);
+ (void)close(output[1]);
+ break;
+ }
+
+ /*
+ * FILTER_RBANG, FILTER_READ:
+ *
+ * Reading is the simple case -- we don't need a parent writer,
+ * so the parent reads the output from the read end of the output
+ * pipe until it finishes, then waits for the child. Ex_readfp
+ * appends to the MARK, and closes ofp.
+ *
+ * For FILTER_RBANG, there is nothing to write to the utility.
+ * Make sure it doesn't wait forever by closing its standard
+ * input.
+ *
+ * !!!
+ * Set the return cursor to the last line read in for FILTER_READ.
+ * Historically, this behaves differently from ":r file" command,
+ * which leaves the cursor at the first line read in. Check to
+ * make sure that it's not past EOF because we were reading into an
+ * empty file.
+ */
+ if (ftype == FILTER_RBANG || ftype == FILTER_READ) {
+ if (ftype == FILTER_RBANG)
+ (void)close(input[1]);
+
+ if (ex_readfp(sp, "filter", ofp, fm, &nread, 1))
+ rval = 1;
+ sp->rptlines[L_ADDED] += nread;
+ if (ftype == FILTER_READ)
+ if (fm->lno == 0)
+ rp->lno = nread;
+ else
+ rp->lno += nread;
+ goto uwait;
+ }
+
+ /*
+ * FILTER_BANG, FILTER_WRITE
+ *
+ * Here we need both a reader and a writer. Temporary files are
+ * expensive and we'd like to avoid disk I/O. Using pipes has the
+ * obvious starvation conditions. It's done as follows:
+ *
+ * fork
+ * child
+ * write lines out
+ * exit
+ * parent
+ * FILTER_BANG:
+ * read lines into the file
+ * delete old lines
+ * FILTER_WRITE
+ * read and display lines
+ * wait for child
+ *
+ * XXX
+ * We get away without locking the underlying database because we know
+ * that none of the records that we're reading will be modified until
+ * after we've read them. This depends on the fact that the current
+ * B+tree implementation doesn't balance pages or similar things when
+ * it inserts new records. When the DB code has locking, we should
+ * treat vi as if it were multiple applications sharing a database, and
+ * do the required locking. If necessary a work-around would be to do
+ * explicit locking in the line.c:db_get() code, based on the flag set
+ * here.
+ */
+ F_SET(sp->ep, F_MULTILOCK);
+ switch (parent_writer_pid = fork()) {
+ case -1: /* Error. */
+ msgq(sp, M_SYSERR, "fork");
+ (void)close(input[1]);
+ (void)close(output[0]);
+ rval = 1;
+ break;
+ case 0: /* Parent-writer. */
+ /*
+ * Write the selected lines to the write end of the input
+ * pipe. This instance of ifp is closed by ex_writefp.
+ */
+ (void)close(output[0]);
+ if ((ifp = fdopen(input[1], "w")) == NULL)
+ _exit (1);
+ _exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1));
+
+ /* NOTREACHED */
+ default: /* Parent-reader. */
+ (void)close(input[1]);
+ if (ftype == FILTER_WRITE) {
+ /*
+ * Read the output from the read end of the output
+ * pipe and display it. Filter_ldisplay closes ofp.
+ */
+ if (filter_ldisplay(sp, ofp))
+ rval = 1;
+ } else {
+ /*
+ * Read the output from the read end of the output
+ * pipe. Ex_readfp appends to the MARK and closes
+ * ofp.
+ */
+ if (ex_readfp(sp, "filter", ofp, tm, &nread, 1))
+ rval = 1;
+ sp->rptlines[L_ADDED] += nread;
+ }
+
+ /* Wait for the parent-writer. */
+ if (proc_wait(sp,
+ (long)parent_writer_pid, "parent-writer", 0, 1))
+ rval = 1;
+
+ /* Delete any lines written to the utility. */
+ if (rval == 0 && ftype == FILTER_BANG &&
+ (cut(sp, NULL, fm, tm, CUT_LINEMODE) ||
+ del(sp, fm, tm, 1))) {
+ rval = 1;
+ break;
+ }
+
+ /*
+ * If the filter had no output, we may have just deleted
+ * the cursor. Don't do any real error correction, we'll
+ * try and recover later.
+ */
+ if (rp->lno > 1 && !db_exist(sp, rp->lno))
+ --rp->lno;
+ break;
+ }
+ F_CLR(sp->ep, F_MULTILOCK);
+
+ /*
+ * !!!
+ * Ignore errors on vi file reads, to make reads prettier. It's
+ * completely inconsistent, and historic practice.
+ */
+uwait: return (proc_wait(sp, (long)utility_pid, cmd,
+ ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval);
+}
+
+/*
+ * filter_ldisplay --
+ * Display output from a utility.
+ *
+ * !!!
+ * Historically, the characters were passed unmodified to the terminal.
+ * We use the ex print routines to make sure they're printable.
+ */
+static int
+filter_ldisplay(sp, fp)
+ SCR *sp;
+ FILE *fp;
+{
+ size_t len;
+
+ EX_PRIVATE *exp;
+
+ for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);)
+ if (ex_ldisplay(sp, exp->ibp, len, 0, 0))
+ break;
+ if (ferror(fp))
+ msgq(sp, M_SYSERR, "filter read");
+ (void)fclose(fp);
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_global.c b/contrib/nvi/ex/ex_global.c
new file mode 100644
index 0000000..aba9dc5
--- /dev/null
+++ b/contrib/nvi/ex/ex_global.c
@@ -0,0 +1,328 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_global.c 10.22 (Berkeley) 10/10/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+enum which {GLOBAL, V};
+
+static int ex_g_setup __P((SCR *, EXCMD *, enum which));
+
+/*
+ * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands]
+ * Exec on lines matching a pattern.
+ *
+ * PUBLIC: int ex_global __P((SCR *, EXCMD *));
+ */
+int
+ex_global(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (ex_g_setup(sp,
+ cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL));
+}
+
+/*
+ * ex_v -- [line [,line]] v /pattern/ [commands]
+ * Exec on lines not matching a pattern.
+ *
+ * PUBLIC: int ex_v __P((SCR *, EXCMD *));
+ */
+int
+ex_v(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (ex_g_setup(sp, cmdp, V));
+}
+
+/*
+ * ex_g_setup --
+ * Ex global and v commands.
+ */
+static int
+ex_g_setup(sp, cmdp, cmd)
+ SCR *sp;
+ EXCMD *cmdp;
+ enum which cmd;
+{
+ CHAR_T *ptrn, *p, *t;
+ EXCMD *ecp;
+ MARK abs;
+ RANGE *rp;
+ busy_t btype;
+ recno_t start, end;
+ regex_t *re;
+ regmatch_t match[1];
+ size_t len;
+ int cnt, delim, eval;
+ char *dbp;
+
+ NEEDFILE(sp, cmdp);
+
+ if (F_ISSET(sp, SC_EX_GLOBAL)) {
+ msgq(sp, M_ERR,
+ "124|The %s command can't be used as part of a global or v command",
+ cmdp->cmd->name);
+ return (1);
+ }
+
+ /*
+ * Skip leading white space. Historic vi allowed any non-alphanumeric
+ * to serve as the global command delimiter.
+ */
+ if (cmdp->argc == 0)
+ goto usage;
+ for (p = cmdp->argv[0]->bp; isblank(*p); ++p);
+ if (*p == '\0' || isalnum(*p) ||
+ *p == '\\' || *p == '|' || *p == '\n') {
+usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+ delim = *p++;
+
+ /*
+ * Get the pattern string, toss escaped characters.
+ *
+ * QUOTING NOTE:
+ * Only toss an escaped character if it escapes a delimiter.
+ */
+ for (ptrn = t = p;;) {
+ if (p[0] == '\0' || p[0] == delim) {
+ if (p[0] == delim)
+ ++p;
+ /*
+ * !!!
+ * Nul terminate the pattern string -- it's passed
+ * to regcomp which doesn't understand anything else.
+ */
+ *t = '\0';
+ break;
+ }
+ if (p[0] == '\\')
+ if (p[1] == delim)
+ ++p;
+ else if (p[1] == '\\')
+ *t++ = *p++;
+ *t++ = *p++;
+ }
+
+ /* If the pattern string is empty, use the last one. */
+ if (*ptrn == '\0') {
+ if (sp->re == NULL) {
+ ex_emsg(sp, NULL, EXM_NOPREVRE);
+ return (1);
+ }
+
+ /* Re-compile the RE if necessary. */
+ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,
+ sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))
+ return (1);
+ } else {
+ /* Compile the RE. */
+ if (re_compile(sp, ptrn, t - ptrn,
+ &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH))
+ return (1);
+
+ /*
+ * Set saved RE. Historic practice is that globals set
+ * direction as well as the RE.
+ */
+ sp->searchdir = FORWARD;
+ }
+ re = &sp->re_c;
+
+ /* The global commands always set the previous context mark. */
+ abs.lno = sp->lno;
+ abs.cno = sp->cno;
+ if (mark_set(sp, ABSMARK1, &abs, 1))
+ return (1);
+
+ /* Get an EXCMD structure. */
+ CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD));
+ CIRCLEQ_INIT(&ecp->rq);
+
+ /*
+ * Get a copy of the command string; the default command is print.
+ * Don't worry about a set of <blank>s with no command, that will
+ * default to print in the ex parser. We need to have two copies
+ * because the ex parser may step on the command string when it's
+ * parsing it.
+ */
+ if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) {
+ p = "pp";
+ len = 1;
+ }
+
+ MALLOC_RET(sp, ecp->cp, char *, len * 2);
+ ecp->o_cp = ecp->cp;
+ ecp->o_clen = len;
+ memcpy(ecp->cp + len, p, len);
+ ecp->range_lno = OOBLNO;
+ FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V);
+ LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q);
+
+ /*
+ * For each line... The semantics of global matching are that we first
+ * have to decide which lines are going to get passed to the command,
+ * and then pass them to the command, ignoring other changes. There's
+ * really no way to do this in a single pass, since arbitrary line
+ * creation, deletion and movement can be done in the ex command. For
+ * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d".
+ * What we do is create linked list of lines that are tracked through
+ * each ex command. There's a callback routine which the DB interface
+ * routines call when a line is created or deleted. This doesn't help
+ * the layering much.
+ */
+ btype = BUSY_ON;
+ cnt = INTERRUPT_CHECK;
+ for (start = cmdp->addr1.lno,
+ end = cmdp->addr2.lno; start <= end; ++start) {
+ if (cnt-- == 0) {
+ if (INTERRUPTED(sp)) {
+ LIST_REMOVE(ecp, q);
+ free(ecp->cp);
+ free(ecp);
+ break;
+ }
+ search_busy(sp, btype);
+ btype = BUSY_UPDATE;
+ cnt = INTERRUPT_CHECK;
+ }
+ if (db_get(sp, start, DBG_FATAL, &dbp, &len))
+ return (1);
+ match[0].rm_so = 0;
+ match[0].rm_eo = len;
+ switch (eval =
+ regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) {
+ case 0:
+ if (cmd == V)
+ continue;
+ break;
+ case REG_NOMATCH:
+ if (cmd == GLOBAL)
+ continue;
+ break;
+ default:
+ re_error(sp, eval, &sp->re_c);
+ break;
+ }
+
+ /* If follows the last entry, extend the last entry's range. */
+ if ((rp = ecp->rq.cqh_last) != (void *)&ecp->rq &&
+ rp->stop == start - 1) {
+ ++rp->stop;
+ continue;
+ }
+
+ /* Allocate a new range, and append it to the list. */
+ CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE));
+ if (rp == NULL)
+ return (1);
+ rp->start = rp->stop = start;
+ CIRCLEQ_INSERT_TAIL(&ecp->rq, rp, q);
+ }
+ search_busy(sp, BUSY_OFF);
+ return (0);
+}
+
+/*
+ * ex_g_insdel --
+ * Update the ranges based on an insertion or deletion.
+ *
+ * PUBLIC: int ex_g_insdel __P((SCR *, lnop_t, recno_t));
+ */
+int
+ex_g_insdel(sp, op, lno)
+ SCR *sp;
+ lnop_t op;
+ recno_t lno;
+{
+ EXCMD *ecp;
+ RANGE *nrp, *rp;
+
+ /* All insert/append operations are done as inserts. */
+ if (op == LINE_APPEND)
+ abort();
+
+ if (op == LINE_RESET)
+ return (0);
+
+ for (ecp = sp->gp->ecq.lh_first; ecp != NULL; ecp = ecp->q.le_next) {
+ if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V))
+ continue;
+ for (rp = ecp->rq.cqh_first; rp != (void *)&ecp->rq; rp = nrp) {
+ nrp = rp->q.cqe_next;
+
+ /* If range less than the line, ignore it. */
+ if (rp->stop < lno)
+ continue;
+
+ /*
+ * If range greater than the line, decrement or
+ * increment the range.
+ */
+ if (rp->start > lno) {
+ if (op == LINE_DELETE) {
+ --rp->start;
+ --rp->stop;
+ } else {
+ ++rp->start;
+ ++rp->stop;
+ }
+ continue;
+ }
+
+ /*
+ * Lno is inside the range, decrement the end point
+ * for deletion, and split the range for insertion.
+ * In the latter case, since we're inserting a new
+ * element, neither range can be exhausted.
+ */
+ if (op == LINE_DELETE) {
+ if (rp->start > --rp->stop) {
+ CIRCLEQ_REMOVE(&ecp->rq, rp, q);
+ free(rp);
+ }
+ } else {
+ CALLOC_RET(sp, nrp, RANGE *, 1, sizeof(RANGE));
+ nrp->start = lno + 1;
+ nrp->stop = rp->stop + 1;
+ rp->stop = lno - 1;
+ CIRCLEQ_INSERT_AFTER(&ecp->rq, rp, nrp, q);
+ rp = nrp;
+ }
+ }
+
+ /*
+ * If the command deleted/inserted lines, the cursor moves to
+ * the line after the deleted/inserted line.
+ */
+ ecp->range_lno = lno;
+ }
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_init.c b/contrib/nvi/ex/ex_init.c
new file mode 100644
index 0000000..6a78416
--- /dev/null
+++ b/contrib/nvi/ex/ex_init.c
@@ -0,0 +1,417 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_init.c 10.26 (Berkeley) 8/12/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h> /* XXX: param.h may not have included types.h */
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <bitstring.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "tag.h"
+#include "pathnames.h"
+
+enum rc { NOEXIST, NOPERM, RCOK };
+static enum rc exrc_isok __P((SCR *, struct stat *, char *, int, int));
+
+static int ex_run_file __P((SCR *, char *));
+
+/*
+ * ex_screen_copy --
+ * Copy ex screen.
+ *
+ * PUBLIC: int ex_screen_copy __P((SCR *, SCR *));
+ */
+int
+ex_screen_copy(orig, sp)
+ SCR *orig, *sp;
+{
+ EX_PRIVATE *oexp, *nexp;
+
+ /* Create the private ex structure. */
+ CALLOC_RET(orig, nexp, EX_PRIVATE *, 1, sizeof(EX_PRIVATE));
+ sp->ex_private = nexp;
+
+ /* Initialize queues. */
+ CIRCLEQ_INIT(&nexp->tq);
+ TAILQ_INIT(&nexp->tagfq);
+ LIST_INIT(&nexp->cscq);
+
+ if (orig == NULL) {
+ } else {
+ oexp = EXP(orig);
+
+ if (oexp->lastbcomm != NULL &&
+ (nexp->lastbcomm = strdup(oexp->lastbcomm)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ return(1);
+ }
+ if (ex_tag_copy(orig, sp))
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * ex_screen_end --
+ * End a vi screen.
+ *
+ * PUBLIC: int ex_screen_end __P((SCR *));
+ */
+int
+ex_screen_end(sp)
+ SCR *sp;
+{
+ EX_PRIVATE *exp;
+ int rval;
+
+ if ((exp = EXP(sp)) == NULL)
+ return (0);
+
+ rval = 0;
+
+ /* Close down script connections. */
+ if (F_ISSET(sp, SC_SCRIPT) && sscr_end(sp))
+ rval = 1;
+
+ if (argv_free(sp))
+ rval = 1;
+
+ if (exp->ibp != NULL)
+ free(exp->ibp);
+
+ if (exp->lastbcomm != NULL)
+ free(exp->lastbcomm);
+
+ if (ex_tag_free(sp))
+ rval = 1;
+
+ /* Free private memory. */
+ free(exp);
+ sp->ex_private = NULL;
+
+ return (rval);
+}
+
+/*
+ * ex_optchange --
+ * Handle change of options for ex.
+ *
+ * PUBLIC: int ex_optchange __P((SCR *, int, char *, u_long *));
+ */
+int
+ex_optchange(sp, offset, str, valp)
+ SCR *sp;
+ int offset;
+ char *str;
+ u_long *valp;
+{
+ switch (offset) {
+ case O_TAGS:
+ return (ex_tagf_alloc(sp, str));
+ }
+ return (0);
+}
+
+/*
+ * ex_exrc --
+ * Read the EXINIT environment variable and the startup exrc files,
+ * and execute their commands.
+ *
+ * PUBLIC: int ex_exrc __P((SCR *));
+ */
+int
+ex_exrc(sp)
+ SCR *sp;
+{
+ struct stat hsb, lsb;
+ char *p, path[MAXPATHLEN];
+
+ /*
+ * Source the system, environment, $HOME and local .exrc values.
+ * Vi historically didn't check $HOME/.exrc if the environment
+ * variable EXINIT was set. This is all done before the file is
+ * read in, because things in the .exrc information can set, for
+ * example, the recovery directory.
+ *
+ * !!!
+ * While nvi can handle any of the options settings of historic vi,
+ * the converse is not true. Since users are going to have to have
+ * files and environmental variables that work with both, we use nvi
+ * versions of both the $HOME and local startup files if they exist,
+ * otherwise the historic ones.
+ *
+ * !!!
+ * For a discussion of permissions and when what .exrc files are
+ * read, see the comment above the exrc_isok() function below.
+ *
+ * !!!
+ * If the user started the historic of vi in $HOME, vi read the user's
+ * .exrc file twice, as $HOME/.exrc and as ./.exrc. We avoid this, as
+ * it's going to make some commands behave oddly, and I can't imagine
+ * anyone depending on it.
+ */
+ switch (exrc_isok(sp, &hsb, _PATH_SYSEXRC, 1, 0)) {
+ case NOEXIST:
+ case NOPERM:
+ break;
+ case RCOK:
+ if (ex_run_file(sp, _PATH_SYSEXRC))
+ return (1);
+ break;
+ }
+
+ /* Run the commands. */
+ if (EXCMD_RUNNING(sp->gp))
+ (void)ex_cmd(sp);
+ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE))
+ return (0);
+
+ if ((p = getenv("NEXINIT")) != NULL) {
+ if (ex_run_str(sp, "NEXINIT", p, strlen(p), 1, 0))
+ return (1);
+ } else if ((p = getenv("EXINIT")) != NULL) {
+ if (ex_run_str(sp, "EXINIT", p, strlen(p), 1, 0))
+ return (1);
+ } else if ((p = getenv("HOME")) != NULL && *p) {
+ (void)snprintf(path, sizeof(path), "%s/%s", p, _PATH_NEXRC);
+ switch (exrc_isok(sp, &hsb, path, 0, 1)) {
+ case NOEXIST:
+ (void)snprintf(path,
+ sizeof(path), "%s/%s", p, _PATH_EXRC);
+ if (exrc_isok(sp,
+ &hsb, path, 0, 1) == RCOK && ex_run_file(sp, path))
+ return (1);
+ break;
+ case NOPERM:
+ break;
+ case RCOK:
+ if (ex_run_file(sp, path))
+ return (1);
+ break;
+ }
+ }
+
+ /* Run the commands. */
+ if (EXCMD_RUNNING(sp->gp))
+ (void)ex_cmd(sp);
+ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE))
+ return (0);
+
+ /* Previous commands may have set the exrc option. */
+ if (O_ISSET(sp, O_EXRC)) {
+ switch (exrc_isok(sp, &lsb, _PATH_NEXRC, 0, 0)) {
+ case NOEXIST:
+ if (exrc_isok(sp, &lsb, _PATH_EXRC, 0, 0) == RCOK &&
+ (lsb.st_dev != hsb.st_dev ||
+ lsb.st_ino != hsb.st_ino) &&
+ ex_run_file(sp, _PATH_EXRC))
+ return (1);
+ break;
+ case NOPERM:
+ break;
+ case RCOK:
+ if ((lsb.st_dev != hsb.st_dev ||
+ lsb.st_ino != hsb.st_ino) &&
+ ex_run_file(sp, _PATH_NEXRC))
+ return (1);
+ break;
+ }
+ /* Run the commands. */
+ if (EXCMD_RUNNING(sp->gp))
+ (void)ex_cmd(sp);
+ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE))
+ return (0);
+ }
+
+ return (0);
+}
+
+/*
+ * ex_run_file --
+ * Set up a file of ex commands to run.
+ */
+static int
+ex_run_file(sp, name)
+ SCR *sp;
+ char *name;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_SOURCE, 0, OOBLNO, OOBLNO, 0, ap);
+ ex_cadd(&cmd, &a, name, strlen(name));
+ return (ex_source(sp, &cmd));
+}
+
+/*
+ * ex_run_str --
+ * Set up a string of ex commands to run.
+ *
+ * PUBLIC: int ex_run_str __P((SCR *, char *, char *, size_t, int, int));
+ */
+int
+ex_run_str(sp, name, str, len, ex_flags, nocopy)
+ SCR *sp;
+ char *name, *str;
+ size_t len;
+ int ex_flags, nocopy;
+{
+ GS *gp;
+ EXCMD *ecp;
+
+ gp = sp->gp;
+ if (EXCMD_RUNNING(gp)) {
+ CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD));
+ LIST_INSERT_HEAD(&gp->ecq, ecp, q);
+ } else
+ ecp = &gp->excmd;
+
+ F_INIT(ecp,
+ ex_flags ? E_BLIGNORE | E_NOAUTO | E_NOPRDEF | E_VLITONLY : 0);
+
+ if (nocopy)
+ ecp->cp = str;
+ else
+ if ((ecp->cp = v_strdup(sp, str, len)) == NULL)
+ return (1);
+ ecp->clen = len;
+
+ if (name == NULL)
+ ecp->if_name = NULL;
+ else {
+ if ((ecp->if_name = v_strdup(sp, name, strlen(name))) == NULL)
+ return (1);
+ ecp->if_lno = 1;
+ F_SET(ecp, E_NAMEDISCARD);
+ }
+
+ return (0);
+}
+
+/*
+ * exrc_isok --
+ * Check a .exrc file for source-ability.
+ *
+ * !!!
+ * Historically, vi read the $HOME and local .exrc files if they were owned
+ * by the user's real ID, or the "sourceany" option was set, regardless of
+ * any other considerations. We no longer support the sourceany option as
+ * it's a security problem of mammoth proportions. We require the system
+ * .exrc file to be owned by root, the $HOME .exrc file to be owned by the
+ * user's effective ID (or that the user's effective ID be root) and the
+ * local .exrc files to be owned by the user's effective ID. In all cases,
+ * the file cannot be writeable by anyone other than its owner.
+ *
+ * In O'Reilly ("Learning the VI Editor", Fifth Ed., May 1992, page 106),
+ * it notes that System V release 3.2 and later has an option "[no]exrc".
+ * The behavior is that local .exrc files are read only if the exrc option
+ * is set. The default for the exrc option was off, so, by default, local
+ * .exrc files were not read. The problem this was intended to solve was
+ * that System V permitted users to give away files, so there's no possible
+ * ownership or writeability test to ensure that the file is safe.
+ *
+ * POSIX 1003.2-1992 standardized exrc as an option. It required the exrc
+ * option to be off by default, thus local .exrc files are not to be read
+ * by default. The Rationale noted (incorrectly) that this was a change
+ * to historic practice, but correctly noted that a default of off improves
+ * system security. POSIX also required that vi check the effective user
+ * ID instead of the real user ID, which is why we've switched from historic
+ * practice.
+ *
+ * We initialize the exrc variable to off. If it's turned on by the system
+ * or $HOME .exrc files, and the local .exrc file passes the ownership and
+ * writeability tests, then we read it. This breaks historic 4BSD practice,
+ * but it gives us a measure of security on systems where users can give away
+ * files.
+ */
+static enum rc
+exrc_isok(sp, sbp, path, rootown, rootid)
+ SCR *sp;
+ struct stat *sbp;
+ char *path;
+ int rootown, rootid;
+{
+ enum { ROOTOWN, OWN, WRITER } etype;
+ uid_t euid;
+ int nf1, nf2;
+ char *a, *b, buf[MAXPATHLEN];
+
+ /* Check for the file's existence. */
+ if (stat(path, sbp))
+ return (NOEXIST);
+
+ /* Check ownership permissions. */
+ euid = geteuid();
+ if (!(rootown && sbp->st_uid == 0) &&
+ !(rootid && euid == 0) && sbp->st_uid != euid) {
+ etype = rootown ? ROOTOWN : OWN;
+ goto denied;
+ }
+
+ /* Check writeability. */
+ if (sbp->st_mode & (S_IWGRP | S_IWOTH)) {
+ etype = WRITER;
+ goto denied;
+ }
+ return (RCOK);
+
+denied: a = msg_print(sp, path, &nf1);
+ if (strchr(path, '/') == NULL && getcwd(buf, sizeof(buf)) != NULL) {
+ b = msg_print(sp, buf, &nf2);
+ switch (etype) {
+ case ROOTOWN:
+ msgq(sp, M_ERR,
+ "125|%s/%s: not sourced: not owned by you or root",
+ b, a);
+ break;
+ case OWN:
+ msgq(sp, M_ERR,
+ "126|%s/%s: not sourced: not owned by you", b, a);
+ break;
+ case WRITER:
+ msgq(sp, M_ERR,
+ "127|%s/%s: not sourced: writeable by a user other than the owner", b, a);
+ break;
+ }
+ if (nf2)
+ FREE_SPACE(sp, b, 0);
+ } else
+ switch (etype) {
+ case ROOTOWN:
+ msgq(sp, M_ERR,
+ "128|%s: not sourced: not owned by you or root", a);
+ break;
+ case OWN:
+ msgq(sp, M_ERR,
+ "129|%s: not sourced: not owned by you", a);
+ break;
+ case WRITER:
+ msgq(sp, M_ERR,
+ "130|%s: not sourced: writeable by a user other than the owner", a);
+ break;
+ }
+
+ if (nf1)
+ FREE_SPACE(sp, a, 0);
+ return (NOPERM);
+}
diff --git a/contrib/nvi/ex/ex_join.c b/contrib/nvi/ex/ex_join.c
new file mode 100644
index 0000000..c26c424
--- /dev/null
+++ b/contrib/nvi/ex/ex_join.c
@@ -0,0 +1,177 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_join.c 10.10 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_join -- :[line [,line]] j[oin][!] [count] [flags]
+ * Join lines.
+ *
+ * PUBLIC: int ex_join __P((SCR *, EXCMD *));
+ */
+int
+ex_join(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ recno_t from, to;
+ size_t blen, clen, len, tlen;
+ int echar, extra, first;
+ char *bp, *p, *tbp;
+
+ NEEDFILE(sp, cmdp);
+
+ from = cmdp->addr1.lno;
+ to = cmdp->addr2.lno;
+
+ /* Check for no lines to join. */
+ if (!db_exist(sp, from + 1)) {
+ msgq(sp, M_ERR, "131|No following lines to join");
+ return (1);
+ }
+
+ GET_SPACE_RET(sp, bp, blen, 256);
+
+ /*
+ * The count for the join command was off-by-one,
+ * historically, to other counts for other commands.
+ */
+ if (FL_ISSET(cmdp->iflags, E_C_COUNT))
+ ++cmdp->addr2.lno;
+
+ /*
+ * If only a single address specified, or, the same address
+ * specified twice, the from/two addresses will be the same.
+ */
+ if (cmdp->addr1.lno == cmdp->addr2.lno)
+ ++cmdp->addr2.lno;
+
+ clen = tlen = 0;
+ for (first = 1,
+ from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) {
+ /*
+ * Get next line. Historic versions of vi allowed "10J" while
+ * less than 10 lines from the end-of-file, so we do too.
+ */
+ if (db_get(sp, from, 0, &p, &len)) {
+ cmdp->addr2.lno = from - 1;
+ break;
+ }
+
+ /* Empty lines just go away. */
+ if (len == 0)
+ continue;
+
+ /*
+ * Get more space if necessary. Note, tlen isn't the length
+ * of the new line, it's roughly the amount of space needed.
+ * tbp - bp is the length of the new line.
+ */
+ tlen += len + 2;
+ ADD_SPACE_RET(sp, bp, blen, tlen);
+ tbp = bp + clen;
+
+ /*
+ * Historic practice:
+ *
+ * If force specified, join without modification.
+ * If the current line ends with whitespace, strip leading
+ * whitespace from the joined line.
+ * If the next line starts with a ), do nothing.
+ * If the current line ends with ., insert two spaces.
+ * Else, insert one space.
+ *
+ * One change -- add ? and ! to the list of characters for
+ * which we insert two spaces. I expect that POSIX 1003.2
+ * will require this as well.
+ *
+ * Echar is the last character in the last line joined.
+ */
+ extra = 0;
+ if (!first && !FL_ISSET(cmdp->iflags, E_C_FORCE)) {
+ if (isblank(echar))
+ for (; len && isblank(*p); --len, ++p);
+ else if (p[0] != ')') {
+ if (strchr(".?!", echar)) {
+ *tbp++ = ' ';
+ ++clen;
+ extra = 1;
+ }
+ *tbp++ = ' ';
+ ++clen;
+ for (; len && isblank(*p); --len, ++p);
+ }
+ }
+
+ if (len != 0) {
+ memcpy(tbp, p, len);
+ tbp += len;
+ clen += len;
+ echar = p[len - 1];
+ } else
+ echar = ' ';
+
+ /*
+ * Historic practice for vi was to put the cursor at the first
+ * inserted whitespace character, if there was one, or the
+ * first character of the joined line, if there wasn't, or the
+ * last character of the line if joined to an empty line. If
+ * a count was specified, the cursor was moved as described
+ * for the first line joined, ignoring subsequent lines. If
+ * the join was a ':' command, the cursor was placed at the
+ * first non-blank character of the line unless the cursor was
+ * "attracted" to the end of line when the command was executed
+ * in which case it moved to the new end of line. There are
+ * probably several more special cases, but frankly, my dear,
+ * I don't give a damn. This implementation puts the cursor
+ * on the first inserted whitespace character, the first
+ * character of the joined line, or the last character of the
+ * line regardless. Note, if the cursor isn't on the joined
+ * line (possible with : commands), it is reset to the starting
+ * line.
+ */
+ if (first) {
+ sp->cno = (tbp - bp) - (1 + extra);
+ first = 0;
+ } else
+ sp->cno = (tbp - bp) - len - (1 + extra);
+ }
+ sp->lno = cmdp->addr1.lno;
+
+ /* Delete the joined lines. */
+ for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; to > from; --to)
+ if (db_delete(sp, to))
+ goto err;
+
+ /* If the original line changed, reset it. */
+ if (!first && db_set(sp, from, bp, tbp - bp)) {
+err: FREE_SPACE(sp, bp, blen);
+ return (1);
+ }
+ FREE_SPACE(sp, bp, blen);
+
+ sp->rptlines[L_JOINED] += (cmdp->addr2.lno - cmdp->addr1.lno) + 1;
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_map.c b/contrib/nvi/ex/ex_map.c
new file mode 100644
index 0000000..bc2cf08
--- /dev/null
+++ b/contrib/nvi/ex/ex_map.c
@@ -0,0 +1,121 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_map.c 10.9 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_map -- :map[!] [input] [replacement]
+ * Map a key/string or display mapped keys.
+ *
+ * Historical note:
+ * Historic vi maps were fairly bizarre, and likely to differ in
+ * very subtle and strange ways from this implementation. Two
+ * things worth noting are that vi would often hang or drop core
+ * if the map was strange enough (ex: map X "xy$@x^V), or, simply
+ * not work. One trick worth remembering is that if you put a
+ * mark at the start of the map, e.g. map X mx"xy ...), or if you
+ * put the map in a .exrc file, things would often work much better.
+ * No clue why.
+ *
+ * PUBLIC: int ex_map __P((SCR *, EXCMD *));
+ */
+int
+ex_map(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ seq_t stype;
+ CHAR_T *input, *p;
+
+ stype = FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND;
+
+ switch (cmdp->argc) {
+ case 0:
+ if (seq_dump(sp, stype, 1) == 0)
+ msgq(sp, M_INFO, stype == SEQ_INPUT ?
+ "132|No input map entries" :
+ "133|No command map entries");
+ return (0);
+ case 2:
+ input = cmdp->argv[0]->bp;
+ break;
+ default:
+ abort();
+ }
+
+ /*
+ * If the mapped string is #[0-9]* (and wasn't quoted) then store the
+ * function key mapping. If the screen specific routine has been set,
+ * call it as well. Note, the SEQ_FUNCMAP type is persistent across
+ * screen types, maybe the next screen type will get it right.
+ */
+ if (input[0] == '#' && isdigit(input[1])) {
+ for (p = input + 2; isdigit(*p); ++p);
+ if (p[0] != '\0')
+ goto nofunc;
+
+ if (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len,
+ cmdp->argv[1]->bp, cmdp->argv[1]->len, stype,
+ SEQ_FUNCMAP | SEQ_USERDEF))
+ return (1);
+ return (sp->gp->scr_fmap == NULL ? 0 :
+ sp->gp->scr_fmap(sp, stype, input, cmdp->argv[0]->len,
+ cmdp->argv[1]->bp, cmdp->argv[1]->len));
+ }
+
+ /* Some single keys may not be remapped in command mode. */
+nofunc: if (stype == SEQ_COMMAND && input[1] == '\0')
+ switch (KEY_VAL(sp, input[0])) {
+ case K_COLON:
+ case K_ESCAPE:
+ case K_NL:
+ msgq(sp, M_ERR,
+ "134|The %s character may not be remapped",
+ KEY_NAME(sp, input[0]));
+ return (1);
+ }
+ return (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len,
+ cmdp->argv[1]->bp, cmdp->argv[1]->len, stype, SEQ_USERDEF));
+}
+
+/*
+ * ex_unmap -- (:unmap[!] key)
+ * Unmap a key.
+ *
+ * PUBLIC: int ex_unmap __P((SCR *, EXCMD *));
+ */
+int
+ex_unmap(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ if (seq_delete(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len,
+ FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND)) {
+ msgq_str(sp, M_INFO,
+ cmdp->argv[0]->bp, "135|\"%s\" isn't currently mapped");
+ return (1);
+ }
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_mark.c b/contrib/nvi/ex/ex_mark.c
new file mode 100644
index 0000000..08ad8c2
--- /dev/null
+++ b/contrib/nvi/ex/ex_mark.c
@@ -0,0 +1,45 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_mark.c 10.8 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_mark -- :mark char
+ * :k char
+ * Mark lines.
+ *
+ *
+ * PUBLIC: int ex_mark __P((SCR *, EXCMD *));
+ */
+int
+ex_mark(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ NEEDFILE(sp, cmdp);
+
+ if (cmdp->argv[0]->len != 1) {
+ msgq(sp, M_ERR, "136|Mark names must be a single character");
+ return (1);
+ }
+ return (mark_set(sp, cmdp->argv[0]->bp[0], &cmdp->addr1, 1));
+}
diff --git a/contrib/nvi/ex/ex_mkexrc.c b/contrib/nvi/ex/ex_mkexrc.c
new file mode 100644
index 0000000..0eb15d4
--- /dev/null
+++ b/contrib/nvi/ex/ex_mkexrc.c
@@ -0,0 +1,101 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_mkexrc.c 10.11 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "pathnames.h"
+
+/*
+ * ex_mkexrc -- :mkexrc[!] [file]
+ *
+ * Create (or overwrite) a .exrc file with the current info.
+ *
+ * PUBLIC: int ex_mkexrc __P((SCR *, EXCMD *));
+ */
+int
+ex_mkexrc(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ struct stat sb;
+ FILE *fp;
+ int fd, sverrno;
+ char *fname;
+
+ switch (cmdp->argc) {
+ case 0:
+ fname = _PATH_EXRC;
+ break;
+ case 1:
+ fname = cmdp->argv[0]->bp;
+ set_alt_name(sp, fname);
+ break;
+ default:
+ abort();
+ }
+
+ if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && !stat(fname, &sb)) {
+ msgq_str(sp, M_ERR, fname,
+ "137|%s exists, not written; use ! to override");
+ return (1);
+ }
+
+ /* Create with max permissions of rw-r--r--. */
+ if ((fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
+ msgq_str(sp, M_SYSERR, fname, "%s");
+ return (1);
+ }
+
+ if ((fp = fdopen(fd, "w")) == NULL) {
+ sverrno = errno;
+ (void)close(fd);
+ goto e2;
+ }
+
+ if (seq_save(sp, fp, "abbreviate ", SEQ_ABBREV) || ferror(fp))
+ goto e1;
+ if (seq_save(sp, fp, "map ", SEQ_COMMAND) || ferror(fp))
+ goto e1;
+ if (seq_save(sp, fp, "map! ", SEQ_INPUT) || ferror(fp))
+ goto e1;
+ if (opts_save(sp, fp) || ferror(fp))
+ goto e1;
+ if (fclose(fp)) {
+ sverrno = errno;
+ goto e2;
+ }
+
+ msgq_str(sp, M_INFO, fname, "138|New exrc file: %s");
+ return (0);
+
+e1: sverrno = errno;
+ (void)fclose(fp);
+e2: errno = sverrno;
+ msgq_str(sp, M_SYSERR, fname, "%s");
+ return (1);
+}
diff --git a/contrib/nvi/ex/ex_move.c b/contrib/nvi/ex/ex_move.c
new file mode 100644
index 0000000..d6e45c3
--- /dev/null
+++ b/contrib/nvi/ex/ex_move.c
@@ -0,0 +1,198 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_move.c 10.10 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_copy -- :[line [,line]] co[py] line [flags]
+ * Copy selected lines.
+ *
+ * PUBLIC: int ex_copy __P((SCR *, EXCMD *));
+ */
+int
+ex_copy(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ CB cb;
+ MARK fm1, fm2, m, tm;
+ recno_t cnt;
+ int rval;
+
+ rval = 0;
+
+ NEEDFILE(sp, cmdp);
+
+ /*
+ * It's possible to copy things into the area that's being
+ * copied, e.g. "2,5copy3" is legitimate. Save the text to
+ * a cut buffer.
+ */
+ fm1 = cmdp->addr1;
+ fm2 = cmdp->addr2;
+ memset(&cb, 0, sizeof(cb));
+ CIRCLEQ_INIT(&cb.textq);
+ for (cnt = fm1.lno; cnt <= fm2.lno; ++cnt)
+ if (cut_line(sp, cnt, 0, 0, &cb)) {
+ rval = 1;
+ goto err;
+ }
+ cb.flags |= CB_LMODE;
+
+ /* Put the text into place. */
+ tm.lno = cmdp->lineno;
+ tm.cno = 0;
+ if (put(sp, &cb, NULL, &tm, &m, 1))
+ rval = 1;
+ else {
+ /*
+ * Copy puts the cursor on the last line copied. The cursor
+ * returned by the put routine is the first line put, not the
+ * last, because that's the historic semantic of vi.
+ */
+ cnt = (fm2.lno - fm1.lno) + 1;
+ sp->lno = m.lno + (cnt - 1);
+ sp->cno = 0;
+ }
+err: text_lfree(&cb.textq);
+ return (rval);
+}
+
+/*
+ * ex_move -- :[line [,line]] mo[ve] line
+ * Move selected lines.
+ *
+ * PUBLIC: int ex_move __P((SCR *, EXCMD *));
+ */
+int
+ex_move(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ LMARK *lmp;
+ MARK fm1, fm2;
+ recno_t cnt, diff, fl, tl, mfl, mtl;
+ size_t blen, len;
+ int mark_reset;
+ char *bp, *p;
+
+ NEEDFILE(sp, cmdp);
+
+ /*
+ * It's not possible to move things into the area that's being
+ * moved.
+ */
+ fm1 = cmdp->addr1;
+ fm2 = cmdp->addr2;
+ if (cmdp->lineno >= fm1.lno && cmdp->lineno <= fm2.lno) {
+ msgq(sp, M_ERR, "139|Destination line is inside move range");
+ return (1);
+ }
+
+ /*
+ * Log the positions of any marks in the to-be-deleted lines. This
+ * has to work with the logging code. What happens is that we log
+ * the old mark positions, make the changes, then log the new mark
+ * positions. Then the marks end up in the right positions no matter
+ * which way the log is traversed.
+ *
+ * XXX
+ * Reset the MARK_USERSET flag so that the log can undo the mark.
+ * This isn't very clean, and should probably be fixed.
+ */
+ fl = fm1.lno;
+ tl = cmdp->lineno;
+
+ /* Log the old positions of the marks. */
+ mark_reset = 0;
+ for (lmp = sp->ep->marks.lh_first; lmp != NULL; lmp = lmp->q.le_next)
+ if (lmp->name != ABSMARK1 &&
+ lmp->lno >= fl && lmp->lno <= tl) {
+ mark_reset = 1;
+ F_CLR(lmp, MARK_USERSET);
+ (void)log_mark(sp, lmp);
+ }
+
+ /* Get memory for the copy. */
+ GET_SPACE_RET(sp, bp, blen, 256);
+
+ /* Move the lines. */
+ diff = (fm2.lno - fm1.lno) + 1;
+ if (tl > fl) { /* Destination > source. */
+ mfl = tl - diff;
+ mtl = tl;
+ for (cnt = diff; cnt--;) {
+ if (db_get(sp, fl, DBG_FATAL, &p, &len))
+ return (1);
+ BINC_RET(sp, bp, blen, len);
+ memcpy(bp, p, len);
+ if (db_append(sp, 1, tl, bp, len))
+ return (1);
+ if (mark_reset)
+ for (lmp = sp->ep->marks.lh_first;
+ lmp != NULL; lmp = lmp->q.le_next)
+ if (lmp->name != ABSMARK1 &&
+ lmp->lno == fl)
+ lmp->lno = tl + 1;
+ if (db_delete(sp, fl))
+ return (1);
+ }
+ } else { /* Destination < source. */
+ mfl = tl;
+ mtl = tl + diff;
+ for (cnt = diff; cnt--;) {
+ if (db_get(sp, fl, DBG_FATAL, &p, &len))
+ return (1);
+ BINC_RET(sp, bp, blen, len);
+ memcpy(bp, p, len);
+ if (db_append(sp, 1, tl++, bp, len))
+ return (1);
+ if (mark_reset)
+ for (lmp = sp->ep->marks.lh_first;
+ lmp != NULL; lmp = lmp->q.le_next)
+ if (lmp->name != ABSMARK1 &&
+ lmp->lno == fl)
+ lmp->lno = tl;
+ ++fl;
+ if (db_delete(sp, fl))
+ return (1);
+ }
+ }
+ FREE_SPACE(sp, bp, blen);
+
+ sp->lno = tl; /* Last line moved. */
+ sp->cno = 0;
+
+ /* Log the new positions of the marks. */
+ if (mark_reset)
+ for (lmp = sp->ep->marks.lh_first;
+ lmp != NULL; lmp = lmp->q.le_next)
+ if (lmp->name != ABSMARK1 &&
+ lmp->lno >= mfl && lmp->lno <= mtl)
+ (void)log_mark(sp, lmp);
+
+
+ sp->rptlines[L_MOVED] += diff;
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_open.c b/contrib/nvi/ex/ex_open.c
new file mode 100644
index 0000000..afffaeb
--- /dev/null
+++ b/contrib/nvi/ex/ex_open.c
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_open.c 10.7 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_open -- :[line] o[pen] [/pattern/] [flags]
+ *
+ * Switch to single line "open" mode.
+ *
+ * PUBLIC: int ex_open __P((SCR *, EXCMD *));
+ */
+int
+ex_open(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ /* If open option off, disallow open command. */
+ if (!O_ISSET(sp, O_OPEN)) {
+ msgq(sp, M_ERR,
+ "140|The open command requires that the open option be set");
+ return (1);
+ }
+
+ msgq(sp, M_ERR, "141|The open command is not yet implemented");
+ return (1);
+}
diff --git a/contrib/nvi/ex/ex_perl.c b/contrib/nvi/ex/ex_perl.c
new file mode 100644
index 0000000..e620352
--- /dev/null
+++ b/contrib/nvi/ex/ex_perl.c
@@ -0,0 +1,69 @@
+/*-
+ * 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.
+ * Copyright (c) 1995
+ * George V. Neville-Neil. All rights reserved.
+ * Copyright (c) 1996
+ * Sven Verdoolaege. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_perl.c 8.10 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_perl -- :[line [,line]] perl [command]
+ * Run a command through the perl interpreter.
+ *
+ * ex_perldo -- :[line [,line]] perldo [command]
+ * Run a set of lines through the perl interpreter.
+ *
+ * PUBLIC: int ex_perl __P((SCR*, EXCMD *));
+ */
+int
+ex_perl(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+#ifdef HAVE_PERL_INTERP
+ CHAR_T *p;
+ size_t len;
+
+ /* Skip leading white space. */
+ if (cmdp->argc != 0)
+ for (p = cmdp->argv[0]->bp,
+ len = cmdp->argv[0]->len; len > 0; --len, ++p)
+ if (!isblank(*p))
+ break;
+ if (cmdp->argc == 0 || len == 0) {
+ ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+ return (cmdp->cmd == &cmds[C_PERLCMD] ?
+ perl_ex_perl(sp, p, len, cmdp->addr1.lno, cmdp->addr2.lno) :
+ perl_ex_perldo(sp, p, len, cmdp->addr1.lno, cmdp->addr2.lno));
+#else
+ msgq(sp, M_ERR, "306|Vi was not loaded with a Perl interpreter");
+ return (1);
+#endif
+}
diff --git a/contrib/nvi/ex/ex_preserve.c b/contrib/nvi/ex/ex_preserve.c
new file mode 100644
index 0000000..5614c88
--- /dev/null
+++ b/contrib/nvi/ex/ex_preserve.c
@@ -0,0 +1,103 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_preserve.c 10.12 (Berkeley) 4/27/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_preserve -- :pre[serve]
+ * Push the file to recovery.
+ *
+ * PUBLIC: int ex_preserve __P((SCR *, EXCMD *));
+ */
+int
+ex_preserve(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ recno_t lno;
+
+ NEEDFILE(sp, cmdp);
+
+ if (!F_ISSET(sp->ep, F_RCV_ON)) {
+ msgq(sp, M_ERR, "142|Preservation of this file not possible");
+ return (1);
+ }
+
+ /* If recovery not initialized, do so. */
+ if (F_ISSET(sp->ep, F_FIRSTMODIFY) && rcv_init(sp))
+ return (1);
+
+ /* Force the file to be read in, in case it hasn't yet. */
+ if (db_last(sp, &lno))
+ return (1);
+
+ /* Sync to disk. */
+ if (rcv_sync(sp, RCV_SNAPSHOT))
+ return (1);
+
+ msgq(sp, M_INFO, "143|File preserved");
+ return (0);
+}
+
+/*
+ * ex_recover -- :rec[over][!] file
+ * Recover the file.
+ *
+ * PUBLIC: int ex_recover __P((SCR *, EXCMD *));
+ */
+int
+ex_recover(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ ARGS *ap;
+ FREF *frp;
+
+ ap = cmdp->argv[0];
+
+ /* Set the alternate file name. */
+ set_alt_name(sp, ap->bp);
+
+ /*
+ * Check for modifications. Autowrite did not historically
+ * affect :recover.
+ */
+ if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
+ return (1);
+
+ /* Get a file structure for the file. */
+ if ((frp = file_add(sp, ap->bp)) == NULL)
+ return (1);
+
+ /* Set the recover bit. */
+ F_SET(frp, FR_RECOVER);
+
+ /* Switch files. */
+ if (file_init(sp, frp, NULL, FS_SETALT |
+ (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0)))
+ return (1);
+
+ F_SET(sp, SC_FSWITCH);
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_print.c b/contrib/nvi/ex/ex_print.c
new file mode 100644
index 0000000..4218e08
--- /dev/null
+++ b/contrib/nvi/ex/ex_print.c
@@ -0,0 +1,352 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_print.c 10.18 (Berkeley) 5/12/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+#include "../common/common.h"
+
+static int ex_prchars __P((SCR *, const char *, size_t *, size_t, u_int, int));
+
+/*
+ * ex_list -- :[line [,line]] l[ist] [count] [flags]
+ *
+ * Display the addressed lines such that the output is unambiguous.
+ *
+ * PUBLIC: int ex_list __P((SCR *, EXCMD *));
+ */
+int
+ex_list(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ if (ex_print(sp, cmdp,
+ &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_LIST))
+ return (1);
+ sp->lno = cmdp->addr2.lno;
+ sp->cno = cmdp->addr2.cno;
+ return (0);
+}
+
+/*
+ * ex_number -- :[line [,line]] nu[mber] [count] [flags]
+ *
+ * Display the addressed lines with a leading line number.
+ *
+ * PUBLIC: int ex_number __P((SCR *, EXCMD *));
+ */
+int
+ex_number(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ if (ex_print(sp, cmdp,
+ &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_HASH))
+ return (1);
+ sp->lno = cmdp->addr2.lno;
+ sp->cno = cmdp->addr2.cno;
+ return (0);
+}
+
+/*
+ * ex_pr -- :[line [,line]] p[rint] [count] [flags]
+ *
+ * Display the addressed lines.
+ *
+ * PUBLIC: int ex_pr __P((SCR *, EXCMD *));
+ */
+int
+ex_pr(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags))
+ return (1);
+ sp->lno = cmdp->addr2.lno;
+ sp->cno = cmdp->addr2.cno;
+ return (0);
+}
+
+/*
+ * ex_print --
+ * Print the selected lines.
+ *
+ * PUBLIC: int ex_print __P((SCR *, EXCMD *, MARK *, MARK *, u_int32_t));
+ */
+int
+ex_print(sp, cmdp, fp, tp, flags)
+ SCR *sp;
+ EXCMD *cmdp;
+ MARK *fp, *tp;
+ u_int32_t flags;
+{
+ GS *gp;
+ recno_t from, to;
+ size_t col, len;
+ char *p, buf[10];
+
+ NEEDFILE(sp, cmdp);
+
+ gp = sp->gp;
+ for (from = fp->lno, to = tp->lno; from <= to; ++from) {
+ col = 0;
+
+ /*
+ * Display the line number. The %6 format is specified
+ * by POSIX 1003.2, and is almost certainly large enough.
+ * Check, though, just in case.
+ */
+ if (LF_ISSET(E_C_HASH)) {
+ if (from <= 999999) {
+ snprintf(buf, sizeof(buf), "%6ld ", from);
+ p = buf;
+ } else
+ p = "TOOBIG ";
+ if (ex_prchars(sp, p, &col, 8, 0, 0))
+ return (1);
+ }
+
+ /*
+ * Display the line. The format for E_C_PRINT isn't very good,
+ * especially in handling end-of-line tabs, but they're almost
+ * backward compatible.
+ */
+ if (db_get(sp, from, DBG_FATAL, &p, &len))
+ return (1);
+
+ if (len == 0 && !LF_ISSET(E_C_LIST))
+ (void)ex_puts(sp, "\n");
+ else if (ex_ldisplay(sp, p, len, col, flags))
+ return (1);
+
+ if (INTERRUPTED(sp))
+ break;
+ }
+ return (0);
+}
+
+/*
+ * ex_ldisplay --
+ * Display a line without any preceding number.
+ *
+ * PUBLIC: int ex_ldisplay __P((SCR *, const char *, size_t, size_t, u_int));
+ */
+int
+ex_ldisplay(sp, p, len, col, flags)
+ SCR *sp;
+ const char *p;
+ size_t len, col;
+ u_int flags;
+{
+ if (len > 0 && ex_prchars(sp, p, &col, len, LF_ISSET(E_C_LIST), 0))
+ return (1);
+ if (!INTERRUPTED(sp) && LF_ISSET(E_C_LIST)) {
+ p = "$";
+ if (ex_prchars(sp, p, &col, 1, LF_ISSET(E_C_LIST), 0))
+ return (1);
+ }
+ if (!INTERRUPTED(sp))
+ (void)ex_puts(sp, "\n");
+ return (0);
+}
+
+/*
+ * ex_scprint --
+ * Display a line for the substitute with confirmation routine.
+ *
+ * PUBLIC: int ex_scprint __P((SCR *, MARK *, MARK *));
+ */
+int
+ex_scprint(sp, fp, tp)
+ SCR *sp;
+ MARK *fp, *tp;
+{
+ const char *p;
+ size_t col, len;
+
+ col = 0;
+ if (O_ISSET(sp, O_NUMBER)) {
+ p = " ";
+ if (ex_prchars(sp, p, &col, 8, 0, 0))
+ return (1);
+ }
+
+ if (db_get(sp, fp->lno, DBG_FATAL, (char **)&p, &len))
+ return (1);
+
+ if (ex_prchars(sp, p, &col, fp->cno, 0, ' '))
+ return (1);
+ p += fp->cno;
+ if (ex_prchars(sp,
+ p, &col, tp->cno == fp->cno ? 1 : tp->cno - fp->cno, 0, '^'))
+ return (1);
+ if (INTERRUPTED(sp))
+ return (1);
+ p = "[ynq]"; /* XXX: should be msg_cat. */
+ if (ex_prchars(sp, p, &col, 5, 0, 0))
+ return (1);
+ (void)ex_fflush(sp);
+ return (0);
+}
+
+/*
+ * ex_prchars --
+ * Local routine to dump characters to the screen.
+ */
+static int
+ex_prchars(sp, p, colp, len, flags, repeatc)
+ SCR *sp;
+ const char *p;
+ size_t *colp, len;
+ u_int flags;
+ int repeatc;
+{
+ CHAR_T ch, *kp;
+ GS *gp;
+ size_t col, tlen, ts;
+
+ if (O_ISSET(sp, O_LIST))
+ LF_SET(E_C_LIST);
+ gp = sp->gp;
+ ts = O_VAL(sp, O_TABSTOP);
+ for (col = *colp; len--;)
+ if ((ch = *p++) == '\t' && !LF_ISSET(E_C_LIST))
+ for (tlen = ts - col % ts;
+ col < sp->cols && tlen--; ++col) {
+ (void)ex_printf(sp,
+ "%c", repeatc ? repeatc : ' ');
+ if (INTERRUPTED(sp))
+ goto intr;
+ }
+ else {
+ kp = KEY_NAME(sp, ch);
+ tlen = KEY_LEN(sp, ch);
+ if (!repeatc && col + tlen < sp->cols) {
+ (void)ex_puts(sp, kp);
+ col += tlen;
+ } else
+ for (; tlen--; ++kp, ++col) {
+ if (col == sp->cols) {
+ col = 0;
+ (void)ex_puts(sp, "\n");
+ }
+ (void)ex_printf(sp,
+ "%c", repeatc ? repeatc : *kp);
+ if (INTERRUPTED(sp))
+ goto intr;
+ }
+ }
+intr: *colp = col;
+ return (0);
+}
+
+/*
+ * ex_printf --
+ * Ex's version of printf.
+ *
+ * PUBLIC: int ex_printf __P((SCR *, const char *, ...));
+ */
+int
+#ifdef __STDC__
+ex_printf(SCR *sp, const char *fmt, ...)
+#else
+ex_printf(sp, fmt, va_alist)
+ SCR *sp;
+ const char *fmt;
+ va_dcl
+#endif
+{
+ EX_PRIVATE *exp;
+ va_list ap;
+ size_t n;
+
+ exp = EXP(sp);
+
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ exp->obp_len += n = vsnprintf(exp->obp + exp->obp_len,
+ sizeof(exp->obp) - exp->obp_len, fmt, ap);
+ va_end(ap);
+
+ /* Flush when reach a <newline> or half the buffer. */
+ if (exp->obp[exp->obp_len - 1] == '\n' ||
+ exp->obp_len > sizeof(exp->obp) / 2)
+ (void)ex_fflush(sp);
+ return (n);
+}
+
+/*
+ * ex_puts --
+ * Ex's version of puts.
+ *
+ * PUBLIC: int ex_puts __P((SCR *, const char *));
+ */
+int
+ex_puts(sp, str)
+ SCR *sp;
+ const char *str;
+{
+ EX_PRIVATE *exp;
+ int doflush, n;
+
+ exp = EXP(sp);
+
+ /* Flush when reach a <newline> or the end of the buffer. */
+ for (doflush = n = 0; *str != '\0'; ++n) {
+ if (exp->obp_len > sizeof(exp->obp))
+ (void)ex_fflush(sp);
+ if ((exp->obp[exp->obp_len++] = *str++) == '\n')
+ doflush = 1;
+ }
+ if (doflush)
+ (void)ex_fflush(sp);
+ return (n);
+}
+
+/*
+ * ex_fflush --
+ * Ex's version of fflush.
+ *
+ * PUBLIC: int ex_fflush __P((SCR *sp));
+ */
+int
+ex_fflush(sp)
+ SCR *sp;
+{
+ EX_PRIVATE *exp;
+
+ exp = EXP(sp);
+
+ if (exp->obp_len != 0) {
+ sp->gp->scr_msg(sp, M_NONE, exp->obp, exp->obp_len);
+ exp->obp_len = 0;
+ }
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_put.c b/contrib/nvi/ex/ex_put.c
new file mode 100644
index 0000000..2facb03
--- /dev/null
+++ b/contrib/nvi/ex/ex_put.c
@@ -0,0 +1,51 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_put.c 10.7 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_put -- [line] pu[t] [buffer]
+ * Append a cut buffer into the file.
+ *
+ * PUBLIC: int ex_put __P((SCR *, EXCMD *));
+ */
+int
+ex_put(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ MARK m;
+
+ NEEDFILE(sp, cmdp);
+
+ m.lno = sp->lno;
+ m.cno = sp->cno;
+ if (put(sp, NULL,
+ FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL,
+ &cmdp->addr1, &m, 1))
+ return (1);
+ sp->lno = m.lno;
+ sp->cno = m.cno;
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_quit.c b/contrib/nvi/ex/ex_quit.c
new file mode 100644
index 0000000..705fa1a
--- /dev/null
+++ b/contrib/nvi/ex/ex_quit.c
@@ -0,0 +1,46 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_quit.c 10.7 (Berkeley) 4/27/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_quit -- :quit[!]
+ * Quit.
+ *
+ * PUBLIC: int ex_quit __P((SCR *, EXCMD *));
+ */
+int
+ex_quit(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ int force;
+
+ force = FL_ISSET(cmdp->iflags, E_C_FORCE);
+
+ /* Check for file modifications, or more files to edit. */
+ if (file_m2(sp, force) || ex_ncheck(sp, force))
+ return (1);
+
+ F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT);
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_read.c b/contrib/nvi/ex/ex_read.c
new file mode 100644
index 0000000..78296ff
--- /dev/null
+++ b/contrib/nvi/ex/ex_read.c
@@ -0,0 +1,360 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_read.c 10.38 (Berkeley) 8/12/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+/*
+ * ex_read -- :read [file]
+ * :read [!cmd]
+ * Read from a file or utility.
+ *
+ * !!!
+ * Historical vi wouldn't undo a filter read, for no apparent reason.
+ *
+ * PUBLIC: int ex_read __P((SCR *, EXCMD *));
+ */
+int
+ex_read(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ enum { R_ARG, R_EXPANDARG, R_FILTER } which;
+ struct stat sb;
+ CHAR_T *arg, *name;
+ EX_PRIVATE *exp;
+ FILE *fp;
+ FREF *frp;
+ GS *gp;
+ MARK rm;
+ recno_t nlines;
+ size_t arglen;
+ int argc, rval;
+ char *p;
+
+ gp = sp->gp;
+
+ /*
+ * 0 args: read the current pathname.
+ * 1 args: check for "read !arg".
+ */
+ switch (cmdp->argc) {
+ case 0:
+ which = R_ARG;
+ break;
+ case 1:
+ arg = cmdp->argv[0]->bp;
+ arglen = cmdp->argv[0]->len;
+ if (*arg == '!') {
+ ++arg;
+ --arglen;
+ which = R_FILTER;
+
+ /* Secure means no shell access. */
+ if (O_ISSET(sp, O_SECURE)) {
+ ex_emsg(sp, cmdp->cmd->name, EXM_SECURE_F);
+ return (1);
+ }
+ } else
+ which = R_EXPANDARG;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ /* Load a temporary file if no file being edited. */
+ if (sp->ep == NULL) {
+ if ((frp = file_add(sp, NULL)) == NULL)
+ return (1);
+ if (file_init(sp, frp, NULL, 0))
+ return (1);
+ }
+
+ switch (which) {
+ case R_FILTER:
+ /*
+ * File name and bang expand the user's argument. If
+ * we don't get an additional argument, it's illegal.
+ */
+ argc = cmdp->argc;
+ if (argv_exp1(sp, cmdp, arg, arglen, 1))
+ return (1);
+ if (argc == cmdp->argc) {
+ ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+ argc = cmdp->argc - 1;
+
+ /* Set the last bang command. */
+ exp = EXP(sp);
+ if (exp->lastbcomm != NULL)
+ free(exp->lastbcomm);
+ if ((exp->lastbcomm =
+ strdup(cmdp->argv[argc]->bp)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+
+ /*
+ * Vi redisplayed the user's argument if it changed, ex
+ * always displayed a !, plus the user's argument if it
+ * changed.
+ */
+ if (F_ISSET(sp, SC_VI)) {
+ if (F_ISSET(cmdp, E_MODIFY))
+ (void)vs_update(sp, "!", cmdp->argv[argc]->bp);
+ } else {
+ if (F_ISSET(cmdp, E_MODIFY))
+ (void)ex_printf(sp,
+ "!%s\n", cmdp->argv[argc]->bp);
+ else
+ (void)ex_puts(sp, "!\n");
+ (void)ex_fflush(sp);
+ }
+
+ /*
+ * Historically, filter reads as the first ex command didn't
+ * wait for the user. If SC_SCR_EXWROTE not already set, set
+ * the don't-wait flag.
+ */
+ if (!F_ISSET(sp, SC_SCR_EXWROTE))
+ F_SET(sp, SC_EX_WAIT_NO);
+
+ /*
+ * Switch into ex canonical mode. The reason to restore the
+ * original terminal modes for read filters is so that users
+ * can do things like ":r! cat /dev/tty".
+ *
+ * !!!
+ * We do not output an extra <newline>, so that we don't touch
+ * the screen on a normal read.
+ */
+ if (F_ISSET(sp, SC_VI)) {
+ if (gp->scr_screen(sp, SC_EX)) {
+ ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON_F);
+ return (1);
+ }
+ /*
+ * !!!
+ * Historically, the read command doesn't switch to
+ * the alternate X11 xterm screen, if doing a filter
+ * read -- don't set SA_ALTERNATE.
+ */
+ F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);
+ }
+
+ if (ex_filter(sp, cmdp, &cmdp->addr1,
+ NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ))
+ return (1);
+
+ /* The filter version of read set the autoprint flag. */
+ F_SET(cmdp, E_AUTOPRINT);
+
+ /*
+ * If in vi mode, move to the first nonblank. Might have
+ * switched into ex mode, so saved the original SC_VI value.
+ */
+ sp->lno = rm.lno;
+ if (F_ISSET(sp, SC_VI)) {
+ sp->cno = 0;
+ (void)nonblank(sp, sp->lno, &sp->cno);
+ }
+ return (0);
+ case R_ARG:
+ name = sp->frp->name;
+ break;
+ case R_EXPANDARG:
+ if (argv_exp2(sp, cmdp, arg, arglen))
+ return (1);
+ /*
+ * 0 args: impossible.
+ * 1 args: impossible (I hope).
+ * 2 args: read it.
+ * >2 args: object, too many args.
+ *
+ * The 1 args case depends on the argv_sexp() function refusing
+ * to return success without at least one non-blank character.
+ */
+ switch (cmdp->argc) {
+ case 0:
+ case 1:
+ abort();
+ /* NOTREACHED */
+ case 2:
+ name = cmdp->argv[1]->bp;
+ /*
+ * !!!
+ * Historically, the read and write commands renamed
+ * "unnamed" files, or, if the file had a name, set
+ * the alternate file name.
+ */
+ if (F_ISSET(sp->frp, FR_TMPFILE) &&
+ !F_ISSET(sp->frp, FR_EXNAMED)) {
+ if ((p = v_strdup(sp, cmdp->argv[1]->bp,
+ cmdp->argv[1]->len)) != NULL) {
+ free(sp->frp->name);
+ sp->frp->name = p;
+ }
+ /*
+ * The file has a real name, it's no longer a
+ * temporary, clear the temporary file flags.
+ */
+ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE);
+ F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED);
+
+ /* Notify the screen. */
+ (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
+ } else
+ set_alt_name(sp, name);
+ break;
+ default:
+ ex_emsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT);
+ return (1);
+
+ }
+ break;
+ }
+
+ /*
+ * !!!
+ * Historically, vi did not permit reads from non-regular files, nor
+ * did it distinguish between "read !" and "read!", so there was no
+ * way to "force" it. We permit reading from named pipes too, since
+ * they didn't exist when the original implementation of vi was done
+ * and they seem a reasonable addition.
+ */
+ if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) {
+ msgq_str(sp, M_SYSERR, name, "%s");
+ return (1);
+ }
+ if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) {
+ (void)fclose(fp);
+ msgq(sp, M_ERR,
+ "145|Only regular files and named pipes may be read");
+ return (1);
+ }
+
+ /* Try and get a lock. */
+ if (file_lock(sp, NULL, NULL, fileno(fp), 0) == LOCK_UNAVAIL)
+ msgq(sp, M_ERR, "146|%s: read lock was unavailable", name);
+
+ rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0);
+
+ /*
+ * In vi, set the cursor to the first line read in, if anything read
+ * in, otherwise, the address. (Historic vi set it to the line after
+ * the address regardless, but since that line may not exist we don't
+ * bother.)
+ *
+ * In ex, set the cursor to the last line read in, if anything read in,
+ * otherwise, the address.
+ */
+ if (F_ISSET(sp, SC_VI)) {
+ sp->lno = cmdp->addr1.lno;
+ if (nlines)
+ ++sp->lno;
+ } else
+ sp->lno = cmdp->addr1.lno + nlines;
+ return (rval);
+}
+
+/*
+ * ex_readfp --
+ * Read lines into the file.
+ *
+ * PUBLIC: int ex_readfp __P((SCR *, char *, FILE *, MARK *, recno_t *, int));
+ */
+int
+ex_readfp(sp, name, fp, fm, nlinesp, silent)
+ SCR *sp;
+ char *name;
+ FILE *fp;
+ MARK *fm;
+ recno_t *nlinesp;
+ int silent;
+{
+ EX_PRIVATE *exp;
+ GS *gp;
+ recno_t lcnt, lno;
+ size_t len;
+ u_long ccnt; /* XXX: can't print off_t portably. */
+ int nf, rval;
+ char *p;
+
+ gp = sp->gp;
+ exp = EXP(sp);
+
+ /*
+ * Add in the lines from the output. Insertion starts at the line
+ * following the address.
+ */
+ ccnt = 0;
+ lcnt = 0;
+ p = "147|Reading...";
+ for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) {
+ if ((lcnt + 1) % INTERRUPT_CHECK == 0) {
+ if (INTERRUPTED(sp))
+ break;
+ if (!silent) {
+ gp->scr_busy(sp, p,
+ p == NULL ? BUSY_UPDATE : BUSY_ON);
+ p = NULL;
+ }
+ }
+ if (db_append(sp, 1, lno, exp->ibp, len))
+ goto err;
+ ccnt += len;
+ }
+
+ if (ferror(fp) || fclose(fp))
+ goto err;
+
+ /* Return the number of lines read in. */
+ if (nlinesp != NULL)
+ *nlinesp = lcnt;
+
+ if (!silent) {
+ p = msg_print(sp, name, &nf);
+ msgq(sp, M_INFO,
+ "148|%s: %lu lines, %lu characters", p, lcnt, ccnt);
+ if (nf)
+ FREE_SPACE(sp, p, 0);
+ }
+
+ rval = 0;
+ if (0) {
+err: msgq_str(sp, M_SYSERR, name, "%s");
+ (void)fclose(fp);
+ rval = 1;
+ }
+
+ if (!silent)
+ gp->scr_busy(sp, NULL, BUSY_OFF);
+ return (rval);
+}
diff --git a/contrib/nvi/ex/ex_screen.c b/contrib/nvi/ex/ex_screen.c
new file mode 100644
index 0000000..9bc5bf0
--- /dev/null
+++ b/contrib/nvi/ex/ex_screen.c
@@ -0,0 +1,138 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_screen.c 10.11 (Berkeley) 6/29/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+/*
+ * ex_bg -- :bg
+ * Hide the screen.
+ *
+ * PUBLIC: int ex_bg __P((SCR *, EXCMD *));
+ */
+int
+ex_bg(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (vs_bg(sp));
+}
+
+/*
+ * ex_fg -- :fg [file]
+ * Show the screen.
+ *
+ * PUBLIC: int ex_fg __P((SCR *, EXCMD *));
+ */
+int
+ex_fg(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ SCR *nsp;
+ int newscreen;
+
+ newscreen = F_ISSET(cmdp, E_NEWSCREEN);
+ if (vs_fg(sp, &nsp, cmdp->argc ? cmdp->argv[0]->bp : NULL, newscreen))
+ return (1);
+
+ /* Set up the switch. */
+ if (newscreen) {
+ sp->nextdisp = nsp;
+ F_SET(sp, SC_SSWITCH);
+ }
+ return (0);
+}
+
+/*
+ * ex_resize -- :resize [+-]rows
+ * Change the screen size.
+ *
+ * PUBLIC: int ex_resize __P((SCR *, EXCMD *));
+ */
+int
+ex_resize(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ adj_t adj;
+
+ switch (FL_ISSET(cmdp->iflags,
+ E_C_COUNT | E_C_COUNT_NEG | E_C_COUNT_POS)) {
+ case E_C_COUNT:
+ adj = A_SET;
+ break;
+ case E_C_COUNT | E_C_COUNT_NEG:
+ adj = A_DECREASE;
+ break;
+ case E_C_COUNT | E_C_COUNT_POS:
+ adj = A_INCREASE;
+ break;
+ default:
+ ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+ return (vs_resize(sp, cmdp->count, adj));
+}
+
+/*
+ * ex_sdisplay --
+ * Display the list of screens.
+ *
+ * PUBLIC: int ex_sdisplay __P((SCR *));
+ */
+int
+ex_sdisplay(sp)
+ SCR *sp;
+{
+ GS *gp;
+ SCR *tsp;
+ int cnt, col, len, sep;
+
+ gp = sp->gp;
+ if ((tsp = gp->hq.cqh_first) == (void *)&gp->hq) {
+ msgq(sp, M_INFO, "149|No background screens to display");
+ return (0);
+ }
+
+ col = len = sep = 0;
+ for (cnt = 1; tsp != (void *)&gp->hq && !INTERRUPTED(sp);
+ tsp = tsp->q.cqe_next) {
+ col += len = strlen(tsp->frp->name) + sep;
+ if (col >= sp->cols - 1) {
+ col = len;
+ sep = 0;
+ (void)ex_puts(sp, "\n");
+ } else if (cnt != 1) {
+ sep = 1;
+ (void)ex_puts(sp, " ");
+ }
+ (void)ex_puts(sp, tsp->frp->name);
+ ++cnt;
+ }
+ if (!INTERRUPTED(sp))
+ (void)ex_puts(sp, "\n");
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_script.c b/contrib/nvi/ex/ex_script.c
new file mode 100644
index 0000000..9ca6d60
--- /dev/null
+++ b/contrib/nvi/ex/ex_script.c
@@ -0,0 +1,798 @@
+/*-
+ * 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.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Brian Hirt.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_script.c 10.30 (Berkeley) 9/24/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <sys/stat.h>
+#ifdef HAVE_SYS5_PTY
+#include <sys/stropts.h>
+#endif
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h> /* XXX: OSF/1 bug: include before <grp.h> */
+#include <grp.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+#include "script.h"
+#include "pathnames.h"
+
+static void sscr_check __P((SCR *));
+static int sscr_getprompt __P((SCR *));
+static int sscr_init __P((SCR *));
+static int sscr_insert __P((SCR *));
+static int sscr_matchprompt __P((SCR *, char *, size_t, size_t *));
+static int sscr_pty __P((int *, int *, char *, struct termios *, void *));
+static int sscr_setprompt __P((SCR *, char *, size_t));
+
+/*
+ * ex_script -- : sc[ript][!] [file]
+ * Switch to script mode.
+ *
+ * PUBLIC: int ex_script __P((SCR *, EXCMD *));
+ */
+int
+ex_script(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ /* Vi only command. */
+ if (!F_ISSET(sp, SC_VI)) {
+ msgq(sp, M_ERR,
+ "150|The script command is only available in vi mode");
+ return (1);
+ }
+
+ /* Switch to the new file. */
+ if (cmdp->argc != 0 && ex_edit(sp, cmdp))
+ return (1);
+
+ /* Create the shell, figure out the prompt. */
+ if (sscr_init(sp))
+ return (1);
+
+ return (0);
+}
+
+/*
+ * sscr_init --
+ * Create a pty setup for a shell.
+ */
+static int
+sscr_init(sp)
+ SCR *sp;
+{
+ SCRIPT *sc;
+ char *sh, *sh_path;
+
+ /* We're going to need a shell. */
+ if (opts_empty(sp, O_SHELL, 0))
+ return (1);
+
+ MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT));
+ sp->script = sc;
+ sc->sh_prompt = NULL;
+ sc->sh_prompt_len = 0;
+
+ /*
+ * There are two different processes running through this code.
+ * They are the shell and the parent.
+ */
+ sc->sh_master = sc->sh_slave = -1;
+
+ if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {
+ msgq(sp, M_SYSERR, "tcgetattr");
+ goto err;
+ }
+
+ /*
+ * Turn off output postprocessing and echo.
+ */
+ sc->sh_term.c_oflag &= ~OPOST;
+ sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);
+
+#ifdef TIOCGWINSZ
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {
+ msgq(sp, M_SYSERR, "tcgetattr");
+ goto err;
+ }
+
+ if (sscr_pty(&sc->sh_master,
+ &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {
+ msgq(sp, M_SYSERR, "pty");
+ goto err;
+ }
+#else
+ if (sscr_pty(&sc->sh_master,
+ &sc->sh_slave, sc->sh_name, &sc->sh_term, NULL) == -1) {
+ msgq(sp, M_SYSERR, "pty");
+ goto err;
+ }
+#endif
+
+ /*
+ * __TK__ huh?
+ * Don't use vfork() here, because the signal semantics differ from
+ * implementation to implementation.
+ */
+ switch (sc->sh_pid = fork()) {
+ case -1: /* Error. */
+ msgq(sp, M_SYSERR, "fork");
+err: if (sc->sh_master != -1)
+ (void)close(sc->sh_master);
+ if (sc->sh_slave != -1)
+ (void)close(sc->sh_slave);
+ return (1);
+ case 0: /* Utility. */
+ /*
+ * XXX
+ * So that shells that do command line editing turn it off.
+ */
+ (void)setenv("TERM", "emacs", 1);
+ (void)setenv("TERMCAP", "emacs:", 1);
+ (void)setenv("EMACS", "t", 1);
+
+ (void)setsid();
+#ifdef TIOCSCTTY
+ /*
+ * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
+ * ioctl, not by opening a terminal device file. POSIX 1003.1
+ * doesn't define a portable way to do this. If TIOCSCTTY is
+ * not available, hope that the open does it.
+ */
+ (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
+#endif
+ (void)close(sc->sh_master);
+ (void)dup2(sc->sh_slave, STDIN_FILENO);
+ (void)dup2(sc->sh_slave, STDOUT_FILENO);
+ (void)dup2(sc->sh_slave, STDERR_FILENO);
+ (void)close(sc->sh_slave);
+
+ /* Assumes that all shells have -i. */
+ sh_path = O_STR(sp, O_SHELL);
+ if ((sh = strrchr(sh_path, '/')) == NULL)
+ sh = sh_path;
+ else
+ ++sh;
+ execl(sh_path, sh, "-i", NULL);
+ msgq_str(sp, M_SYSERR, sh_path, "execl: %s");
+ _exit(127);
+ default: /* Parent. */
+ break;
+ }
+
+ if (sscr_getprompt(sp))
+ return (1);
+
+ F_SET(sp, SC_SCRIPT);
+ F_SET(sp->gp, G_SCRWIN);
+ return (0);
+}
+
+/*
+ * sscr_getprompt --
+ * Eat lines printed by the shell until a line with no trailing
+ * carriage return comes; set the prompt from that line.
+ */
+static int
+sscr_getprompt(sp)
+ SCR *sp;
+{
+ struct timeval tv;
+ CHAR_T *endp, *p, *t, buf[1024];
+ SCRIPT *sc;
+ fd_set fdset;
+ recno_t lline;
+ size_t llen, len;
+ u_int value;
+ int nr;
+
+ FD_ZERO(&fdset);
+ endp = buf;
+ len = sizeof(buf);
+
+ /* Wait up to a second for characters to read. */
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+ sc = sp->script;
+ FD_SET(sc->sh_master, &fdset);
+ switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
+ case -1: /* Error or interrupt. */
+ msgq(sp, M_SYSERR, "select");
+ goto prompterr;
+ case 0: /* Timeout */
+ msgq(sp, M_ERR, "Error: timed out");
+ goto prompterr;
+ case 1: /* Characters to read. */
+ break;
+ }
+
+ /* Read the characters. */
+more: len = sizeof(buf) - (endp - buf);
+ switch (nr = read(sc->sh_master, endp, len)) {
+ case 0: /* EOF. */
+ msgq(sp, M_ERR, "Error: shell: EOF");
+ goto prompterr;
+ case -1: /* Error or interrupt. */
+ msgq(sp, M_SYSERR, "shell");
+ goto prompterr;
+ default:
+ endp += nr;
+ break;
+ }
+
+ /* If any complete lines, push them into the file. */
+ for (p = t = buf; p < endp; ++p) {
+ value = KEY_VAL(sp, *p);
+ if (value == K_CR || value == K_NL) {
+ if (db_last(sp, &lline) ||
+ db_append(sp, 0, lline, t, p - t))
+ goto prompterr;
+ t = p + 1;
+ }
+ }
+ if (p > buf) {
+ memmove(buf, t, endp - t);
+ endp = buf + (endp - t);
+ }
+ if (endp == buf)
+ goto more;
+
+ /* Wait up 1/10 of a second to make sure that we got it all. */
+ tv.tv_sec = 0;
+ tv.tv_usec = 100000;
+ switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
+ case -1: /* Error or interrupt. */
+ msgq(sp, M_SYSERR, "select");
+ goto prompterr;
+ case 0: /* Timeout */
+ break;
+ case 1: /* Characters to read. */
+ goto more;
+ }
+
+ /* Timed out, so theoretically we have a prompt. */
+ llen = endp - buf;
+ endp = buf;
+
+ /* Append the line into the file. */
+ if (db_last(sp, &lline) || db_append(sp, 0, lline, buf, llen)) {
+prompterr: sscr_end(sp);
+ return (1);
+ }
+
+ return (sscr_setprompt(sp, buf, llen));
+}
+
+/*
+ * sscr_exec --
+ * Take a line and hand it off to the shell.
+ *
+ * PUBLIC: int sscr_exec __P((SCR *, recno_t));
+ */
+int
+sscr_exec(sp, lno)
+ SCR *sp;
+ recno_t lno;
+{
+ SCRIPT *sc;
+ recno_t last_lno;
+ size_t blen, len, last_len, tlen;
+ int isempty, matchprompt, nw, rval;
+ char *bp, *p;
+
+ /* If there's a prompt on the last line, append the command. */
+ if (db_last(sp, &last_lno))
+ return (1);
+ if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len))
+ return (1);
+ if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
+ matchprompt = 1;
+ GET_SPACE_RET(sp, bp, blen, last_len + 128);
+ memmove(bp, p, last_len);
+ } else
+ matchprompt = 0;
+
+ /* Get something to execute. */
+ if (db_eget(sp, lno, &p, &len, &isempty)) {
+ if (isempty)
+ goto empty;
+ goto err1;
+ }
+
+ /* Empty lines aren't interesting. */
+ if (len == 0)
+ goto empty;
+
+ /* Delete any prompt. */
+ if (sscr_matchprompt(sp, p, len, &tlen)) {
+ if (tlen == len) {
+empty: msgq(sp, M_BERR, "151|No command to execute");
+ goto err1;
+ }
+ p += (len - tlen);
+ len = tlen;
+ }
+
+ /* Push the line to the shell. */
+ sc = sp->script;
+ if ((nw = write(sc->sh_master, p, len)) != len)
+ goto err2;
+ rval = 0;
+ if (write(sc->sh_master, "\n", 1) != 1) {
+err2: if (nw == 0)
+ errno = EIO;
+ msgq(sp, M_SYSERR, "shell");
+ goto err1;
+ }
+
+ if (matchprompt) {
+ ADD_SPACE_RET(sp, bp, blen, last_len + len);
+ memmove(bp + last_len, p, len);
+ if (db_set(sp, last_lno, bp, last_len + len))
+err1: rval = 1;
+ }
+ if (matchprompt)
+ FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
+
+/*
+ * sscr_input --
+ * Read any waiting shell input.
+ *
+ * PUBLIC: int sscr_input __P((SCR *));
+ */
+int
+sscr_input(sp)
+ SCR *sp;
+{
+ GS *gp;
+ struct timeval poll;
+ fd_set rdfd;
+ int maxfd;
+
+ gp = sp->gp;
+
+loop: maxfd = 0;
+ FD_ZERO(&rdfd);
+ poll.tv_sec = 0;
+ poll.tv_usec = 0;
+
+ /* Set up the input mask. */
+ for (sp = gp->dq.cqh_first; sp != (void *)&gp->dq; sp = sp->q.cqe_next)
+ if (F_ISSET(sp, SC_SCRIPT)) {
+ FD_SET(sp->script->sh_master, &rdfd);
+ if (sp->script->sh_master > maxfd)
+ maxfd = sp->script->sh_master;
+ }
+
+ /* Check for input. */
+ switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) {
+ case -1:
+ msgq(sp, M_SYSERR, "select");
+ return (1);
+ case 0:
+ return (0);
+ default:
+ break;
+ }
+
+ /* Read the input. */
+ for (sp = gp->dq.cqh_first; sp != (void *)&gp->dq; sp = sp->q.cqe_next)
+ if (F_ISSET(sp, SC_SCRIPT) &&
+ FD_ISSET(sp->script->sh_master, &rdfd) && sscr_insert(sp))
+ return (1);
+ goto loop;
+}
+
+/*
+ * sscr_insert --
+ * Take a line from the shell and insert it into the file.
+ */
+static int
+sscr_insert(sp)
+ SCR *sp;
+{
+ struct timeval tv;
+ CHAR_T *endp, *p, *t;
+ SCRIPT *sc;
+ fd_set rdfd;
+ recno_t lno;
+ size_t blen, len, tlen;
+ u_int value;
+ int nr, rval;
+ char *bp;
+
+ /* Find out where the end of the file is. */
+ if (db_last(sp, &lno))
+ return (1);
+
+#define MINREAD 1024
+ GET_SPACE_RET(sp, bp, blen, MINREAD);
+ endp = bp;
+
+ /* Read the characters. */
+ rval = 1;
+ sc = sp->script;
+more: switch (nr = read(sc->sh_master, endp, MINREAD)) {
+ case 0: /* EOF; shell just exited. */
+ sscr_end(sp);
+ rval = 0;
+ goto ret;
+ case -1: /* Error or interrupt. */
+ msgq(sp, M_SYSERR, "shell");
+ goto ret;
+ default:
+ endp += nr;
+ break;
+ }
+
+ /* Append the lines into the file. */
+ for (p = t = bp; p < endp; ++p) {
+ value = KEY_VAL(sp, *p);
+ if (value == K_CR || value == K_NL) {
+ len = p - t;
+ if (db_append(sp, 1, lno++, t, len))
+ goto ret;
+ t = p + 1;
+ }
+ }
+ if (p > t) {
+ len = p - t;
+ /*
+ * If the last thing from the shell isn't another prompt, wait
+ * up to 1/10 of a second for more stuff to show up, so that
+ * we don't break the output into two separate lines. Don't
+ * want to hang indefinitely because some program is hanging,
+ * confused the shell, or whatever.
+ */
+ if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
+ tv.tv_sec = 0;
+ tv.tv_usec = 100000;
+ FD_ZERO(&rdfd);
+ FD_SET(sc->sh_master, &rdfd);
+ if (select(sc->sh_master + 1,
+ &rdfd, NULL, NULL, &tv) == 1) {
+ memmove(bp, t, len);
+ endp = bp + len;
+ goto more;
+ }
+ }
+ if (sscr_setprompt(sp, t, len))
+ return (1);
+ if (db_append(sp, 1, lno++, t, len))
+ goto ret;
+ }
+
+ /* The cursor moves to EOF. */
+ sp->lno = lno;
+ sp->cno = len ? len - 1 : 0;
+ rval = vs_refresh(sp, 1);
+
+ret: FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
+
+/*
+ * sscr_setprompt --
+ *
+ * Set the prompt to the last line we got from the shell.
+ *
+ */
+static int
+sscr_setprompt(sp, buf, len)
+ SCR *sp;
+ char *buf;
+ size_t len;
+{
+ SCRIPT *sc;
+
+ sc = sp->script;
+ if (sc->sh_prompt)
+ free(sc->sh_prompt);
+ MALLOC(sp, sc->sh_prompt, char *, len + 1);
+ if (sc->sh_prompt == NULL) {
+ sscr_end(sp);
+ return (1);
+ }
+ memmove(sc->sh_prompt, buf, len);
+ sc->sh_prompt_len = len;
+ sc->sh_prompt[len] = '\0';
+ return (0);
+}
+
+/*
+ * sscr_matchprompt --
+ * Check to see if a line matches the prompt. Nul's indicate
+ * parts that can change, in both content and size.
+ */
+static int
+sscr_matchprompt(sp, lp, line_len, lenp)
+ SCR *sp;
+ char *lp;
+ size_t line_len, *lenp;
+{
+ SCRIPT *sc;
+ size_t prompt_len;
+ char *pp;
+
+ sc = sp->script;
+ if (line_len < (prompt_len = sc->sh_prompt_len))
+ return (0);
+
+ for (pp = sc->sh_prompt;
+ prompt_len && line_len; --prompt_len, --line_len) {
+ if (*pp == '\0') {
+ for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
+ if (!prompt_len)
+ return (0);
+ for (; line_len && *lp != *pp; --line_len, ++lp);
+ if (!line_len)
+ return (0);
+ }
+ if (*pp++ != *lp++)
+ break;
+ }
+
+ if (prompt_len)
+ return (0);
+ if (lenp != NULL)
+ *lenp = line_len;
+ return (1);
+}
+
+/*
+ * sscr_end --
+ * End the pipe to a shell.
+ *
+ * PUBLIC: int sscr_end __P((SCR *));
+ */
+int
+sscr_end(sp)
+ SCR *sp;
+{
+ SCRIPT *sc;
+
+ if ((sc = sp->script) == NULL)
+ return (0);
+
+ /* Turn off the script flags. */
+ F_CLR(sp, SC_SCRIPT);
+ sscr_check(sp);
+
+ /* Close down the parent's file descriptors. */
+ if (sc->sh_master != -1)
+ (void)close(sc->sh_master);
+ if (sc->sh_slave != -1)
+ (void)close(sc->sh_slave);
+
+ /* This should have killed the child. */
+ (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0);
+
+ /* Free memory. */
+ free(sc->sh_prompt);
+ free(sc);
+ sp->script = NULL;
+
+ return (0);
+}
+
+/*
+ * sscr_check --
+ * Set/clear the global scripting bit.
+ */
+static void
+sscr_check(sp)
+ SCR *sp;
+{
+ GS *gp;
+
+ gp = sp->gp;
+ for (sp = gp->dq.cqh_first; sp != (void *)&gp->dq; sp = sp->q.cqe_next)
+ if (F_ISSET(sp, SC_SCRIPT)) {
+ F_SET(gp, G_SCRWIN);
+ return;
+ }
+ F_CLR(gp, G_SCRWIN);
+}
+
+#ifdef HAVE_SYS5_PTY
+static int ptys_open __P((int, char *));
+static int ptym_open __P((char *));
+
+static int
+sscr_pty(amaster, aslave, name, termp, winp)
+ int *amaster, *aslave;
+ char *name;
+ struct termios *termp;
+ void *winp;
+{
+ int master, slave, ttygid;
+
+ /* open master terminal */
+ if ((master = ptym_open(name)) < 0) {
+ errno = ENOENT; /* out of ptys */
+ return (-1);
+ }
+
+ /* open slave terminal */
+ if ((slave = ptys_open(master, name)) >= 0) {
+ *amaster = master;
+ *aslave = slave;
+ } else {
+ errno = ENOENT; /* out of ptys */
+ return (-1);
+ }
+
+ if (termp)
+ (void) tcsetattr(slave, TCSAFLUSH, termp);
+#ifdef TIOCSWINSZ
+ if (winp != NULL)
+ (void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp);
+#endif
+ return (0);
+}
+
+/*
+ * ptym_open --
+ * This function opens a master pty and returns the file descriptor
+ * to it. pts_name is also returned which is the name of the slave.
+ */
+static int
+ptym_open(pts_name)
+ char *pts_name;
+{
+ int fdm;
+ char *ptr, *ptsname();
+
+ strcpy(pts_name, _PATH_SYSV_PTY);
+ if ((fdm = open(pts_name, O_RDWR)) < 0 )
+ return (-1);
+
+ if (grantpt(fdm) < 0) {
+ close(fdm);
+ return (-2);
+ }
+
+ if (unlockpt(fdm) < 0) {
+ close(fdm);
+ return (-3);
+ }
+
+ if (unlockpt(fdm) < 0) {
+ close(fdm);
+ return (-3);
+ }
+
+ /* get slave's name */
+ if ((ptr = ptsname(fdm)) == NULL) {
+ close(fdm);
+ return (-3);
+ }
+ strcpy(pts_name, ptr);
+ return (fdm);
+}
+
+/*
+ * ptys_open --
+ * This function opens the slave pty.
+ */
+static int
+ptys_open(fdm, pts_name)
+ int fdm;
+ char *pts_name;
+{
+ int fds;
+
+ if ((fds = open(pts_name, O_RDWR)) < 0) {
+ close(fdm);
+ return (-5);
+ }
+
+ if (ioctl(fds, I_PUSH, "ptem") < 0) {
+ close(fds);
+ close(fdm);
+ return (-6);
+ }
+
+ if (ioctl(fds, I_PUSH, "ldterm") < 0) {
+ close(fds);
+ close(fdm);
+ return (-7);
+ }
+
+ if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
+ close(fds);
+ close(fdm);
+ return (-8);
+ }
+
+ return (fds);
+}
+
+#else /* !HAVE_SYS5_PTY */
+
+static int
+sscr_pty(amaster, aslave, name, termp, winp)
+ int *amaster, *aslave;
+ char *name;
+ struct termios *termp;
+ void *winp;
+{
+ static char line[] = "/dev/ptyXX";
+ register char *cp1, *cp2;
+ register int master, slave, ttygid;
+ struct group *gr;
+
+ if ((gr = getgrnam("tty")) != NULL)
+ ttygid = gr->gr_gid;
+ else
+ ttygid = -1;
+
+ for (cp1 = "pqrs"; *cp1; cp1++) {
+ line[8] = *cp1;
+ for (cp2 = "0123456789abcdef"; *cp2; cp2++) {
+ line[5] = 'p';
+ line[9] = *cp2;
+ if ((master = open(line, O_RDWR, 0)) == -1) {
+ if (errno == ENOENT)
+ return (-1); /* out of ptys */
+ } else {
+ line[5] = 't';
+ (void) chown(line, getuid(), ttygid);
+ (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
+#ifdef HAVE_REVOKE
+ (void) revoke(line);
+#endif
+ if ((slave = open(line, O_RDWR, 0)) != -1) {
+ *amaster = master;
+ *aslave = slave;
+ if (name)
+ strcpy(name, line);
+ if (termp)
+ (void) tcsetattr(slave,
+ TCSAFLUSH, termp);
+#ifdef TIOCSWINSZ
+ if (winp)
+ (void) ioctl(slave, TIOCSWINSZ,
+ (char *)winp);
+#endif
+ return (0);
+ }
+ (void) close(master);
+ }
+ }
+ }
+ errno = ENOENT; /* out of ptys */
+ return (-1);
+}
+#endif /* HAVE_SYS5_PTY */
diff --git a/contrib/nvi/ex/ex_set.c b/contrib/nvi/ex/ex_set.c
new file mode 100644
index 0000000..11e9297
--- /dev/null
+++ b/contrib/nvi/ex/ex_set.c
@@ -0,0 +1,46 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_set.c 10.7 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_set -- :set
+ * Ex set option.
+ *
+ * PUBLIC: int ex_set __P((SCR *, EXCMD *));
+ */
+int
+ex_set(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ switch(cmdp->argc) {
+ case 0:
+ opts_dump(sp, CHANGED_DISPLAY);
+ break;
+ default:
+ if (opts_set(sp, cmdp->argv, cmdp->cmd->usage))
+ return (1);
+ break;
+ }
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_shell.c b/contrib/nvi/ex/ex_shell.c
new file mode 100644
index 0000000..9516803
--- /dev/null
+++ b/contrib/nvi/ex/ex_shell.c
@@ -0,0 +1,378 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_shell.c 10.38 (Berkeley) 8/19/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+static const char *sigmsg __P((int));
+
+/*
+ * ex_shell -- :sh[ell]
+ * Invoke the program named in the SHELL environment variable
+ * with the argument -i.
+ *
+ * PUBLIC: int ex_shell __P((SCR *, EXCMD *));
+ */
+int
+ex_shell(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ int rval;
+ char buf[MAXPATHLEN];
+
+ /* We'll need a shell. */
+ if (opts_empty(sp, O_SHELL, 0))
+ return (1);
+
+ /*
+ * XXX
+ * Assumes all shells use -i.
+ */
+ (void)snprintf(buf, sizeof(buf), "%s -i", O_STR(sp, O_SHELL));
+
+ /* Restore the window name. */
+ (void)sp->gp->scr_rename(sp, NULL, 0);
+
+ /* If we're still in a vi screen, move out explicitly. */
+ rval = ex_exec_proc(sp, cmdp, buf, NULL, !F_ISSET(sp, SC_SCR_EXWROTE));
+
+ /* Set the window name. */
+ (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
+
+ /*
+ * !!!
+ * Historically, vi didn't require a continue message after the
+ * return of the shell. Match it.
+ */
+ F_SET(sp, SC_EX_WAIT_NO);
+
+ return (rval);
+}
+
+/*
+ * ex_exec_proc --
+ * Run a separate process.
+ *
+ * PUBLIC: int ex_exec_proc __P((SCR *, EXCMD *, char *, const char *, int));
+ */
+int
+ex_exec_proc(sp, cmdp, cmd, msg, need_newline)
+ SCR *sp;
+ EXCMD *cmdp;
+ char *cmd;
+ const char *msg;
+ int need_newline;
+{
+ GS *gp;
+ const char *name;
+ pid_t pid;
+
+ gp = sp->gp;
+
+ /* We'll need a shell. */
+ if (opts_empty(sp, O_SHELL, 0))
+ return (1);
+
+ /* Enter ex mode. */
+ if (F_ISSET(sp, SC_VI)) {
+ if (gp->scr_screen(sp, SC_EX)) {
+ ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON);
+ return (1);
+ }
+ (void)gp->scr_attr(sp, SA_ALTERNATE, 0);
+ F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);
+ }
+
+ /* Put out additional newline, message. */
+ if (need_newline)
+ (void)ex_puts(sp, "\n");
+ if (msg != NULL) {
+ (void)ex_puts(sp, msg);
+ (void)ex_puts(sp, "\n");
+ }
+ (void)ex_fflush(sp);
+
+ switch (pid = vfork()) {
+ case -1: /* Error. */
+ msgq(sp, M_SYSERR, "vfork");
+ return (1);
+ case 0: /* Utility. */
+ if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)
+ name = O_STR(sp, O_SHELL);
+ else
+ ++name;
+ execl(O_STR(sp, O_SHELL), name, "-c", cmd, NULL);
+ msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s");
+ _exit(127);
+ /* NOTREACHED */
+ default: /* Parent. */
+ return (proc_wait(sp, (long)pid, cmd, 0, 0));
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * proc_wait --
+ * Wait for one of the processes.
+ *
+ * !!!
+ * The pid_t type varies in size from a short to a long depending on the
+ * system. It has to be cast into something or the standard promotion
+ * rules get you. I'm using a long based on the belief that nobody is
+ * going to make it unsigned and it's unlikely to be a quad.
+ *
+ * PUBLIC: int proc_wait __P((SCR *, long, const char *, int, int));
+ */
+int
+proc_wait(sp, pid, cmd, silent, okpipe)
+ SCR *sp;
+ long pid;
+ const char *cmd;
+ int silent, okpipe;
+{
+ size_t len;
+ int nf, pstat;
+ char *p;
+
+ /* Wait for the utility, ignoring interruptions. */
+ for (;;) {
+ errno = 0;
+ if (waitpid((pid_t)pid, &pstat, 0) != -1)
+ break;
+ if (errno != EINTR) {
+ msgq(sp, M_SYSERR, "waitpid");
+ return (1);
+ }
+ }
+
+ /*
+ * Display the utility's exit status. Ignore SIGPIPE from the
+ * parent-writer, as that only means that the utility chose to
+ * exit before reading all of its input.
+ */
+ if (WIFSIGNALED(pstat) && (!okpipe || WTERMSIG(pstat) != SIGPIPE)) {
+ for (; isblank(*cmd); ++cmd);
+ p = msg_print(sp, cmd, &nf);
+ len = strlen(p);
+ msgq(sp, M_ERR, "%.*s%s: received signal: %s%s",
+ MIN(len, 20), p, len > 20 ? " ..." : "",
+ sigmsg(WTERMSIG(pstat)),
+ WCOREDUMP(pstat) ? "; core dumped" : "");
+ if (nf)
+ FREE_SPACE(sp, p, 0);
+ return (1);
+ }
+
+ if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) {
+ /*
+ * Remain silent for "normal" errors when doing shell file
+ * name expansions, they almost certainly indicate nothing
+ * more than a failure to match.
+ *
+ * Remain silent for vi read filter errors. It's historic
+ * practice.
+ */
+ if (!silent) {
+ for (; isblank(*cmd); ++cmd);
+ p = msg_print(sp, cmd, &nf);
+ len = strlen(p);
+ msgq(sp, M_ERR, "%.*s%s: exited with status %d",
+ MIN(len, 20), p, len > 20 ? " ..." : "",
+ WEXITSTATUS(pstat));
+ if (nf)
+ FREE_SPACE(sp, p, 0);
+ }
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * XXX
+ * The sys_siglist[] table in the C library has this information, but there's
+ * no portable way to get to it. (Believe me, I tried.)
+ */
+typedef struct _sigs {
+ int number; /* signal number */
+ char *message; /* related message */
+} SIGS;
+
+SIGS const sigs[] = {
+#ifdef SIGABRT
+ SIGABRT, "Abort trap",
+#endif
+#ifdef SIGALRM
+ SIGALRM, "Alarm clock",
+#endif
+#ifdef SIGBUS
+ SIGBUS, "Bus error",
+#endif
+#ifdef SIGCLD
+ SIGCLD, "Child exited or stopped",
+#endif
+#ifdef SIGCHLD
+ SIGCHLD, "Child exited",
+#endif
+#ifdef SIGCONT
+ SIGCONT, "Continued",
+#endif
+#ifdef SIGDANGER
+ SIGDANGER, "System crash imminent",
+#endif
+#ifdef SIGEMT
+ SIGEMT, "EMT trap",
+#endif
+#ifdef SIGFPE
+ SIGFPE, "Floating point exception",
+#endif
+#ifdef SIGGRANT
+ SIGGRANT, "HFT monitor mode granted",
+#endif
+#ifdef SIGHUP
+ SIGHUP, "Hangup",
+#endif
+#ifdef SIGILL
+ SIGILL, "Illegal instruction",
+#endif
+#ifdef SIGINFO
+ SIGINFO, "Information request",
+#endif
+#ifdef SIGINT
+ SIGINT, "Interrupt",
+#endif
+#ifdef SIGIO
+ SIGIO, "I/O possible",
+#endif
+#ifdef SIGIOT
+ SIGIOT, "IOT trap",
+#endif
+#ifdef SIGKILL
+ SIGKILL, "Killed",
+#endif
+#ifdef SIGLOST
+ SIGLOST, "Record lock",
+#endif
+#ifdef SIGMIGRATE
+ SIGMIGRATE, "Migrate process to another CPU",
+#endif
+#ifdef SIGMSG
+ SIGMSG, "HFT input data pending",
+#endif
+#ifdef SIGPIPE
+ SIGPIPE, "Broken pipe",
+#endif
+#ifdef SIGPOLL
+ SIGPOLL, "I/O possible",
+#endif
+#ifdef SIGPRE
+ SIGPRE, "Programming error",
+#endif
+#ifdef SIGPROF
+ SIGPROF, "Profiling timer expired",
+#endif
+#ifdef SIGPWR
+ SIGPWR, "Power failure imminent",
+#endif
+#ifdef SIGRETRACT
+ SIGRETRACT, "HFT monitor mode retracted",
+#endif
+#ifdef SIGQUIT
+ SIGQUIT, "Quit",
+#endif
+#ifdef SIGSAK
+ SIGSAK, "Secure Attention Key",
+#endif
+#ifdef SIGSEGV
+ SIGSEGV, "Segmentation fault",
+#endif
+#ifdef SIGSOUND
+ SIGSOUND, "HFT sound sequence completed",
+#endif
+#ifdef SIGSTOP
+ SIGSTOP, "Suspended (signal)",
+#endif
+#ifdef SIGSYS
+ SIGSYS, "Bad system call",
+#endif
+#ifdef SIGTERM
+ SIGTERM, "Terminated",
+#endif
+#ifdef SIGTRAP
+ SIGTRAP, "Trace/BPT trap",
+#endif
+#ifdef SIGTSTP
+ SIGTSTP, "Suspended",
+#endif
+#ifdef SIGTTIN
+ SIGTTIN, "Stopped (tty input)",
+#endif
+#ifdef SIGTTOU
+ SIGTTOU, "Stopped (tty output)",
+#endif
+#ifdef SIGURG
+ SIGURG, "Urgent I/O condition",
+#endif
+#ifdef SIGUSR1
+ SIGUSR1, "User defined signal 1",
+#endif
+#ifdef SIGUSR2
+ SIGUSR2, "User defined signal 2",
+#endif
+#ifdef SIGVTALRM
+ SIGVTALRM, "Virtual timer expired",
+#endif
+#ifdef SIGWINCH
+ SIGWINCH, "Window size changes",
+#endif
+#ifdef SIGXCPU
+ SIGXCPU, "Cputime limit exceeded",
+#endif
+#ifdef SIGXFSZ
+ SIGXFSZ, "Filesize limit exceeded",
+#endif
+};
+
+/*
+ * sigmsg --
+ * Return a pointer to a message describing a signal.
+ */
+static const char *
+sigmsg(signo)
+ int signo;
+{
+ static char buf[40];
+ const SIGS *sigp;
+ int n;
+
+ for (n = 0,
+ sigp = &sigs[0]; n < sizeof(sigs) / sizeof(sigs[0]); ++n, ++sigp)
+ if (sigp->number == signo)
+ return (sigp->message);
+ (void)snprintf(buf, sizeof(buf), "Unknown signal: %d", signo);
+ return (buf);
+}
diff --git a/contrib/nvi/ex/ex_shift.c b/contrib/nvi/ex/ex_shift.c
new file mode 100644
index 0000000..83bd36d
--- /dev/null
+++ b/contrib/nvi/ex/ex_shift.c
@@ -0,0 +1,191 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_shift.c 10.11 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+enum which {LEFT, RIGHT};
+static int shift __P((SCR *, EXCMD *, enum which));
+
+/*
+ * ex_shiftl -- :<[<...]
+ *
+ *
+ * PUBLIC: int ex_shiftl __P((SCR *, EXCMD *));
+ */
+int
+ex_shiftl(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (shift(sp, cmdp, LEFT));
+}
+
+/*
+ * ex_shiftr -- :>[>...]
+ *
+ * PUBLIC: int ex_shiftr __P((SCR *, EXCMD *));
+ */
+int
+ex_shiftr(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (shift(sp, cmdp, RIGHT));
+}
+
+/*
+ * shift --
+ * Ex shift support.
+ */
+static int
+shift(sp, cmdp, rl)
+ SCR *sp;
+ EXCMD *cmdp;
+ enum which rl;
+{
+ recno_t from, to;
+ size_t blen, len, newcol, newidx, oldcol, oldidx, sw;
+ int curset;
+ char *p, *bp, *tbp;
+
+ NEEDFILE(sp, cmdp);
+
+ if (O_VAL(sp, O_SHIFTWIDTH) == 0) {
+ msgq(sp, M_INFO, "152|shiftwidth option set to 0");
+ return (0);
+ }
+
+ /* Copy the lines being shifted into the unnamed buffer. */
+ if (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE))
+ return (1);
+
+ /*
+ * The historic version of vi permitted the user to string any number
+ * of '>' or '<' characters together, resulting in an indent of the
+ * appropriate levels. There's a special hack in ex_cmd() so that
+ * cmdp->argv[0] points to the string of '>' or '<' characters.
+ *
+ * Q: What's the difference between the people adding features
+ * to vi and the Girl Scouts?
+ * A: The Girl Scouts have mint cookies and adult supervision.
+ */
+ for (p = cmdp->argv[0]->bp, sw = 0; *p == '>' || *p == '<'; ++p)
+ sw += O_VAL(sp, O_SHIFTWIDTH);
+
+ GET_SPACE_RET(sp, bp, blen, 256);
+
+ curset = 0;
+ for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) {
+ if (db_get(sp, from, DBG_FATAL, &p, &len))
+ goto err;
+ if (!len) {
+ if (sp->lno == from)
+ curset = 1;
+ continue;
+ }
+
+ /*
+ * Calculate the old indent amount and the number of
+ * characters it used.
+ */
+ for (oldidx = 0, oldcol = 0; oldidx < len; ++oldidx)
+ if (p[oldidx] == ' ')
+ ++oldcol;
+ else if (p[oldidx] == '\t')
+ oldcol += O_VAL(sp, O_TABSTOP) -
+ oldcol % O_VAL(sp, O_TABSTOP);
+ else
+ break;
+
+ /* Calculate the new indent amount. */
+ if (rl == RIGHT)
+ newcol = oldcol + sw;
+ else {
+ newcol = oldcol < sw ? 0 : oldcol - sw;
+ if (newcol == oldcol) {
+ if (sp->lno == from)
+ curset = 1;
+ continue;
+ }
+ }
+
+ /* Get a buffer that will hold the new line. */
+ ADD_SPACE_RET(sp, bp, blen, newcol + len);
+
+ /*
+ * Build a new indent string and count the number of
+ * characters it uses.
+ */
+ for (tbp = bp, newidx = 0;
+ newcol >= O_VAL(sp, O_TABSTOP); ++newidx) {
+ *tbp++ = '\t';
+ newcol -= O_VAL(sp, O_TABSTOP);
+ }
+ for (; newcol > 0; --newcol, ++newidx)
+ *tbp++ = ' ';
+
+ /* Add the original line. */
+ memcpy(tbp, p + oldidx, len - oldidx);
+
+ /* Set the replacement line. */
+ if (db_set(sp, from, bp, (tbp + (len - oldidx)) - bp)) {
+err: FREE_SPACE(sp, bp, blen);
+ return (1);
+ }
+
+ /*
+ * !!!
+ * The shift command in historic vi had the usual bizarre
+ * collection of cursor semantics. If called from vi, the
+ * cursor was repositioned to the first non-blank character
+ * of the lowest numbered line shifted. If called from ex,
+ * the cursor was repositioned to the first non-blank of the
+ * highest numbered line shifted. Here, if the cursor isn't
+ * part of the set of lines that are moved, move it to the
+ * first non-blank of the last line shifted. (This makes
+ * ":3>>" in vi work reasonably.) If the cursor is part of
+ * the shifted lines, it doesn't get moved at all. This
+ * permits shifting of marked areas, i.e. ">'a." shifts the
+ * marked area twice, something that couldn't be done with
+ * historic vi.
+ */
+ if (sp->lno == from) {
+ curset = 1;
+ if (newidx > oldidx)
+ sp->cno += newidx - oldidx;
+ else if (sp->cno >= oldidx - newidx)
+ sp->cno -= oldidx - newidx;
+ }
+ }
+ if (!curset) {
+ sp->lno = to;
+ sp->cno = 0;
+ (void)nonblank(sp, to, &sp->cno);
+ }
+
+ FREE_SPACE(sp, bp, blen);
+
+ sp->rptlines[L_SHIFT] += cmdp->addr2.lno - cmdp->addr1.lno + 1;
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_source.c b/contrib/nvi/ex/ex_source.c
new file mode 100644
index 0000000..b52c527
--- /dev/null
+++ b/contrib/nvi/ex/ex_source.c
@@ -0,0 +1,85 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_source.c 10.12 (Berkeley) 8/10/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_source -- :source file
+ * Execute ex commands from a file.
+ *
+ * PUBLIC: int ex_source __P((SCR *, EXCMD *));
+ */
+int
+ex_source(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ struct stat sb;
+ int fd, len;
+ char *bp, *name;
+
+ name = cmdp->argv[0]->bp;
+ if ((fd = open(name, O_RDONLY, 0)) < 0 || fstat(fd, &sb))
+ goto err;
+
+ /*
+ * XXX
+ * I'd like to test to see if the file is too large 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. So, put an fairly unreasonable limit on it,
+ * I don't want to be dropping core here.
+ */
+#define MEGABYTE 1048576
+ if (sb.st_size > MEGABYTE) {
+ errno = ENOMEM;
+ goto err;
+ }
+
+ MALLOC(sp, bp, char *, (size_t)sb.st_size + 1);
+ if (bp == NULL) {
+ (void)close(fd);
+ return (1);
+ }
+ bp[sb.st_size] = '\0';
+
+ /* Read the file into memory. */
+ len = read(fd, bp, (int)sb.st_size);
+ (void)close(fd);
+ if (len == -1 || len != sb.st_size) {
+ if (len != sb.st_size)
+ errno = EIO;
+ free(bp);
+err: msgq_str(sp, M_SYSERR, name, "%s");
+ return (1);
+ }
+
+ /* Put it on the ex queue. */
+ return (ex_run_str(sp, name, bp, (size_t)sb.st_size, 1, 1));
+}
diff --git a/contrib/nvi/ex/ex_stop.c b/contrib/nvi/ex/ex_stop.c
new file mode 100644
index 0000000..bc55fd2
--- /dev/null
+++ b/contrib/nvi/ex/ex_stop.c
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1992, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_stop.c 10.10 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_stop -- :stop[!]
+ * :suspend[!]
+ * Suspend execution.
+ *
+ * PUBLIC: int ex_stop __P((SCR *, EXCMD *));
+ */
+int
+ex_stop(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ int allowed;
+
+ /* For some strange reason, the force flag turns off autowrite. */
+ if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && file_aw(sp, FS_ALL))
+ return (1);
+
+ if (sp->gp->scr_suspend(sp, &allowed))
+ return (1);
+ if (!allowed)
+ ex_emsg(sp, NULL, EXM_NOSUSPEND);
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_subst.c b/contrib/nvi/ex/ex_subst.c
new file mode 100644
index 0000000..0ebb81d
--- /dev/null
+++ b/contrib/nvi/ex/ex_subst.c
@@ -0,0 +1,1459 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_subst.c 10.37 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+#define SUB_FIRST 0x01 /* The 'r' flag isn't reasonable. */
+#define SUB_MUSTSETR 0x02 /* The 'r' flag is required. */
+
+static int re_conv __P((SCR *, char **, size_t *, int *));
+static int re_cscope_conv __P((SCR *, char **, size_t *, int *));
+static int re_sub __P((SCR *,
+ char *, char **, size_t *, size_t *, regmatch_t [10]));
+static int re_tag_conv __P((SCR *, char **, size_t *, int *));
+static int s __P((SCR *, EXCMD *, char *, regex_t *, u_int));
+
+/*
+ * ex_s --
+ * [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]]
+ *
+ * Substitute on lines matching a pattern.
+ *
+ * PUBLIC: int ex_s __P((SCR *, EXCMD *));
+ */
+int
+ex_s(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ regex_t *re;
+ size_t blen, len;
+ u_int flags;
+ int delim;
+ char *bp, *ptrn, *rep, *p, *t;
+
+ /*
+ * Skip leading white space.
+ *
+ * !!!
+ * Historic vi allowed any non-alphanumeric to serve as the
+ * substitution command delimiter.
+ *
+ * !!!
+ * If the arguments are empty, it's the same as &, i.e. we
+ * repeat the last substitution.
+ */
+ if (cmdp->argc == 0)
+ goto subagain;
+ for (p = cmdp->argv[0]->bp,
+ len = cmdp->argv[0]->len; len > 0; --len, ++p) {
+ if (!isblank(*p))
+ break;
+ }
+ if (len == 0)
+subagain: return (ex_subagain(sp, cmdp));
+
+ delim = *p++;
+ if (isalnum(delim) || delim == '\\')
+ return (s(sp, cmdp, p, &sp->subre_c, SUB_MUSTSETR));
+
+ /*
+ * !!!
+ * The full-blown substitute command reset the remembered
+ * state of the 'c' and 'g' suffices.
+ */
+ sp->c_suffix = sp->g_suffix = 0;
+
+ /*
+ * Get the pattern string, toss escaping characters.
+ *
+ * !!!
+ * Historic vi accepted any of the following forms:
+ *
+ * :s/abc/def/ change "abc" to "def"
+ * :s/abc/def change "abc" to "def"
+ * :s/abc/ delete "abc"
+ * :s/abc delete "abc"
+ *
+ * QUOTING NOTE:
+ *
+ * Only toss an escaping character if it escapes a delimiter.
+ * This means that "s/A/\\\\f" replaces "A" with "\\f". It
+ * would be nice to be more regular, i.e. for each layer of
+ * escaping a single escaping character is removed, but that's
+ * not how the historic vi worked.
+ */
+ for (ptrn = t = p;;) {
+ if (p[0] == '\0' || p[0] == delim) {
+ if (p[0] == delim)
+ ++p;
+ /*
+ * !!!
+ * Nul terminate the pattern string -- it's passed
+ * to regcomp which doesn't understand anything else.
+ */
+ *t = '\0';
+ break;
+ }
+ if (p[0] == '\\')
+ if (p[1] == delim)
+ ++p;
+ else if (p[1] == '\\')
+ *t++ = *p++;
+ *t++ = *p++;
+ }
+
+ /*
+ * If the pattern string is empty, use the last RE (not just the
+ * last substitution RE).
+ */
+ if (*ptrn == '\0') {
+ if (sp->re == NULL) {
+ ex_emsg(sp, NULL, EXM_NOPREVRE);
+ return (1);
+ }
+
+ /* Re-compile the RE if necessary. */
+ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,
+ sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))
+ return (1);
+ flags = 0;
+ } else {
+ /*
+ * !!!
+ * Compile the RE. Historic practice is that substitutes set
+ * the search direction as well as both substitute and search
+ * RE's. We compile the RE twice, as we don't want to bother
+ * ref counting the pattern string and (opaque) structure.
+ */
+ if (re_compile(sp, ptrn, t - ptrn,
+ &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH))
+ return (1);
+ if (re_compile(sp, ptrn, t - ptrn,
+ &sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST))
+ return (1);
+
+ flags = SUB_FIRST;
+ sp->searchdir = FORWARD;
+ }
+ re = &sp->re_c;
+
+ /*
+ * Get the replacement string.
+ *
+ * The special character & (\& if O_MAGIC not set) matches the
+ * entire RE. No handling of & is required here, it's done by
+ * re_sub().
+ *
+ * The special character ~ (\~ if O_MAGIC not set) inserts the
+ * previous replacement string into this replacement string.
+ * Count ~'s to figure out how much space we need. We could
+ * special case nonexistent last patterns or whether or not
+ * O_MAGIC is set, but it's probably not worth the effort.
+ *
+ * QUOTING NOTE:
+ *
+ * Only toss an escaping character if it escapes a delimiter or
+ * if O_MAGIC is set and it escapes a tilde.
+ *
+ * !!!
+ * If the entire replacement pattern is "%", then use the last
+ * replacement pattern. This semantic was added to vi in System
+ * V and then percolated elsewhere, presumably around the time
+ * that it was added to their version of ed(1).
+ */
+ if (p[0] == '\0' || p[0] == delim) {
+ if (p[0] == delim)
+ ++p;
+ if (sp->repl != NULL)
+ free(sp->repl);
+ sp->repl = NULL;
+ sp->repl_len = 0;
+ } else if (p[0] == '%' && (p[1] == '\0' || p[1] == delim))
+ p += p[1] == delim ? 2 : 1;
+ else {
+ for (rep = p, len = 0;
+ p[0] != '\0' && p[0] != delim; ++p, ++len)
+ if (p[0] == '~')
+ len += sp->repl_len;
+ GET_SPACE_RET(sp, bp, blen, len);
+ for (t = bp, len = 0, p = rep;;) {
+ if (p[0] == '\0' || p[0] == delim) {
+ if (p[0] == delim)
+ ++p;
+ break;
+ }
+ if (p[0] == '\\') {
+ if (p[1] == delim)
+ ++p;
+ else if (p[1] == '\\') {
+ *t++ = *p++;
+ ++len;
+ } else if (p[1] == '~') {
+ ++p;
+ if (!O_ISSET(sp, O_MAGIC))
+ goto tilde;
+ }
+ } else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) {
+tilde: ++p;
+ memcpy(t, sp->repl, sp->repl_len);
+ t += sp->repl_len;
+ len += sp->repl_len;
+ continue;
+ }
+ *t++ = *p++;
+ ++len;
+ }
+ if ((sp->repl_len = len) != 0) {
+ if (sp->repl != NULL)
+ free(sp->repl);
+ if ((sp->repl = malloc(len)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ FREE_SPACE(sp, bp, blen);
+ return (1);
+ }
+ memcpy(sp->repl, bp, len);
+ }
+ FREE_SPACE(sp, bp, blen);
+ }
+ return (s(sp, cmdp, p, re, flags));
+}
+
+/*
+ * ex_subagain --
+ * [line [,line]] & [cgr] [count] [#lp]]
+ *
+ * Substitute using the last substitute RE and replacement pattern.
+ *
+ * PUBLIC: int ex_subagain __P((SCR *, EXCMD *));
+ */
+int
+ex_subagain(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ if (sp->subre == NULL) {
+ ex_emsg(sp, NULL, EXM_NOPREVRE);
+ return (1);
+ }
+ if (!F_ISSET(sp, SC_RE_SUBST) && re_compile(sp,
+ sp->subre, sp->subre_len, NULL, NULL, &sp->subre_c, RE_C_SUBST))
+ return (1);
+ return (s(sp,
+ cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->subre_c, 0));
+}
+
+/*
+ * ex_subtilde --
+ * [line [,line]] ~ [cgr] [count] [#lp]]
+ *
+ * Substitute using the last RE and last substitute replacement pattern.
+ *
+ * PUBLIC: int ex_subtilde __P((SCR *, EXCMD *));
+ */
+int
+ex_subtilde(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ if (sp->re == NULL) {
+ ex_emsg(sp, NULL, EXM_NOPREVRE);
+ return (1);
+ }
+ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,
+ sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))
+ return (1);
+ return (s(sp,
+ cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->re_c, 0));
+}
+
+/*
+ * s --
+ * Do the substitution. This stuff is *really* tricky. There are lots of
+ * special cases, and general nastiness. Don't mess with it unless you're
+ * pretty confident.
+ *
+ * The nasty part of the substitution is what happens when the replacement
+ * string contains newlines. It's a bit tricky -- consider the information
+ * that has to be retained for "s/f\(o\)o/^M\1^M\1/". The solution here is
+ * to build a set of newline offsets which we use to break the line up later,
+ * when the replacement is done. Don't change it unless you're *damned*
+ * confident.
+ */
+#define NEEDNEWLINE(sp) { \
+ if (sp->newl_len == sp->newl_cnt) { \
+ sp->newl_len += 25; \
+ REALLOC(sp, sp->newl, size_t *, \
+ sp->newl_len * sizeof(size_t)); \
+ if (sp->newl == NULL) { \
+ sp->newl_len = 0; \
+ return (1); \
+ } \
+ } \
+}
+
+#define BUILD(sp, l, len) { \
+ if (lbclen + (len) > lblen) { \
+ lblen += MAX(lbclen + (len), 256); \
+ REALLOC(sp, lb, char *, lblen); \
+ if (lb == NULL) { \
+ lbclen = 0; \
+ return (1); \
+ } \
+ } \
+ memcpy(lb + lbclen, l, len); \
+ lbclen += len; \
+}
+
+#define NEEDSP(sp, len, pnt) { \
+ if (lbclen + (len) > lblen) { \
+ lblen += MAX(lbclen + (len), 256); \
+ REALLOC(sp, lb, char *, lblen); \
+ if (lb == NULL) { \
+ lbclen = 0; \
+ return (1); \
+ } \
+ pnt = lb + lbclen; \
+ } \
+}
+
+static int
+s(sp, cmdp, s, re, flags)
+ SCR *sp;
+ EXCMD *cmdp;
+ char *s;
+ regex_t *re;
+ u_int flags;
+{
+ EVENT ev;
+ MARK from, to;
+ TEXTH tiq;
+ recno_t elno, lno, slno;
+ regmatch_t match[10];
+ size_t blen, cnt, last, lbclen, lblen, len, llen;
+ size_t offset, saved_offset, scno;
+ int cflag, lflag, nflag, pflag, rflag;
+ int didsub, do_eol_match, eflags, empty_ok, eval;
+ int linechanged, matched, quit, rval;
+ char *bp, *lb;
+
+ NEEDFILE(sp, cmdp);
+
+ slno = sp->lno;
+ scno = sp->cno;
+
+ /*
+ * !!!
+ * Historically, the 'g' and 'c' suffices were always toggled as flags,
+ * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was
+ * not set, they were initialized to 0 for all substitute commands. If
+ * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user
+ * specified substitute/replacement patterns (see ex_s()).
+ */
+ if (!O_ISSET(sp, O_EDCOMPATIBLE))
+ sp->c_suffix = sp->g_suffix = 0;
+
+ /*
+ * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but
+ * it only displayed the last change. I'd disallow them, but they are
+ * useful in combination with the [v]global commands. In the current
+ * model the problem is combining them with the 'c' flag -- the screen
+ * would have to flip back and forth between the confirm screen and the
+ * ex print screen, which would be pretty awful. We do display all
+ * changes, though, for what that's worth.
+ *
+ * !!!
+ * Historic vi was fairly strict about the order of "options", the
+ * count, and "flags". I'm somewhat fuzzy on the difference between
+ * options and flags, anyway, so this is a simpler approach, and we
+ * just take it them in whatever order the user gives them. (The ex
+ * usage statement doesn't reflect this.)
+ */
+ cflag = lflag = nflag = pflag = rflag = 0;
+ if (s == NULL)
+ goto noargs;
+ for (lno = OOBLNO; *s != '\0'; ++s)
+ switch (*s) {
+ case ' ':
+ case '\t':
+ continue;
+ case '+':
+ ++cmdp->flagoff;
+ break;
+ case '-':
+ --cmdp->flagoff;
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (lno != OOBLNO)
+ goto usage;
+ errno = 0;
+ lno = strtoul(s, &s, 10);
+ if (*s == '\0') /* Loop increment correction. */
+ --s;
+ if (errno == ERANGE) {
+ if (lno == LONG_MAX)
+ msgq(sp, M_ERR, "153|Count overflow");
+ else if (lno == LONG_MIN)
+ msgq(sp, M_ERR, "154|Count underflow");
+ else
+ msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+ /*
+ * In historic vi, the count was inclusive from the
+ * second address.
+ */
+ cmdp->addr1.lno = cmdp->addr2.lno;
+ cmdp->addr2.lno += lno - 1;
+ if (!db_exist(sp, cmdp->addr2.lno) &&
+ db_last(sp, &cmdp->addr2.lno))
+ return (1);
+ break;
+ case '#':
+ nflag = 1;
+ break;
+ case 'c':
+ sp->c_suffix = !sp->c_suffix;
+
+ /* Ex text structure initialization. */
+ if (F_ISSET(sp, SC_EX)) {
+ memset(&tiq, 0, sizeof(TEXTH));
+ CIRCLEQ_INIT(&tiq);
+ }
+ break;
+ case 'g':
+ sp->g_suffix = !sp->g_suffix;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'r':
+ if (LF_ISSET(SUB_FIRST)) {
+ msgq(sp, M_ERR,
+ "155|Regular expression specified; r flag meaningless");
+ return (1);
+ }
+ if (!F_ISSET(sp, SC_RE_SEARCH)) {
+ ex_emsg(sp, NULL, EXM_NOPREVRE);
+ return (1);
+ }
+ rflag = 1;
+ re = &sp->re_c;
+ break;
+ default:
+ goto usage;
+ }
+
+ if (*s != '\0' || !rflag && LF_ISSET(SUB_MUSTSETR)) {
+usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+
+noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) {
+ msgq(sp, M_ERR,
+"156|The #, l and p flags may not be combined with the c flag in vi mode");
+ return (1);
+ }
+
+ /*
+ * bp: if interactive, line cache
+ * blen: if interactive, line cache length
+ * lb: build buffer pointer.
+ * lbclen: current length of built buffer.
+ * lblen; length of build buffer.
+ */
+ bp = lb = NULL;
+ blen = lbclen = lblen = 0;
+
+ /* For each line... */
+ for (matched = quit = 0, lno = cmdp->addr1.lno,
+ elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) {
+
+ /* Someone's unhappy, time to stop. */
+ if (INTERRUPTED(sp))
+ break;
+
+ /* Get the line. */
+ if (db_get(sp, lno, DBG_FATAL, &s, &llen))
+ goto err;
+
+ /*
+ * Make a local copy if doing confirmation -- when calling
+ * the confirm routine we're likely to lose the cached copy.
+ */
+ if (sp->c_suffix) {
+ if (bp == NULL) {
+ GET_SPACE_RET(sp, bp, blen, llen);
+ } else
+ ADD_SPACE_RET(sp, bp, blen, llen);
+ memcpy(bp, s, llen);
+ s = bp;
+ }
+
+ /* Start searching from the beginning. */
+ offset = 0;
+ len = llen;
+
+ /* Reset the build buffer offset. */
+ lbclen = 0;
+
+ /* Reset empty match flag. */
+ empty_ok = 1;
+
+ /*
+ * We don't want to have to do a setline if the line didn't
+ * change -- keep track of whether or not this line changed.
+ * If doing confirmations, don't want to keep setting the
+ * line if change is refused -- keep track of substitutions.
+ */
+ didsub = linechanged = 0;
+
+ /* New line, do an EOL match. */
+ do_eol_match = 1;
+
+ /* It's not nul terminated, but we pretend it is. */
+ eflags = REG_STARTEND;
+
+ /*
+ * The search area is from s + offset to the EOL.
+ *
+ * Generally, match[0].rm_so is the offset of the start
+ * of the match from the start of the search, and offset
+ * is the offset of the start of the last search.
+ */
+nextmatch: match[0].rm_so = 0;
+ match[0].rm_eo = len;
+
+ /* Get the next match. */
+ eval = regexec(re, (char *)s + offset, 10, match, eflags);
+
+ /*
+ * There wasn't a match or if there was an error, deal with
+ * it. If there was a previous match in this line, resolve
+ * the changes into the database. Otherwise, just move on.
+ */
+ if (eval == REG_NOMATCH)
+ goto endmatch;
+ if (eval != 0) {
+ re_error(sp, eval, re);
+ goto err;
+ }
+ matched = 1;
+
+ /* Only the first search can match an anchored expression. */
+ eflags |= REG_NOTBOL;
+
+ /*
+ * !!!
+ * It's possible to match 0-length strings -- for example, the
+ * command s;a*;X;, when matched against the string "aabb" will
+ * result in "XbXbX", i.e. the matches are "aa", the space
+ * between the b's and the space between the b's and the end of
+ * the string. There is a similar space between the beginning
+ * of the string and the a's. The rule that we use (because vi
+ * historically used it) is that any 0-length match, occurring
+ * immediately after a match, is ignored. Otherwise, the above
+ * example would have resulted in "XXbXbX". Another example is
+ * incorrectly using " *" to replace groups of spaces with one
+ * space.
+ *
+ * The way we do this is that if we just had a successful match,
+ * the starting offset does not skip characters, and the match
+ * is empty, ignore the match and move forward. If there's no
+ * more characters in the string, we were attempting to match
+ * after the last character, so quit.
+ */
+ if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) {
+ empty_ok = 1;
+ if (len == 0)
+ goto endmatch;
+ BUILD(sp, s + offset, 1)
+ ++offset;
+ --len;
+ goto nextmatch;
+ }
+
+ /* Confirm change. */
+ if (sp->c_suffix) {
+ /*
+ * Set the cursor position for confirmation. Note,
+ * if we matched on a '$', the cursor may be past
+ * the end of line.
+ */
+ from.lno = to.lno = lno;
+ from.cno = match[0].rm_so + offset;
+ to.cno = match[0].rm_eo + offset;
+ /*
+ * Both ex and vi have to correct for a change before
+ * the first character in the line.
+ */
+ if (llen == 0)
+ from.cno = to.cno = 0;
+ if (F_ISSET(sp, SC_VI)) {
+ /*
+ * Only vi has to correct for a change after
+ * the last character in the line.
+ *
+ * XXX
+ * It would be nice to change the vi code so
+ * that we could display a cursor past EOL.
+ */
+ if (to.cno >= llen)
+ to.cno = llen - 1;
+ if (from.cno >= llen)
+ from.cno = llen - 1;
+
+ sp->lno = from.lno;
+ sp->cno = from.cno;
+ if (vs_refresh(sp, 1))
+ goto err;
+
+ vs_update(sp, msg_cat(sp,
+ "169|Confirm change? [n]", NULL), NULL);
+
+ if (v_event_get(sp, &ev, 0, 0))
+ goto err;
+ switch (ev.e_event) {
+ case E_CHARACTER:
+ break;
+ case E_EOF:
+ case E_ERR:
+ case E_INTERRUPT:
+ goto lquit;
+ default:
+ v_event_err(sp, &ev);
+ goto lquit;
+ }
+ } else {
+ if (ex_print(sp, cmdp, &from, &to, 0) ||
+ ex_scprint(sp, &from, &to))
+ goto lquit;
+ if (ex_txt(sp, &tiq, 0, TXT_CR))
+ goto err;
+ ev.e_c = tiq.cqh_first->lb[0];
+ }
+
+ switch (ev.e_c) {
+ case CH_YES:
+ break;
+ default:
+ case CH_NO:
+ didsub = 0;
+ BUILD(sp, s +offset, match[0].rm_eo);
+ goto skip;
+ case CH_QUIT:
+ /* Set the quit/interrupted flags. */
+lquit: quit = 1;
+ F_SET(sp->gp, G_INTERRUPTED);
+
+ /*
+ * Resolve any changes, then return to (and
+ * exit from) the main loop.
+ */
+ goto endmatch;
+ }
+ }
+
+ /*
+ * Set the cursor to the last position changed, converting
+ * from 1-based to 0-based.
+ */
+ sp->lno = lno;
+ sp->cno = match[0].rm_so;
+
+ /* Copy the bytes before the match into the build buffer. */
+ BUILD(sp, s + offset, match[0].rm_so);
+
+ /* Substitute the matching bytes. */
+ didsub = 1;
+ if (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match))
+ goto err;
+
+ /* Set the change flag so we know this line was modified. */
+ linechanged = 1;
+
+ /* Move past the matched bytes. */
+skip: offset += match[0].rm_eo;
+ len -= match[0].rm_eo;
+
+ /* A match cannot be followed by an empty pattern. */
+ empty_ok = 0;
+
+ /*
+ * If doing a global change with confirmation, we have to
+ * update the screen. The basic idea is to store the line
+ * so the screen update routines can find it, and restart.
+ */
+ if (didsub && sp->c_suffix && sp->g_suffix) {
+ /*
+ * The new search offset will be the end of the
+ * modified line.
+ */
+ saved_offset = lbclen;
+
+ /* Copy the rest of the line. */
+ if (len)
+ BUILD(sp, s + offset, len)
+
+ /* Set the new offset. */
+ offset = saved_offset;
+
+ /* Store inserted lines, adjusting the build buffer. */
+ last = 0;
+ if (sp->newl_cnt) {
+ for (cnt = 0;
+ cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) {
+ if (db_insert(sp, lno,
+ lb + last, sp->newl[cnt] - last))
+ goto err;
+ last = sp->newl[cnt] + 1;
+ ++sp->rptlines[L_ADDED];
+ }
+ lbclen -= last;
+ offset -= last;
+ sp->newl_cnt = 0;
+ }
+
+ /* Store and retrieve the line. */
+ if (db_set(sp, lno, lb + last, lbclen))
+ goto err;
+ if (db_get(sp, lno, DBG_FATAL, &s, &llen))
+ goto err;
+ ADD_SPACE_RET(sp, bp, blen, llen)
+ memcpy(bp, s, llen);
+ s = bp;
+ len = llen - offset;
+
+ /* Restart the build. */
+ lbclen = 0;
+ BUILD(sp, s, offset);
+
+ /*
+ * If we haven't already done the after-the-string
+ * match, do one. Set REG_NOTEOL so the '$' pattern
+ * only matches once.
+ */
+ if (!do_eol_match)
+ goto endmatch;
+ if (offset == len) {
+ do_eol_match = 0;
+ eflags |= REG_NOTEOL;
+ }
+ goto nextmatch;
+ }
+
+ /*
+ * If it's a global:
+ *
+ * If at the end of the string, do a test for the after
+ * the string match. Set REG_NOTEOL so the '$' pattern
+ * only matches once.
+ */
+ if (sp->g_suffix && do_eol_match) {
+ if (len == 0) {
+ do_eol_match = 0;
+ eflags |= REG_NOTEOL;
+ }
+ goto nextmatch;
+ }
+
+endmatch: if (!linechanged)
+ continue;
+
+ /* Copy any remaining bytes into the build buffer. */
+ if (len)
+ BUILD(sp, s + offset, len)
+
+ /* Store inserted lines, adjusting the build buffer. */
+ last = 0;
+ if (sp->newl_cnt) {
+ for (cnt = 0;
+ cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) {
+ if (db_insert(sp,
+ lno, lb + last, sp->newl[cnt] - last))
+ goto err;
+ last = sp->newl[cnt] + 1;
+ ++sp->rptlines[L_ADDED];
+ }
+ lbclen -= last;
+ sp->newl_cnt = 0;
+ }
+
+ /* Store the changed line. */
+ if (db_set(sp, lno, lb + last, lbclen))
+ goto err;
+
+ /* Update changed line counter. */
+ if (sp->rptlchange != lno) {
+ sp->rptlchange = lno;
+ ++sp->rptlines[L_CHANGED];
+ }
+
+ /*
+ * !!!
+ * Display as necessary. Historic practice is to only
+ * display the last line of a line split into multiple
+ * lines.
+ */
+ if (lflag || nflag || pflag) {
+ from.lno = to.lno = lno;
+ from.cno = to.cno = 0;
+ if (lflag)
+ (void)ex_print(sp, cmdp, &from, &to, E_C_LIST);
+ if (nflag)
+ (void)ex_print(sp, cmdp, &from, &to, E_C_HASH);
+ if (pflag)
+ (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT);
+ }
+ }
+
+ /*
+ * !!!
+ * Historically, vi attempted to leave the cursor at the same place if
+ * the substitution was done at the current cursor position. Otherwise
+ * it moved it to the first non-blank of the last line changed. There
+ * were some problems: for example, :s/$/foo/ with the cursor on the
+ * last character of the line left the cursor on the last character, or
+ * the & command with multiple occurrences of the matching string in the
+ * line usually left the cursor in a fairly random position.
+ *
+ * We try to do the same thing, with the exception that if the user is
+ * doing substitution with confirmation, we move to the last line about
+ * which the user was consulted, as opposed to the last line that they
+ * actually changed. This prevents a screen flash if the user doesn't
+ * change many of the possible lines.
+ */
+ if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) {
+ sp->cno = 0;
+ (void)nonblank(sp, sp->lno, &sp->cno);
+ }
+
+ /*
+ * If not in a global command, and nothing matched, say so.
+ * Else, if none of the lines displayed, put something up.
+ */
+ rval = 0;
+ if (!matched) {
+ if (!F_ISSET(sp, SC_EX_GLOBAL)) {
+ msgq(sp, M_ERR, "157|No match found");
+ goto err;
+ }
+ } else if (!lflag && !nflag && !pflag)
+ F_SET(cmdp, E_AUTOPRINT);
+
+ if (0) {
+err: rval = 1;
+ }
+
+ if (bp != NULL)
+ FREE_SPACE(sp, bp, blen);
+ if (lb != NULL)
+ free(lb);
+ return (rval);
+}
+
+/*
+ * re_compile --
+ * Compile the RE.
+ *
+ * PUBLIC: int re_compile __P((SCR *,
+ * PUBLIC: char *, size_t, char **, size_t *, regex_t *, u_int));
+ */
+int
+re_compile(sp, ptrn, plen, ptrnp, lenp, rep, flags)
+ SCR *sp;
+ char *ptrn, **ptrnp;
+ size_t plen, *lenp;
+ regex_t *rep;
+ u_int flags;
+{
+ size_t len;
+ int reflags, replaced, rval;
+ char *p;
+
+ /* Set RE flags. */
+ reflags = 0;
+ if (!LF_ISSET(RE_C_CSCOPE | RE_C_TAG)) {
+ if (O_ISSET(sp, O_EXTENDED))
+ reflags |= REG_EXTENDED;
+ if (O_ISSET(sp, O_IGNORECASE))
+ reflags |= REG_ICASE;
+ if (O_ISSET(sp, O_ICLOWER)) {
+ for (p = ptrn, len = plen; len > 0; ++p, --len)
+ if (isupper(*p))
+ break;
+ if (len == 0)
+ reflags |= REG_ICASE;
+ }
+ }
+
+ /* If we're replacing a saved value, clear the old one. */
+ if (LF_ISSET(RE_C_SEARCH) && F_ISSET(sp, SC_RE_SEARCH)) {
+ regfree(&sp->re_c);
+ F_CLR(sp, SC_RE_SEARCH);
+ }
+ if (LF_ISSET(RE_C_SUBST) && F_ISSET(sp, SC_RE_SUBST)) {
+ regfree(&sp->subre_c);
+ F_CLR(sp, SC_RE_SUBST);
+ }
+
+ /*
+ * If we're saving the string, it's a pattern we haven't seen before,
+ * so convert the vi-style RE's to POSIX 1003.2 RE's. Save a copy for
+ * later recompilation. Free any previously saved value.
+ */
+ if (ptrnp != NULL) {
+ if (LF_ISSET(RE_C_CSCOPE)) {
+ if (re_cscope_conv(sp, &ptrn, &plen, &replaced))
+ return (1);
+ /*
+ * XXX
+ * Currently, the match-any-<blank> expression used in
+ * re_cscope_conv() requires extended RE's. This may
+ * not be right or safe.
+ */
+ reflags |= REG_EXTENDED;
+ } else if (LF_ISSET(RE_C_TAG)) {
+ if (re_tag_conv(sp, &ptrn, &plen, &replaced))
+ return (1);
+ } else
+ if (re_conv(sp, &ptrn, &plen, &replaced))
+ return (1);
+
+ /* Discard previous pattern. */
+ if (*ptrnp != NULL) {
+ free(*ptrnp);
+ *ptrnp = NULL;
+ }
+ if (lenp != NULL)
+ *lenp = plen;
+
+ /*
+ * Copy the string into allocated memory.
+ *
+ * XXX
+ * Regcomp isn't 8-bit clean, so the pattern is nul-terminated
+ * for now. There's just no other solution.
+ */
+ MALLOC(sp, *ptrnp, char *, plen + 1);
+ if (*ptrnp != NULL) {
+ memcpy(*ptrnp, ptrn, plen);
+ (*ptrnp)[plen] = '\0';
+ }
+
+ /* Free up conversion-routine-allocated memory. */
+ if (replaced)
+ FREE_SPACE(sp, ptrn, 0);
+
+ if (*ptrnp == NULL)
+ return (1);
+
+ ptrn = *ptrnp;
+ }
+
+ /*
+ * XXX
+ * Regcomp isn't 8-bit clean, so we just lost if the pattern
+ * contained a nul. Bummer!
+ */
+ if ((rval = regcomp(rep, ptrn, /* plen, */ reflags)) != 0) {
+ if (!LF_ISSET(RE_C_SILENT))
+ re_error(sp, rval, rep);
+ return (1);
+ }
+
+ if (LF_ISSET(RE_C_SEARCH))
+ F_SET(sp, SC_RE_SEARCH);
+ if (LF_ISSET(RE_C_SUBST))
+ F_SET(sp, SC_RE_SUBST);
+
+ return (0);
+}
+
+/*
+ * re_conv --
+ * Convert vi's regular expressions into something that the
+ * the POSIX 1003.2 RE functions can handle.
+ *
+ * There are three conversions we make to make vi's RE's (specifically
+ * the global, search, and substitute patterns) work with POSIX RE's.
+ *
+ * 1: If O_MAGIC is not set, strip backslashes from the magic character
+ * set (.[*~) that have them, and add them to the ones that don't.
+ * 2: If O_MAGIC is not set, the string "\~" is replaced with the text
+ * from the last substitute command's replacement string. If O_MAGIC
+ * is set, it's the string "~".
+ * 3: The pattern \<ptrn\> does "word" searches, convert it to use the
+ * new RE escapes.
+ *
+ * !!!/XXX
+ * This doesn't exactly match the historic behavior of vi because we do
+ * the ~ substitution before calling the RE engine, so magic characters
+ * in the replacement string will be expanded by the RE engine, and they
+ * weren't historically. It's a bug.
+ */
+static int
+re_conv(sp, ptrnp, plenp, replacedp)
+ SCR *sp;
+ char **ptrnp;
+ size_t *plenp;
+ int *replacedp;
+{
+ size_t blen, len, needlen;
+ int magic;
+ char *bp, *p, *t;
+
+ /*
+ * First pass through, we figure out how much space we'll need.
+ * We do it in two passes, on the grounds that most of the time
+ * the user is doing a search and won't have magic characters.
+ * That way we can skip most of the memory allocation and copies.
+ */
+ magic = 0;
+ for (p = *ptrnp, len = *plenp, needlen = 0; len > 0; ++p, --len)
+ switch (*p) {
+ case '\\':
+ if (len > 1) {
+ --len;
+ switch (*++p) {
+ case '<':
+ magic = 1;
+ needlen += sizeof(RE_WSTART);
+ break;
+ case '>':
+ magic = 1;
+ needlen += sizeof(RE_WSTOP);
+ break;
+ case '~':
+ if (!O_ISSET(sp, O_MAGIC)) {
+ magic = 1;
+ needlen += sp->repl_len;
+ }
+ break;
+ case '.':
+ case '[':
+ case '*':
+ if (!O_ISSET(sp, O_MAGIC)) {
+ magic = 1;
+ needlen += 1;
+ }
+ break;
+ default:
+ needlen += 2;
+ }
+ } else
+ needlen += 1;
+ break;
+ case '~':
+ if (O_ISSET(sp, O_MAGIC)) {
+ magic = 1;
+ needlen += sp->repl_len;
+ }
+ break;
+ case '.':
+ case '[':
+ case '*':
+ if (!O_ISSET(sp, O_MAGIC)) {
+ magic = 1;
+ needlen += 2;
+ }
+ break;
+ default:
+ needlen += 1;
+ break;
+ }
+
+ if (!magic) {
+ *replacedp = 0;
+ return (0);
+ }
+
+ /* Get enough memory to hold the final pattern. */
+ *replacedp = 1;
+ GET_SPACE_RET(sp, bp, blen, needlen);
+
+ for (p = *ptrnp, len = *plenp, t = bp; len > 0; ++p, --len)
+ switch (*p) {
+ case '\\':
+ if (len > 1) {
+ --len;
+ switch (*++p) {
+ case '<':
+ memcpy(t,
+ RE_WSTART, sizeof(RE_WSTART) - 1);
+ t += sizeof(RE_WSTART) - 1;
+ break;
+ case '>':
+ memcpy(t,
+ RE_WSTOP, sizeof(RE_WSTOP) - 1);
+ t += sizeof(RE_WSTOP) - 1;
+ break;
+ case '~':
+ if (O_ISSET(sp, O_MAGIC))
+ *t++ = '~';
+ else {
+ memcpy(t,
+ sp->repl, sp->repl_len);
+ t += sp->repl_len;
+ }
+ break;
+ case '.':
+ case '[':
+ case '*':
+ if (O_ISSET(sp, O_MAGIC))
+ *t++ = '\\';
+ *t++ = *p;
+ break;
+ default:
+ *t++ = '\\';
+ *t++ = *p;
+ }
+ } else
+ *t++ = '\\';
+ break;
+ case '~':
+ if (O_ISSET(sp, O_MAGIC)) {
+ memcpy(t, sp->repl, sp->repl_len);
+ t += sp->repl_len;
+ } else
+ *t++ = '~';
+ break;
+ case '.':
+ case '[':
+ case '*':
+ if (!O_ISSET(sp, O_MAGIC))
+ *t++ = '\\';
+ *t++ = *p;
+ break;
+ default:
+ *t++ = *p;
+ break;
+ }
+
+ *ptrnp = bp;
+ *plenp = t - bp;
+ return (0);
+}
+
+/*
+ * re_tag_conv --
+ * Convert a tags search path into something that the POSIX
+ * 1003.2 RE functions can handle.
+ */
+static int
+re_tag_conv(sp, ptrnp, plenp, replacedp)
+ SCR *sp;
+ char **ptrnp;
+ size_t *plenp;
+ int *replacedp;
+{
+ size_t blen, len;
+ int lastdollar;
+ char *bp, *p, *t;
+
+ len = *plenp;
+
+ /* Max memory usage is 2 times the length of the string. */
+ *replacedp = 1;
+ GET_SPACE_RET(sp, bp, blen, len * 2);
+
+ p = *ptrnp;
+ t = bp;
+
+ /* If the last character is a '/' or '?', we just strip it. */
+ if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?'))
+ --len;
+
+ /* If the next-to-last or last character is a '$', it's magic. */
+ if (len > 0 && p[len - 1] == '$') {
+ --len;
+ lastdollar = 1;
+ } else
+ lastdollar = 0;
+
+ /* If the first character is a '/' or '?', we just strip it. */
+ if (len > 0 && (p[0] == '/' || p[0] == '?')) {
+ ++p;
+ --len;
+ }
+
+ /* If the first or second character is a '^', it's magic. */
+ if (p[0] == '^') {
+ *t++ = *p++;
+ --len;
+ }
+
+ /*
+ * Escape every other magic character we can find, meanwhile stripping
+ * the backslashes ctags inserts when escaping the search delimiter
+ * characters.
+ */
+ for (; len > 0; --len) {
+ if (p[0] == '\\' && (p[1] == '/' || p[1] == '?')) {
+ ++p;
+ --len;
+ } else if (strchr("^.[]$*", p[0]))
+ *t++ = '\\';
+ *t++ = *p++;
+ }
+ if (lastdollar)
+ *t++ = '$';
+
+ *ptrnp = bp;
+ *plenp = t - bp;
+ return (0);
+}
+
+/*
+ * re_cscope_conv --
+ * Convert a cscope search path into something that the POSIX
+ * 1003.2 RE functions can handle.
+ */
+static int
+re_cscope_conv(sp, ptrnp, plenp, replacedp)
+ SCR *sp;
+ char **ptrnp;
+ size_t *plenp;
+ int *replacedp;
+{
+ size_t blen, len, nspaces;
+ char *bp, *p, *t;
+
+ /*
+ * Each space in the source line printed by cscope represents an
+ * arbitrary sequence of spaces, tabs, and comments.
+ */
+#define CSCOPE_RE_SPACE "([ \t]|/\\*([^*]|\\*/)*\\*/)*"
+ for (nspaces = 0, p = *ptrnp, len = *plenp; len > 0; ++p, --len)
+ if (*p == ' ')
+ ++nspaces;
+
+ /*
+ * Allocate plenty of space:
+ * the string, plus potential escaping characters;
+ * nspaces + 2 copies of CSCOPE_RE_SPACE;
+ * ^, $, nul terminator characters.
+ */
+ *replacedp = 1;
+ len = (p - *ptrnp) * 2 + (nspaces + 2) * sizeof(CSCOPE_RE_SPACE) + 3;
+ GET_SPACE_RET(sp, bp, blen, len);
+
+ p = *ptrnp;
+ t = bp;
+
+ *t++ = '^';
+ memcpy(t, CSCOPE_RE_SPACE, sizeof(CSCOPE_RE_SPACE) - 1);
+ t += sizeof(CSCOPE_RE_SPACE) - 1;
+
+ for (len = *plenp; len > 0; ++p, --len)
+ if (*p == ' ') {
+ memcpy(t, CSCOPE_RE_SPACE, sizeof(CSCOPE_RE_SPACE) - 1);
+ t += sizeof(CSCOPE_RE_SPACE) - 1;
+ } else {
+ if (strchr("\\^.[]$*+?()|{}", *p))
+ *t++ = '\\';
+ *t++ = *p;
+ }
+
+ memcpy(t, CSCOPE_RE_SPACE, sizeof(CSCOPE_RE_SPACE) - 1);
+ t += sizeof(CSCOPE_RE_SPACE) - 1;
+ *t++ = '$';
+
+ *ptrnp = bp;
+ *plenp = t - bp;
+ return (0);
+}
+
+/*
+ * re_error --
+ * Report a regular expression error.
+ *
+ * PUBLIC: void re_error __P((SCR *, int, regex_t *));
+ */
+void
+re_error(sp, errcode, preg)
+ SCR *sp;
+ int errcode;
+ regex_t *preg;
+{
+ size_t s;
+ char *oe;
+
+ s = regerror(errcode, preg, "", 0);
+ if ((oe = malloc(s)) == NULL)
+ msgq(sp, M_SYSERR, NULL);
+ else {
+ (void)regerror(errcode, preg, oe, s);
+ msgq(sp, M_ERR, "RE error: %s", oe);
+ free(oe);
+ }
+}
+
+/*
+ * re_sub --
+ * Do the substitution for a regular expression.
+ */
+static int
+re_sub(sp, ip, lbp, lbclenp, lblenp, match)
+ SCR *sp;
+ char *ip; /* Input line. */
+ char **lbp;
+ size_t *lbclenp, *lblenp;
+ regmatch_t match[10];
+{
+ enum { C_NOTSET, C_LOWER, C_ONELOWER, C_ONEUPPER, C_UPPER } conv;
+ size_t lbclen, lblen; /* Local copies. */
+ size_t mlen; /* Match length. */
+ size_t rpl; /* Remaining replacement length. */
+ char *rp; /* Replacement pointer. */
+ int ch;
+ int no; /* Match replacement offset. */
+ char *p, *t; /* Buffer pointers. */
+ char *lb; /* Local copies. */
+
+ lb = *lbp; /* Get local copies. */
+ lbclen = *lbclenp;
+ lblen = *lblenp;
+
+ /*
+ * QUOTING NOTE:
+ *
+ * There are some special sequences that vi provides in the
+ * replacement patterns.
+ * & string the RE matched (\& if nomagic set)
+ * \# n-th regular subexpression
+ * \E end \U, \L conversion
+ * \e end \U, \L conversion
+ * \l convert the next character to lower-case
+ * \L convert to lower-case, until \E, \e, or end of replacement
+ * \u convert the next character to upper-case
+ * \U convert to upper-case, until \E, \e, or end of replacement
+ *
+ * Otherwise, since this is the lowest level of replacement, discard
+ * all escaping characters. This (hopefully) matches historic practice.
+ */
+#define OUTCH(ch, nltrans) { \
+ CHAR_T __ch = (ch); \
+ u_int __value = KEY_VAL(sp, __ch); \
+ if (nltrans && (__value == K_CR || __value == K_NL)) { \
+ NEEDNEWLINE(sp); \
+ sp->newl[sp->newl_cnt++] = lbclen; \
+ } else if (conv != C_NOTSET) { \
+ switch (conv) { \
+ case C_ONELOWER: \
+ conv = C_NOTSET; \
+ /* FALLTHROUGH */ \
+ case C_LOWER: \
+ if (isupper(__ch)) \
+ __ch = tolower(__ch); \
+ break; \
+ case C_ONEUPPER: \
+ conv = C_NOTSET; \
+ /* FALLTHROUGH */ \
+ case C_UPPER: \
+ if (islower(__ch)) \
+ __ch = toupper(__ch); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } \
+ NEEDSP(sp, 1, p); \
+ *p++ = __ch; \
+ ++lbclen; \
+}
+ conv = C_NOTSET;
+ for (rp = sp->repl, rpl = sp->repl_len, p = lb + lbclen; rpl--;) {
+ switch (ch = *rp++) {
+ case '&':
+ if (O_ISSET(sp, O_MAGIC)) {
+ no = 0;
+ goto subzero;
+ }
+ break;
+ case '\\':
+ if (rpl == 0)
+ break;
+ --rpl;
+ switch (ch = *rp) {
+ case '&':
+ ++rp;
+ if (!O_ISSET(sp, O_MAGIC)) {
+ no = 0;
+ goto subzero;
+ }
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ no = *rp++ - '0';
+subzero: if (match[no].rm_so == -1 ||
+ match[no].rm_eo == -1)
+ break;
+ mlen = match[no].rm_eo - match[no].rm_so;
+ for (t = ip + match[no].rm_so; mlen--; ++t)
+ OUTCH(*t, 0);
+ continue;
+ case 'e':
+ case 'E':
+ ++rp;
+ conv = C_NOTSET;
+ continue;
+ case 'l':
+ ++rp;
+ conv = C_ONELOWER;
+ continue;
+ case 'L':
+ ++rp;
+ conv = C_LOWER;
+ continue;
+ case 'u':
+ ++rp;
+ conv = C_ONEUPPER;
+ continue;
+ case 'U':
+ ++rp;
+ conv = C_UPPER;
+ continue;
+ default:
+ ++rp;
+ break;
+ }
+ }
+ OUTCH(ch, 1);
+ }
+
+ *lbp = lb; /* Update caller's information. */
+ *lbclenp = lbclen;
+ *lblenp = lblen;
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_tag.c b/contrib/nvi/ex/ex_tag.c
new file mode 100644
index 0000000..461b152
--- /dev/null
+++ b/contrib/nvi/ex/ex_tag.c
@@ -0,0 +1,1324 @@
+/*-
+ * 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.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * David Hitz of Auspex Systems, Inc.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_tag.c 10.36 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h> /* XXX: param.h may not have included types.h */
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+#include "tag.h"
+
+static char *binary_search __P((char *, char *, char *));
+static int compare __P((char *, char *, char *));
+static void ctag_file __P((SCR *, TAGF *, char *, char **, size_t *));
+static int ctag_search __P((SCR *, char *, size_t, char *));
+static int ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *));
+static TAGQ *ctag_slist __P((SCR *, char *));
+static char *linear_search __P((char *, char *, char *));
+static int tag_copy __P((SCR *, TAG *, TAG **));
+static int tag_pop __P((SCR *, TAGQ *, int));
+static int tagf_copy __P((SCR *, TAGF *, TAGF **));
+static int tagf_free __P((SCR *, TAGF *));
+static int tagq_copy __P((SCR *, TAGQ *, TAGQ **));
+
+/*
+ * ex_tag_first --
+ * The tag code can be entered from main, e.g., "vi -t tag".
+ *
+ * PUBLIC: int ex_tag_first __P((SCR *, char *));
+ */
+int
+ex_tag_first(sp, tagarg)
+ SCR *sp;
+ char *tagarg;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ /* Build an argument for the ex :tag command. */
+ ex_cinit(&cmd, C_TAG, 0, OOBLNO, OOBLNO, 0, ap);
+ ex_cadd(&cmd, &a, tagarg, strlen(tagarg));
+
+ /*
+ * XXX
+ * Historic vi went ahead and created a temporary file when it failed
+ * to find the tag. We match historic practice, but don't distinguish
+ * between real error and failure to find the tag.
+ */
+ if (ex_tag_push(sp, &cmd))
+ return (0);
+
+ /* Display tags in the center of the screen. */
+ F_CLR(sp, SC_SCR_TOP);
+ F_SET(sp, SC_SCR_CENTER);
+
+ return (0);
+}
+
+/*
+ * ex_tag_push -- ^]
+ * :tag[!] [string]
+ *
+ * Enter a new TAGQ context based on a ctag string.
+ *
+ * PUBLIC: int ex_tag_push __P((SCR *, EXCMD *));
+ */
+int
+ex_tag_push(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ EX_PRIVATE *exp;
+ FREF *frp;
+ TAG *rtp;
+ TAGQ *rtqp, *tqp;
+ recno_t lno;
+ size_t cno;
+ long tl;
+ int force, istmp;
+
+ exp = EXP(sp);
+ switch (cmdp->argc) {
+ case 1:
+ if (exp->tag_last != NULL)
+ free(exp->tag_last);
+
+ if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+
+ /* Taglength may limit the number of characters. */
+ if ((tl =
+ O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tag_last) > tl)
+ exp->tag_last[tl] = '\0';
+ break;
+ case 0:
+ if (exp->tag_last == NULL) {
+ msgq(sp, M_ERR, "158|No previous tag entered");
+ return (1);
+ }
+ break;
+ default:
+ abort();
+ }
+
+ /* Get the tag information. */
+ if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)
+ return (1);
+
+ /*
+ * Allocate all necessary memory before swapping screens. Initialize
+ * flags so we know what to free.
+ */
+ rtp = NULL;
+ rtqp = NULL;
+ if (exp->tq.cqh_first == (void *)&exp->tq) {
+ /* Initialize the `local context' tag queue structure. */
+ CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ));
+ CIRCLEQ_INIT(&rtqp->tagq);
+
+ /* Initialize and link in its tag structure. */
+ CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG));
+ CIRCLEQ_INSERT_HEAD(&rtqp->tagq, rtp, q);
+ rtqp->current = rtp;
+ }
+
+ /*
+ * Stick the current context information in a convenient place, we're
+ * about to lose it. Note, if we're called on editor startup, there
+ * will be no FREF structure.
+ */
+ frp = sp->frp;
+ lno = sp->lno;
+ cno = sp->cno;
+ istmp = frp == NULL ||
+ F_ISSET(frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN);
+
+ /* Try to switch to the tag. */
+ force = FL_ISSET(cmdp->iflags, E_C_FORCE);
+ if (F_ISSET(cmdp, E_NEWSCREEN)) {
+ if (ex_tag_Nswitch(sp, tqp->tagq.cqh_first, force))
+ goto err;
+
+ /* Everything else gets done in the new screen. */
+ sp = sp->nextdisp;
+ exp = EXP(sp);
+ } else
+ if (ex_tag_nswitch(sp, tqp->tagq.cqh_first, force))
+ goto err;
+
+ /*
+ * If this is the first tag, put a `current location' queue entry
+ * in place, so we can pop all the way back to the current mark.
+ * Note, it doesn't point to much of anything, it's a placeholder.
+ */
+ if (exp->tq.cqh_first == (void *)&exp->tq) {
+ CIRCLEQ_INSERT_HEAD(&exp->tq, rtqp, q);
+ } else
+ rtqp = exp->tq.cqh_first;
+
+ /* Link the new TAGQ structure into place. */
+ CIRCLEQ_INSERT_HEAD(&exp->tq, tqp, q);
+
+ (void)ctag_search(sp,
+ tqp->current->search, tqp->current->slen, tqp->tag);
+
+ /*
+ * Move the current context from the temporary save area into the
+ * right structure.
+ *
+ * If we were in a temporary file, we don't have a context to which
+ * we can return, so just make it be the same as what we're moving
+ * to. It will be a little odd that ^T doesn't change anything, but
+ * I don't think it's a big deal.
+ */
+ if (istmp) {
+ rtqp->current->frp = sp->frp;
+ rtqp->current->lno = sp->lno;
+ rtqp->current->cno = sp->cno;
+ } else {
+ rtqp->current->frp = frp;
+ rtqp->current->lno = lno;
+ rtqp->current->cno = cno;
+ }
+ return (0);
+
+err:
+alloc_err:
+ if (rtqp != NULL)
+ free(rtqp);
+ if (rtp != NULL)
+ free(rtp);
+ tagq_free(sp, tqp);
+ return (1);
+}
+
+/*
+ * ex_tag_next --
+ * Switch context to the next TAG.
+ *
+ * PUBLIC: int ex_tag_next __P((SCR *, EXCMD *));
+ */
+int
+ex_tag_next(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ EX_PRIVATE *exp;
+ TAG *tp;
+ TAGQ *tqp;
+
+ exp = EXP(sp);
+ if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
+ tag_msg(sp, TAG_EMPTY, NULL);
+ return (1);
+ }
+ if ((tp = tqp->current->q.cqe_next) == (void *)&tqp->tagq) {
+ msgq(sp, M_ERR, "282|Already at the last tag of this group");
+ return (1);
+ }
+ if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
+ return (1);
+ tqp->current = tp;
+
+ if (F_ISSET(tqp, TAG_CSCOPE))
+ (void)cscope_search(sp, tqp, tp);
+ else
+ (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
+ return (0);
+}
+
+/*
+ * ex_tag_prev --
+ * Switch context to the next TAG.
+ *
+ * PUBLIC: int ex_tag_prev __P((SCR *, EXCMD *));
+ */
+int
+ex_tag_prev(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ EX_PRIVATE *exp;
+ TAG *tp;
+ TAGQ *tqp;
+
+ exp = EXP(sp);
+ if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
+ tag_msg(sp, TAG_EMPTY, NULL);
+ return (0);
+ }
+ if ((tp = tqp->current->q.cqe_prev) == (void *)&tqp->tagq) {
+ msgq(sp, M_ERR, "255|Already at the first tag of this group");
+ return (1);
+ }
+ if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
+ return (1);
+ tqp->current = tp;
+
+ if (F_ISSET(tqp, TAG_CSCOPE))
+ (void)cscope_search(sp, tqp, tp);
+ else
+ (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
+ return (0);
+}
+
+/*
+ * ex_tag_nswitch --
+ * Switch context to the specified TAG.
+ *
+ * PUBLIC: int ex_tag_nswitch __P((SCR *, TAG *, int));
+ */
+int
+ex_tag_nswitch(sp, tp, force)
+ SCR *sp;
+ TAG *tp;
+ int force;
+{
+ /* Get a file structure. */
+ if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
+ return (1);
+
+ /* If not changing files, return, we're done. */
+ if (tp->frp == sp->frp)
+ return (0);
+
+ /* Check for permission to leave. */
+ if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
+ return (1);
+
+ /* Initialize the new file. */
+ if (file_init(sp, tp->frp, NULL, FS_SETALT))
+ return (1);
+
+ /* Display tags in the center of the screen. */
+ F_CLR(sp, SC_SCR_TOP);
+ F_SET(sp, SC_SCR_CENTER);
+
+ /* Switch. */
+ F_SET(sp, SC_FSWITCH);
+ return (0);
+}
+
+/*
+ * ex_tag_Nswitch --
+ * Switch context to the specified TAG in a new screen.
+ *
+ * PUBLIC: int ex_tag_Nswitch __P((SCR *, TAG *, int));
+ */
+int
+ex_tag_Nswitch(sp, tp, force)
+ SCR *sp;
+ TAG *tp;
+ int force;
+{
+ SCR *new;
+
+ /* Get a file structure. */
+ if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
+ return (1);
+
+ /* Get a new screen. */
+ if (screen_init(sp->gp, sp, &new))
+ return (1);
+ if (vs_split(sp, new, 0)) {
+ (void)file_end(new, new->ep, 1);
+ (void)screen_end(new);
+ return (1);
+ }
+
+ /* Get a backing file. */
+ if (tp->frp == sp->frp) {
+ /* Copy file state. */
+ new->ep = sp->ep;
+ ++new->ep->refcnt;
+
+ new->frp = tp->frp;
+ new->frp->flags = sp->frp->flags;
+ } else if (file_init(new, tp->frp, NULL, force)) {
+ (void)vs_discard(new, NULL);
+ (void)screen_end(new);
+ return (1);
+ }
+
+ /* Create the argument list. */
+ new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name);
+
+ /* Display tags in the center of the screen. */
+ F_CLR(new, SC_SCR_TOP);
+ F_SET(new, SC_SCR_CENTER);
+
+ /* Switch. */
+ sp->nextdisp = new;
+ F_SET(sp, SC_SSWITCH);
+
+ return (0);
+}
+
+/*
+ * ex_tag_pop -- ^T
+ * :tagp[op][!] [number | file]
+ *
+ * Pop to a previous TAGQ context.
+ *
+ * PUBLIC: int ex_tag_pop __P((SCR *, EXCMD *));
+ */
+int
+ex_tag_pop(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ EX_PRIVATE *exp;
+ TAGQ *tqp, *dtqp;
+ size_t arglen;
+ long off;
+ char *arg, *p, *t;
+
+ /* Check for an empty stack. */
+ exp = EXP(sp);
+ if (exp->tq.cqh_first == (void *)&exp->tq) {
+ tag_msg(sp, TAG_EMPTY, NULL);
+ return (1);
+ }
+
+ /* Find the last TAG structure that we're going to DISCARD! */
+ switch (cmdp->argc) {
+ case 0: /* Pop one tag. */
+ dtqp = exp->tq.cqh_first;
+ break;
+ case 1: /* Name or number. */
+ arg = cmdp->argv[0]->bp;
+ off = strtol(arg, &p, 10);
+ if (*p != '\0')
+ goto filearg;
+
+ /* Number: pop that many queue entries. */
+ if (off < 1)
+ return (0);
+ for (tqp = exp->tq.cqh_first;
+ tqp != (void *)&exp->tq && --off > 1;
+ tqp = tqp->q.cqe_next);
+ if (tqp == (void *)&exp->tq) {
+ msgq(sp, M_ERR,
+ "159|Less than %s entries on the tags stack; use :display t[ags]",
+ arg);
+ return (1);
+ }
+ dtqp = tqp;
+ break;
+
+ /* File argument: pop to that queue entry. */
+filearg: arglen = strlen(arg);
+ for (tqp = exp->tq.cqh_first;
+ tqp != (void *)&exp->tq;
+ dtqp = tqp, tqp = tqp->q.cqe_next) {
+ /* Don't pop to the current file. */
+ if (tqp == exp->tq.cqh_first)
+ continue;
+ p = tqp->current->frp->name;
+ if ((t = strrchr(p, '/')) == NULL)
+ t = p;
+ else
+ ++t;
+ if (!strncmp(arg, t, arglen))
+ break;
+ }
+ if (tqp == (void *)&exp->tq) {
+ msgq_str(sp, M_ERR, arg,
+ "160|No file %s on the tags stack to return to; use :display t[ags]");
+ return (1);
+ }
+ if (tqp == exp->tq.cqh_first)
+ return (0);
+ break;
+ default:
+ abort();
+ }
+
+ return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));
+}
+
+/*
+ * ex_tag_top -- :tagt[op][!]
+ * Clear the tag stack.
+ *
+ * PUBLIC: int ex_tag_top __P((SCR *, EXCMD *));
+ */
+int
+ex_tag_top(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ EX_PRIVATE *exp;
+
+ exp = EXP(sp);
+
+ /* Check for an empty stack. */
+ if (exp->tq.cqh_first == (void *)&exp->tq) {
+ tag_msg(sp, TAG_EMPTY, NULL);
+ return (1);
+ }
+
+ /* Return to the oldest information. */
+ return (tag_pop(sp,
+ exp->tq.cqh_last->q.cqe_prev, FL_ISSET(cmdp->iflags, E_C_FORCE)));
+}
+
+/*
+ * tag_pop --
+ * Pop up to and including the specified TAGQ context.
+ */
+static int
+tag_pop(sp, dtqp, force)
+ SCR *sp;
+ TAGQ *dtqp;
+ int force;
+{
+ EX_PRIVATE *exp;
+ TAG *tp;
+ TAGQ *tqp;
+
+ exp = EXP(sp);
+
+ /*
+ * Update the cursor from the saved TAG information of the TAG
+ * structure we're moving to.
+ */
+ tp = dtqp->q.cqe_next->current;
+ if (tp->frp == sp->frp) {
+ sp->lno = tp->lno;
+ sp->cno = tp->cno;
+ } else {
+ if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
+ return (1);
+
+ tp->frp->lno = tp->lno;
+ tp->frp->cno = tp->cno;
+ F_SET(sp->frp, FR_CURSORSET);
+ if (file_init(sp, tp->frp, NULL, FS_SETALT))
+ return (1);
+
+ F_SET(sp, SC_FSWITCH);
+ }
+
+ /* Pop entries off the queue up to and including dtqp. */
+ do {
+ tqp = exp->tq.cqh_first;
+ if (tagq_free(sp, tqp))
+ return (0);
+ } while (tqp != dtqp);
+
+ /*
+ * If only a single tag left, we've returned to the first tag point,
+ * and the stack is now empty.
+ */
+ if (exp->tq.cqh_first->q.cqe_next == (void *)&exp->tq)
+ tagq_free(sp, exp->tq.cqh_first);
+
+ return (0);
+}
+
+/*
+ * ex_tag_display --
+ * Display the list of tags.
+ *
+ * PUBLIC: int ex_tag_display __P((SCR *));
+ */
+int
+ex_tag_display(sp)
+ SCR *sp;
+{
+ EX_PRIVATE *exp;
+ TAG *tp;
+ TAGQ *tqp;
+ int cnt;
+ size_t len;
+ char *p, *sep;
+
+ exp = EXP(sp);
+ if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
+ tag_msg(sp, TAG_EMPTY, NULL);
+ return (0);
+ }
+
+ /*
+ * We give the file name 20 columns and the search string the rest.
+ * If there's not enough room, we don't do anything special, it's
+ * not worth the effort, it just makes the display more confusing.
+ *
+ * We also assume that characters in file names map 1-1 to printing
+ * characters. This might not be true, but I don't think it's worth
+ * fixing. (The obvious fix is to pass the filenames through the
+ * msg_print function.)
+ */
+#define L_NAME 30 /* Name. */
+#define L_SLOP 4 /* Leading number plus trailing *. */
+#define L_SPACE 5 /* Spaces after name, before tag. */
+#define L_TAG 20 /* Tag. */
+ if (sp->cols <= L_NAME + L_SLOP) {
+ msgq(sp, M_ERR, "292|Display too small.");
+ return (0);
+ }
+
+ /*
+ * Display the list of tags for each queue entry. The first entry
+ * is numbered, and the current tag entry has an asterisk appended.
+ */
+ for (cnt = 1, tqp = exp->tq.cqh_first; !INTERRUPTED(sp) &&
+ tqp != (void *)&exp->tq; ++cnt, tqp = tqp->q.cqe_next)
+ for (tp = tqp->tagq.cqh_first;
+ tp != (void *)&tqp->tagq; tp = tp->q.cqe_next) {
+ if (tp == tqp->tagq.cqh_first)
+ (void)ex_printf(sp, "%2d ", cnt);
+ else
+ (void)ex_printf(sp, " ");
+ p = tp->frp == NULL ? tp->fname : tp->frp->name;
+ if ((len = strlen(p)) > L_NAME) {
+ len = len - (L_NAME - 4);
+ (void)ex_printf(sp, " ... %*.*s",
+ L_NAME - 4, L_NAME - 4, p + len);
+ } else
+ (void)ex_printf(sp,
+ " %*.*s", L_NAME, L_NAME, p);
+ if (tqp->current == tp)
+ (void)ex_printf(sp, "*");
+
+ if (tp == tqp->tagq.cqh_first && tqp->tag != NULL &&
+ (sp->cols - L_NAME) >= L_TAG + L_SPACE) {
+ len = strlen(tqp->tag);
+ if (len > sp->cols - (L_NAME + L_SPACE))
+ len = sp->cols - (L_NAME + L_SPACE);
+ (void)ex_printf(sp, "%s%.*s",
+ tqp->current == tp ? " " : " ",
+ (int)len, tqp->tag);
+ }
+ (void)ex_printf(sp, "\n");
+ }
+ return (0);
+}
+
+/*
+ * ex_tag_copy --
+ * Copy a screen's tag structures.
+ *
+ * PUBLIC: int ex_tag_copy __P((SCR *, SCR *));
+ */
+int
+ex_tag_copy(orig, sp)
+ SCR *orig, *sp;
+{
+ EX_PRIVATE *oexp, *nexp;
+ TAGQ *aqp, *tqp;
+ TAG *ap, *tp;
+ TAGF *atfp, *tfp;
+
+ oexp = EXP(orig);
+ nexp = EXP(sp);
+
+ /* Copy tag queue and tags stack. */
+ for (aqp = oexp->tq.cqh_first;
+ aqp != (void *)&oexp->tq; aqp = aqp->q.cqe_next) {
+ if (tagq_copy(sp, aqp, &tqp))
+ return (1);
+ for (ap = aqp->tagq.cqh_first;
+ ap != (void *)&aqp->tagq; ap = ap->q.cqe_next) {
+ if (tag_copy(sp, ap, &tp))
+ return (1);
+ /* Set the current pointer. */
+ if (aqp->current == ap)
+ tqp->current = tp;
+ CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
+ }
+ CIRCLEQ_INSERT_TAIL(&nexp->tq, tqp, q);
+ }
+
+ /* Copy list of tag files. */
+ for (atfp = oexp->tagfq.tqh_first;
+ atfp != NULL; atfp = atfp->q.tqe_next) {
+ if (tagf_copy(sp, atfp, &tfp))
+ return (1);
+ TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
+ }
+
+ /* Copy the last tag. */
+ if (oexp->tag_last != NULL &&
+ (nexp->tag_last = strdup(oexp->tag_last)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * tagf_copy --
+ * Copy a TAGF structure and return it in new memory.
+ */
+static int
+tagf_copy(sp, otfp, tfpp)
+ SCR *sp;
+ TAGF *otfp, **tfpp;
+{
+ TAGF *tfp;
+
+ MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
+ *tfp = *otfp;
+
+ /* XXX: Allocate as part of the TAGF structure!!! */
+ if ((tfp->name = strdup(otfp->name)) == NULL)
+ return (1);
+
+ *tfpp = tfp;
+ return (0);
+}
+
+/*
+ * tagq_copy --
+ * Copy a TAGQ structure and return it in new memory.
+ */
+static int
+tagq_copy(sp, otqp, tqpp)
+ SCR *sp;
+ TAGQ *otqp, **tqpp;
+{
+ TAGQ *tqp;
+ size_t len;
+
+ len = sizeof(TAGQ);
+ if (otqp->tag != NULL)
+ len += otqp->tlen + 1;
+ MALLOC_RET(sp, tqp, TAGQ *, len);
+ memcpy(tqp, otqp, len);
+
+ CIRCLEQ_INIT(&tqp->tagq);
+ tqp->current = NULL;
+ if (otqp->tag != NULL)
+ tqp->tag = tqp->buf;
+
+ *tqpp = tqp;
+ return (0);
+}
+
+/*
+ * tag_copy --
+ * Copy a TAG structure and return it in new memory.
+ */
+static int
+tag_copy(sp, otp, tpp)
+ SCR *sp;
+ TAG *otp, **tpp;
+{
+ TAG *tp;
+ size_t len;
+
+ len = sizeof(TAG);
+ if (otp->fname != NULL)
+ len += otp->fnlen + 1;
+ if (otp->search != NULL)
+ len += otp->slen + 1;
+ MALLOC_RET(sp, tp, TAG *, len);
+ memcpy(tp, otp, len);
+
+ if (otp->fname != NULL)
+ tp->fname = tp->buf;
+ if (otp->search != NULL)
+ tp->search = tp->fname + otp->fnlen + 1;
+
+ *tpp = tp;
+ return (0);
+}
+
+/*
+ * tagf_free --
+ * Free a TAGF structure.
+ */
+static int
+tagf_free(sp, tfp)
+ SCR *sp;
+ TAGF *tfp;
+{
+ EX_PRIVATE *exp;
+
+ exp = EXP(sp);
+ TAILQ_REMOVE(&exp->tagfq, tfp, q);
+ free(tfp->name);
+ free(tfp);
+ return (0);
+}
+
+/*
+ * tagq_free --
+ * Free a TAGQ structure (and associated TAG structures).
+ *
+ * PUBLIC: int tagq_free __P((SCR *, TAGQ *));
+ */
+int
+tagq_free(sp, tqp)
+ SCR *sp;
+ TAGQ *tqp;
+{
+ EX_PRIVATE *exp;
+ TAG *tp;
+
+ exp = EXP(sp);
+ while ((tp = tqp->tagq.cqh_first) != (void *)&tqp->tagq) {
+ CIRCLEQ_REMOVE(&tqp->tagq, tp, q);
+ free(tp);
+ }
+ /*
+ * !!!
+ * If allocated and then the user failed to switch files, the TAGQ
+ * structure was never attached to any list.
+ */
+ if (tqp->q.cqe_next != NULL)
+ CIRCLEQ_REMOVE(&exp->tq, tqp, q);
+ free(tqp);
+ return (0);
+}
+
+/*
+ * tag_msg
+ * A few common messages.
+ *
+ * PUBLIC: void tag_msg __P((SCR *, tagmsg_t, char *));
+ */
+void
+tag_msg(sp, msg, tag)
+ SCR *sp;
+ tagmsg_t msg;
+ char *tag;
+{
+ switch (msg) {
+ case TAG_BADLNO:
+ msgq_str(sp, M_ERR, tag,
+ "164|%s: the tag's line number is past the end of the file");
+ break;
+ case TAG_EMPTY:
+ msgq(sp, M_INFO, "165|The tags stack is empty");
+ break;
+ case TAG_SEARCH:
+ msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found");
+ break;
+ default:
+ abort();
+ }
+}
+
+/*
+ * ex_tagf_alloc --
+ * Create a new list of ctag files.
+ *
+ * PUBLIC: int ex_tagf_alloc __P((SCR *, char *));
+ */
+int
+ex_tagf_alloc(sp, str)
+ SCR *sp;
+ char *str;
+{
+ EX_PRIVATE *exp;
+ TAGF *tfp;
+ size_t len;
+ char *p, *t;
+
+ /* Free current queue. */
+ exp = EXP(sp);
+ while ((tfp = exp->tagfq.tqh_first) != NULL)
+ tagf_free(sp, tfp);
+
+ /* Create new queue. */
+ for (p = t = str;; ++p) {
+ if (*p == '\0' || isblank(*p)) {
+ if ((len = p - t) > 1) {
+ MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
+ MALLOC(sp, tfp->name, char *, len + 1);
+ if (tfp->name == NULL) {
+ free(tfp);
+ return (1);
+ }
+ memcpy(tfp->name, t, len);
+ tfp->name[len] = '\0';
+ tfp->flags = 0;
+ TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q);
+ }
+ t = p + 1;
+ }
+ if (*p == '\0')
+ break;
+ }
+ return (0);
+}
+ /* Free previous queue. */
+/*
+ * ex_tag_free --
+ * Free the ex tag information.
+ *
+ * PUBLIC: int ex_tag_free __P((SCR *));
+ */
+int
+ex_tag_free(sp)
+ SCR *sp;
+{
+ EX_PRIVATE *exp;
+ TAGF *tfp;
+ TAGQ *tqp;
+
+ /* Free up tag information. */
+ exp = EXP(sp);
+ while ((tqp = exp->tq.cqh_first) != (void *)&exp->tq)
+ tagq_free(sp, tqp);
+ while ((tfp = exp->tagfq.tqh_first) != NULL)
+ tagf_free(sp, tfp);
+ if (exp->tag_last != NULL)
+ free(exp->tag_last);
+ return (0);
+}
+
+/*
+ * ctag_search --
+ * Search a file for a tag.
+ */
+static int
+ctag_search(sp, search, slen, tag)
+ SCR *sp;
+ char *search, *tag;
+ size_t slen;
+{
+ MARK m;
+ char *p;
+
+ /*
+ * !!!
+ * The historic tags file format (from a long, long time ago...)
+ * used a line number, not a search string. I got complaints, so
+ * people are still using the format. POSIX 1003.2 permits it.
+ */
+ if (isdigit(search[0])) {
+ m.lno = atoi(search);
+ if (!db_exist(sp, m.lno)) {
+ tag_msg(sp, TAG_BADLNO, tag);
+ return (1);
+ }
+ } else {
+ /*
+ * Search for the tag; cheap fallback for C functions
+ * if the name is the same but the arguments have changed.
+ */
+ m.lno = 1;
+ m.cno = 0;
+ if (f_search(sp, &m, &m,
+ search, slen, NULL, SEARCH_FILE | SEARCH_TAG))
+ if ((p = strrchr(search, '(')) != NULL) {
+ slen = p - search;
+ if (f_search(sp, &m, &m, search, slen,
+ NULL, SEARCH_FILE | SEARCH_TAG))
+ goto notfound;
+ } else {
+notfound: tag_msg(sp, TAG_SEARCH, tag);
+ return (1);
+ }
+ /*
+ * !!!
+ * Historically, tags set the search direction if it wasn't
+ * already set.
+ */
+ if (sp->searchdir == NOTSET)
+ sp->searchdir = FORWARD;
+ }
+
+ /*
+ * !!!
+ * Tags move to the first non-blank, NOT the search pattern start.
+ */
+ sp->lno = m.lno;
+ sp->cno = 0;
+ (void)nonblank(sp, sp->lno, &sp->cno);
+ return (0);
+}
+
+/*
+ * ctag_slist --
+ * Search the list of tags files for a tag, and return tag queue.
+ */
+static TAGQ *
+ctag_slist(sp, tag)
+ SCR *sp;
+ char *tag;
+{
+ EX_PRIVATE *exp;
+ TAGF *tfp;
+ TAGQ *tqp;
+ size_t len;
+ int echk;
+
+ exp = EXP(sp);
+
+ /* Allocate and initialize the tag queue structure. */
+ len = strlen(tag);
+ CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
+ CIRCLEQ_INIT(&tqp->tagq);
+ tqp->tag = tqp->buf;
+ memcpy(tqp->tag, tag, (tqp->tlen = len) + 1);
+
+ /*
+ * Find the tag, only display missing file messages once, and
+ * then only if we didn't find the tag.
+ */
+ for (echk = 0,
+ tfp = exp->tagfq.tqh_first; tfp != NULL; tfp = tfp->q.tqe_next)
+ if (ctag_sfile(sp, tfp, tqp, tag)) {
+ echk = 1;
+ F_SET(tfp, TAGF_ERR);
+ } else
+ F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);
+
+ /* Check to see if we found anything. */
+ if (tqp->tagq.cqh_first == (void *)&tqp->tagq) {
+ msgq_str(sp, M_ERR, tag, "162|%s: tag not found");
+ if (echk)
+ for (tfp = exp->tagfq.tqh_first;
+ tfp != NULL; tfp = tfp->q.tqe_next)
+ if (F_ISSET(tfp, TAGF_ERR) &&
+ !F_ISSET(tfp, TAGF_ERR_WARN)) {
+ errno = tfp->errnum;
+ msgq_str(sp, M_SYSERR, tfp->name, "%s");
+ F_SET(tfp, TAGF_ERR_WARN);
+ }
+ free(tqp);
+ return (NULL);
+ }
+
+ tqp->current = tqp->tagq.cqh_first;
+ return (tqp);
+
+alloc_err:
+ return (NULL);
+}
+
+/*
+ * ctag_sfile --
+ * Search a tags file for a tag, adding any found to the tag queue.
+ */
+static int
+ctag_sfile(sp, tfp, tqp, tname)
+ SCR *sp;
+ TAGF *tfp;
+ TAGQ *tqp;
+ char *tname;
+{
+ struct stat sb;
+ TAG *tp;
+ size_t dlen, nlen, slen;
+ int fd, i, nf1, nf2;
+ char *back, *cname, *dname, *front, *map, *name, *p, *search, *t;
+
+ if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) {
+ tfp->errnum = errno;
+ return (1);
+ }
+
+ /*
+ * XXX
+ * Some old BSD systems require MAP_FILE as an argument when mapping
+ * regular files.
+ */
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+ /*
+ * XXX
+ * We'd like to test if the file is too big to mmap. 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 mmap fails if the file is too large.
+ */
+ if (fstat(fd, &sb) != 0 ||
+ (map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE,
+ MAP_FILE | MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
+ tfp->errnum = errno;
+ (void)close(fd);
+ return (1);
+ }
+
+ front = map;
+ back = front + sb.st_size;
+ front = binary_search(tname, front, back);
+ front = linear_search(tname, front, back);
+ if (front == NULL)
+ goto done;
+
+ /*
+ * Initialize and link in the tag structure(s). The historic ctags
+ * file format only permitted a single tag location per tag. The
+ * obvious extension to permit multiple tags locations per tag is to
+ * output multiple records in the standard format. Unfortunately,
+ * this won't work correctly with historic ex/vi implementations,
+ * because their binary search assumes that there's only one record
+ * per tag, and so will use a random tag entry if there si more than
+ * one. This code handles either format.
+ *
+ * The tags file is in the following format:
+ *
+ * <tag> <filename> <line number> | <pattern>
+ *
+ * Figure out how long everything is so we can allocate in one swell
+ * foop, but discard anything that looks wrong.
+ */
+ for (;;) {
+ /* Nul-terminate the end of the line. */
+ for (p = front; p < back && *p != '\n'; ++p);
+ if (p == back || *p != '\n')
+ break;
+ *p = '\0';
+
+ /* Update the pointers for the next time. */
+ t = p + 1;
+ p = front;
+ front = t;
+
+ /* Break the line into tokens. */
+ for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i)
+ switch (i) {
+ case 0: /* Tag. */
+ cname = t;
+ break;
+ case 1: /* Filename. */
+ name = t;
+ nlen = strlen(name);
+ break;
+ }
+
+ /* Check for corruption. */
+ if (i != 2 || p == NULL || t == NULL)
+ goto corrupt;
+
+ /* The rest of the string is the search pattern. */
+ search = p;
+ if ((slen = strlen(p)) == 0) {
+corrupt: p = msg_print(sp, tname, &nf1);
+ t = msg_print(sp, tfp->name, &nf2);
+ msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t);
+ if (nf1)
+ FREE_SPACE(sp, p, 0);
+ if (nf2)
+ FREE_SPACE(sp, t, 0);
+ continue;
+ }
+
+ /* Check for passing the last entry. */
+ if (strcmp(tname, cname))
+ break;
+
+ /* Resolve the file name. */
+ ctag_file(sp, tfp, name, &dname, &dlen);
+
+ CALLOC_GOTO(sp, tp,
+ TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1);
+ tp->fname = tp->buf;
+ if (dlen != 0) {
+ memcpy(tp->fname, dname, dlen);
+ tp->fname[dlen] = '/';
+ ++dlen;
+ }
+ memcpy(tp->fname + dlen, name, nlen + 1);
+ tp->fnlen = dlen + nlen;
+ tp->search = tp->fname + tp->fnlen + 1;
+ memcpy(tp->search, search, (tp->slen = slen) + 1);
+ CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
+ }
+
+alloc_err:
+done: if (munmap(map, (size_t)sb.st_size))
+ msgq(sp, M_SYSERR, "munmap");
+ if (close(fd))
+ msgq(sp, M_SYSERR, "close");
+ return (0);
+}
+
+/*
+ * ctag_file --
+ * Search for the right path to this file.
+ */
+static void
+ctag_file(sp, tfp, name, dirp, dlenp)
+ SCR *sp;
+ TAGF *tfp;
+ char *name, **dirp;
+ size_t *dlenp;
+{
+ struct stat sb;
+ size_t len;
+ char *p, buf[MAXPATHLEN];
+
+ /*
+ * !!!
+ * If the tag file path is a relative path, see if it exists. If it
+ * doesn't, look relative to the tags file path. It's okay for a tag
+ * file to not exist, and historically, vi simply displayed a "new"
+ * file. However, if the path exists relative to the tag file, it's
+ * pretty clear what's happening, so we may as well get it right.
+ */
+ *dlenp = 0;
+ if (name[0] != '/' &&
+ stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
+ *p = '\0';
+ len = snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name);
+ *p = '/';
+ if (stat(buf, &sb) == 0) {
+ *dirp = tfp->name;
+ *dlenp = strlen(*dirp);
+ }
+ }
+}
+
+/*
+ * Binary search for "string" in memory between "front" and "back".
+ *
+ * This routine is expected to return a pointer to the start of a line at
+ * *or before* the first word matching "string". Relaxing the constraint
+ * this way simplifies the algorithm.
+ *
+ * Invariants:
+ * front points to the beginning of a line at or before the first
+ * matching string.
+ *
+ * back points to the beginning of a line at or after the first
+ * matching line.
+ *
+ * Base of the Invariants.
+ * front = NULL;
+ * back = EOF;
+ *
+ * Advancing the Invariants:
+ *
+ * p = first newline after halfway point from front to back.
+ *
+ * If the string at "p" is not greater than the string to match,
+ * p is the new front. Otherwise it is the new back.
+ *
+ * Termination:
+ *
+ * The definition of the routine allows it return at any point,
+ * since front is always at or before the line to print.
+ *
+ * In fact, it returns when the chosen "p" equals "back". This
+ * implies that there exists a string is least half as long as
+ * (back - front), which in turn implies that a linear search will
+ * be no more expensive than the cost of simply printing a string or two.
+ *
+ * Trying to continue with binary search at this point would be
+ * more trouble than it's worth.
+ */
+#define EQUAL 0
+#define GREATER 1
+#define LESS (-1)
+
+#define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n');
+
+static char *
+binary_search(string, front, back)
+ register char *string, *front, *back;
+{
+ register char *p;
+
+ p = front + (back - front) / 2;
+ SKIP_PAST_NEWLINE(p, back);
+
+ while (p != back) {
+ if (compare(string, p, back) == GREATER)
+ front = p;
+ else
+ back = p;
+ p = front + (back - front) / 2;
+ SKIP_PAST_NEWLINE(p, back);
+ }
+ return (front);
+}
+
+/*
+ * Find the first line that starts with string, linearly searching from front
+ * to back.
+ *
+ * Return NULL for no such line.
+ *
+ * This routine assumes:
+ *
+ * o front points at the first character in a line.
+ * o front is before or at the first line to be printed.
+ */
+static char *
+linear_search(string, front, back)
+ char *string, *front, *back;
+{
+ while (front < back) {
+ switch (compare(string, front, back)) {
+ case EQUAL: /* Found it. */
+ return (front);
+ case LESS: /* No such string. */
+ return (NULL);
+ case GREATER: /* Keep going. */
+ break;
+ }
+ SKIP_PAST_NEWLINE(front, back);
+ }
+ return (NULL);
+}
+
+/*
+ * Return LESS, GREATER, or EQUAL depending on how the string1 compares
+ * with string2 (s1 ??? s2).
+ *
+ * o Matches up to len(s1) are EQUAL.
+ * o Matches up to len(s2) are GREATER.
+ *
+ * The string "s1" is null terminated. The string s2 is '\t', space, (or
+ * "back") terminated.
+ *
+ * !!!
+ * Reasonably modern ctags programs use tabs as separators, not spaces.
+ * However, historic programs did use spaces, and, I got complaints.
+ */
+static int
+compare(s1, s2, back)
+ register char *s1, *s2, *back;
+{
+ for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
+ if (*s1 != *s2)
+ return (*s1 < *s2 ? LESS : GREATER);
+ return (*s1 ? GREATER : s2 < back &&
+ (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);
+}
diff --git a/contrib/nvi/ex/ex_tcl.c b/contrib/nvi/ex/ex_tcl.c
new file mode 100644
index 0000000..06736a7
--- /dev/null
+++ b/contrib/nvi/ex/ex_tcl.c
@@ -0,0 +1,80 @@
+/*-
+ * 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.
+ * Copyright (c) 1995
+ * George V. Neville-Neil. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_tcl.c 8.10 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+#ifdef HAVE_TCL_INTERP
+#include <tcl.h>
+#endif
+
+/*
+ * ex_tcl -- :[line [,line]] tcl [command]
+ * Run a command through the tcl interpreter.
+ *
+ * PUBLIC: int ex_tcl __P((SCR*, EXCMD *));
+ */
+int
+ex_tcl(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+#ifdef HAVE_TCL_INTERP
+ CHAR_T *p;
+ GS *gp;
+ size_t len;
+ char buf[128];
+
+ /* Initialize the interpreter. */
+ gp = sp->gp;
+ if (gp->tcl_interp == NULL && tcl_init(gp))
+ return (1);
+
+ /* Skip leading white space. */
+ if (cmdp->argc != 0)
+ for (p = cmdp->argv[0]->bp,
+ len = cmdp->argv[0]->len; len > 0; --len, ++p)
+ if (!isblank(*p))
+ break;
+ if (cmdp->argc == 0 || len == 0) {
+ ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+
+ (void)snprintf(buf, sizeof(buf),
+ "set viScreenId %d\nset viStartLine %lu\nset viStopLine %lu",
+ sp->id, cmdp->addr1.lno, cmdp->addr2.lno);
+ if (Tcl_Eval(gp->tcl_interp, buf) == TCL_OK &&
+ Tcl_Eval(gp->tcl_interp, cmdp->argv[0]->bp) == TCL_OK)
+ return (0);
+
+ msgq(sp, M_ERR, "Tcl: %s", ((Tcl_Interp *)gp->tcl_interp)->result);
+ return (1);
+#else
+ msgq(sp, M_ERR, "302|Vi was not loaded with a Tcl interpreter");
+ return (1);
+#endif /* HAVE_TCL_INTERP */
+}
diff --git a/contrib/nvi/ex/ex_txt.c b/contrib/nvi/ex/ex_txt.c
new file mode 100644
index 0000000..2f62ff5
--- /dev/null
+++ b/contrib/nvi/ex/ex_txt.c
@@ -0,0 +1,430 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_txt.c 10.17 (Berkeley) 10/10/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * !!!
+ * The backslash characters was special when it preceded a newline as part of
+ * a substitution replacement pattern. For example, the input ":a\<cr>" would
+ * failed immediately with an error, as the <cr> wasn't part of a substitution
+ * replacement pattern. This implies a frightening integration of the editor
+ * and the parser and/or the RE engine. There's no way I'm going to reproduce
+ * those semantics.
+ *
+ * So, if backslashes are special, this code inserts the backslash and the next
+ * character into the string, without regard for the character or the command
+ * being entered. Since "\<cr>" was illegal historically (except for the one
+ * special case), and the command will fail eventually, no historical scripts
+ * should break (presuming they didn't depend on the failure mode itself or the
+ * characters remaining when failure occurred.
+ */
+
+static int txt_dent __P((SCR *, TEXT *));
+static void txt_prompt __P((SCR *, TEXT *, ARG_CHAR_T, u_int32_t));
+
+/*
+ * ex_txt --
+ * Get lines from the terminal for ex.
+ *
+ * PUBLIC: int ex_txt __P((SCR *, TEXTH *, ARG_CHAR_T, u_int32_t));
+ */
+int
+ex_txt(sp, tiqh, prompt, flags)
+ SCR *sp;
+ TEXTH *tiqh;
+ ARG_CHAR_T prompt;
+ u_int32_t flags;
+{
+ EVENT ev;
+ GS *gp;
+ TEXT ait, *ntp, *tp;
+ carat_t carat_st;
+ size_t cnt;
+ int rval;
+
+ rval = 0;
+
+ /*
+ * Get a TEXT structure with some initial buffer space, reusing the
+ * last one if it's big enough. (All TEXT bookkeeping fields default
+ * to 0 -- text_init() handles this.)
+ */
+ if (tiqh->cqh_first != (void *)tiqh) {
+ tp = tiqh->cqh_first;
+ if (tp->q.cqe_next != (void *)tiqh || tp->lb_len < 32) {
+ text_lfree(tiqh);
+ goto newtp;
+ }
+ tp->len = 0;
+ } else {
+newtp: if ((tp = text_init(sp, NULL, 0, 32)) == NULL)
+ goto err;
+ CIRCLEQ_INSERT_HEAD(tiqh, tp, q);
+ }
+
+ /* Set the starting line number. */
+ tp->lno = sp->lno + 1;
+
+ /*
+ * If it's a terminal, set up autoindent, put out the prompt, and
+ * set it up so we know we were suspended. Otherwise, turn off
+ * the autoindent flag, as that requires less special casing below.
+ *
+ * XXX
+ * Historic practice is that ^Z suspended command mode (but, because
+ * it ran in cooked mode, it was unaffected by the autowrite option.)
+ * On restart, any "current" input was discarded, whether in insert
+ * mode or not, and ex was in command mode. This code matches historic
+ * practice, but not 'cause it's easier.
+ */
+ gp = sp->gp;
+ if (F_ISSET(gp, G_SCRIPTED))
+ LF_CLR(TXT_AUTOINDENT);
+ else {
+ if (LF_ISSET(TXT_AUTOINDENT)) {
+ LF_SET(TXT_EOFCHAR);
+ if (v_txt_auto(sp, sp->lno, NULL, 0, tp))
+ goto err;
+ }
+ txt_prompt(sp, tp, prompt, flags);
+ }
+
+ for (carat_st = C_NOTSET;;) {
+ if (v_event_get(sp, &ev, 0, 0))
+ goto err;
+
+ /* Deal with all non-character events. */
+ switch (ev.e_event) {
+ case E_CHARACTER:
+ break;
+ case E_ERR:
+ goto err;
+ case E_REPAINT:
+ case E_WRESIZE:
+ continue;
+ case E_EOF:
+ rval = 1;
+ /* FALLTHROUGH */
+ case E_INTERRUPT:
+ /*
+ * Handle EOF/SIGINT events by discarding partially
+ * entered text and returning. EOF returns failure,
+ * E_INTERRUPT returns success.
+ */
+ goto notlast;
+ default:
+ v_event_err(sp, &ev);
+ goto notlast;
+ }
+
+ /*
+ * Deal with character events.
+ *
+ * Check to see if the character fits into the input buffer.
+ * (Use tp->len, ignore overwrite and non-printable chars.)
+ */
+ BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1);
+
+ switch (ev.e_value) {
+ case K_CR:
+ /*
+ * !!!
+ * Historically, <carriage-return>'s in the command
+ * weren't special, so the ex parser would return an
+ * unknown command error message. However, if they
+ * terminated the command if they were in a map. I'm
+ * pretty sure this still isn't right, but it handles
+ * what I've seen so far.
+ */
+ if (!F_ISSET(&ev.e_ch, CH_MAPPED))
+ goto ins_ch;
+ /* FALLTHROUGH */
+ case K_NL:
+ /*
+ * '\' can escape <carriage-return>/<newline>. We
+ * don't discard the backslash because we need it
+ * to get the <newline> through the ex parser.
+ */
+ if (LF_ISSET(TXT_BACKSLASH) &&
+ tp->len != 0 && tp->lb[tp->len - 1] == '\\')
+ goto ins_ch;
+
+ /*
+ * CR returns from the ex command line.
+ *
+ * XXX
+ * Terminate with a nul, needed by filter.
+ */
+ if (LF_ISSET(TXT_CR)) {
+ tp->lb[tp->len] = '\0';
+ goto done;
+ }
+
+ /*
+ * '.' may terminate text input mode; free the current
+ * TEXT.
+ */
+ if (LF_ISSET(TXT_DOTTERM) && tp->len == tp->ai + 1 &&
+ tp->lb[tp->len - 1] == '.') {
+notlast: CIRCLEQ_REMOVE(tiqh, tp, q);
+ text_free(tp);
+ goto done;
+ }
+
+ /* Set up bookkeeping for the new line. */
+ if ((ntp = text_init(sp, NULL, 0, 32)) == NULL)
+ goto err;
+ ntp->lno = tp->lno + 1;
+
+ /*
+ * Reset the autoindent line value. 0^D keeps the ai
+ * line from changing, ^D changes the level, even if
+ * there were no characters in the old line. Note, if
+ * using the current tp structure, use the cursor as
+ * the length, the autoindent characters may have been
+ * erased.
+ */
+ if (LF_ISSET(TXT_AUTOINDENT)) {
+ if (carat_st == C_NOCHANGE) {
+ if (v_txt_auto(sp,
+ OOBLNO, &ait, ait.ai, ntp))
+ goto err;
+ free(ait.lb);
+ } else
+ if (v_txt_auto(sp,
+ OOBLNO, tp, tp->len, ntp))
+ goto err;
+ carat_st = C_NOTSET;
+ }
+ txt_prompt(sp, ntp, prompt, flags);
+
+ /*
+ * Swap old and new TEXT's, and insert the new TEXT
+ * into the queue.
+ */
+ tp = ntp;
+ CIRCLEQ_INSERT_TAIL(tiqh, tp, q);
+ break;
+ case K_CARAT: /* Delete autoindent chars. */
+ if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT))
+ carat_st = C_CARATSET;
+ goto ins_ch;
+ case K_ZERO: /* Delete autoindent chars. */
+ if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT))
+ carat_st = C_ZEROSET;
+ goto ins_ch;
+ case K_CNTRLD: /* Delete autoindent char. */
+ /*
+ * !!!
+ * Historically, the ^D command took (but then ignored)
+ * a count. For simplicity, we don't return it unless
+ * it's the first character entered. The check for len
+ * equal to 0 is okay, TXT_AUTOINDENT won't be set.
+ */
+ if (LF_ISSET(TXT_CNTRLD)) {
+ for (cnt = 0; cnt < tp->len; ++cnt)
+ if (!isblank(tp->lb[cnt]))
+ break;
+ if (cnt == tp->len) {
+ tp->len = 1;
+ tp->lb[0] = ev.e_c;
+ tp->lb[1] = '\0';
+
+ /*
+ * Put out a line separator, in case
+ * the command fails.
+ */
+ (void)putchar('\n');
+ goto done;
+ }
+ }
+
+ /*
+ * POSIX 1003.1b-1993, paragraph 7.1.1.9, states that
+ * the EOF characters are discarded if there are other
+ * characters to process in the line, i.e. if the EOF
+ * is not the first character in the line. For this
+ * reason, historic ex discarded the EOF characters,
+ * even if occurring in the middle of the input line.
+ * We match that historic practice.
+ *
+ * !!!
+ * The test for discarding in the middle of the line is
+ * done in the switch, because the CARAT forms are N+1,
+ * not N.
+ *
+ * !!!
+ * There's considerable magic to make the terminal code
+ * return the EOF character at all. See that code for
+ * details.
+ */
+ if (!LF_ISSET(TXT_AUTOINDENT) || tp->len == 0)
+ continue;
+ switch (carat_st) {
+ case C_CARATSET: /* ^^D */
+ if (tp->len > tp->ai + 1)
+ continue;
+
+ /* Save the ai string for later. */
+ ait.lb = NULL;
+ ait.lb_len = 0;
+ BINC_GOTO(sp, ait.lb, ait.lb_len, tp->ai);
+ memcpy(ait.lb, tp->lb, tp->ai);
+ ait.ai = ait.len = tp->ai;
+
+ carat_st = C_NOCHANGE;
+ goto leftmargin;
+ case C_ZEROSET: /* 0^D */
+ if (tp->len > tp->ai + 1)
+ continue;
+
+ carat_st = C_NOTSET;
+leftmargin: (void)gp->scr_ex_adjust(sp, EX_TERM_CE);
+ tp->ai = tp->len = 0;
+ break;
+ case C_NOTSET: /* ^D */
+ if (tp->len > tp->ai)
+ continue;
+
+ if (txt_dent(sp, tp))
+ goto err;
+ break;
+ default:
+ abort();
+ }
+
+ /* Clear and redisplay the line. */
+ (void)gp->scr_ex_adjust(sp, EX_TERM_CE);
+ txt_prompt(sp, tp, prompt, flags);
+ break;
+ default:
+ /*
+ * See the TXT_BEAUTIFY comment in vi/v_txt_ev.c.
+ *
+ * Silently eliminate any iscntrl() character that was
+ * not already handled specially, except for <tab> and
+ * <ff>.
+ */
+ins_ch: if (LF_ISSET(TXT_BEAUTIFY) && iscntrl(ev.e_c) &&
+ ev.e_value != K_FORMFEED && ev.e_value != K_TAB)
+ break;
+
+ tp->lb[tp->len++] = ev.e_c;
+ break;
+ }
+ }
+ /* NOTREACHED */
+
+done: return (rval);
+
+err:
+alloc_err:
+ return (1);
+}
+
+/*
+ * txt_prompt --
+ * Display the ex prompt, line number, ai characters. Characters had
+ * better be printable by the terminal driver, but that's its problem,
+ * not ours.
+ */
+static void
+txt_prompt(sp, tp, prompt, flags)
+ SCR *sp;
+ TEXT *tp;
+ ARG_CHAR_T prompt;
+ u_int32_t flags;
+{
+ /* Display the prompt. */
+ if (LF_ISSET(TXT_PROMPT))
+ (void)printf("%c", prompt);
+
+ /* Display the line number. */
+ if (LF_ISSET(TXT_NUMBER) && O_ISSET(sp, O_NUMBER))
+ (void)printf("%6lu ", (u_long)tp->lno);
+
+ /* Print out autoindent string. */
+ if (LF_ISSET(TXT_AUTOINDENT))
+ (void)printf("%.*s", (int)tp->ai, tp->lb);
+ (void)fflush(stdout);
+}
+
+/*
+ * txt_dent --
+ * Handle ^D outdents.
+ *
+ * Ex version of vi/v_ntext.c:txt_dent(). See that code for the (usual)
+ * ranting and raving. This is a fair bit simpler as ^T isn't special.
+ */
+static int
+txt_dent(sp, tp)
+ SCR *sp;
+ TEXT *tp;
+{
+ u_long sw, ts;
+ size_t cno, off, scno, spaces, tabs;
+
+ ts = O_VAL(sp, O_TABSTOP);
+ sw = O_VAL(sp, O_SHIFTWIDTH);
+
+ /* Get the current screen column. */
+ for (off = scno = 0; off < tp->len; ++off)
+ if (tp->lb[off] == '\t')
+ scno += COL_OFF(scno, ts);
+ else
+ ++scno;
+
+ /* Get the previous shiftwidth column. */
+ cno = scno;
+ scno -= --scno % sw;
+
+ /*
+ * Since we don't know what comes before the character(s) being
+ * deleted, we have to resolve the autoindent characters . The
+ * example is a <tab>, which doesn't take up a full shiftwidth
+ * number of columns because it's preceded by <space>s. This is
+ * easy to get if the user sets shiftwidth to a value less than
+ * tabstop, and then uses ^T to indent, and ^D to outdent.
+ *
+ * Count up spaces/tabs needed to get to the target.
+ */
+ for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs)
+ cno += COL_OFF(cno, ts);
+ spaces = scno - cno;
+
+ /* Make sure there's enough room. */
+ BINC_RET(sp, tp->lb, tp->lb_len, tabs + spaces + 1);
+
+ /* Adjust the final ai character count. */
+ tp->ai = tabs + spaces;
+
+ /* Enter the replacement characters. */
+ for (tp->len = 0; tabs > 0; --tabs)
+ tp->lb[tp->len++] = '\t';
+ for (; spaces > 0; --spaces)
+ tp->lb[tp->len++] = ' ';
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_undo.c b/contrib/nvi/ex/ex_undo.c
new file mode 100644
index 0000000..0b0b5b2
--- /dev/null
+++ b/contrib/nvi/ex/ex_undo.c
@@ -0,0 +1,77 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_undo.c 10.6 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_undo -- u
+ * Undo the last change.
+ *
+ * PUBLIC: int ex_undo __P((SCR *, EXCMD *));
+ */
+int
+ex_undo(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ EXF *ep;
+ MARK m;
+
+ /*
+ * !!!
+ * Historic undo always set the previous context mark.
+ */
+ m.lno = sp->lno;
+ m.cno = sp->cno;
+ if (mark_set(sp, ABSMARK1, &m, 1))
+ return (1);
+
+ /*
+ * !!!
+ * Multiple undo isn't available in ex, as there's no '.' command.
+ * Whether 'u' is undo or redo is toggled each time, unless there
+ * was a change since the last undo, in which case it's an undo.
+ */
+ ep = sp->ep;
+ if (!F_ISSET(ep, F_UNDO)) {
+ F_SET(ep, F_UNDO);
+ ep->lundo = FORWARD;
+ }
+ switch (ep->lundo) {
+ case BACKWARD:
+ if (log_forward(sp, &m))
+ return (1);
+ ep->lundo = FORWARD;
+ break;
+ case FORWARD:
+ if (log_backward(sp, &m))
+ return (1);
+ ep->lundo = BACKWARD;
+ break;
+ case NOTSET:
+ abort();
+ }
+ sp->lno = m.lno;
+ sp->cno = m.cno;
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_usage.c b/contrib/nvi/ex/ex_usage.c
new file mode 100644
index 0000000..cddf7a6
--- /dev/null
+++ b/contrib/nvi/ex/ex_usage.c
@@ -0,0 +1,196 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_usage.c 10.13 (Berkeley) 5/3/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+/*
+ * ex_help -- :help
+ * Display help message.
+ *
+ * PUBLIC: int ex_help __P((SCR *, EXCMD *));
+ */
+int
+ex_help(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ (void)ex_puts(sp,
+ "To see the list of vi commands, enter \":viusage<CR>\"\n");
+ (void)ex_puts(sp,
+ "To see the list of ex commands, enter \":exusage<CR>\"\n");
+ (void)ex_puts(sp,
+ "For an ex command usage statement enter \":exusage [cmd]<CR>\"\n");
+ (void)ex_puts(sp,
+ "For a vi key usage statement enter \":viusage [key]<CR>\"\n");
+ (void)ex_puts(sp, "To exit, enter \":q!\"\n");
+ return (0);
+}
+
+/*
+ * ex_usage -- :exusage [cmd]
+ * Display ex usage strings.
+ *
+ * PUBLIC: int ex_usage __P((SCR *, EXCMD *));
+ */
+int
+ex_usage(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ ARGS *ap;
+ EXCMDLIST const *cp;
+ int newscreen;
+ char *name, *p, nb[MAXCMDNAMELEN + 5];
+
+ switch (cmdp->argc) {
+ case 1:
+ ap = cmdp->argv[0];
+ if (isupper(ap->bp[0])) {
+ newscreen = 1;
+ ap->bp[0] = tolower(ap->bp[0]);
+ } else
+ newscreen = 0;
+ for (cp = cmds; cp->name != NULL &&
+ memcmp(ap->bp, cp->name, ap->len); ++cp);
+ if (cp->name == NULL ||
+ newscreen && !F_ISSET(cp, E_NEWSCREEN)) {
+ if (newscreen)
+ ap->bp[0] = toupper(ap->bp[0]);
+ (void)ex_printf(sp, "The %.*s command is unknown\n",
+ (int)ap->len, ap->bp);
+ } else {
+ (void)ex_printf(sp,
+ "Command: %s\n Usage: %s\n", cp->help, cp->usage);
+ /*
+ * !!!
+ * The "visual" command has two modes, one from ex,
+ * one from the vi colon line. Don't ask.
+ */
+ if (cp != &cmds[C_VISUAL_EX] &&
+ cp != &cmds[C_VISUAL_VI])
+ break;
+ if (cp == &cmds[C_VISUAL_EX])
+ cp = &cmds[C_VISUAL_VI];
+ else
+ cp = &cmds[C_VISUAL_EX];
+ (void)ex_printf(sp,
+ "Command: %s\n Usage: %s\n", cp->help, cp->usage);
+ }
+ break;
+ case 0:
+ for (cp = cmds; cp->name != NULL && !INTERRUPTED(sp); ++cp) {
+ /*
+ * The ^D command has an unprintable name.
+ *
+ * XXX
+ * We display both capital and lower-case versions of
+ * the appropriate commands -- no need to add in extra
+ * room, they're all short names.
+ */
+ if (cp == &cmds[C_SCROLL])
+ name = "^D";
+ else if (F_ISSET(cp, E_NEWSCREEN)) {
+ nb[0] = '[';
+ nb[1] = toupper(cp->name[0]);
+ nb[2] = cp->name[0];
+ nb[3] = ']';
+ for (name = cp->name + 1,
+ p = nb + 4; (*p++ = *name++) != '\0';);
+ name = nb;
+ } else
+ name = cp->name;
+ (void)ex_printf(sp,
+ "%*s: %s\n", MAXCMDNAMELEN, name, cp->help);
+ }
+ break;
+ default:
+ abort();
+ }
+ return (0);
+}
+
+/*
+ * ex_viusage -- :viusage [key]
+ * Display vi usage strings.
+ *
+ * PUBLIC: int ex_viusage __P((SCR *, EXCMD *));
+ */
+int
+ex_viusage(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ GS *gp;
+ VIKEYS const *kp;
+ int key;
+
+ gp = sp->gp;
+ switch (cmdp->argc) {
+ case 1:
+ if (cmdp->argv[0]->len != 1) {
+ ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+ key = cmdp->argv[0]->bp[0];
+ if (key > MAXVIKEY)
+ goto nokey;
+
+ /* Special case: '[' and ']' commands. */
+ if ((key == '[' || key == ']') && cmdp->argv[0]->bp[1] != key)
+ goto nokey;
+
+ /* Special case: ~ command. */
+ if (key == '~' && O_ISSET(sp, O_TILDEOP))
+ kp = &tmotion;
+ else
+ kp = &vikeys[key];
+
+ if (kp->usage == NULL)
+nokey: (void)ex_printf(sp,
+ "The %s key has no current meaning\n",
+ KEY_NAME(sp, key));
+ else
+ (void)ex_printf(sp,
+ " Key:%s%s\nUsage: %s\n",
+ isblank(*kp->help) ? "" : " ", kp->help, kp->usage);
+ break;
+ case 0:
+ for (key = 0; key <= MAXVIKEY && !INTERRUPTED(sp); ++key) {
+ /* Special case: ~ command. */
+ if (key == '~' && O_ISSET(sp, O_TILDEOP))
+ kp = &tmotion;
+ else
+ kp = &vikeys[key];
+ if (kp->help != NULL)
+ (void)ex_printf(sp, "%s\n", kp->help);
+ }
+ break;
+ default:
+ abort();
+ }
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_util.c b/contrib/nvi/ex/ex_util.c
new file mode 100644
index 0000000..6c4772e
--- /dev/null
+++ b/contrib/nvi/ex/ex_util.c
@@ -0,0 +1,234 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_util.c 10.23 (Berkeley) 6/19/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_cinit --
+ * Create an EX command structure.
+ *
+ * PUBLIC: void ex_cinit __P((EXCMD *,
+ * PUBLIC: int, int, recno_t, recno_t, int, ARGS **));
+ */
+void
+ex_cinit(cmdp, cmd_id, naddr, lno1, lno2, force, ap)
+ EXCMD *cmdp;
+ int cmd_id, force, naddr;
+ recno_t lno1, lno2;
+ ARGS **ap;
+{
+ memset(cmdp, 0, sizeof(EXCMD));
+ cmdp->cmd = &cmds[cmd_id];
+ cmdp->addrcnt = naddr;
+ cmdp->addr1.lno = lno1;
+ cmdp->addr2.lno = lno2;
+ cmdp->addr1.cno = cmdp->addr2.cno = 1;
+ if (force)
+ cmdp->iflags |= E_C_FORCE;
+ cmdp->argc = 0;
+ if ((cmdp->argv = ap) != NULL)
+ cmdp->argv[0] = NULL;
+}
+
+/*
+ * ex_cadd --
+ * Add an argument to an EX command structure.
+ *
+ * PUBLIC: void ex_cadd __P((EXCMD *, ARGS *, char *, size_t));
+ */
+void
+ex_cadd(cmdp, ap, arg, len)
+ EXCMD *cmdp;
+ ARGS *ap;
+ char *arg;
+ size_t len;
+{
+ cmdp->argv[cmdp->argc] = ap;
+ ap->bp = arg;
+ ap->len = len;
+ cmdp->argv[++cmdp->argc] = NULL;
+}
+
+/*
+ * ex_getline --
+ * Return a line from the file.
+ *
+ * PUBLIC: int ex_getline __P((SCR *, FILE *, size_t *));
+ */
+int
+ex_getline(sp, fp, lenp)
+ SCR *sp;
+ FILE *fp;
+ size_t *lenp;
+{
+ EX_PRIVATE *exp;
+ size_t off;
+ int ch;
+ char *p;
+
+ exp = EXP(sp);
+ for (errno = 0, off = 0, p = exp->ibp;;) {
+ if (off >= exp->ibp_len) {
+ BINC_RET(sp, exp->ibp, exp->ibp_len, off + 1);
+ p = exp->ibp + off;
+ }
+ if ((ch = getc(fp)) == EOF && !feof(fp)) {
+ if (errno == EINTR) {
+ errno = 0;
+ clearerr(fp);
+ continue;
+ }
+ return (1);
+ }
+ if (ch == EOF || ch == '\n') {
+ if (ch == EOF && !off)
+ return (1);
+ *lenp = off;
+ return (0);
+ }
+ *p++ = ch;
+ ++off;
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * ex_ncheck --
+ * Check for more files to edit.
+ *
+ * PUBLIC: int ex_ncheck __P((SCR *, int));
+ */
+int
+ex_ncheck(sp, force)
+ SCR *sp;
+ int force;
+{
+ char **ap;
+
+ /*
+ * !!!
+ * Historic practice: quit! or two quit's done in succession
+ * (where ZZ counts as a quit) didn't check for other files.
+ */
+ if (!force && sp->ccnt != sp->q_ccnt + 1 &&
+ sp->cargv != NULL && sp->cargv[1] != NULL) {
+ sp->q_ccnt = sp->ccnt;
+
+ for (ap = sp->cargv + 1; *ap != NULL; ++ap);
+ msgq(sp, M_ERR,
+ "167|%d more files to edit", (ap - sp->cargv) - 1);
+
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * ex_init --
+ * Init the screen for ex.
+ *
+ * PUBLIC: int ex_init __P((SCR *));
+ */
+int
+ex_init(sp)
+ SCR *sp;
+{
+ GS *gp;
+
+ gp = sp->gp;
+
+ if (gp->scr_screen(sp, SC_EX))
+ return (1);
+ (void)gp->scr_attr(sp, SA_ALTERNATE, 0);
+
+ sp->rows = O_VAL(sp, O_LINES);
+ sp->cols = O_VAL(sp, O_COLUMNS);
+
+ F_CLR(sp, SC_VI);
+ F_SET(sp, SC_EX | SC_SCR_EX);
+ return (0);
+}
+
+/*
+ * ex_emsg --
+ * Display a few common ex and vi error messages.
+ *
+ * PUBLIC: void ex_emsg __P((SCR *, char *, exm_t));
+ */
+void
+ex_emsg(sp, p, which)
+ SCR *sp;
+ char *p;
+ exm_t which;
+{
+ switch (which) {
+ case EXM_EMPTYBUF:
+ msgq(sp, M_ERR, "168|Buffer %s is empty", p);
+ break;
+ case EXM_FILECOUNT:
+ msgq_str(sp, M_ERR, p,
+ "144|%s: expanded into too many file names");
+ break;
+ case EXM_NOCANON:
+ msgq(sp, M_ERR,
+ "283|The %s command requires the ex terminal interface", p);
+ break;
+ case EXM_NOCANON_F:
+ msgq(sp, M_ERR,
+ "272|That form of %s requires the ex terminal interface",
+ p);
+ break;
+ case EXM_NOFILEYET:
+ if (p == NULL)
+ msgq(sp, M_ERR,
+ "274|Command failed, no file read in yet.");
+ else
+ msgq(sp, M_ERR,
+ "173|The %s command requires that a file have already been read in", p);
+ break;
+ case EXM_NOPREVBUF:
+ msgq(sp, M_ERR, "171|No previous buffer to execute");
+ break;
+ case EXM_NOPREVRE:
+ msgq(sp, M_ERR, "172|No previous regular expression");
+ break;
+ case EXM_NOSUSPEND:
+ msgq(sp, M_ERR, "230|This screen may not be suspended");
+ break;
+ case EXM_SECURE:
+ msgq(sp, M_ERR,
+"290|The %s command is not supported when the secure edit option is set", p);
+ break;
+ case EXM_SECURE_F:
+ msgq(sp, M_ERR,
+"284|That form of %s is not supported when the secure edit option is set", p);
+ break;
+ case EXM_USAGE:
+ msgq(sp, M_ERR, "174|Usage: %s", p);
+ break;
+ }
+}
diff --git a/contrib/nvi/ex/ex_version.c b/contrib/nvi/ex/ex_version.c
new file mode 100644
index 0000000..d7363c8
--- /dev/null
+++ b/contrib/nvi/ex/ex_version.c
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1991, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_version.c 10.31 (Berkeley) 8/22/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+#include "version.h"
+
+/*
+ * ex_version -- :version
+ * Display the program version.
+ *
+ * PUBLIC: int ex_version __P((SCR *, EXCMD *));
+ */
+int
+ex_version(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ msgq(sp, M_INFO, VI_VERSION);
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_visual.c b/contrib/nvi/ex/ex_visual.c
new file mode 100644
index 0000000..82e503d
--- /dev/null
+++ b/contrib/nvi/ex/ex_visual.c
@@ -0,0 +1,161 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_visual.c 10.13 (Berkeley) 6/28/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "../vi/vi.h"
+
+/*
+ * ex_visual -- :[line] vi[sual] [^-.+] [window_size] [flags]
+ * Switch to visual mode.
+ *
+ * PUBLIC: int ex_visual __P((SCR *, EXCMD *));
+ */
+int
+ex_visual(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ SCR *tsp;
+ size_t len;
+ int pos;
+ char buf[256];
+
+ /* If open option off, disallow visual command. */
+ if (!O_ISSET(sp, O_OPEN)) {
+ msgq(sp, M_ERR,
+ "175|The visual command requires that the open option be set");
+ return (1);
+ }
+
+ /* Move to the address. */
+ sp->lno = cmdp->addr1.lno == 0 ? 1 : cmdp->addr1.lno;
+
+ /*
+ * Push a command based on the line position flags. If no
+ * flag specified, the line goes at the top of the screen.
+ */
+ switch (FL_ISSET(cmdp->iflags,
+ E_C_CARAT | E_C_DASH | E_C_DOT | E_C_PLUS)) {
+ case E_C_CARAT:
+ pos = '^';
+ break;
+ case E_C_DASH:
+ pos = '-';
+ break;
+ case E_C_DOT:
+ pos = '.';
+ break;
+ case E_C_PLUS:
+ pos = '+';
+ break;
+ default:
+ sp->frp->lno = sp->lno;
+ sp->frp->cno = 0;
+ (void)nonblank(sp, sp->lno, &sp->cno);
+ F_SET(sp->frp, FR_CURSORSET);
+ goto nopush;
+ }
+
+ if (FL_ISSET(cmdp->iflags, E_C_COUNT))
+ len = snprintf(buf, sizeof(buf),
+ "%luz%c%lu", sp->lno, pos, cmdp->count);
+ else
+ len = snprintf(buf, sizeof(buf), "%luz%c", sp->lno, pos);
+ (void)v_event_push(sp, NULL, buf, len, CH_NOMAP | CH_QUOTED);
+
+ /*
+ * !!!
+ * Historically, if no line address was specified, the [p#l] flags
+ * caused the cursor to be moved to the last line of the file, which
+ * was then positioned as described above. This seems useless, so
+ * I haven't implemented it.
+ */
+ switch (FL_ISSET(cmdp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)) {
+ case E_C_HASH:
+ O_SET(sp, O_NUMBER);
+ break;
+ case E_C_LIST:
+ O_SET(sp, O_LIST);
+ break;
+ case E_C_PRINT:
+ break;
+ }
+
+nopush: /*
+ * !!!
+ * You can call the visual part of the editor from within an ex
+ * global command.
+ *
+ * XXX
+ * Historically, undoing a visual session was a single undo command,
+ * i.e. you could undo all of the changes you made in visual mode.
+ * We don't get this right; I'm waiting for the new logging code to
+ * be available.
+ *
+ * It's explicit, don't have to wait for the user, unless there's
+ * already a reason to wait.
+ */
+ if (!F_ISSET(sp, SC_SCR_EXWROTE))
+ F_SET(sp, SC_EX_WAIT_NO);
+
+ if (F_ISSET(sp, SC_EX_GLOBAL)) {
+ /*
+ * When the vi screen(s) exit, we don't want to lose our hold
+ * on this screen or this file, otherwise we're going to fail
+ * fairly spectacularly.
+ */
+ ++sp->refcnt;
+ ++sp->ep->refcnt;
+
+ /*
+ * Fake up a screen pointer -- vi doesn't get to change our
+ * underlying file, regardless.
+ */
+ tsp = sp;
+ if (vi(&tsp))
+ return (1);
+
+ /*
+ * !!!
+ * Historically, if the user exited the vi screen(s) using an
+ * ex quit command (e.g. :wq, :q) ex/vi exited, it was only if
+ * they exited vi using the Q command that ex continued. Some
+ * early versions of nvi continued in ex regardless, but users
+ * didn't like the semantic.
+ *
+ * Reset the screen.
+ */
+ if (ex_init(sp))
+ return (1);
+
+ /* Move out of the vi screen. */
+ (void)ex_puts(sp, "\n");
+ } else {
+ F_CLR(sp, SC_EX | SC_SCR_EX);
+ F_SET(sp, SC_VI);
+ }
+ return (0);
+}
diff --git a/contrib/nvi/ex/ex_write.c b/contrib/nvi/ex/ex_write.c
new file mode 100644
index 0000000..b3122e3
--- /dev/null
+++ b/contrib/nvi/ex/ex_write.c
@@ -0,0 +1,375 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_write.c 10.30 (Berkeley) 7/12/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+enum which {WN, WQ, WRITE, XIT};
+static int exwr __P((SCR *, EXCMD *, enum which));
+
+/*
+ * ex_wn -- :wn[!] [>>] [file]
+ * Write to a file and switch to the next one.
+ *
+ * PUBLIC: int ex_wn __P((SCR *, EXCMD *));
+ */
+int
+ex_wn(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ if (exwr(sp, cmdp, WN))
+ return (1);
+ if (file_m3(sp, 0))
+ return (1);
+
+ /* The file name isn't a new file to edit. */
+ cmdp->argc = 0;
+
+ return (ex_next(sp, cmdp));
+}
+
+/*
+ * ex_wq -- :wq[!] [>>] [file]
+ * Write to a file and quit.
+ *
+ * PUBLIC: int ex_wq __P((SCR *, EXCMD *));
+ */
+int
+ex_wq(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ int force;
+
+ if (exwr(sp, cmdp, WQ))
+ return (1);
+ if (file_m3(sp, 0))
+ return (1);
+
+ force = FL_ISSET(cmdp->iflags, E_C_FORCE);
+
+ if (ex_ncheck(sp, force))
+ return (1);
+
+ F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT);
+ return (0);
+}
+
+/*
+ * ex_write -- :write[!] [>>] [file]
+ * :write [!] [cmd]
+ * Write to a file.
+ *
+ * PUBLIC: int ex_write __P((SCR *, EXCMD *));
+ */
+int
+ex_write(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ return (exwr(sp, cmdp, WRITE));
+}
+
+
+/*
+ * ex_xit -- :x[it]! [file]
+ * Write out any modifications and quit.
+ *
+ * PUBLIC: int ex_xit __P((SCR *, EXCMD *));
+ */
+int
+ex_xit(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ int force;
+
+ NEEDFILE(sp, cmdp);
+
+ if (F_ISSET(sp->ep, F_MODIFIED) && exwr(sp, cmdp, XIT))
+ return (1);
+ if (file_m3(sp, 0))
+ return (1);
+
+ force = FL_ISSET(cmdp->iflags, E_C_FORCE);
+
+ if (ex_ncheck(sp, force))
+ return (1);
+
+ F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT);
+ return (0);
+}
+
+/*
+ * exwr --
+ * The guts of the ex write commands.
+ */
+static int
+exwr(sp, cmdp, cmd)
+ SCR *sp;
+ EXCMD *cmdp;
+ enum which cmd;
+{
+ MARK rm;
+ int flags;
+ char *name, *p;
+
+ NEEDFILE(sp, cmdp);
+
+ /* All write commands can have an associated '!'. */
+ LF_INIT(FS_POSSIBLE);
+ if (FL_ISSET(cmdp->iflags, E_C_FORCE))
+ LF_SET(FS_FORCE);
+
+ /* Skip any leading whitespace. */
+ if (cmdp->argc != 0)
+ for (p = cmdp->argv[0]->bp; *p != '\0' && isblank(*p); ++p);
+
+ /* If "write !" it's a pipe to a utility. */
+ if (cmdp->argc != 0 && cmd == WRITE && *p == '!') {
+ /* Secure means no shell access. */
+ if (O_ISSET(sp, O_SECURE)) {
+ ex_emsg(sp, cmdp->cmd->name, EXM_SECURE_F);
+ return (1);
+ }
+
+ /* Expand the argument. */
+ for (++p; *p && isblank(*p); ++p);
+ if (*p == '\0') {
+ ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
+ return (1);
+ }
+ if (argv_exp1(sp, cmdp, p, strlen(p), 1))
+ return (1);
+
+ /*
+ * Historically, vi waited after a write filter even if there
+ * wasn't any output from the command. People complained when
+ * nvi waited only if there was output, wanting the visual cue
+ * that the program hadn't written anything.
+ */
+ F_SET(sp, SC_EX_WAIT_YES);
+
+ /*
+ * !!!
+ * Ignore the return cursor position, the cursor doesn't
+ * move.
+ */
+ if (ex_filter(sp, cmdp, &cmdp->addr1,
+ &cmdp->addr2, &rm, cmdp->argv[1]->bp, FILTER_WRITE))
+ return (1);
+
+ /* Ex terminates with a bang, even if the command fails. */
+ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT))
+ (void)ex_puts(sp, "!\n");
+
+ return (0);
+ }
+
+ /* Set the FS_ALL flag if we're writing the entire file. */
+ if (cmdp->addr1.lno <= 1 && !db_exist(sp, cmdp->addr2.lno + 1))
+ LF_SET(FS_ALL);
+
+ /* If "write >>" it's an append to a file. */
+ if (cmdp->argc != 0 && cmd != XIT && p[0] == '>' && p[1] == '>') {
+ LF_SET(FS_APPEND);
+
+ /* Skip ">>" and whitespace. */
+ for (p += 2; *p && isblank(*p); ++p);
+ }
+
+ /* If no other arguments, just write the file back. */
+ if (cmdp->argc == 0 || *p == '\0')
+ return (file_write(sp,
+ &cmdp->addr1, &cmdp->addr2, NULL, flags));
+
+ /* Build an argv so we get an argument count and file expansion. */
+ if (argv_exp2(sp, cmdp, p, strlen(p)))
+ return (1);
+
+ /*
+ * 0 args: impossible.
+ * 1 args: impossible (I hope).
+ * 2 args: read it.
+ * >2 args: object, too many args.
+ *
+ * The 1 args case depends on the argv_sexp() function refusing
+ * to return success without at least one non-blank character.
+ */
+ switch (cmdp->argc) {
+ case 0:
+ case 1:
+ abort();
+ /* NOTREACHED */
+ case 2:
+ name = cmdp->argv[1]->bp;
+
+ /*
+ * !!!
+ * Historically, the read and write commands renamed
+ * "unnamed" files, or, if the file had a name, set
+ * the alternate file name.
+ */
+ if (F_ISSET(sp->frp, FR_TMPFILE) &&
+ !F_ISSET(sp->frp, FR_EXNAMED)) {
+ if ((p = v_strdup(sp,
+ cmdp->argv[1]->bp, cmdp->argv[1]->len)) != NULL) {
+ free(sp->frp->name);
+ sp->frp->name = p;
+ }
+ /*
+ * The file has a real name, it's no longer a
+ * temporary, clear the temporary file flags.
+ *
+ * !!!
+ * If we're writing the whole file, FR_NAMECHANGE
+ * will be cleared by the write routine -- this is
+ * historic practice.
+ */
+ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE);
+ F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED);
+
+ /* Notify the screen. */
+ (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
+ } else
+ set_alt_name(sp, name);
+ break;
+ default:
+ ex_emsg(sp, p, EXM_FILECOUNT);
+ return (1);
+ }
+
+ return (file_write(sp, &cmdp->addr1, &cmdp->addr2, name, flags));
+}
+
+/*
+ * ex_writefp --
+ * Write a range of lines to a FILE *.
+ *
+ * PUBLIC: int ex_writefp __P((SCR *,
+ * PUBLIC: char *, FILE *, MARK *, MARK *, u_long *, u_long *, int));
+ */
+int
+ex_writefp(sp, name, fp, fm, tm, nlno, nch, silent)
+ SCR *sp;
+ char *name;
+ FILE *fp;
+ MARK *fm, *tm;
+ u_long *nlno, *nch;
+ int silent;
+{
+ struct stat sb;
+ GS *gp;
+ u_long ccnt; /* XXX: can't print off_t portably. */
+ recno_t fline, tline, lcnt;
+ size_t len;
+ int rval;
+ char *msg, *p;
+
+ gp = sp->gp;
+ fline = fm->lno;
+ tline = tm->lno;
+
+ if (nlno != NULL) {
+ *nch = 0;
+ *nlno = 0;
+ }
+
+ /*
+ * The vi filter code has multiple processes running simultaneously,
+ * and one of them calls ex_writefp(). The "unsafe" function calls
+ * in this code are to db_get() and msgq(). Db_get() is safe, see
+ * the comment in ex_filter.c:ex_filter() for details. We don't call
+ * msgq if the multiple process bit in the EXF is set.
+ *
+ * !!!
+ * Historic vi permitted files of 0 length to be written. However,
+ * since the way vi got around dealing with "empty" files was to
+ * always have a line in the file no matter what, it wrote them as
+ * files of a single, empty line. We write empty files.
+ *
+ * "Alex, I'll take vi trivia for $1000."
+ */
+ ccnt = 0;
+ lcnt = 0;
+ msg = "253|Writing...";
+ if (tline != 0)
+ for (; fline <= tline; ++fline, ++lcnt) {
+ /* Caller has to provide any interrupt message. */
+ if ((lcnt + 1) % INTERRUPT_CHECK == 0) {
+ if (INTERRUPTED(sp))
+ break;
+ if (!silent) {
+ gp->scr_busy(sp, msg, msg == NULL ?
+ BUSY_UPDATE : BUSY_ON);
+ msg = NULL;
+ }
+ }
+ if (db_get(sp, fline, DBG_FATAL, &p, &len))
+ goto err;
+ if (fwrite(p, 1, len, fp) != len)
+ goto err;
+ ccnt += len;
+ if (putc('\n', fp) != '\n')
+ break;
+ ++ccnt;
+ }
+
+ if (fflush(fp))
+ goto err;
+ /*
+ * XXX
+ * I don't trust NFS -- check to make sure that we're talking to
+ * a regular file and sync so that NFS is forced to flush.
+ */
+ if (!fstat(fileno(fp), &sb) &&
+ S_ISREG(sb.st_mode) && fsync(fileno(fp)))
+ goto err;
+
+ if (fclose(fp))
+ goto err;
+
+ rval = 0;
+ if (0) {
+err: if (!F_ISSET(sp->ep, F_MULTILOCK))
+ msgq_str(sp, M_SYSERR, name, "%s");
+ (void)fclose(fp);
+ rval = 1;
+ }
+
+ if (!silent)
+ gp->scr_busy(sp, NULL, BUSY_OFF);
+
+ /* Report the possibly partial transfer. */
+ if (nlno != NULL) {
+ *nch = ccnt;
+ *nlno = lcnt;
+ }
+ return (rval);
+}
diff --git a/contrib/nvi/ex/ex_yank.c b/contrib/nvi/ex/ex_yank.c
new file mode 100644
index 0000000..778dc7d
--- /dev/null
+++ b/contrib/nvi/ex/ex_yank.c
@@ -0,0 +1,46 @@
+/*-
+ * 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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_yank.c 10.7 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_yank -- :[line [,line]] ya[nk] [buffer] [count]
+ * Yank the lines into a buffer.
+ *
+ * PUBLIC: int ex_yank __P((SCR *, EXCMD *));
+ */
+int
+ex_yank(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ NEEDFILE(sp, cmdp);
+
+ /*
+ * !!!
+ * Historically, yanking lines in ex didn't count toward the
+ * number-of-lines-yanked report.
+ */
+ return (cut(sp,
+ FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL,
+ &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE));
+}
diff --git a/contrib/nvi/ex/ex_z.c b/contrib/nvi/ex/ex_z.c
new file mode 100644
index 0000000..41b72ad
--- /dev/null
+++ b/contrib/nvi/ex/ex_z.c
@@ -0,0 +1,150 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)ex_z.c 10.10 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+
+/*
+ * ex_z -- :[line] z [^-.+=] [count] [flags]
+ * Adjust window.
+ *
+ * PUBLIC: int ex_z __P((SCR *, EXCMD *));
+ */
+int
+ex_z(sp, cmdp)
+ SCR *sp;
+ EXCMD *cmdp;
+{
+ MARK abs;
+ recno_t cnt, equals, lno;
+ int eofcheck;
+
+ NEEDFILE(sp, cmdp);
+
+ /*
+ * !!!
+ * If no count specified, use either two times the size of the
+ * scrolling region, or the size of the window option. POSIX
+ * 1003.2 claims that the latter is correct, but historic ex/vi
+ * documentation and practice appear to use the scrolling region.
+ * I'm using the window size as it means that the entire screen
+ * is used instead of losing a line to roundoff. Note, we drop
+ * a line from the cnt if using the window size to leave room for
+ * the next ex prompt.
+ */
+ if (FL_ISSET(cmdp->iflags, E_C_COUNT))
+ cnt = cmdp->count;
+ else
+#ifdef HISTORIC_PRACTICE
+ cnt = O_VAL(sp, O_SCROLL) * 2;
+#else
+ cnt = O_VAL(sp, O_WINDOW) - 1;
+#endif
+
+ equals = 0;
+ eofcheck = 0;
+ lno = cmdp->addr1.lno;
+
+ switch (FL_ISSET(cmdp->iflags,
+ E_C_CARAT | E_C_DASH | E_C_DOT | E_C_EQUAL | E_C_PLUS)) {
+ case E_C_CARAT: /* Display cnt * 2 before the line. */
+ eofcheck = 1;
+ if (lno > cnt * 2)
+ cmdp->addr1.lno = (lno - cnt * 2) + 1;
+ else
+ cmdp->addr1.lno = 1;
+ cmdp->addr2.lno = (cmdp->addr1.lno + cnt) - 1;
+ break;
+ case E_C_DASH: /* Line goes at the bottom of the screen. */
+ cmdp->addr1.lno = lno > cnt ? (lno - cnt) + 1 : 1;
+ cmdp->addr2.lno = lno;
+ break;
+ case E_C_DOT: /* Line goes in the middle of the screen. */
+ /*
+ * !!!
+ * Historically, the "middleness" of the line overrode the
+ * count, so that "3z.19" or "3z.20" would display the first
+ * 12 lines of the file, i.e. (N - 1) / 2 lines before and
+ * after the specified line.
+ */
+ eofcheck = 1;
+ cnt = (cnt - 1) / 2;
+ cmdp->addr1.lno = lno > cnt ? lno - cnt : 1;
+ cmdp->addr2.lno = lno + cnt;
+
+ /*
+ * !!!
+ * Historically, z. set the absolute cursor mark.
+ */
+ abs.lno = sp->lno;
+ abs.cno = sp->cno;
+ (void)mark_set(sp, ABSMARK1, &abs, 1);
+ break;
+ case E_C_EQUAL: /* Center with hyphens. */
+ /*
+ * !!!
+ * Strangeness. The '=' flag is like the '.' flag (see the
+ * above comment, it applies here as well) but with a special
+ * little hack. Print out lines of hyphens before and after
+ * the specified line. Additionally, the cursor remains set
+ * on that line.
+ */
+ eofcheck = 1;
+ cnt = (cnt - 1) / 2;
+ cmdp->addr1.lno = lno > cnt ? lno - cnt : 1;
+ cmdp->addr2.lno = lno - 1;
+ if (ex_pr(sp, cmdp))
+ return (1);
+ (void)ex_puts(sp, "----------------------------------------\n");
+ cmdp->addr2.lno = cmdp->addr1.lno = equals = lno;
+ if (ex_pr(sp, cmdp))
+ return (1);
+ (void)ex_puts(sp, "----------------------------------------\n");
+ cmdp->addr1.lno = lno + 1;
+ cmdp->addr2.lno = (lno + cnt) - 1;
+ break;
+ default:
+ /* If no line specified, move to the next one. */
+ if (F_ISSET(cmdp, E_ADDR_DEF))
+ ++lno;
+ /* FALLTHROUGH */
+ case E_C_PLUS: /* Line goes at the top of the screen. */
+ eofcheck = 1;
+ cmdp->addr1.lno = lno;
+ cmdp->addr2.lno = (lno + cnt) - 1;
+ break;
+ }
+
+ if (eofcheck) {
+ if (db_last(sp, &lno))
+ return (1);
+ if (cmdp->addr2.lno > lno)
+ cmdp->addr2.lno = lno;
+ }
+
+ if (ex_pr(sp, cmdp))
+ return (1);
+ if (equals)
+ sp->lno = equals;
+ return (0);
+}
diff --git a/contrib/nvi/ex/script.h b/contrib/nvi/ex/script.h
new file mode 100644
index 0000000..e29f633
--- /dev/null
+++ b/contrib/nvi/ex/script.h
@@ -0,0 +1,23 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ *
+ * @(#)script.h 10.2 (Berkeley) 3/6/96
+ */
+
+struct _script {
+ pid_t sh_pid; /* Shell pid. */
+ int sh_master; /* Master pty fd. */
+ int sh_slave; /* Slave pty fd. */
+ char *sh_prompt; /* Prompt. */
+ size_t sh_prompt_len; /* Prompt length. */
+ char sh_name[64]; /* Pty name */
+#ifdef TIOCGWINSZ
+ struct winsize sh_win; /* Window size. */
+#endif
+ struct termios sh_term; /* Terminal information. */
+};
diff --git a/contrib/nvi/ex/tag.h b/contrib/nvi/ex/tag.h
new file mode 100644
index 0000000..aee3dd2
--- /dev/null
+++ b/contrib/nvi/ex/tag.h
@@ -0,0 +1,107 @@
+/*-
+ * 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.
+ * Copyright (c) 1994, 1996
+ * Rob Mayoff. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ *
+ * @(#)tag.h 10.5 (Berkeley) 5/15/96
+ */
+
+/*
+ * Cscope connection information. One of these is maintained per cscope
+ * connection, linked from the EX_PRIVATE structure.
+ */
+struct _csc {
+ LIST_ENTRY(_csc) q; /* Linked list of cscope connections. */
+
+ char *dname; /* Base directory of this cscope connection. */
+ size_t dlen; /* Length of base directory. */
+ pid_t pid; /* PID of the connected cscope process. */
+ time_t mtime; /* Last modification time of cscope database. */
+
+ FILE *from_fp; /* from cscope: FILE. */
+ int from_fd; /* from cscope: file descriptor. */
+ FILE *to_fp; /* to cscope: FILE. */
+ int to_fd; /* to cscope: file descriptor. */
+
+ char **paths; /* Array of search paths for this cscope. */
+ char *pbuf; /* Search path buffer. */
+ size_t pblen; /* Search path buffer length. */
+
+ char buf[1]; /* Variable length buffer. */
+};
+
+/*
+ * Tag file information. One of these is maintained per tag file, linked
+ * from the EXPRIVATE structure.
+ */
+struct _tagf { /* Tag files. */
+ TAILQ_ENTRY(_tagf) q; /* Linked list of tag files. */
+ char *name; /* Tag file name. */
+ int errnum; /* Errno. */
+
+#define TAGF_ERR 0x01 /* Error occurred. */
+#define TAGF_ERR_WARN 0x02 /* Error reported. */
+ u_int8_t flags;
+};
+
+/*
+ * Tags are structured internally as follows:
+ *
+ * +----+ +----+ +----+ +----+
+ * | EP | -> | Q1 | <-- | T1 | <-- | T2 |
+ * +----+ +----+ --> +----+ --> +----+
+ * |
+ * +----+ +----+
+ * | Q2 | <-- | T1 |
+ * +----+ --> +----+
+ * |
+ * +----+ +----+
+ * | Q3 | <-- | T1 |
+ * +----+ --> +----+
+ *
+ * Each Q is a TAGQ, or tag "query", which is the result of one tag or cscope
+ * command. Each Q references one or more TAG's, or tagged file locations.
+ *
+ * tag: put a new Q at the head (^])
+ * tagnext: T1 -> T2 inside Q (^N)
+ * tagprev: T2 -> T1 inside Q (^P)
+ * tagpop: discard Q (^T)
+ * tagtop: discard all Q
+ */
+struct _tag { /* Tag list. */
+ CIRCLEQ_ENTRY(_tag) q; /* Linked list of tags. */
+
+ /* Tag pop/return information. */
+ FREF *frp; /* Saved file. */
+ recno_t lno; /* Saved line number. */
+ size_t cno; /* Saved column number. */
+
+ char *fname; /* Filename. */
+ size_t fnlen; /* Filename length. */
+ recno_t slno; /* Search line number. */
+ char *search; /* Search string. */
+ size_t slen; /* Search string length. */
+
+ char buf[1]; /* Variable length buffer. */
+};
+
+struct _tagq { /* Tag queue. */
+ CIRCLEQ_ENTRY(_tagq) q; /* Linked list of tag queues. */
+ /* This queue's tag list. */
+ CIRCLEQ_HEAD(_tagqh, _tag) tagq;
+
+ TAG *current; /* Current TAG within the queue. */
+
+ char *tag; /* Tag string. */
+ size_t tlen; /* Tag string length. */
+
+#define TAG_CSCOPE 0x01 /* Cscope tag. */
+ u_int8_t flags;
+
+ char buf[1]; /* Variable length buffer. */
+};
diff --git a/contrib/nvi/ex/version.h b/contrib/nvi/ex/version.h
new file mode 100644
index 0000000..7d657b6
--- /dev/null
+++ b/contrib/nvi/ex/version.h
@@ -0,0 +1,2 @@
+#define VI_VERSION \
+ "Version 1.79 (10/23/96) The CSRG, University of California, Berkeley."
OpenPOWER on IntegriCloud