summaryrefslogtreecommitdiffstats
path: root/contrib/nvi/vi
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/vi
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/vi')
-rw-r--r--contrib/nvi/vi/getc.c234
-rw-r--r--contrib/nvi/vi/v_at.c109
-rw-r--r--contrib/nvi/vi/v_ch.c295
-rw-r--r--contrib/nvi/vi/v_cmd.c505
-rw-r--r--contrib/nvi/vi/v_delete.c107
-rw-r--r--contrib/nvi/vi/v_ex.c696
-rw-r--r--contrib/nvi/vi/v_increment.c267
-rw-r--r--contrib/nvi/vi/v_init.c126
-rw-r--r--contrib/nvi/vi/v_itxt.c537
-rw-r--r--contrib/nvi/vi/v_left.c293
-rw-r--r--contrib/nvi/vi/v_mark.c234
-rw-r--r--contrib/nvi/vi/v_match.c170
-rw-r--r--contrib/nvi/vi/v_paragraph.c344
-rw-r--r--contrib/nvi/vi/v_put.c146
-rw-r--r--contrib/nvi/vi/v_redraw.c39
-rw-r--r--contrib/nvi/vi/v_replace.c203
-rw-r--r--contrib/nvi/vi/v_right.c145
-rw-r--r--contrib/nvi/vi/v_screen.c65
-rw-r--r--contrib/nvi/vi/v_scroll.c474
-rw-r--r--contrib/nvi/vi/v_search.c515
-rw-r--r--contrib/nvi/vi/v_section.c252
-rw-r--r--contrib/nvi/vi/v_sentence.c359
-rw-r--r--contrib/nvi/vi/v_status.c41
-rw-r--r--contrib/nvi/vi/v_txt.c2950
-rw-r--r--contrib/nvi/vi/v_ulcase.c179
-rw-r--r--contrib/nvi/vi/v_undo.c139
-rw-r--r--contrib/nvi/vi/v_util.c180
-rw-r--r--contrib/nvi/vi/v_word.c547
-rw-r--r--contrib/nvi/vi/v_xchar.c107
-rw-r--r--contrib/nvi/vi/v_yank.c82
-rw-r--r--contrib/nvi/vi/v_z.c149
-rw-r--r--contrib/nvi/vi/v_zexit.c54
-rw-r--r--contrib/nvi/vi/vi.c1251
-rw-r--r--contrib/nvi/vi/vi.h377
-rw-r--r--contrib/nvi/vi/vs_line.c514
-rw-r--r--contrib/nvi/vi/vs_msg.c927
-rw-r--r--contrib/nvi/vi/vs_refresh.c885
-rw-r--r--contrib/nvi/vi/vs_relative.c305
-rw-r--r--contrib/nvi/vi/vs_smap.c1260
-rw-r--r--contrib/nvi/vi/vs_split.c607
40 files changed, 16669 insertions, 0 deletions
diff --git a/contrib/nvi/vi/getc.c b/contrib/nvi/vi/getc.c
new file mode 100644
index 0000000..5e60ade
--- /dev/null
+++ b/contrib/nvi/vi/getc.c
@@ -0,0 +1,234 @@
+/*-
+ * 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[] = "@(#)getc.c 10.10 (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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * Character stream routines --
+ * These routines return the file a character at a time. There are two
+ * special cases. First, the end of a line, end of a file, start of a
+ * file and empty lines are returned as special cases, and no character
+ * is returned. Second, empty lines include lines that have only white
+ * space in them, because the vi search functions don't care about white
+ * space, and this makes it easier for them to be consistent.
+ */
+
+/*
+ * cs_init --
+ * Initialize character stream routines.
+ *
+ * PUBLIC: int cs_init __P((SCR *, VCS *));
+ */
+int
+cs_init(sp, csp)
+ SCR *sp;
+ VCS *csp;
+{
+ int isempty;
+
+ if (db_eget(sp, csp->cs_lno, &csp->cs_bp, &csp->cs_len, &isempty)) {
+ if (isempty)
+ msgq(sp, M_BERR, "177|Empty file");
+ return (1);
+ }
+ if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) {
+ csp->cs_cno = 0;
+ csp->cs_flags = CS_EMP;
+ } else {
+ csp->cs_flags = 0;
+ csp->cs_ch = csp->cs_bp[csp->cs_cno];
+ }
+ return (0);
+}
+
+/*
+ * cs_next --
+ * Retrieve the next character.
+ *
+ * PUBLIC: int cs_next __P((SCR *, VCS *));
+ */
+int
+cs_next(sp, csp)
+ SCR *sp;
+ VCS *csp;
+{
+ char *p;
+
+ switch (csp->cs_flags) {
+ case CS_EMP: /* EMP; get next line. */
+ case CS_EOL: /* EOL; get next line. */
+ if (db_get(sp, ++csp->cs_lno, 0, &p, &csp->cs_len)) {
+ --csp->cs_lno;
+ csp->cs_flags = CS_EOF;
+ } else {
+ csp->cs_bp = p;
+ if (csp->cs_len == 0 ||
+ v_isempty(csp->cs_bp, csp->cs_len)) {
+ csp->cs_cno = 0;
+ csp->cs_flags = CS_EMP;
+ } else {
+ csp->cs_flags = 0;
+ csp->cs_ch = csp->cs_bp[csp->cs_cno = 0];
+ }
+ }
+ break;
+ case 0:
+ if (csp->cs_cno == csp->cs_len - 1)
+ csp->cs_flags = CS_EOL;
+ else
+ csp->cs_ch = csp->cs_bp[++csp->cs_cno];
+ break;
+ case CS_EOF: /* EOF. */
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+ return (0);
+}
+
+/*
+ * cs_fspace --
+ * If on a space, eat forward until something other than a
+ * whitespace character.
+ *
+ * XXX
+ * Semantics of checking the current character were coded for the fword()
+ * function -- once the other word routines are converted, they may have
+ * to change.
+ *
+ * PUBLIC: int cs_fspace __P((SCR *, VCS *));
+ */
+int
+cs_fspace(sp, csp)
+ SCR *sp;
+ VCS *csp;
+{
+ if (csp->cs_flags != 0 || !isblank(csp->cs_ch))
+ return (0);
+ for (;;) {
+ if (cs_next(sp, csp))
+ return (1);
+ if (csp->cs_flags != 0 || !isblank(csp->cs_ch))
+ break;
+ }
+ return (0);
+}
+
+/*
+ * cs_fblank --
+ * Eat forward to the next non-whitespace character.
+ *
+ * PUBLIC: int cs_fblank __P((SCR *, VCS *));
+ */
+int
+cs_fblank(sp, csp)
+ SCR *sp;
+ VCS *csp;
+{
+ for (;;) {
+ if (cs_next(sp, csp))
+ return (1);
+ if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP ||
+ csp->cs_flags == 0 && isblank(csp->cs_ch))
+ continue;
+ break;
+ }
+ return (0);
+}
+
+/*
+ * cs_prev --
+ * Retrieve the previous character.
+ *
+ * PUBLIC: int cs_prev __P((SCR *, VCS *));
+ */
+int
+cs_prev(sp, csp)
+ SCR *sp;
+ VCS *csp;
+{
+ switch (csp->cs_flags) {
+ case CS_EMP: /* EMP; get previous line. */
+ case CS_EOL: /* EOL; get previous line. */
+ if (csp->cs_lno == 1) { /* SOF. */
+ csp->cs_flags = CS_SOF;
+ break;
+ }
+ if (db_get(sp, /* The line should exist. */
+ --csp->cs_lno, DBG_FATAL, &csp->cs_bp, &csp->cs_len)) {
+ ++csp->cs_lno;
+ return (1);
+ }
+ if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) {
+ csp->cs_cno = 0;
+ csp->cs_flags = CS_EMP;
+ } else {
+ csp->cs_flags = 0;
+ csp->cs_cno = csp->cs_len - 1;
+ csp->cs_ch = csp->cs_bp[csp->cs_cno];
+ }
+ break;
+ case CS_EOF: /* EOF: get previous char. */
+ case 0:
+ if (csp->cs_cno == 0)
+ if (csp->cs_lno == 1)
+ csp->cs_flags = CS_SOF;
+ else
+ csp->cs_flags = CS_EOL;
+ else
+ csp->cs_ch = csp->cs_bp[--csp->cs_cno];
+ break;
+ case CS_SOF: /* SOF. */
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+ return (0);
+}
+
+/*
+ * cs_bblank --
+ * Eat backward to the next non-whitespace character.
+ *
+ * PUBLIC: int cs_bblank __P((SCR *, VCS *));
+ */
+int
+cs_bblank(sp, csp)
+ SCR *sp;
+ VCS *csp;
+{
+ for (;;) {
+ if (cs_prev(sp, csp))
+ return (1);
+ if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP ||
+ csp->cs_flags == 0 && isblank(csp->cs_ch))
+ continue;
+ break;
+ }
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_at.c b/contrib/nvi/vi/v_at.c
new file mode 100644
index 0000000..d266f27
--- /dev/null
+++ b/contrib/nvi/vi/v_at.c
@@ -0,0 +1,109 @@
+/*-
+ * 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[] = "@(#)v_at.c 10.8 (Berkeley) 4/27/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_at -- @
+ * Execute a buffer.
+ *
+ * PUBLIC: int v_at __P((SCR *, VICMD *));
+ */
+int
+v_at(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ CB *cbp;
+ CHAR_T name;
+ TEXT *tp;
+ size_t len;
+ char nbuf[20];
+
+ /*
+ * !!!
+ * Historically, [@*]<carriage-return> and [@*][@*] executed the most
+ * recently executed buffer in ex mode. In vi mode, only @@ repeated
+ * the last buffer. We change historic practice and make @* work from
+ * vi mode as well, it's simpler and more consistent.
+ *
+ * My intent is that *[buffer] will, in the future, pass the buffer to
+ * whatever interpreter is loaded.
+ */
+ name = F_ISSET(vp, VC_BUFFER) ? vp->buffer : '@';
+ if (name == '@' || name == '*') {
+ if (!F_ISSET(sp, SC_AT_SET)) {
+ ex_emsg(sp, NULL, EXM_NOPREVBUF);
+ return (1);
+ }
+ name = sp->at_lbuf;
+ }
+ F_SET(sp, SC_AT_SET);
+
+ CBNAME(sp, cbp, name);
+ if (cbp == NULL) {
+ ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF);
+ return (1);
+ }
+
+ /* Save for reuse. */
+ sp->at_lbuf = name;
+
+ /*
+ * The buffer is executed in vi mode, while in vi mode, so simply
+ * push it onto the terminal queue and continue.
+ *
+ * !!!
+ * Historic practice is that if the buffer was cut in line mode,
+ * <newlines> were appended to each line as it was pushed onto
+ * the stack. If the buffer was cut in character mode, <newlines>
+ * were appended to all lines but the last one.
+ *
+ * XXX
+ * Historic practice is that execution of an @ buffer could be
+ * undone by a single 'u' command, i.e. the changes were grouped
+ * together. We don't get this right; I'm waiting for the new DB
+ * logging code to be available.
+ */
+ for (tp = cbp->textq.cqh_last;
+ tp != (void *)&cbp->textq; tp = tp->q.cqe_prev)
+ if ((F_ISSET(cbp, CB_LMODE) ||
+ tp->q.cqe_next != (void *)&cbp->textq) &&
+ v_event_push(sp, NULL, "\n", 1, 0) ||
+ v_event_push(sp, NULL, tp->lb, tp->len, 0))
+ return (1);
+
+ /*
+ * !!!
+ * If any count was supplied, it applies to the first command in the
+ * at buffer.
+ */
+ if (F_ISSET(vp, VC_C1SET)) {
+ len = snprintf(nbuf, sizeof(nbuf), "%lu", vp->count);
+ if (v_event_push(sp, NULL, nbuf, len, 0))
+ return (1);
+ }
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_ch.c b/contrib/nvi/vi/v_ch.c
new file mode 100644
index 0000000..6a1b611
--- /dev/null
+++ b/contrib/nvi/vi/v_ch.c
@@ -0,0 +1,295 @@
+/*-
+ * 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[] = "@(#)v_ch.c 10.8 (Berkeley) 3/6/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 "../common/common.h"
+#include "vi.h"
+
+static void notfound __P((SCR *, ARG_CHAR_T));
+static void noprev __P((SCR *));
+
+/*
+ * v_chrepeat -- [count];
+ * Repeat the last F, f, T or t search.
+ *
+ * PUBLIC: int v_chrepeat __P((SCR *, VICMD *));
+ */
+int
+v_chrepeat(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ vp->character = VIP(sp)->lastckey;
+
+ switch (VIP(sp)->csearchdir) {
+ case CNOTSET:
+ noprev(sp);
+ return (1);
+ case FSEARCH:
+ return (v_chF(sp, vp));
+ case fSEARCH:
+ return (v_chf(sp, vp));
+ case TSEARCH:
+ return (v_chT(sp, vp));
+ case tSEARCH:
+ return (v_cht(sp, vp));
+ default:
+ abort();
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * v_chrrepeat -- [count],
+ * Repeat the last F, f, T or t search in the reverse direction.
+ *
+ * PUBLIC: int v_chrrepeat __P((SCR *, VICMD *));
+ */
+int
+v_chrrepeat(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ cdir_t savedir;
+ int rval;
+
+ vp->character = VIP(sp)->lastckey;
+ savedir = VIP(sp)->csearchdir;
+
+ switch (VIP(sp)->csearchdir) {
+ case CNOTSET:
+ noprev(sp);
+ return (1);
+ case FSEARCH:
+ rval = v_chf(sp, vp);
+ break;
+ case fSEARCH:
+ rval = v_chF(sp, vp);
+ break;
+ case TSEARCH:
+ rval = v_cht(sp, vp);
+ break;
+ case tSEARCH:
+ rval = v_chT(sp, vp);
+ break;
+ default:
+ abort();
+ }
+ VIP(sp)->csearchdir = savedir;
+ return (rval);
+}
+
+/*
+ * v_cht -- [count]tc
+ * Search forward in the line for the character before the next
+ * occurrence of the specified character.
+ *
+ * PUBLIC: int v_cht __P((SCR *, VICMD *));
+ */
+int
+v_cht(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ if (v_chf(sp, vp))
+ return (1);
+
+ /*
+ * v_chf places the cursor on the character, where the 't'
+ * command wants it to its left. We know this is safe since
+ * we had to move right for v_chf() to have succeeded.
+ */
+ --vp->m_stop.cno;
+
+ /*
+ * Make any necessary correction to the motion decision made
+ * by the v_chf routine.
+ */
+ if (!ISMOTION(vp))
+ vp->m_final = vp->m_stop;
+
+ VIP(sp)->csearchdir = tSEARCH;
+ return (0);
+}
+
+/*
+ * v_chf -- [count]fc
+ * Search forward in the line for the next occurrence of the
+ * specified character.
+ *
+ * PUBLIC: int v_chf __P((SCR *, VICMD *));
+ */
+int
+v_chf(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ u_long cnt;
+ int isempty, key;
+ char *endp, *p, *startp;
+
+ /*
+ * !!!
+ * If it's a dot command, it doesn't reset the key for which we're
+ * searching, e.g. in "df1|f2|.|;", the ';' searches for a '2'.
+ */
+ key = vp->character;
+ if (!F_ISSET(vp, VC_ISDOT))
+ VIP(sp)->lastckey = key;
+ VIP(sp)->csearchdir = fSEARCH;
+
+ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (isempty)
+ goto empty;
+ return (1);
+ }
+
+ if (len == 0) {
+empty: notfound(sp, key);
+ return (1);
+ }
+
+ endp = (startp = p) + len;
+ p += vp->m_start.cno;
+ for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
+ while (++p < endp && *p != key);
+ if (p == endp) {
+ notfound(sp, key);
+ return (1);
+ }
+ }
+
+ vp->m_stop.cno = p - startp;
+
+ /*
+ * Non-motion commands move to the end of the range.
+ * Delete and yank stay at the start, ignore others.
+ */
+ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_chT -- [count]Tc
+ * Search backward in the line for the character after the next
+ * occurrence of the specified character.
+ *
+ * PUBLIC: int v_chT __P((SCR *, VICMD *));
+ */
+int
+v_chT(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ if (v_chF(sp, vp))
+ return (1);
+
+ /*
+ * v_chF places the cursor on the character, where the 'T'
+ * command wants it to its right. We know this is safe since
+ * we had to move left for v_chF() to have succeeded.
+ */
+ ++vp->m_stop.cno;
+ vp->m_final = vp->m_stop;
+
+ VIP(sp)->csearchdir = TSEARCH;
+ return (0);
+}
+
+/*
+ * v_chF -- [count]Fc
+ * Search backward in the line for the next occurrence of the
+ * specified character.
+ *
+ * PUBLIC: int v_chF __P((SCR *, VICMD *));
+ */
+int
+v_chF(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ u_long cnt;
+ int isempty, key;
+ char *endp, *p;
+
+ /*
+ * !!!
+ * If it's a dot command, it doesn't reset the key for which
+ * we're searching, e.g. in "df1|f2|.|;", the ';' searches
+ * for a '2'.
+ */
+ key = vp->character;
+ if (!F_ISSET(vp, VC_ISDOT))
+ VIP(sp)->lastckey = key;
+ VIP(sp)->csearchdir = FSEARCH;
+
+ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (isempty)
+ goto empty;
+ return (1);
+ }
+
+ if (len == 0) {
+empty: notfound(sp, key);
+ return (1);
+ }
+
+ endp = p - 1;
+ p += vp->m_start.cno;
+ for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
+ while (--p > endp && *p != key);
+ if (p == endp) {
+ notfound(sp, key);
+ return (1);
+ }
+ }
+
+ vp->m_stop.cno = (p - endp) - 1;
+
+ /*
+ * All commands move to the end of the range. Motion commands
+ * adjust the starting point to the character before the current
+ * one.
+ */
+ vp->m_final = vp->m_stop;
+ if (ISMOTION(vp))
+ --vp->m_start.cno;
+ return (0);
+}
+
+static void
+noprev(sp)
+ SCR *sp;
+{
+ msgq(sp, M_BERR, "178|No previous F, f, T or t search");
+}
+
+static void
+notfound(sp, ch)
+ SCR *sp;
+ ARG_CHAR_T ch;
+{
+ msgq(sp, M_BERR, "179|%s not found", KEY_NAME(sp, ch));
+}
diff --git a/contrib/nvi/vi/v_cmd.c b/contrib/nvi/vi/v_cmd.c
new file mode 100644
index 0000000..cc9e30f
--- /dev/null
+++ b/contrib/nvi/vi/v_cmd.c
@@ -0,0 +1,505 @@
+/*-
+ * 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[] = "@(#)v_cmd.c 10.9 (Berkeley) 3/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * This array maps keystrokes to vi command functions. It is known
+ * in ex/ex_usage.c that it takes four columns to name a vi character.
+ */
+VIKEYS const vikeys [MAXVIKEY + 1] = {
+/* 000 NUL -- The code in vi.c expects key 0 to be undefined. */
+ {NULL},
+/* 001 ^A */
+ {v_searchw, V_ABS|V_CNT|V_MOVE|V_KEYW|VM_CUTREQ|VM_RCM_SET,
+ "[count]^A",
+ "^A search forward for cursor word"},
+/* 002 ^B */
+ {v_pageup, V_CNT|VM_RCM_SET,
+ "[count]^B",
+ "^B scroll up by screens"},
+/* 003 ^C */
+ {NULL, 0,
+ "^C",
+ "^C interrupt an operation (e.g. read, write, search)"},
+/* 004 ^D */
+ {v_hpagedown, V_CNT|VM_RCM_SET,
+ "[count]^D",
+ "^D scroll down by half screens (setting count)"},
+/* 005 ^E */
+ {v_linedown, V_CNT,
+ "[count]^E",
+ "^E scroll down by lines"},
+/* 006 ^F */
+ {v_pagedown, V_CNT|VM_RCM_SET,
+ "[count]^F",
+ "^F scroll down by screens"},
+/* 007 ^G */
+ {v_status, 0,
+ "^G",
+ "^G file status"},
+/* 010 ^H */
+ {v_left, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]^H",
+ "^H move left by characters"},
+/* 011 ^I */
+ {NULL},
+/* 012 ^J */
+ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM,
+ "[count]^J",
+ "^J move down by lines"},
+/* 013 ^K */
+ {NULL},
+/* 014 ^L */
+ {v_redraw, 0,
+ "^L",
+ "^L redraw screen"},
+/* 015 ^M */
+ {v_cr, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB,
+ "[count]^M",
+ "^M move down by lines (to first non-blank)"},
+/* 016 ^N */
+ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM,
+ "[count]^N",
+ "^N move down by lines"},
+/* 017 ^O */
+ {NULL},
+/* 020 ^P */
+ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM,
+ "[count]^P",
+ "^P move up by lines"},
+/* 021 ^Q -- same as ^V if not used for hardware flow control. */
+ {NULL},
+/* 022 ^R */
+ {v_redraw, 0,
+ "^R",
+ "^R redraw screen"},
+/* 023 ^S -- not available, used for hardware flow control. */
+ {NULL},
+/* 024 ^T */
+ {v_tagpop, V_ABS|VM_RCM_SET,
+ "^T",
+ "^T tag pop"},
+/* 025 ^U */
+ {v_hpageup, V_CNT|VM_RCM_SET,
+ "[count]^U",
+ "^U half page up (set count)"},
+/* 026 ^V */
+ {NULL, 0,
+ "^V",
+ "^V input a literal character"},
+/* 027 ^W */
+ {v_screen, 0,
+ "^W",
+ "^W move to next screen"},
+/* 030 ^X */
+ {NULL},
+/* 031 ^Y */
+ {v_lineup, V_CNT,
+ "[count]^Y",
+ "^Y page up by lines"},
+/* 032 ^Z */
+ {v_suspend, V_SECURE,
+ "^Z",
+ "^Z suspend editor"},
+/* 033 ^[ */
+ {NULL, 0,
+ "^[ <escape>",
+ "^[ <escape> exit input mode, cancel partial commands"},
+/* 034 ^\ */
+ {v_exmode, 0,
+ "^\\",
+ " ^\\ switch to ex mode"},
+/* 035 ^] */
+ {v_tagpush, V_ABS|V_KEYW|VM_RCM_SET,
+ "^]",
+ "^] tag push cursor word"},
+/* 036 ^^ */
+ {v_switch, 0,
+ "^^",
+ "^^ switch to previous file"},
+/* 037 ^_ */
+ {NULL},
+/* 040 ' ' */
+ {v_right, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]' '",
+ " <space> move right by columns"},
+/* 041 ! */
+ {v_filter, V_CNT|V_DOT|V_MOTION|V_SECURE|VM_RCM_SET,
+ "[count]![count]motion command(s)",
+ " ! filter through command(s) to motion"},
+/* 042 " */
+ {NULL},
+/* 043 # */
+ {v_increment, V_CHAR|V_CNT|V_DOT|VM_RCM_SET,
+ "[count]# +|-|#",
+ " # number increment/decrement"},
+/* 044 $ */
+ {v_dollar, V_CNT|V_MOVE|VM_RCM_SETLAST,
+ " [count]$",
+ " $ move to last column"},
+/* 045 % */
+ {v_match, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "%",
+ " % move to match"},
+/* 046 & */
+ {v_again, 0,
+ "&",
+ " & repeat substitution"},
+/* 047 ' */
+ {v_fmark, V_ABS_L|V_CHAR|V_MOVE|VM_LMODE|VM_RCM_SET,
+ "'['a-z]",
+ " ' move to mark (to first non-blank)"},
+/* 050 ( */
+ {v_sentenceb, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "[count](",
+ " ( move back sentence"},
+/* 051 ) */
+ {v_sentencef, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "[count])",
+ " ) move forward sentence"},
+/* 052 * */
+ {NULL},
+/* 053 + */
+ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB,
+ "[count]+",
+ " + move down by lines (to first non-blank)"},
+/* 054 , */
+ {v_chrrepeat, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count],",
+ " , reverse last F, f, T or t search"},
+/* 055 - */
+ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB,
+ "[count]-",
+ " - move up by lines (to first non-blank)"},
+/* 056 . */
+ {NULL, 0,
+ ".",
+ " . repeat the last command"},
+/* 057 / */
+ {v_searchf, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "/RE[/ offset]",
+ " / search forward"},
+/* 060 0 */
+ {v_zero, V_MOVE|VM_RCM_SET,
+ "0",
+ " 0 move to first character"},
+/* 061 1 */
+ {NULL},
+/* 062 2 */
+ {NULL},
+/* 063 3 */
+ {NULL},
+/* 064 4 */
+ {NULL},
+/* 065 5 */
+ {NULL},
+/* 066 6 */
+ {NULL},
+/* 067 7 */
+ {NULL},
+/* 070 8 */
+ {NULL},
+/* 071 9 */
+ {NULL},
+/* 072 : */
+ {v_ex, 0,
+ ":command [| command] ...",
+ " : ex command"},
+/* 073 ; */
+ {v_chrepeat, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count];",
+ " ; repeat last F, f, T or t search"},
+/* 074 < */
+ {v_shiftl, V_CNT|V_DOT|V_MOTION|VM_RCM_SET,
+ "[count]<[count]motion",
+ " < shift lines left to motion"},
+/* 075 = */
+ {NULL},
+/* 076 > */
+ {v_shiftr, V_CNT|V_DOT|V_MOTION|VM_RCM_SET,
+ "[count]>[count]motion",
+ " > shift lines right to motion"},
+/* 077 ? */
+ {v_searchb, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "?RE[? offset]",
+ " ? search backward"},
+/* 100 @ */
+ {v_at, V_CNT|V_RBUF|VM_RCM_SET,
+ "@buffer",
+ " @ execute buffer"},
+/* 101 A */
+ {v_iA, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]A",
+ " A append to the line"},
+/* 102 B */
+ {v_wordB, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]B",
+ " B move back bigword"},
+/* 103 C */
+ {NULL, 0,
+ "[buffer][count]C",
+ " C change to end-of-line"},
+/* 104 D */
+ {NULL, 0,
+ "[buffer]D",
+ " D delete to end-of-line"},
+/* 105 E */
+ {v_wordE, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]E",
+ " E move to end of bigword"},
+/* 106 F */
+ {v_chF, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]F character",
+ " F character in line backward search"},
+/* 107 G */
+ {v_lgoto, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB,
+ "[count]G",
+ " G move to line"},
+/* 110 H */
+ {v_home, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB,
+ "[count]H",
+ " H move to count lines from screen top"},
+/* 111 I */
+ {v_iI, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]I",
+ " I insert before first nonblank"},
+/* 112 J */
+ {v_join, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]J",
+ " J join lines"},
+/* 113 K */
+ {NULL},
+/* 114 L */
+ {v_bottom, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB,
+ "[count]L",
+ " L move to screen bottom"},
+/* 115 M */
+ {v_middle, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB,
+ "M",
+ " M move to screen middle"},
+/* 116 N */
+ {v_searchN, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "n",
+ " N reverse last search"},
+/* 117 O */
+ {v_iO, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]O",
+ " O insert above line"},
+/* 120 P */
+ {v_Put, V_CNT|V_DOT|V_OBUF|VM_RCM_SET,
+ "[buffer]P",
+ " P insert before cursor from buffer"},
+/* 121 Q */
+ {v_exmode, 0,
+ "Q",
+ " Q switch to ex mode"},
+/* 122 R */
+ {v_Replace, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]R",
+ " R replace characters"},
+/* 123 S */
+ {NULL, 0,
+ "[buffer][count]S",
+ " S substitute for the line(s)"},
+/* 124 T */
+ {v_chT, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]T character",
+ " T before character in line backward search"},
+/* 125 U */
+ {v_Undo, VM_RCM_SET,
+ "U",
+ " U Restore the current line"},
+/* 126 V */
+ {NULL},
+/* 127 W */
+ {v_wordW, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]W",
+ " W move to next bigword"},
+/* 130 X */
+ {v_Xchar, V_CNT|V_DOT|V_OBUF|VM_RCM_SET,
+ "[buffer][count]X",
+ " X delete character before cursor"},
+/* 131 Y */
+ {NULL, 0,
+ "[buffer][count]Y",
+ " Y copy line"},
+/* 132 Z */
+ {v_zexit, 0,
+ "ZZ",
+ "ZZ save file and exit"},
+/* 133 [ */
+ {v_sectionb, V_ABS|V_CNT|V_MOVE|VM_RCM_SET,
+ "[[",
+ "[[ move back section"},
+/* 134 \ */
+ {NULL},
+/* 135 ] */
+ {v_sectionf, V_ABS|V_CNT|V_MOVE|VM_RCM_SET,
+ "]]",
+ "]] move forward section"},
+/* 136 ^ */
+ /*
+ * DON'T set the VM_RCM_SETFNB flag, the function has to do the work
+ * anyway, in case it's a motion component. DO set VM_RCM_SET, so
+ * that any motion that's part of a command is preserved.
+ */
+ {v_first, V_CNT|V_MOVE|VM_RCM_SET,
+ "^",
+ " ^ move to first non-blank"},
+/* 137 _ */
+ /*
+ * Needs both to set the VM_RCM_SETFNB flag, and to do the work
+ * in the function, in case it's a delete.
+ */
+ {v_cfirst, V_CNT|V_MOVE|VM_RCM_SETFNB,
+ "_",
+ " _ move to first non-blank"},
+/* 140 ` */
+ {v_bmark, V_ABS_C|V_CHAR|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "`[`a-z]",
+ " ` move to mark"},
+/* 141 a */
+ {v_ia, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]a",
+ " a append after cursor"},
+/* 142 b */
+ {v_wordb, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]b",
+ " b move back word"},
+/* 143 c */
+ {v_change, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET,
+ "[buffer][count]c[count]motion",
+ " c change to motion"},
+/* 144 d */
+ {v_delete, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET,
+ "[buffer][count]d[count]motion",
+ " d delete to motion"},
+/* 145 e */
+ {v_worde, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]e",
+ " e move to end of word"},
+/* 146 f */
+ {v_chf, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]f character",
+ " f character in line forward search"},
+/* 147 g */
+ {NULL},
+/* 150 h */
+ {v_left, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]h",
+ " h move left by columns"},
+/* 151 i */
+ {v_ii, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]i",
+ " i insert before cursor"},
+/* 152 j */
+ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM,
+ "[count]j",
+ " j move down by lines"},
+/* 153 k */
+ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM,
+ "[count]k",
+ " k move up by lines"},
+/* 154 l */
+ {v_right, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]l",
+ " l move right by columns"},
+/* 155 m */
+ {v_mark, V_CHAR,
+ "m[a-z]",
+ " m set mark"},
+/* 156 n */
+ {v_searchn, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "n",
+ " n repeat last search"},
+/* 157 o */
+ {v_io, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]o",
+ " o append after line"},
+/* 160 p */
+ {v_put, V_CNT|V_DOT|V_OBUF|VM_RCM_SET,
+ "[buffer]p",
+ " p insert after cursor from buffer"},
+/* 161 q */
+ {NULL},
+/* 162 r */
+ {v_replace, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]r character",
+ " r replace character"},
+/* 163 s */
+ {v_subst, V_CNT|V_DOT|V_OBUF|VM_RCM_SET,
+ "[buffer][count]s",
+ " s substitute character"},
+/* 164 t */
+ {v_cht, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]t character",
+ " t before character in line forward search"},
+/* 165 u */
+ /*
+ * DON'T set the V_DOT flag, it' more complicated than that.
+ * See vi/vi.c for details.
+ */
+ {v_undo, VM_RCM_SET,
+ "u",
+ " u undo last change"},
+/* 166 v */
+ {NULL},
+/* 167 w */
+ {v_wordw, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]w",
+ " w move to next word"},
+/* 170 x */
+ {v_xchar, V_CNT|V_DOT|V_OBUF|VM_RCM_SET,
+ "[buffer][count]x",
+ " x delete character"},
+/* 171 y */
+ {v_yank, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET,
+ "[buffer][count]y[count]motion",
+ " y copy text to motion into a cut buffer"},
+/* 172 z */
+ /*
+ * DON'T set the V_CHAR flag, the char isn't required,
+ * so it's handled specially in getcmd().
+ */
+ {v_z, V_ABS_L|V_CNT|VM_RCM_SETFNB,
+ "[line]z[window_size][-|.|+|^|<CR>]",
+ " z reposition the screen"},
+/* 173 { */
+ {v_paragraphb, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "[count]{",
+ " { move back paragraph"},
+/* 174 | */
+ {v_ncol, V_CNT|V_MOVE|VM_RCM_SET,
+ "[count]|",
+ " | move to column"},
+/* 175 } */
+ {v_paragraphf, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET,
+ "[count]}",
+ " } move forward paragraph"},
+/* 176 ~ */
+ {v_ulcase, V_CNT|V_DOT|VM_RCM_SET,
+ "[count]~",
+ " ~ reverse case"},
+};
diff --git a/contrib/nvi/vi/v_delete.c b/contrib/nvi/vi/v_delete.c
new file mode 100644
index 0000000..70df78b
--- /dev/null
+++ b/contrib/nvi/vi/v_delete.c
@@ -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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)v_delete.c 10.9 (Berkeley) 10/23/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_delete -- [buffer][count]d[count]motion
+ * [buffer][count]D
+ * Delete a range of text.
+ *
+ * PUBLIC: int v_delete __P((SCR *, VICMD *));
+ */
+int
+v_delete(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t nlines;
+ size_t len;
+ int lmode;
+
+ lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0;
+
+ /* Yank the lines. */
+ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
+ &vp->m_start, &vp->m_stop,
+ lmode | (F_ISSET(vp, VM_CUTREQ) ? CUT_NUMREQ : CUT_NUMOPT)))
+ return (1);
+
+ /* Delete the lines. */
+ if (del(sp, &vp->m_start, &vp->m_stop, lmode))
+ return (1);
+
+ /*
+ * Check for deletion of the entire file. Try to check a close
+ * by line so we don't go to the end of the file unnecessarily.
+ */
+ if (!db_exist(sp, vp->m_final.lno + 1)) {
+ if (db_last(sp, &nlines))
+ return (1);
+ if (nlines == 0) {
+ vp->m_final.lno = 1;
+ vp->m_final.cno = 0;
+ return (0);
+ }
+ }
+
+ /*
+ * One special correction, in case we've deleted the current line or
+ * character. We check it here instead of checking in every command
+ * that can be a motion component.
+ */
+ if (db_get(sp, vp->m_final.lno, 0, NULL, &len)) {
+ if (db_get(sp, nlines, DBG_FATAL, NULL, &len))
+ return (1);
+ vp->m_final.lno = nlines;
+ }
+
+ /*
+ * !!!
+ * Cursor movements, other than those caused by a line mode command
+ * moving to another line, historically reset the relative position.
+ *
+ * This currently matches the check made in v_yank(), I'm hoping that
+ * they should be consistent...
+ */
+ if (!F_ISSET(vp, VM_LMODE)) {
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SET);
+
+ /* Make sure the set cursor position exists. */
+ if (vp->m_final.cno >= len)
+ vp->m_final.cno = len ? len - 1 : 0;
+ }
+
+ /*
+ * !!!
+ * The "dd" command moved to the first non-blank; "d<motion>"
+ * didn't.
+ */
+ if (F_ISSET(vp, VM_LDOUBLE)) {
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SETFNB);
+ }
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_ex.c b/contrib/nvi/vi/v_ex.c
new file mode 100644
index 0000000..359080c
--- /dev/null
+++ b/contrib/nvi/vi/v_ex.c
@@ -0,0 +1,696 @@
+/*-
+ * 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[] = "@(#)v_ex.c 10.42 (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.h"
+
+static int v_ecl __P((SCR *));
+static int v_ecl_init __P((SCR *));
+static int v_ecl_log __P((SCR *, TEXT *));
+static int v_ex_done __P((SCR *, VICMD *));
+static int v_exec_ex __P((SCR *, VICMD *, EXCMD *));
+
+/*
+ * v_again -- &
+ * Repeat the previous substitution.
+ *
+ * PUBLIC: int v_again __P((SCR *, VICMD *));
+ */
+int
+v_again(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_SUBAGAIN, 2, vp->m_start.lno, vp->m_start.lno, 1, ap);
+ ex_cadd(&cmd, &a, "", 1);
+
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_exmode -- Q
+ * Switch the editor into EX mode.
+ *
+ * PUBLIC: int v_exmode __P((SCR *, VICMD *));
+ */
+int
+v_exmode(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ GS *gp;
+
+ gp = sp->gp;
+
+ /* Try and switch screens -- the screen may not permit it. */
+ if (gp->scr_screen(sp, SC_EX)) {
+ msgq(sp, M_ERR,
+ "207|The Q command requires the ex terminal interface");
+ return (1);
+ }
+ (void)gp->scr_attr(sp, SA_ALTERNATE, 0);
+
+ /* Save the current cursor position. */
+ sp->frp->lno = sp->lno;
+ sp->frp->cno = sp->cno;
+ F_SET(sp->frp, FR_CURSORSET);
+
+ /* Switch to ex mode. */
+ F_CLR(sp, SC_VI | SC_SCR_VI);
+ F_SET(sp, SC_EX);
+
+ /* Move out of the vi screen. */
+ (void)ex_puts(sp, "\n");
+
+ return (0);
+}
+
+/*
+ * v_join -- [count]J
+ * Join lines together.
+ *
+ * PUBLIC: int v_join __P((SCR *, VICMD *));
+ */
+int
+v_join(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ EXCMD cmd;
+ int lno;
+
+ /*
+ * YASC.
+ * The general rule is that '#J' joins # lines, counting the current
+ * line. However, 'J' and '1J' are the same as '2J', i.e. join the
+ * current and next lines. This doesn't map well into the ex command
+ * (which takes two line numbers), so we handle it here. Note that
+ * we never test for EOF -- historically going past the end of file
+ * worked just fine.
+ */
+ lno = vp->m_start.lno + 1;
+ if (F_ISSET(vp, VC_C1SET) && vp->count > 2)
+ lno = vp->m_start.lno + (vp->count - 1);
+
+ ex_cinit(&cmd, C_JOIN, 2, vp->m_start.lno, lno, 0, NULL);
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_shiftl -- [count]<motion
+ * Shift lines left.
+ *
+ * PUBLIC: int v_shiftl __P((SCR *, VICMD *));
+ */
+int
+v_shiftl(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_SHIFTL, 2, vp->m_start.lno, vp->m_stop.lno, 0, ap);
+ ex_cadd(&cmd, &a, "<", 1);
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_shiftr -- [count]>motion
+ * Shift lines right.
+ *
+ * PUBLIC: int v_shiftr __P((SCR *, VICMD *));
+ */
+int
+v_shiftr(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_SHIFTR, 2, vp->m_start.lno, vp->m_stop.lno, 0, ap);
+ ex_cadd(&cmd, &a, ">", 1);
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_suspend -- ^Z
+ * Suspend vi.
+ *
+ * PUBLIC: int v_suspend __P((SCR *, VICMD *));
+ */
+int
+v_suspend(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_STOP, 0, OOBLNO, OOBLNO, 0, ap);
+ ex_cadd(&cmd, &a, "suspend", sizeof("suspend") - 1);
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_switch -- ^^
+ * Switch to the previous file.
+ *
+ * PUBLIC: int v_switch __P((SCR *, VICMD *));
+ */
+int
+v_switch(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+ char *name;
+
+ /*
+ * Try the alternate file name, then the previous file
+ * name. Use the real name, not the user's current name.
+ */
+ if ((name = sp->alt_name) == NULL) {
+ msgq(sp, M_ERR, "180|No previous file to edit");
+ return (1);
+ }
+
+ /* If autowrite is set, write out the file. */
+ if (file_m1(sp, 0, FS_ALL))
+ return (1);
+
+ ex_cinit(&cmd, C_EDIT, 0, OOBLNO, OOBLNO, 0, ap);
+ ex_cadd(&cmd, &a, name, strlen(name));
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_tagpush -- ^[
+ * Do a tag search on the cursor keyword.
+ *
+ * PUBLIC: int v_tagpush __P((SCR *, VICMD *));
+ */
+int
+v_tagpush(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_TAG, 0, OOBLNO, 0, 0, ap);
+ ex_cadd(&cmd, &a, VIP(sp)->keyw, strlen(VIP(sp)->keyw));
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_tagpop -- ^T
+ * Pop the tags stack.
+ *
+ * PUBLIC: int v_tagpop __P((SCR *, VICMD *));
+ */
+int
+v_tagpop(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_TAGPOP, 0, OOBLNO, 0, 0, NULL);
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_filter -- [count]!motion command(s)
+ * Run range through shell commands, replacing text.
+ *
+ * PUBLIC: int v_filter __P((SCR *, VICMD *));
+ */
+int
+v_filter(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ EXCMD cmd;
+ TEXT *tp;
+
+ /*
+ * !!!
+ * Historical vi permitted "!!" in an empty file, and it's handled
+ * as a special case in the ex_bang routine. Don't modify this setup
+ * without understanding that one. In particular, note that we're
+ * manipulating the ex argument structures behind ex's back.
+ *
+ * !!!
+ * Historical vi did not permit the '!' command to be associated with
+ * a non-line oriented motion command, in general, although it did
+ * with search commands. So, !f; and !w would fail, but !/;<CR>
+ * would succeed, even if they all moved to the same location in the
+ * current line. I don't see any reason to disallow '!' using any of
+ * the possible motion commands.
+ *
+ * !!!
+ * Historical vi ran the last bang command if N or n was used as the
+ * search motion.
+ */
+ if (F_ISSET(vp, VC_ISDOT) ||
+ ISCMD(vp->rkp, 'N') || ISCMD(vp->rkp, 'n')) {
+ ex_cinit(&cmd, C_BANG,
+ 2, vp->m_start.lno, vp->m_stop.lno, 0, NULL);
+ EXP(sp)->argsoff = 0; /* XXX */
+
+ if (argv_exp1(sp, &cmd, "!", 1, 1))
+ return (1);
+ cmd.argc = EXP(sp)->argsoff; /* XXX */
+ cmd.argv = EXP(sp)->args; /* XXX */
+ return (v_exec_ex(sp, vp, &cmd));
+ }
+
+ /* Get the command from the user. */
+ if (v_tcmd(sp, vp,
+ '!', TXT_BS | TXT_CR | TXT_ESCAPE | TXT_FILEC | TXT_PROMPT))
+ return (1);
+
+ /*
+ * Check to see if the user changed their mind.
+ *
+ * !!!
+ * Entering <escape> on an empty line was historically an error,
+ * this implementation doesn't bother.
+ */
+ tp = sp->tiq.cqh_first;
+ if (tp->term != TERM_OK) {
+ vp->m_final.lno = sp->lno;
+ vp->m_final.cno = sp->cno;
+ return (0);
+ }
+
+ /* Home the cursor. */
+ vs_home(sp);
+
+ ex_cinit(&cmd, C_BANG, 2, vp->m_start.lno, vp->m_stop.lno, 0, NULL);
+ EXP(sp)->argsoff = 0; /* XXX */
+
+ if (argv_exp1(sp, &cmd, tp->lb + 1, tp->len - 1, 1))
+ return (1);
+ cmd.argc = EXP(sp)->argsoff; /* XXX */
+ cmd.argv = EXP(sp)->args; /* XXX */
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_event_exec --
+ * Execute some command(s) based on an event.
+ *
+ * PUBLIC: int v_event_exec __P((SCR *, VICMD *));
+ */
+int
+v_event_exec(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ EXCMD cmd;
+
+ switch (vp->ev.e_event) {
+ case E_QUIT:
+ ex_cinit(&cmd, C_QUIT, 0, OOBLNO, OOBLNO, 0, NULL);
+ break;
+ case E_WRITE:
+ ex_cinit(&cmd, C_WRITE, 0, OOBLNO, OOBLNO, 0, NULL);
+ break;
+ default:
+ abort();
+ }
+ return (v_exec_ex(sp, vp, &cmd));
+}
+
+/*
+ * v_exec_ex --
+ * Execute an ex command.
+ */
+static int
+v_exec_ex(sp, vp, exp)
+ SCR *sp;
+ VICMD *vp;
+ EXCMD *exp;
+{
+ int rval;
+
+ rval = exp->cmd->fn(sp, exp);
+ return (v_ex_done(sp, vp) || rval);
+}
+
+/*
+ * v_ex -- :
+ * Execute a colon command line.
+ *
+ * PUBLIC: int v_ex __P((SCR *, VICMD *));
+ */
+int
+v_ex(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ GS *gp;
+ TEXT *tp;
+ int do_cedit, do_resolution, ifcontinue;
+
+ gp = sp->gp;
+
+ /*
+ * !!!
+ * If we put out more than a single line of messages, or ex trashes
+ * the screen, the user may continue entering ex commands. We find
+ * this out when we do the screen/message resolution. We can't enter
+ * completely into ex mode however, because the user can elect to
+ * return into vi mode by entering any key, i.e. we have to be in raw
+ * mode.
+ */
+ for (do_cedit = do_resolution = 0;;) {
+ /*
+ * !!!
+ * There may already be an ex command waiting to run. If
+ * so, we continue with it.
+ */
+ if (!EXCMD_RUNNING(gp)) {
+ /* Get a command. */
+ if (v_tcmd(sp, vp, ':',
+ TXT_BS | TXT_CEDIT | TXT_FILEC | TXT_PROMPT))
+ return (1);
+ tp = sp->tiq.cqh_first;
+
+ /*
+ * If the user entered a single <esc>, they want to
+ * edit their colon command history. If they already
+ * entered some text, move it into the edit history.
+ */
+ if (tp->term == TERM_CEDIT) {
+ if (tp->len > 1 && v_ecl_log(sp, tp))
+ return (1);
+ do_cedit = 1;
+ break;
+ }
+
+ /* If the user didn't enter anything, return. */
+ if (tp->term == TERM_BS)
+ break;
+
+ /* Log the command. */
+ if (O_STR(sp, O_CEDIT) != NULL && v_ecl_log(sp, tp))
+ return (1);
+
+ /* Push a command on the command stack. */
+ if (ex_run_str(sp, NULL, tp->lb, tp->len, 0, 1))
+ return (1);
+ }
+
+ /* Home the cursor. */
+ vs_home(sp);
+
+ /*
+ * !!!
+ * If the editor wrote the screen behind curses back, put out
+ * a <newline> so that we don't overwrite the user's command
+ * with its output or the next want-to-continue? message. This
+ * doesn't belong here, but I can't find another place to put
+ * it. See, we resolved the output from the last ex command,
+ * and the user entered another one. This is the only place
+ * where we have control before the ex command writes output.
+ * We could get control in vs_msg(), but we have no way to know
+ * if command didn't put out any output when we try and resolve
+ * this command. This fixes a bug where combinations of ex
+ * commands, e.g. ":set<CR>:!date<CR>:set" didn't look right.
+ */
+ if (F_ISSET(sp, SC_SCR_EXWROTE))
+ (void)putchar('\n');
+
+ /* Call the ex parser. */
+ (void)ex_cmd(sp);
+
+ /* Flush ex messages. */
+ (void)ex_fflush(sp);
+
+ /* Resolve any messages. */
+ if (vs_ex_resolve(sp, &ifcontinue))
+ return (1);
+
+ /*
+ * Continue or return. If continuing, make sure that we
+ * eventually do resolution.
+ */
+ if (!ifcontinue)
+ break;
+ do_resolution = 1;
+
+ /* If we're continuing, it's a new command. */
+ ++sp->ccnt;
+ }
+
+ /*
+ * If the user previously continued an ex command, we have to do
+ * resolution to clean up the screen. Don't wait, we already did
+ * that.
+ */
+ if (do_resolution) {
+ F_SET(sp, SC_EX_WAIT_NO);
+ if (vs_ex_resolve(sp, &ifcontinue))
+ return (1);
+ }
+
+ /* Cleanup from the ex command. */
+ if (v_ex_done(sp, vp))
+ return (1);
+
+ /* The user may want to edit their colon command history. */
+ if (do_cedit)
+ return (v_ecl(sp));
+
+ return (0);
+}
+
+/*
+ * v_ex_done --
+ * Cleanup from an ex command.
+ */
+static int
+v_ex_done(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+
+ /*
+ * The only cursor modifications are real, however, the underlying
+ * line may have changed; don't trust anything. This code has been
+ * a remarkably fertile place for bugs. Do a reality check on a
+ * cursor value, and make sure it's okay. If necessary, change it.
+ * Ex keeps track of the line number, but it cares less about the
+ * column and it may have disappeared.
+ *
+ * Don't trust ANYTHING.
+ *
+ * XXX
+ * Ex will soon have to start handling the column correctly; see
+ * the POSIX 1003.2 standard.
+ */
+ if (db_eget(sp, sp->lno, NULL, &len, NULL)) {
+ sp->lno = 1;
+ sp->cno = 0;
+ } else if (sp->cno >= len)
+ sp->cno = len ? len - 1 : 0;
+
+ vp->m_final.lno = sp->lno;
+ vp->m_final.cno = sp->cno;
+
+ /*
+ * Don't re-adjust the cursor after executing an ex command,
+ * and ex movements are permanent.
+ */
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SET);
+
+ return (0);
+}
+
+/*
+ * v_ecl --
+ * Start an edit window on the colon command-line commands.
+ */
+static int
+v_ecl(sp)
+ SCR *sp;
+{
+ GS *gp;
+ SCR *new;
+
+ /* Initialize the screen, if necessary. */
+ gp = sp->gp;
+ if (gp->ccl_sp == NULL && v_ecl_init(sp))
+ return (1);
+
+ /* Get a new screen. */
+ if (screen_init(gp, sp, &new))
+ return (1);
+ if (vs_split(sp, new, 1)) {
+ (void)screen_end(new);
+ return (1);
+ }
+
+ /* Attach to the screen. */
+ new->ep = gp->ccl_sp->ep;
+ ++new->ep->refcnt;
+
+ new->frp = gp->ccl_sp->frp;
+ new->frp->flags = sp->frp->flags;
+
+ /* Move the cursor to the end. */
+ (void)db_last(new, &new->lno);
+ if (new->lno == 0)
+ new->lno = 1;
+
+ /* Remember the originating window. */
+ sp->ccl_parent = sp;
+
+ /* It's a special window. */
+ F_SET(new, SC_COMEDIT);
+
+ /* Set up the switch. */
+ sp->nextdisp = new;
+ F_SET(sp, SC_SSWITCH);
+ return (0);
+}
+
+/*
+ * v_ecl_exec --
+ * Execute a command from a colon command-line window.
+ *
+ * PUBLIC: int v_ecl_exec __P((SCR *));
+ */
+int
+v_ecl_exec(sp)
+ SCR *sp;
+{
+ size_t len;
+ char *p;
+
+ if (db_get(sp, sp->lno, 0, &p, &len) && sp->lno == 1) {
+ v_emsg(sp, NULL, VIM_EMPTY);
+ return (1);
+ }
+ if (len == 0) {
+ msgq(sp, M_BERR, "307|No ex command to execute");
+ return (1);
+ }
+
+ /* Push the command on the command stack. */
+ if (ex_run_str(sp, NULL, p, len, 0, 0))
+ return (1);
+
+ /* Set up the switch. */
+ sp->nextdisp = sp->ccl_parent;
+ F_SET(sp, SC_EXIT);
+ return (0);
+}
+
+/*
+ * v_ecl_log --
+ * Log a command into the colon command-line log file.
+ */
+static int
+v_ecl_log(sp, tp)
+ SCR *sp;
+ TEXT *tp;
+{
+ EXF *save_ep;
+ recno_t lno;
+ int rval;
+
+ /* Initialize the screen, if necessary. */
+ if (sp->gp->ccl_sp == NULL && v_ecl_init(sp))
+ return (1);
+
+ /*
+ * Don't log colon command window commands into the colon command
+ * window...
+ */
+ if (sp->ep == sp->gp->ccl_sp->ep)
+ return (0);
+
+ /*
+ * XXX
+ * Swap the current EXF with the colon command file EXF. This
+ * isn't pretty, but too many routines "know" that sp->ep points
+ * to the current EXF.
+ */
+ save_ep = sp->ep;
+ sp->ep = sp->gp->ccl_sp->ep;
+ if (db_last(sp, &lno)) {
+ sp->ep = save_ep;
+ return (1);
+ }
+ rval = db_append(sp, 0, lno, tp->lb, tp->len);
+ sp->ep = save_ep;
+ return (rval);
+}
+
+/*
+ * v_ecl_init --
+ * Initialize the colon command-line log file.
+ */
+static int
+v_ecl_init(sp)
+ SCR *sp;
+{
+ FREF *frp;
+ GS *gp;
+
+ gp = sp->gp;
+
+ /* Get a temporary file. */
+ if ((frp = file_add(sp, NULL)) == NULL)
+ return (1);
+
+ /*
+ * XXX
+ * Create a screen -- the file initialization code wants one.
+ */
+ if (screen_init(gp, sp, &gp->ccl_sp))
+ return (1);
+ if (file_init(gp->ccl_sp, frp, NULL, 0)) {
+ (void)screen_end(gp->ccl_sp);
+ return (1);
+ }
+
+ /* The underlying file isn't recoverable. */
+ F_CLR(gp->ccl_sp->ep, F_RCV_ON);
+
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_increment.c b/contrib/nvi/vi/v_increment.c
new file mode 100644
index 0000000..45e763f
--- /dev/null
+++ b/contrib/nvi/vi/v_increment.c
@@ -0,0 +1,267 @@
+/*-
+ * 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[] = "@(#)v_increment.c 10.12 (Berkeley) 3/19/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 "../common/common.h"
+#include "vi.h"
+
+static char * const fmt[] = {
+#define DEC 0
+ "%ld",
+#define SDEC 1
+ "%+ld",
+#define HEXC 2
+ "0X%0*lX",
+#define HEXL 3
+ "0x%0*lx",
+#define OCTAL 4
+ "%#0*lo",
+};
+
+static void inc_err __P((SCR *, enum nresult));
+
+/*
+ * v_increment -- [count]#[#+-]
+ * Increment/decrement a keyword number.
+ *
+ * PUBLIC: int v_increment __P((SCR *, VICMD *));
+ */
+int
+v_increment(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ enum nresult nret;
+ u_long ulval;
+ long change, ltmp, lval;
+ size_t beg, blen, end, len, nlen, wlen;
+ int base, isempty, rval;
+ char *bp, *ntype, *p, *t, nbuf[100];
+
+ /* Validate the operator. */
+ if (vp->character == '#')
+ vp->character = '+';
+ if (vp->character != '+' && vp->character != '-') {
+ v_emsg(sp, vp->kp->usage, VIM_USAGE);
+ return (1);
+ }
+
+ /* If new value set, save it off, but it has to fit in a long. */
+ if (F_ISSET(vp, VC_C1SET)) {
+ if (vp->count > LONG_MAX) {
+ inc_err(sp, NUM_OVER);
+ return (1);
+ }
+ change = vp->count;
+ } else
+ change = 1;
+
+ /* Get the line. */
+ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (isempty)
+ goto nonum;
+ return (1);
+ }
+
+ /*
+ * Skip any leading space before the number. Getting a cursor word
+ * implies moving the cursor to its beginning, if we moved, refresh
+ * now.
+ */
+ for (beg = vp->m_start.cno; beg < len && isspace(p[beg]); ++beg);
+ if (beg >= len)
+ goto nonum;
+ if (beg != vp->m_start.cno) {
+ sp->cno = beg;
+ (void)vs_refresh(sp, 0);
+ }
+
+#undef ishex
+#define ishex(c) (isdigit(c) || strchr("abcdefABCDEF", c))
+#undef isoctal
+#define isoctal(c) (isdigit(c) && (c) != '8' && (c) != '9')
+
+ /*
+ * Look for 0[Xx], or leading + or - signs, guess at the base.
+ * The character after that must be a number. Wlen is set to
+ * the remaining characters in the line that could be part of
+ * the number.
+ */
+ wlen = len - beg;
+ if (p[beg] == '0' && wlen > 2 &&
+ (p[beg + 1] == 'X' || p[beg + 1] == 'x')) {
+ base = 16;
+ end = beg + 2;
+ if (!ishex(p[end]))
+ goto decimal;
+ ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL];
+ } else if (p[beg] == '0' && wlen > 1) {
+ base = 8;
+ end = beg + 1;
+ if (!isoctal(p[end]))
+ goto decimal;
+ ntype = fmt[OCTAL];
+ } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) {
+ base = 10;
+ end = beg + 1;
+ ntype = fmt[SDEC];
+ if (!isdigit(p[end]))
+ goto nonum;
+ } else {
+decimal: base = 10;
+ end = beg;
+ ntype = fmt[DEC];
+ if (!isdigit(p[end])) {
+nonum: msgq(sp, M_ERR, "181|Cursor not in a number");
+ return (1);
+ }
+ }
+
+ /* Find the end of the word, possibly correcting the base. */
+ while (++end < len) {
+ switch (base) {
+ case 8:
+ if (isoctal(p[end]))
+ continue;
+ if (p[end] == '8' || p[end] == '9') {
+ base = 10;
+ ntype = fmt[DEC];
+ continue;
+ }
+ break;
+ case 10:
+ if (isdigit(p[end]))
+ continue;
+ break;
+ case 16:
+ if (ishex(p[end]))
+ continue;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+ break;
+ }
+ wlen = (end - beg);
+
+ /*
+ * XXX
+ * If the line was at the end of the buffer, we have to copy it
+ * so we can guarantee that it's NULL-terminated. We make the
+ * buffer big enough to fit the line changes as well, and only
+ * allocate once.
+ */
+ GET_SPACE_RET(sp, bp, blen, len + 50);
+ if (end == len) {
+ memmove(bp, &p[beg], wlen);
+ bp[wlen] = '\0';
+ t = bp;
+ } else
+ t = &p[beg];
+
+ /*
+ * Octal or hex deal in unsigned longs, everything else is done
+ * in signed longs.
+ */
+ if (base == 10) {
+ if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK)
+ goto err;
+ ltmp = vp->character == '-' ? -change : change;
+ if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) {
+ nret = NUM_OVER;
+ goto err;
+ }
+ if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) {
+ nret = NUM_UNDER;
+ goto err;
+ }
+ lval += ltmp;
+ /* If we cross 0, signed numbers lose their sign. */
+ if (lval == 0 && ntype == fmt[SDEC])
+ ntype = fmt[DEC];
+ nlen = snprintf(nbuf, sizeof(nbuf), ntype, lval);
+ } else {
+ if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK)
+ goto err;
+ if (vp->character == '+') {
+ if (!NPFITS(ULONG_MAX, ulval, change)) {
+ nret = NUM_OVER;
+ goto err;
+ }
+ ulval += change;
+ } else {
+ if (ulval < change) {
+ nret = NUM_UNDER;
+ goto err;
+ }
+ ulval -= change;
+ }
+
+ /* Correct for literal "0[Xx]" in format. */
+ if (base == 16)
+ wlen -= 2;
+
+ nlen = snprintf(nbuf, sizeof(nbuf), ntype, wlen, ulval);
+ }
+
+ /* Build the new line. */
+ memmove(bp, p, beg);
+ memmove(bp + beg, nbuf, nlen);
+ memmove(bp + beg + nlen, p + end, len - beg - (end - beg));
+ len = beg + nlen + (len - beg - (end - beg));
+
+ nret = NUM_OK;
+ rval = db_set(sp, vp->m_start.lno, bp, len);
+
+ if (0) {
+err: rval = 1;
+ inc_err(sp, nret);
+ }
+ if (bp != NULL)
+ FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
+
+static void
+inc_err(sp, nret)
+ SCR *sp;
+ enum nresult nret;
+{
+ switch (nret) {
+ case NUM_ERR:
+ break;
+ case NUM_OK:
+ abort();
+ /* NOREACHED */
+ case NUM_OVER:
+ msgq(sp, M_ERR, "182|Resulting number too large");
+ break;
+ case NUM_UNDER:
+ msgq(sp, M_ERR, "183|Resulting number too small");
+ break;
+ }
+}
diff --git a/contrib/nvi/vi/v_init.c b/contrib/nvi/vi/v_init.c
new file mode 100644
index 0000000..ee58de3
--- /dev/null
+++ b/contrib/nvi/vi/v_init.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[] = "@(#)v_init.c 10.8 (Berkeley) 3/30/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.h"
+
+/*
+ * v_screen_copy --
+ * Copy vi screen.
+ *
+ * PUBLIC: int v_screen_copy __P((SCR *, SCR *));
+ */
+int
+v_screen_copy(orig, sp)
+ SCR *orig, *sp;
+{
+ VI_PRIVATE *ovip, *nvip;
+
+ /* Create the private vi structure. */
+ CALLOC_RET(orig, nvip, VI_PRIVATE *, 1, sizeof(VI_PRIVATE));
+ sp->vi_private = nvip;
+
+ /* Invalidate the line size cache. */
+ VI_SCR_CFLUSH(nvip);
+
+ if (orig == NULL) {
+ nvip->csearchdir = CNOTSET;
+ } else {
+ ovip = VIP(orig);
+
+ /* User can replay the last input, but nothing else. */
+ if (ovip->rep_len != 0) {
+ MALLOC_RET(orig, nvip->rep, EVENT *, ovip->rep_len);
+ memmove(nvip->rep, ovip->rep, ovip->rep_len);
+ nvip->rep_len = ovip->rep_len;
+ }
+
+ /* Copy the paragraph/section information. */
+ if (ovip->ps != NULL && (nvip->ps =
+ v_strdup(sp, ovip->ps, strlen(ovip->ps))) == NULL)
+ return (1);
+
+ nvip->lastckey = ovip->lastckey;
+ nvip->csearchdir = ovip->csearchdir;
+
+ nvip->srows = ovip->srows;
+ }
+ return (0);
+}
+
+/*
+ * v_screen_end --
+ * End a vi screen.
+ *
+ * PUBLIC: int v_screen_end __P((SCR *));
+ */
+int
+v_screen_end(sp)
+ SCR *sp;
+{
+ VI_PRIVATE *vip;
+
+ if ((vip = VIP(sp)) == NULL)
+ return (0);
+ if (vip->keyw != NULL)
+ free(vip->keyw);
+ if (vip->rep != NULL)
+ free(vip->rep);
+ if (vip->ps != NULL)
+ free(vip->ps);
+
+ if (HMAP != NULL)
+ free(HMAP);
+
+ free(vip);
+ sp->vi_private = NULL;
+
+ return (0);
+}
+
+/*
+ * v_optchange --
+ * Handle change of options for vi.
+ *
+ * PUBLIC: int v_optchange __P((SCR *, int, char *, u_long *));
+ */
+int
+v_optchange(sp, offset, str, valp)
+ SCR *sp;
+ int offset;
+ char *str;
+ u_long *valp;
+{
+ switch (offset) {
+ case O_PARAGRAPHS:
+ return (v_buildps(sp, str, O_STR(sp, O_SECTIONS)));
+ case O_SECTIONS:
+ return (v_buildps(sp, O_STR(sp, O_PARAGRAPHS), str));
+ case O_WINDOW:
+ return (vs_crel(sp, *valp));
+ }
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_itxt.c b/contrib/nvi/vi/v_itxt.c
new file mode 100644
index 0000000..6cf9377
--- /dev/null
+++ b/contrib/nvi/vi/v_itxt.c
@@ -0,0 +1,537 @@
+/*-
+ * 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[] = "@(#)v_itxt.c 10.16 (Berkeley) 10/23/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * !!!
+ * Repeated input in the historic vi is mostly wrong and this isn't very
+ * backward compatible. For example, if the user entered "3Aab\ncd" in
+ * the historic vi, the "ab" was repeated 3 times, and the "\ncd" was then
+ * appended to the result. There was also a hack which I don't remember
+ * right now, where "3o" would open 3 lines and then let the user fill them
+ * in, to make screen movements on 300 baud modems more tolerable. I don't
+ * think it's going to be missed.
+ *
+ * !!!
+ * There's a problem with the way that we do logging for change commands with
+ * implied motions (e.g. A, I, O, cc, etc.). Since the main vi loop logs the
+ * starting cursor position before the change command "moves" the cursor, the
+ * cursor position to which we return on undo will be where the user entered
+ * the change command, not the start of the change. Several of the following
+ * routines re-log the cursor to make this work correctly. Historic vi tried
+ * to do the same thing, and mostly got it right. (The only spectacular way
+ * it fails is if the user entered 'o' from anywhere but the last character of
+ * the line, the undo returned the cursor to the start of the line. If the
+ * user was on the last character of the line, the cursor returned to that
+ * position.) We also check for mapped keys waiting, i.e. if we're in the
+ * middle of a map, don't bother logging the cursor.
+ */
+#define LOG_CORRECT { \
+ if (!MAPPED_KEYS_WAITING(sp)) \
+ (void)log_cursor(sp); \
+}
+
+static u_int32_t set_txt_std __P((SCR *, VICMD *, u_int32_t));
+
+/*
+ * v_iA -- [count]A
+ * Append text to the end of the line.
+ *
+ * PUBLIC: int v_iA __P((SCR *, VICMD *));
+ */
+int
+v_iA(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+
+ if (!db_get(sp, vp->m_start.lno, 0, NULL, &len))
+ sp->cno = len == 0 ? 0 : len - 1;
+
+ LOG_CORRECT;
+
+ return (v_ia(sp, vp));
+}
+
+/*
+ * v_ia -- [count]a
+ * [count]A
+ * Append text to the cursor position.
+ *
+ * PUBLIC: int v_ia __P((SCR *, VICMD *));
+ */
+int
+v_ia(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ u_int32_t flags;
+ int isempty;
+ char *p;
+
+ flags = set_txt_std(sp, vp, 0);
+ sp->showmode = SM_APPEND;
+ sp->lno = vp->m_start.lno;
+
+ /* Move the cursor one column to the right and repaint the screen. */
+ if (db_eget(sp, sp->lno, &p, &len, &isempty)) {
+ if (!isempty)
+ return (1);
+ len = 0;
+ LF_SET(TXT_APPENDEOL);
+ } else if (len) {
+ if (len == sp->cno + 1) {
+ sp->cno = len;
+ LF_SET(TXT_APPENDEOL);
+ } else
+ ++sp->cno;
+ } else
+ LF_SET(TXT_APPENDEOL);
+
+ return (v_txt(sp, vp, NULL, p, len,
+ 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));
+}
+
+/*
+ * v_iI -- [count]I
+ * Insert text at the first nonblank.
+ *
+ * PUBLIC: int v_iI __P((SCR *, VICMD *));
+ */
+int
+v_iI(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ sp->cno = 0;
+ if (nonblank(sp, vp->m_start.lno, &sp->cno))
+ return (1);
+
+ LOG_CORRECT;
+
+ return (v_ii(sp, vp));
+}
+
+/*
+ * v_ii -- [count]i
+ * [count]I
+ * Insert text at the cursor position.
+ *
+ * PUBLIC: int v_ii __P((SCR *, VICMD *));
+ */
+int
+v_ii(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ u_int32_t flags;
+ int isempty;
+ char *p;
+
+ flags = set_txt_std(sp, vp, 0);
+ sp->showmode = SM_INSERT;
+ sp->lno = vp->m_start.lno;
+
+ if (db_eget(sp, sp->lno, &p, &len, &isempty)) {
+ if (!isempty)
+ return (1);
+ len = 0;
+ }
+
+ if (len == 0)
+ LF_SET(TXT_APPENDEOL);
+ return (v_txt(sp, vp, NULL, p, len,
+ 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));
+}
+
+enum which { o_cmd, O_cmd };
+static int io __P((SCR *, VICMD *, enum which));
+
+/*
+ * v_iO -- [count]O
+ * Insert text above this line.
+ *
+ * PUBLIC: int v_iO __P((SCR *, VICMD *));
+ */
+int
+v_iO(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (io(sp, vp, O_cmd));
+}
+
+/*
+ * v_io -- [count]o
+ * Insert text after this line.
+ *
+ * PUBLIC: int v_io __P((SCR *, VICMD *));
+ */
+int
+v_io(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (io(sp, vp, o_cmd));
+}
+
+static int
+io(sp, vp, cmd)
+ SCR *sp;
+ VICMD *vp;
+ enum which cmd;
+{
+ recno_t ai_line, lno;
+ size_t len;
+ u_int32_t flags;
+ char *p;
+
+ flags = set_txt_std(sp, vp, TXT_ADDNEWLINE | TXT_APPENDEOL);
+ sp->showmode = SM_INSERT;
+
+ if (sp->lno == 1) {
+ if (db_last(sp, &lno))
+ return (1);
+ if (lno != 0)
+ goto insert;
+ p = NULL;
+ len = 0;
+ ai_line = OOBLNO;
+ } else {
+insert: p = "";
+ sp->cno = 0;
+ LOG_CORRECT;
+
+ if (cmd == O_cmd) {
+ if (db_insert(sp, sp->lno, p, 0))
+ return (1);
+ if (db_get(sp, sp->lno, DBG_FATAL, &p, &len))
+ return (1);
+ ai_line = sp->lno + 1;
+ } else {
+ if (db_append(sp, 1, sp->lno, p, 0))
+ return (1);
+ if (db_get(sp, ++sp->lno, DBG_FATAL, &p, &len))
+ return (1);
+ ai_line = sp->lno - 1;
+ }
+ }
+ return (v_txt(sp, vp, NULL, p, len,
+ 0, ai_line, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));
+}
+
+/*
+ * v_change -- [buffer][count]c[count]motion
+ * [buffer][count]C
+ * [buffer][count]S
+ * Change command.
+ *
+ * PUBLIC: int v_change __P((SCR *, VICMD *));
+ */
+int
+v_change(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t blen, len;
+ u_int32_t flags;
+ int isempty, lmode, rval;
+ char *bp, *p;
+
+ /*
+ * 'c' can be combined with motion commands that set the resulting
+ * cursor position, i.e. "cG". Clear the VM_RCM flags and make the
+ * resulting cursor position stick, inserting text has its own rules
+ * for cursor positioning.
+ */
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SET);
+
+ /*
+ * Find out if the file is empty, it's easier to handle it as a
+ * special case.
+ */
+ if (vp->m_start.lno == vp->m_stop.lno &&
+ db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (!isempty)
+ return (1);
+ return (v_ia(sp, vp));
+ }
+
+ flags = set_txt_std(sp, vp, 0);
+ sp->showmode = SM_CHANGE;
+
+ /*
+ * Move the cursor to the start of the change. Note, if autoindent
+ * is turned on, the cc command in line mode changes from the first
+ * *non-blank* character of the line, not the first character. And,
+ * to make it just a bit more exciting, the initial space is handled
+ * as auto-indent characters.
+ */
+ lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0;
+ if (lmode) {
+ vp->m_start.cno = 0;
+ if (O_ISSET(sp, O_AUTOINDENT)) {
+ if (nonblank(sp, vp->m_start.lno, &vp->m_start.cno))
+ return (1);
+ LF_SET(TXT_AICHARS);
+ }
+ }
+ sp->lno = vp->m_start.lno;
+ sp->cno = vp->m_start.cno;
+
+ LOG_CORRECT;
+
+ /*
+ * If not in line mode and changing within a single line, copy the
+ * text and overwrite it.
+ */
+ if (!lmode && vp->m_start.lno == vp->m_stop.lno) {
+ /*
+ * !!!
+ * Historic practice, c did not cut into the numeric buffers,
+ * only the unnamed one.
+ */
+ if (cut(sp,
+ F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
+ &vp->m_start, &vp->m_stop, lmode))
+ return (1);
+ if (len == 0)
+ LF_SET(TXT_APPENDEOL);
+ LF_SET(TXT_EMARK | TXT_OVERWRITE);
+ return (v_txt(sp, vp, &vp->m_stop, p, len,
+ 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));
+ }
+
+ /*
+ * It's trickier if in line mode or changing over multiple lines. If
+ * we're in line mode delete all of the lines and insert a replacement
+ * line which the user edits. If there was leading whitespace in the
+ * first line being changed, we copy it and use it as the replacement.
+ * If we're not in line mode, we delete the text and start inserting.
+ *
+ * !!!
+ * Copy the text. Historic practice, c did not cut into the numeric
+ * buffers, only the unnamed one.
+ */
+ if (cut(sp,
+ F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
+ &vp->m_start, &vp->m_stop, lmode))
+ return (1);
+
+ /* If replacing entire lines and there's leading text. */
+ if (lmode && vp->m_start.cno) {
+ /*
+ * Get a copy of the first line changed, and copy out the
+ * leading text.
+ */
+ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len))
+ return (1);
+ GET_SPACE_RET(sp, bp, blen, vp->m_start.cno);
+ memmove(bp, p, vp->m_start.cno);
+ } else
+ bp = NULL;
+
+ /* Delete the text. */
+ if (del(sp, &vp->m_start, &vp->m_stop, lmode))
+ return (1);
+
+ /* If replacing entire lines, insert a replacement line. */
+ if (lmode) {
+ if (db_insert(sp, vp->m_start.lno, bp, vp->m_start.cno))
+ return (1);
+ sp->lno = vp->m_start.lno;
+ len = sp->cno = vp->m_start.cno;
+ }
+
+ /* Get the line we're editing. */
+ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (!isempty)
+ return (1);
+ len = 0;
+ }
+
+ /* Check to see if we're appending to the line. */
+ if (vp->m_start.cno >= len)
+ LF_SET(TXT_APPENDEOL);
+
+ rval = v_txt(sp, vp, NULL, p, len,
+ 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags);
+
+ if (bp != NULL)
+ FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
+
+/*
+ * v_Replace -- [count]R
+ * Overwrite multiple characters.
+ *
+ * PUBLIC: int v_Replace __P((SCR *, VICMD *));
+ */
+int
+v_Replace(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ u_int32_t flags;
+ int isempty;
+ char *p;
+
+ flags = set_txt_std(sp, vp, 0);
+ sp->showmode = SM_REPLACE;
+
+ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (!isempty)
+ return (1);
+ len = 0;
+ LF_SET(TXT_APPENDEOL);
+ } else {
+ if (len == 0)
+ LF_SET(TXT_APPENDEOL);
+ LF_SET(TXT_OVERWRITE | TXT_REPLACE);
+ }
+ vp->m_stop.lno = vp->m_start.lno;
+ vp->m_stop.cno = len ? len - 1 : 0;
+
+ return (v_txt(sp, vp, &vp->m_stop, p, len,
+ 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));
+}
+
+/*
+ * v_subst -- [buffer][count]s
+ * Substitute characters.
+ *
+ * PUBLIC: int v_subst __P((SCR *, VICMD *));
+ */
+int
+v_subst(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ u_int32_t flags;
+ int isempty;
+ char *p;
+
+ flags = set_txt_std(sp, vp, 0);
+ sp->showmode = SM_CHANGE;
+
+ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (!isempty)
+ return (1);
+ len = 0;
+ LF_SET(TXT_APPENDEOL);
+ } else {
+ if (len == 0)
+ LF_SET(TXT_APPENDEOL);
+ LF_SET(TXT_EMARK | TXT_OVERWRITE);
+ }
+
+ vp->m_stop.lno = vp->m_start.lno;
+ vp->m_stop.cno =
+ vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0);
+ if (vp->m_stop.cno > len - 1)
+ vp->m_stop.cno = len - 1;
+
+ if (p != NULL && cut(sp,
+ F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
+ &vp->m_start, &vp->m_stop, 0))
+ return (1);
+
+ return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, 1, flags));
+}
+
+/*
+ * set_txt_std --
+ * Initialize text processing flags.
+ */
+static u_int32_t
+set_txt_std(sp, vp, flags)
+ SCR *sp;
+ VICMD *vp;
+ u_int32_t flags;
+{
+ LF_SET(TXT_CNTRLT |
+ TXT_ESCAPE | TXT_MAPINPUT | TXT_RECORD | TXT_RESOLVE);
+
+ if (F_ISSET(vp, VC_ISDOT))
+ LF_SET(TXT_REPLAY);
+
+ if (O_ISSET(sp, O_ALTWERASE))
+ LF_SET(TXT_ALTWERASE);
+ if (O_ISSET(sp, O_AUTOINDENT))
+ LF_SET(TXT_AUTOINDENT);
+ if (O_ISSET(sp, O_BEAUTIFY))
+ LF_SET(TXT_BEAUTIFY);
+ if (O_ISSET(sp, O_SHOWMATCH))
+ LF_SET(TXT_SHOWMATCH);
+ if (F_ISSET(sp, SC_SCRIPT))
+ LF_SET(TXT_CR);
+ if (O_ISSET(sp, O_TTYWERASE))
+ LF_SET(TXT_TTYWERASE);
+
+ /*
+ * !!!
+ * Mapped keys were sometimes unaffected by the wrapmargin option
+ * in the historic 4BSD vi. Consider the following commands, where
+ * each is executed on an empty line, in an 80 column screen, with
+ * the wrapmargin value set to 60.
+ *
+ * aABC DEF <ESC>....
+ * :map K aABC DEF ^V<ESC><CR>KKKKK
+ * :map K 5aABC DEF ^V<ESC><CR>K
+ *
+ * The first and second commands are affected by wrapmargin. The
+ * third is not. (If the inserted text is itself longer than the
+ * wrapmargin value, i.e. if the "ABC DEF " string is replaced by
+ * something that's longer than 60 columns from the beginning of
+ * the line, the first two commands behave as before, but the third
+ * command gets fairly strange.) The problem is that people wrote
+ * macros that depended on the third command NOT being affected by
+ * wrapmargin, as in this gem which centers lines:
+ *
+ * map #c $mq81a ^V^[81^V^V|D`qld0:s/ / /g^V^M$p
+ *
+ * For compatibility reasons, we try and make it all work here. I
+ * offer no hope that this is right, but it's probably pretty close.
+ *
+ * XXX
+ * Once I work my courage up, this is all gonna go away. It's too
+ * evil to survive.
+ */
+ if ((O_ISSET(sp, O_WRAPLEN) || O_ISSET(sp, O_WRAPMARGIN)) &&
+ (!MAPPED_KEYS_WAITING(sp) || !F_ISSET(vp, VC_C1SET)))
+ LF_SET(TXT_WRAPMARGIN);
+ return (flags);
+}
diff --git a/contrib/nvi/vi/v_left.c b/contrib/nvi/vi/v_left.c
new file mode 100644
index 0000000..cfed80c
--- /dev/null
+++ b/contrib/nvi/vi/v_left.c
@@ -0,0 +1,293 @@
+/*-
+ * 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[] = "@(#)v_left.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 <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_left -- [count]^H, [count]h
+ * Move left by columns.
+ *
+ * PUBLIC: int v_left __P((SCR *, VICMD *));
+ */
+int
+v_left(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t cnt;
+
+ /*
+ * !!!
+ * The ^H and h commands always failed in the first column.
+ */
+ if (vp->m_start.cno == 0) {
+ v_sol(sp);
+ return (1);
+ }
+
+ /* Find the end of the range. */
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ if (vp->m_start.cno > cnt)
+ vp->m_stop.cno = vp->m_start.cno - cnt;
+ else
+ vp->m_stop.cno = 0;
+
+ /*
+ * All commands move to the end of the range. Motion commands
+ * adjust the starting point to the character before the current
+ * one.
+ */
+ if (ISMOTION(vp))
+ --vp->m_start.cno;
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_cfirst -- [count]_
+ * Move to the first non-blank character in a line.
+ *
+ * PUBLIC: int v_cfirst __P((SCR *, VICMD *));
+ */
+int
+v_cfirst(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t cnt, lno;
+
+ /*
+ * !!!
+ * If the _ is a motion component, it makes the command a line motion
+ * e.g. "d_" deletes the line. It also means that the cursor doesn't
+ * move.
+ *
+ * The _ command never failed in the first column.
+ */
+ if (ISMOTION(vp))
+ F_SET(vp, VM_LMODE);
+ /*
+ * !!!
+ * Historically a specified count makes _ move down count - 1
+ * rows, so, "3_" is the same as "2j_".
+ */
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ if (cnt != 1) {
+ --vp->count;
+ return (v_down(sp, vp));
+ }
+
+ /*
+ * Move to the first non-blank.
+ *
+ * Can't just use RCM_SET_FNB, in case _ is used as the motion
+ * component of another command.
+ */
+ vp->m_stop.cno = 0;
+ if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))
+ return (1);
+
+ /*
+ * !!!
+ * The _ command has to fail if the file is empty and we're doing
+ * a delete. If deleting line 1, and 0 is the first nonblank,
+ * make the check.
+ */
+ if (vp->m_stop.lno == 1 &&
+ vp->m_stop.cno == 0 && ISCMD(vp->rkp, 'd')) {
+ if (db_last(sp, &lno))
+ return (1);
+ if (lno == 0) {
+ v_sol(sp);
+ return (1);
+ }
+ }
+
+ /*
+ * Delete and non-motion commands move to the end of the range,
+ * yank stays at the start. Ignore others.
+ */
+ vp->m_final =
+ ISMOTION(vp) && ISCMD(vp->rkp, 'y') ? vp->m_start : vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_first -- ^
+ * Move to the first non-blank character in this line.
+ *
+ * PUBLIC: int v_first __P((SCR *, VICMD *));
+ */
+int
+v_first(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * !!!
+ * Yielding to none in our quest for compatibility with every
+ * historical blemish of vi, no matter how strange it might be,
+ * we permit the user to enter a count and then ignore it.
+ */
+
+ /*
+ * Move to the first non-blank.
+ *
+ * Can't just use RCM_SET_FNB, in case ^ is used as the motion
+ * component of another command.
+ */
+ vp->m_stop.cno = 0;
+ if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))
+ return (1);
+
+ /*
+ * !!!
+ * The ^ command succeeded if used as a command when the cursor was
+ * on the first non-blank in the line, but failed if used as a motion
+ * component in the same situation.
+ */
+ if (ISMOTION(vp) && vp->m_start.cno == vp->m_stop.cno) {
+ v_sol(sp);
+ return (1);
+ }
+
+ /*
+ * If moving right, non-motion commands move to the end of the range.
+ * Delete and yank stay at the start. Motion commands adjust the
+ * ending point to the character before the current ending charcter.
+ *
+ * If moving left, all commands move to the end of the range. Motion
+ * commands adjust the starting point to the character before the
+ * current starting character.
+ */
+ if (vp->m_start.cno < vp->m_stop.cno)
+ if (ISMOTION(vp)) {
+ --vp->m_stop.cno;
+ vp->m_final = vp->m_start;
+ } else
+ vp->m_final = vp->m_stop;
+ else {
+ if (ISMOTION(vp))
+ --vp->m_start.cno;
+ vp->m_final = vp->m_stop;
+ }
+ return (0);
+}
+
+/*
+ * v_ncol -- [count]|
+ * Move to column count or the first column on this line. If the
+ * requested column is past EOL, move to EOL. The nasty part is
+ * that we have to know character column widths to make this work.
+ *
+ * PUBLIC: int v_ncol __P((SCR *, VICMD *));
+ */
+int
+v_ncol(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ if (F_ISSET(vp, VC_C1SET) && vp->count > 1) {
+ --vp->count;
+ vp->m_stop.cno =
+ vs_colpos(sp, vp->m_start.lno, (size_t)vp->count);
+ /*
+ * !!!
+ * The | command succeeded if used as a command and the cursor
+ * didn't move, but failed if used as a motion component in the
+ * same situation.
+ */
+ if (ISMOTION(vp) && vp->m_stop.cno == vp->m_start.cno) {
+ v_nomove(sp);
+ return (1);
+ }
+ } else {
+ /*
+ * !!!
+ * The | command succeeded if used as a command in column 0
+ * without a count, but failed if used as a motion component
+ * in the same situation.
+ */
+ if (ISMOTION(vp) && vp->m_start.cno == 0) {
+ v_sol(sp);
+ return (1);
+ }
+ vp->m_stop.cno = 0;
+ }
+
+ /*
+ * If moving right, non-motion commands move to the end of the range.
+ * Delete and yank stay at the start. Motion commands adjust the
+ * ending point to the character before the current ending charcter.
+ *
+ * If moving left, all commands move to the end of the range. Motion
+ * commands adjust the starting point to the character before the
+ * current starting character.
+ */
+ if (vp->m_start.cno < vp->m_stop.cno)
+ if (ISMOTION(vp)) {
+ --vp->m_stop.cno;
+ vp->m_final = vp->m_start;
+ } else
+ vp->m_final = vp->m_stop;
+ else {
+ if (ISMOTION(vp))
+ --vp->m_start.cno;
+ vp->m_final = vp->m_stop;
+ }
+ return (0);
+}
+
+/*
+ * v_zero -- 0
+ * Move to the first column on this line.
+ *
+ * PUBLIC: int v_zero __P((SCR *, VICMD *));
+ */
+int
+v_zero(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * !!!
+ * The 0 command succeeded if used as a command in the first column
+ * but failed if used as a motion component in the same situation.
+ */
+ if (ISMOTION(vp) && vp->m_start.cno == 0) {
+ v_sol(sp);
+ return (1);
+ }
+
+ /*
+ * All commands move to the end of the range. Motion commands
+ * adjust the starting point to the character before the current
+ * one.
+ */
+ vp->m_stop.cno = 0;
+ if (ISMOTION(vp))
+ --vp->m_start.cno;
+ vp->m_final = vp->m_stop;
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_mark.c b/contrib/nvi/vi/v_mark.c
new file mode 100644
index 0000000..447430e
--- /dev/null
+++ b/contrib/nvi/vi/v_mark.c
@@ -0,0 +1,234 @@
+/*-
+ * 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[] = "@(#)v_mark.c 10.8 (Berkeley) 9/20/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_mark -- m[a-z]
+ * Set a mark.
+ *
+ * PUBLIC: int v_mark __P((SCR *, VICMD *));
+ */
+int
+v_mark(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (mark_set(sp, vp->character, &vp->m_start, 1));
+}
+
+enum which {BQMARK, FQMARK};
+static int mark __P((SCR *, VICMD *, enum which));
+
+
+/*
+ * v_bmark -- `['`a-z]
+ * Move to a mark.
+ *
+ * Moves to a mark, setting both row and column.
+ *
+ * !!!
+ * Although not commonly known, the "'`" and "'`" forms are historically
+ * valid. The behavior is determined by the first character, so "`'" is
+ * the same as "``". Remember this fact -- you'll be amazed at how many
+ * people don't know it and will be delighted that you are able to tell
+ * them.
+ *
+ * PUBLIC: int v_bmark __P((SCR *, VICMD *));
+ */
+int
+v_bmark(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (mark(sp, vp, BQMARK));
+}
+
+/*
+ * v_fmark -- '['`a-z]
+ * Move to a mark.
+ *
+ * Move to the first nonblank character of the line containing the mark.
+ *
+ * PUBLIC: int v_fmark __P((SCR *, VICMD *));
+ */
+int
+v_fmark(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (mark(sp, vp, FQMARK));
+}
+
+/*
+ * mark --
+ * Mark commands.
+ */
+static int
+mark(sp, vp, cmd)
+ SCR *sp;
+ VICMD *vp;
+ enum which cmd;
+{
+ dir_t dir;
+ MARK m;
+ size_t len;
+
+ if (mark_get(sp, vp->character, &vp->m_stop, M_BERR))
+ return (1);
+
+ /*
+ * !!!
+ * Historically, BQMARKS for character positions that no longer
+ * existed acted as FQMARKS.
+ *
+ * FQMARKS move to the first non-blank.
+ */
+ switch (cmd) {
+ case BQMARK:
+ if (db_get(sp, vp->m_stop.lno, DBG_FATAL, NULL, &len))
+ return (1);
+ if (vp->m_stop.cno < len ||
+ vp->m_stop.cno == len && len == 0)
+ break;
+
+ if (ISMOTION(vp))
+ F_SET(vp, VM_LMODE);
+ cmd = FQMARK;
+ /* FALLTHROUGH */
+ case FQMARK:
+ vp->m_stop.cno = 0;
+ if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))
+ return (1);
+ break;
+ default:
+ abort();
+ }
+
+ /* Non-motion commands move to the end of the range. */
+ if (!ISMOTION(vp)) {
+ vp->m_final = vp->m_stop;
+ return (0);
+ }
+
+ /*
+ * !!!
+ * If a motion component to a BQMARK, the cursor has to move.
+ */
+ if (cmd == BQMARK &&
+ vp->m_stop.lno == vp->m_start.lno &&
+ vp->m_stop.cno == vp->m_start.cno) {
+ v_nomove(sp);
+ return (1);
+ }
+
+ /*
+ * If the motion is in the reverse direction, switch the start and
+ * stop MARK's so that it's in a forward direction. (There's no
+ * reason for this other than to make the tests below easier. The
+ * code in vi.c:vi() would have done the switch.) Both forward
+ * and backward motions can happen for any kind of search command.
+ */
+ if (vp->m_start.lno > vp->m_stop.lno ||
+ vp->m_start.lno == vp->m_stop.lno &&
+ vp->m_start.cno > vp->m_stop.cno) {
+ m = vp->m_start;
+ vp->m_start = vp->m_stop;
+ vp->m_stop = m;
+ dir = BACKWARD;
+ } else
+ dir = FORWARD;
+
+ /*
+ * Yank cursor motion, when associated with marks as motion commands,
+ * historically behaved as follows:
+ *
+ * ` motion ' motion
+ * Line change? Line change?
+ * Y N Y N
+ * -------------- ---------------
+ * FORWARD: | NM NM | NM NM
+ * | |
+ * BACKWARD: | M M | M NM(1)
+ *
+ * where NM means the cursor didn't move, and M means the cursor
+ * moved to the mark.
+ *
+ * As the cursor was usually moved for yank commands associated
+ * with backward motions, this implementation regularizes it by
+ * changing the NM at position (1) to be an M. This makes mark
+ * motions match search motions, which is probably A Good Thing.
+ *
+ * Delete cursor motion was always to the start of the text region,
+ * regardless. Ignore other motion commands.
+ */
+#ifdef HISTORICAL_PRACTICE
+ if (ISCMD(vp->rkp, 'y')) {
+ if ((cmd == BQMARK ||
+ cmd == FQMARK && vp->m_start.lno != vp->m_stop.lno) &&
+ (vp->m_start.lno > vp->m_stop.lno ||
+ vp->m_start.lno == vp->m_stop.lno &&
+ vp->m_start.cno > vp->m_stop.cno))
+ vp->m_final = vp->m_stop;
+ } else if (ISCMD(vp->rkp, 'd'))
+ if (vp->m_start.lno > vp->m_stop.lno ||
+ vp->m_start.lno == vp->m_stop.lno &&
+ vp->m_start.cno > vp->m_stop.cno)
+ vp->m_final = vp->m_stop;
+#else
+ vp->m_final = vp->m_start;
+#endif
+
+ /*
+ * Forward marks are always line oriented, and it's set in the
+ * vcmd.c table.
+ */
+ if (cmd == FQMARK)
+ return (0);
+
+ /*
+ * BQMARK'S moving backward and starting at column 0, and ones moving
+ * forward and ending at column 0 are corrected to the last column of
+ * the previous line. Otherwise, adjust the starting/ending point to
+ * the character before the current one (this is safe because we know
+ * the search had to move to succeed).
+ *
+ * Mark motions become line mode opertions if they start at the first
+ * nonblank and end at column 0 of another line.
+ */
+ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) {
+ if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len))
+ return (1);
+ vp->m_stop.cno = len ? len - 1 : 0;
+ len = 0;
+ if (nonblank(sp, vp->m_start.lno, &len))
+ return (1);
+ if (vp->m_start.cno <= len)
+ F_SET(vp, VM_LMODE);
+ } else
+ --vp->m_stop.cno;
+
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_match.c b/contrib/nvi/vi/v_match.c
new file mode 100644
index 0000000..3996560
--- /dev/null
+++ b/contrib/nvi/vi/v_match.c
@@ -0,0 +1,170 @@
+/*-
+ * 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[] = "@(#)v_match.c 10.8 (Berkeley) 3/6/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 <string.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_match -- %
+ * Search to matching character.
+ *
+ * PUBLIC: int v_match __P((SCR *, VICMD *));
+ */
+int
+v_match(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ VCS cs;
+ MARK *mp;
+ size_t cno, len, off;
+ int cnt, isempty, matchc, startc, (*gc)__P((SCR *, VCS *));
+ char *p;
+
+ /*
+ * !!!
+ * Historic practice; ignore the count.
+ *
+ * !!!
+ * Historical practice was to search for the initial character in the
+ * forward direction only.
+ */
+ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (isempty)
+ goto nomatch;
+ return (1);
+ }
+ for (off = vp->m_start.cno;; ++off) {
+ if (off >= len) {
+nomatch: msgq(sp, M_BERR, "184|No match character on this line");
+ return (1);
+ }
+ switch (startc = p[off]) {
+ case '(':
+ matchc = ')';
+ gc = cs_next;
+ break;
+ case ')':
+ matchc = '(';
+ gc = cs_prev;
+ break;
+ case '[':
+ matchc = ']';
+ gc = cs_next;
+ break;
+ case ']':
+ matchc = '[';
+ gc = cs_prev;
+ break;
+ case '{':
+ matchc = '}';
+ gc = cs_next;
+ break;
+ case '}':
+ matchc = '{';
+ gc = cs_prev;
+ break;
+ case '<':
+ matchc = '>';
+ gc = cs_next;
+ break;
+ case '>':
+ matchc = '<';
+ gc = cs_prev;
+ break;
+ default:
+ continue;
+ }
+ break;
+ }
+
+ cs.cs_lno = vp->m_start.lno;
+ cs.cs_cno = off;
+ if (cs_init(sp, &cs))
+ return (1);
+ for (cnt = 1;;) {
+ if (gc(sp, &cs))
+ return (1);
+ if (cs.cs_flags != 0) {
+ if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF)
+ break;
+ continue;
+ }
+ if (cs.cs_ch == startc)
+ ++cnt;
+ else if (cs.cs_ch == matchc && --cnt == 0)
+ break;
+ }
+ if (cnt) {
+ msgq(sp, M_BERR, "185|Matching character not found");
+ return (1);
+ }
+
+ vp->m_stop.lno = cs.cs_lno;
+ vp->m_stop.cno = cs.cs_cno;
+
+ /*
+ * If moving right, non-motion commands move to the end of the range.
+ * Delete and yank stay at the start.
+ *
+ * If moving left, all commands move to the end of the range.
+ *
+ * !!!
+ * Don't correct for leftward movement -- historic vi deleted the
+ * starting cursor position when deleting to a match.
+ */
+ if (vp->m_start.lno < vp->m_stop.lno ||
+ vp->m_start.lno == vp->m_stop.lno &&
+ vp->m_start.cno < vp->m_stop.cno)
+ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
+ else
+ vp->m_final = vp->m_stop;
+
+ /*
+ * !!!
+ * If the motion is across lines, and the earliest cursor position
+ * is at or before any non-blank characters in the line, i.e. the
+ * movement is cutting all of the line's text, and the later cursor
+ * position has nothing other than whitespace characters between it
+ * and the end of its line, the buffer is in line mode.
+ */
+ if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno)
+ return (0);
+ mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop;
+ if (mp->cno != 0) {
+ cno = 0;
+ if (nonblank(sp, mp->lno, &cno))
+ return (1);
+ if (cno < mp->cno)
+ return (0);
+ }
+ mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start;
+ if (db_get(sp, mp->lno, DBG_FATAL, &p, &len))
+ return (1);
+ for (p += mp->cno + 1, len -= mp->cno; --len; ++p)
+ if (!isblank(*p))
+ return (0);
+ F_SET(vp, VM_LMODE);
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_paragraph.c b/contrib/nvi/vi/v_paragraph.c
new file mode 100644
index 0000000..762e38e
--- /dev/null
+++ b/contrib/nvi/vi/v_paragraph.c
@@ -0,0 +1,344 @@
+/*-
+ * 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[] = "@(#)v_paragraph.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 <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+#define INTEXT_CHECK { \
+ if (len == 0 || v_isempty(p, len)) { \
+ if (!--cnt) \
+ goto found; \
+ pstate = P_INBLANK; \
+ } \
+ /* \
+ * !!! \
+ * Historic documentation (USD:15-11, 4.2) said that formfeed \
+ * characters (^L) in the first column delimited paragraphs. \
+ * The historic vi code mentions formfeed characters, but never \
+ * implements them. It seems reasonable, do it. \
+ */ \
+ if (p[0] == '\014') { \
+ if (!--cnt) \
+ goto found; \
+ continue; \
+ } \
+ if (p[0] != '.' || len < 2) \
+ continue; \
+ for (lp = VIP(sp)->ps; *lp != '\0'; lp += 2) \
+ if (lp[0] == p[1] && \
+ (lp[1] == ' ' && len == 2 || lp[1] == p[2]) && \
+ !--cnt) \
+ goto found; \
+}
+
+/*
+ * v_paragraphf -- [count]}
+ * Move forward count paragraphs.
+ *
+ * Paragraphs are empty lines after text, formfeed characters, or values
+ * from the paragraph or section options.
+ *
+ * PUBLIC: int v_paragraphf __P((SCR *, VICMD *));
+ */
+int
+v_paragraphf(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ enum { P_INTEXT, P_INBLANK } pstate;
+ size_t lastlen, len;
+ recno_t cnt, lastlno, lno;
+ int isempty;
+ char *p, *lp;
+
+ /*
+ * !!!
+ * If the starting cursor position is at or before any non-blank
+ * characters in the line, i.e. the movement is cutting all of the
+ * line's text, the buffer is in line mode. It's a lot easier to
+ * check here, because we know that the end is going to be the start
+ * or end of a line.
+ *
+ * This was historical practice in vi, with a single exception. If
+ * the paragraph movement was from the start of the last line to EOF,
+ * then all the characters were deleted from the last line, but the
+ * line itself remained. If somebody complains, don't pause, don't
+ * hesitate, just hit them.
+ */
+ if (ISMOTION(vp))
+ if (vp->m_start.cno == 0)
+ F_SET(vp, VM_LMODE);
+ else {
+ vp->m_stop = vp->m_start;
+ vp->m_stop.cno = 0;
+ if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))
+ return (1);
+ if (vp->m_start.cno <= vp->m_stop.cno)
+ F_SET(vp, VM_LMODE);
+ }
+
+ /* Figure out what state we're currently in. */
+ lno = vp->m_start.lno;
+ if (db_get(sp, lno, 0, &p, &len))
+ goto eof;
+
+ /*
+ * If we start in text, we want to switch states
+ * (2 * N - 1) times, in non-text, (2 * N) times.
+ */
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ cnt *= 2;
+ if (len == 0 || v_isempty(p, len))
+ pstate = P_INBLANK;
+ else {
+ --cnt;
+ pstate = P_INTEXT;
+ }
+
+ for (;;) {
+ lastlno = lno;
+ lastlen = len;
+ if (db_get(sp, ++lno, 0, &p, &len))
+ goto eof;
+ switch (pstate) {
+ case P_INTEXT:
+ INTEXT_CHECK;
+ break;
+ case P_INBLANK:
+ if (len == 0 || v_isempty(p, len))
+ break;
+ if (--cnt) {
+ pstate = P_INTEXT;
+ break;
+ }
+ /*
+ * !!!
+ * Non-motion commands move to the end of the range,
+ * delete and yank stay at the start. Ignore others.
+ * Adjust the end of the range for motion commands;
+ * historically, a motion component was to the end of
+ * the previous line, whereas the movement command was
+ * to the start of the new "paragraph".
+ */
+found: if (ISMOTION(vp)) {
+ vp->m_stop.lno = lastlno;
+ vp->m_stop.cno = lastlen ? lastlen - 1 : 0;
+ vp->m_final = vp->m_start;
+ } else {
+ vp->m_stop.lno = lno;
+ vp->m_stop.cno = 0;
+ vp->m_final = vp->m_stop;
+ }
+ return (0);
+ default:
+ abort();
+ }
+ }
+
+ /*
+ * !!!
+ * Adjust end of the range for motion commands; EOF is a movement
+ * sink. The } command historically moved to the end of the last
+ * line, not the beginning, from any position before the end of the
+ * last line. It also historically worked on empty files, so we
+ * have to make it okay.
+ */
+eof: if (vp->m_start.lno == lno || vp->m_start.lno == lno - 1) {
+ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
+ if (!isempty)
+ return (1);
+ vp->m_start.cno = 0;
+ return (0);
+ }
+ if (vp->m_start.cno == (len ? len - 1 : 0)) {
+ v_eof(sp, NULL);
+ return (1);
+ }
+ }
+ /*
+ * !!!
+ * Non-motion commands move to the end of the range, delete
+ * and yank stay at the start. Ignore others.
+ *
+ * If deleting the line (which happens if deleting to EOF), then
+ * cursor movement is to the first nonblank.
+ */
+ if (ISMOTION(vp) && ISCMD(vp->rkp, 'd')) {
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SETFNB);
+ }
+ vp->m_stop.lno = lno - 1;
+ vp->m_stop.cno = len ? len - 1 : 0;
+ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_paragraphb -- [count]{
+ * Move backward count paragraphs.
+ *
+ * PUBLIC: int v_paragraphb __P((SCR *, VICMD *));
+ */
+int
+v_paragraphb(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ enum { P_INTEXT, P_INBLANK } pstate;
+ size_t len;
+ recno_t cnt, lno;
+ char *p, *lp;
+
+ /*
+ * !!!
+ * Check for SOF. The historic vi didn't complain if users hit SOF
+ * repeatedly, unless it was part of a motion command. There is no
+ * question but that Emerson's editor of choice was vi.
+ *
+ * The { command historically moved to the beginning of the first
+ * line if invoked on the first line.
+ *
+ * !!!
+ * If the starting cursor position is in the first column (backward
+ * paragraph movements did NOT historically pay attention to non-blank
+ * characters) i.e. the movement is cutting the entire line, the buffer
+ * is in line mode. Cuts from the beginning of the line also did not
+ * cut the current line, but started at the previous EOL.
+ *
+ * Correct for a left motion component while we're thinking about it.
+ */
+ lno = vp->m_start.lno;
+
+ if (ISMOTION(vp))
+ if (vp->m_start.cno == 0) {
+ if (vp->m_start.lno == 1) {
+ v_sof(sp, &vp->m_start);
+ return (1);
+ } else
+ --vp->m_start.lno;
+ F_SET(vp, VM_LMODE);
+ } else
+ --vp->m_start.cno;
+
+ if (vp->m_start.lno <= 1)
+ goto sof;
+
+ /* Figure out what state we're currently in. */
+ if (db_get(sp, lno, 0, &p, &len))
+ goto sof;
+
+ /*
+ * If we start in text, we want to switch states
+ * (2 * N - 1) times, in non-text, (2 * N) times.
+ */
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ cnt *= 2;
+ if (len == 0 || v_isempty(p, len))
+ pstate = P_INBLANK;
+ else {
+ --cnt;
+ pstate = P_INTEXT;
+
+ /*
+ * !!!
+ * If the starting cursor is past the first column,
+ * the current line is checked for a paragraph.
+ */
+ if (vp->m_start.cno > 0)
+ ++lno;
+ }
+
+ for (;;) {
+ if (db_get(sp, --lno, 0, &p, &len))
+ goto sof;
+ switch (pstate) {
+ case P_INTEXT:
+ INTEXT_CHECK;
+ break;
+ case P_INBLANK:
+ if (len != 0 && !v_isempty(p, len)) {
+ if (!--cnt)
+ goto found;
+ pstate = P_INTEXT;
+ }
+ break;
+ default:
+ abort();
+ }
+ }
+
+ /* SOF is a movement sink. */
+sof: lno = 1;
+
+found: vp->m_stop.lno = lno;
+ vp->m_stop.cno = 0;
+
+ /*
+ * All commands move to the end of the range. (We already
+ * adjusted the start of the range for motion commands).
+ */
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_buildps --
+ * Build the paragraph command search pattern.
+ *
+ * PUBLIC: int v_buildps __P((SCR *, char *, char *));
+ */
+int
+v_buildps(sp, p_p, s_p)
+ SCR *sp;
+ char *p_p, *s_p;
+{
+ VI_PRIVATE *vip;
+ size_t p_len, s_len;
+ char *p;
+
+ /*
+ * The vi paragraph command searches for either a paragraph or
+ * section option macro.
+ */
+ p_len = p_p == NULL ? 0 : strlen(p_p);
+ s_len = s_p == NULL ? 0 : strlen(s_p);
+
+ if (p_len == 0 && s_len == 0)
+ return (0);
+
+ MALLOC_RET(sp, p, char *, p_len + s_len + 1);
+
+ vip = VIP(sp);
+ if (vip->ps != NULL)
+ free(vip->ps);
+
+ if (p_p != NULL)
+ memmove(p, p_p, p_len + 1);
+ if (s_p != NULL)
+ memmove(p + p_len, s_p, s_len + 1);
+ vip->ps = p;
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_put.c b/contrib/nvi/vi/v_put.c
new file mode 100644
index 0000000..77220ea
--- /dev/null
+++ b/contrib/nvi/vi/v_put.c
@@ -0,0 +1,146 @@
+/*-
+ * 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[] = "@(#)v_put.c 10.5 (Berkeley) 3/6/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 "../common/common.h"
+#include "vi.h"
+
+static void inc_buf __P((SCR *, VICMD *));
+
+/*
+ * v_Put -- [buffer]P
+ * Insert the contents of the buffer before the cursor.
+ *
+ * PUBLIC: int v_Put __P((SCR *, VICMD *));
+ */
+int
+v_Put(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ u_long cnt;
+
+ if (F_ISSET(vp, VC_ISDOT))
+ inc_buf(sp, vp);
+
+ /*
+ * !!!
+ * Historic vi did not support a count with the 'p' and 'P'
+ * commands. It's useful, so we do.
+ */
+ for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
+ if (put(sp, NULL,
+ F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
+ &vp->m_start, &vp->m_final, 0))
+ return (1);
+ vp->m_start = vp->m_final;
+ if (INTERRUPTED(sp))
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * v_put -- [buffer]p
+ * Insert the contents of the buffer after the cursor.
+ *
+ * PUBLIC: int v_put __P((SCR *, VICMD *));
+ */
+int
+v_put(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ u_long cnt;
+
+ if (F_ISSET(vp, VC_ISDOT))
+ inc_buf(sp, vp);
+
+ /*
+ * !!!
+ * Historic vi did not support a count with the 'p' and 'P'
+ * commands. It's useful, so we do.
+ */
+ for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
+ if (put(sp, NULL,
+ F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
+ &vp->m_start, &vp->m_final, 1))
+ return (1);
+ vp->m_start = vp->m_final;
+ if (INTERRUPTED(sp))
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * !!!
+ * Historical whackadoo. The dot command `puts' the numbered buffer
+ * after the last one put. For example, `"4p.' would put buffer #4
+ * and buffer #5. If the user continued to enter '.', the #9 buffer
+ * would be repeatedly output. This was not documented, and is a bit
+ * tricky to reconstruct. Historical versions of vi also dropped the
+ * contents of the default buffer after each put, so after `"4p' the
+ * default buffer would be empty. This makes no sense to me, so we
+ * don't bother. Don't assume sequential order of numeric characters.
+ *
+ * And, if that weren't exciting enough, failed commands don't normally
+ * set the dot command. Well, boys and girls, an exception is that
+ * the buffer increment gets done regardless of the success of the put.
+ */
+static void
+inc_buf(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ CHAR_T v;
+
+ switch (vp->buffer) {
+ case '1':
+ v = '2';
+ break;
+ case '2':
+ v = '3';
+ break;
+ case '3':
+ v = '4';
+ break;
+ case '4':
+ v = '5';
+ break;
+ case '5':
+ v = '6';
+ break;
+ case '6':
+ v = '7';
+ break;
+ case '7':
+ v = '8';
+ break;
+ case '8':
+ v = '9';
+ break;
+ default:
+ return;
+ }
+ VIP(sp)->sdot.buffer = vp->buffer = v;
+}
diff --git a/contrib/nvi/vi/v_redraw.c b/contrib/nvi/vi/v_redraw.c
new file mode 100644
index 0000000..4c965c7
--- /dev/null
+++ b/contrib/nvi/vi/v_redraw.c
@@ -0,0 +1,39 @@
+/*-
+ * 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[] = "@(#)v_redraw.c 10.6 (Berkeley) 3/6/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_redraw -- ^L, ^R
+ * Redraw the screen.
+ *
+ * PUBLIC: int v_redraw __P((SCR *, VICMD *));
+ */
+int
+v_redraw(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (sp->gp->scr_refresh(sp, 1));
+}
diff --git a/contrib/nvi/vi/v_replace.c b/contrib/nvi/vi/v_replace.c
new file mode 100644
index 0000000..a4712b6
--- /dev/null
+++ b/contrib/nvi/vi/v_replace.c
@@ -0,0 +1,203 @@
+/*-
+ * 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[] = "@(#)v_replace.c 10.17 (Berkeley) 6/30/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_replace -- [count]r<char>
+ *
+ * !!!
+ * The r command in historic vi was almost beautiful in its badness. For
+ * example, "r<erase>" and "r<word erase>" beeped the terminal and deleted
+ * a single character. "Nr<carriage return>", where N was greater than 1,
+ * inserted a single carriage return. "r<escape>" did cancel the command,
+ * but "r<literal><escape>" erased a single character. To enter a literal
+ * <literal> character, it required three <literal> characters after the
+ * command. This may not be right, but at least it's not insane.
+ *
+ * PUBLIC: int v_replace __P((SCR *, VICMD *));
+ */
+int
+v_replace(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ EVENT ev;
+ VI_PRIVATE *vip;
+ TEXT *tp;
+ size_t blen, len;
+ u_long cnt;
+ int quote, rval;
+ char *bp, *p;
+
+ vip = VIP(sp);
+
+ /*
+ * If the line doesn't exist, or it's empty, replacement isn't
+ * allowed. It's not hard to implement, but:
+ *
+ * 1: It's historic practice (vi beeped before the replacement
+ * character was even entered).
+ * 2: For consistency, this change would require that the more
+ * general case, "Nr", when the user is < N characters from
+ * the end of the line, also work, which would be a bit odd.
+ * 3: Replacing with a <newline> has somewhat odd semantics.
+ */
+ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len))
+ return (1);
+ if (len == 0) {
+ msgq(sp, M_BERR, "186|No characters to replace");
+ return (1);
+ }
+
+ /*
+ * Figure out how many characters to be replace. For no particular
+ * reason (other than that the semantics of replacing the newline
+ * are confusing) only permit the replacement of the characters in
+ * the current line. I suppose we could append replacement characters
+ * to the line, but I see no compelling reason to do so. Check this
+ * before we get the character to match historic practice, where Nr
+ * failed immediately if there were less than N characters from the
+ * cursor to the end of the line.
+ */
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ vp->m_stop.lno = vp->m_start.lno;
+ vp->m_stop.cno = vp->m_start.cno + cnt - 1;
+ if (vp->m_stop.cno > len - 1) {
+ v_eol(sp, &vp->m_start);
+ return (1);
+ }
+
+ /*
+ * If it's not a repeat, reset the current mode and get a replacement
+ * character.
+ */
+ quote = 0;
+ if (!F_ISSET(vp, VC_ISDOT)) {
+ sp->showmode = SM_REPLACE;
+ if (vs_refresh(sp, 0))
+ return (1);
+next: if (v_event_get(sp, &ev, 0, 0))
+ return (1);
+
+ switch (ev.e_event) {
+ case E_CHARACTER:
+ /*
+ * <literal_next> means escape the next character.
+ * <escape> means they changed their minds.
+ */
+ if (!quote) {
+ if (ev.e_value == K_VLNEXT) {
+ quote = 1;
+ goto next;
+ }
+ if (ev.e_value == K_ESCAPE)
+ return (0);
+ }
+ vip->rlast = ev.e_c;
+ vip->rvalue = ev.e_value;
+ break;
+ case E_ERR:
+ case E_EOF:
+ F_SET(sp, SC_EXIT_FORCE);
+ return (1);
+ case E_INTERRUPT:
+ /* <interrupt> means they changed their minds. */
+ return (0);
+ case E_WRESIZE:
+ /* <resize> interrupts the input mode. */
+ v_emsg(sp, NULL, VIM_WRESIZE);
+ return (0);
+ case E_REPAINT:
+ if (vs_repaint(sp, &ev))
+ return (1);
+ goto next;
+ default:
+ v_event_err(sp, &ev);
+ return (0);
+ }
+ }
+
+ /* Copy the line. */
+ GET_SPACE_RET(sp, bp, blen, len);
+ memmove(bp, p, len);
+ p = bp;
+
+ /*
+ * Versions of nvi before 1.57 created N new lines when they replaced
+ * N characters with <carriage-return> or <newline> characters. This
+ * is different from the historic vi, which replaced N characters with
+ * a single new line. Users complained, so we match historic practice.
+ */
+ if (!quote && vip->rvalue == K_CR || vip->rvalue == K_NL) {
+ /* Set return line. */
+ vp->m_stop.lno = vp->m_start.lno + 1;
+ vp->m_stop.cno = 0;
+
+ /* The first part of the current line. */
+ if (db_set(sp, vp->m_start.lno, p, vp->m_start.cno))
+ goto err_ret;
+
+ /*
+ * The rest of the current line. And, of course, now it gets
+ * tricky. If there are characters left in the line and if
+ * the autoindent edit option is set, white space after the
+ * replaced character is discarded, autoindent is applied, and
+ * the cursor moves to the last indent character.
+ */
+ p += vp->m_start.cno + cnt;
+ len -= vp->m_start.cno + cnt;
+ if (len != 0 && O_ISSET(sp, O_AUTOINDENT))
+ for (; len && isblank(*p); --len, ++p);
+
+ if ((tp = text_init(sp, p, len, len)) == NULL)
+ goto err_ret;
+
+ if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) {
+ if (v_txt_auto(sp, vp->m_start.lno, NULL, 0, tp))
+ goto err_ret;
+ vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0;
+ } else
+ vp->m_stop.cno = 0;
+
+ vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0;
+ if (db_append(sp, 1, vp->m_start.lno, tp->lb, tp->len))
+err_ret: rval = 1;
+ else {
+ text_free(tp);
+ rval = 0;
+ }
+ } else {
+ memset(bp + vp->m_start.cno, vip->rlast, cnt);
+ rval = db_set(sp, vp->m_start.lno, bp, len);
+ }
+ FREE_SPACE(sp, bp, blen);
+
+ vp->m_final = vp->m_stop;
+ return (rval);
+}
diff --git a/contrib/nvi/vi/v_right.c b/contrib/nvi/vi/v_right.c
new file mode 100644
index 0000000..c2f349e
--- /dev/null
+++ b/contrib/nvi/vi/v_right.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[] = "@(#)v_right.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 <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_right -- [count]' ', [count]l
+ * Move right by columns.
+ *
+ * PUBLIC: int v_right __P((SCR *, VICMD *));
+ */
+int
+v_right(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ int isempty;
+
+ if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) {
+ if (isempty)
+ goto eol;
+ return (1);
+ }
+
+ /* It's always illegal to move right on empty lines. */
+ if (len == 0) {
+eol: v_eol(sp, NULL);
+ return (1);
+ }
+
+ /*
+ * Non-motion commands move to the end of the range. Delete and
+ * yank stay at the start. Ignore others. Adjust the end of the
+ * range for motion commands.
+ *
+ * !!!
+ * Historically, "[cdsy]l" worked at the end of a line. Also,
+ * EOL is a count sink.
+ */
+ vp->m_stop.cno = vp->m_start.cno +
+ (F_ISSET(vp, VC_C1SET) ? vp->count : 1);
+ if (vp->m_start.cno == len - 1 && !ISMOTION(vp)) {
+ v_eol(sp, NULL);
+ return (1);
+ }
+ if (vp->m_stop.cno >= len) {
+ vp->m_stop.cno = len - 1;
+ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
+ } else if (ISMOTION(vp)) {
+ --vp->m_stop.cno;
+ vp->m_final = vp->m_start;
+ } else
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_dollar -- [count]$
+ * Move to the last column.
+ *
+ * PUBLIC: int v_dollar __P((SCR *, VICMD *));
+ */
+int
+v_dollar(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ int isempty;
+
+ /*
+ * !!!
+ * A count moves down count - 1 rows, so, "3$" is the same as "2j$".
+ */
+ if ((F_ISSET(vp, VC_C1SET) ? vp->count : 1) != 1) {
+ /*
+ * !!!
+ * Historically, if the $ is a motion, and deleting from
+ * at or before the first non-blank of the line, it's a
+ * line motion, and the line motion flag is set.
+ */
+ vp->m_stop.cno = 0;
+ if (nonblank(sp, vp->m_start.lno, &vp->m_stop.cno))
+ return (1);
+ if (ISMOTION(vp) && vp->m_start.cno <= vp->m_stop.cno)
+ F_SET(vp, VM_LMODE);
+
+ --vp->count;
+ if (v_down(sp, vp))
+ return (1);
+ }
+
+ /*
+ * !!!
+ * Historically, it was illegal to use $ as a motion command on
+ * an empty line. Unfortunately, even though C was historically
+ * aliased to c$, it (and not c$) was special cased to work on
+ * empty lines. Since we alias C to c$ too, we have a problem.
+ * To fix it, we let c$ go through, on the assumption that it's
+ * not a problem for it to work.
+ */
+ if (db_eget(sp, vp->m_stop.lno, NULL, &len, &isempty)) {
+ if (!isempty)
+ return (1);
+ len = 0;
+ }
+
+ if (len == 0) {
+ if (ISMOTION(vp) && !ISCMD(vp->rkp, 'c')) {
+ v_eol(sp, NULL);
+ return (1);
+ }
+ return (0);
+ }
+
+ /*
+ * Non-motion commands move to the end of the range. Delete
+ * and yank stay at the start. Ignore others.
+ */
+ vp->m_stop.cno = len ? len - 1 : 0;
+ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_screen.c b/contrib/nvi/vi/v_screen.c
new file mode 100644
index 0000000..85cd1e3
--- /dev/null
+++ b/contrib/nvi/vi/v_screen.c
@@ -0,0 +1,65 @@
+/*-
+ * 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[] = "@(#)v_screen.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 <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_screen -- ^W
+ * Switch screens.
+ *
+ * PUBLIC: int v_screen __P((SCR *, VICMD *));
+ */
+int
+v_screen(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * You can't leave a colon command-line edit window -- it's not that
+ * it won't work, but it gets real weird, real fast when you execute
+ * a colon command out of a window that was forked from a window that's
+ * now backgrounded... You get the idea.
+ */
+ if (F_ISSET(sp, SC_COMEDIT)) {
+ msgq(sp, M_ERR,
+ "308|Enter <CR> to execute a command, :q to exit");
+ return (1);
+ }
+
+ /*
+ * Try for the next lower screen, or, go back to the first
+ * screen on the stack.
+ */
+ if (sp->q.cqe_next != (void *)&sp->gp->dq)
+ sp->nextdisp = sp->q.cqe_next;
+ else if (sp->gp->dq.cqh_first == sp) {
+ msgq(sp, M_ERR, "187|No other screen to switch to");
+ return (1);
+ } else
+ sp->nextdisp = sp->gp->dq.cqh_first;
+
+ F_SET(sp->nextdisp, SC_STATUS);
+ F_SET(sp, SC_SSWITCH | SC_STATUS);
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_scroll.c b/contrib/nvi/vi/v_scroll.c
new file mode 100644
index 0000000..92def4b
--- /dev/null
+++ b/contrib/nvi/vi/v_scroll.c
@@ -0,0 +1,474 @@
+/*-
+ * 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[] = "@(#)v_scroll.c 10.9 (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 "../common/common.h"
+#include "vi.h"
+
+static void goto_adjust __P((VICMD *));
+
+/*
+ * The historic vi had a problem in that all movements were by physical
+ * lines, not by logical, or screen lines. Arguments can be made that this
+ * is the right thing to do. For example, single line movements, such as
+ * 'j' or 'k', should probably work on physical lines. Commands like "dj",
+ * or "j.", where '.' is a change command, make more sense for physical lines
+ * than they do for logical lines.
+ *
+ * These arguments, however, don't apply to scrolling commands like ^D and
+ * ^F -- if the window is fairly small, using physical lines can result in
+ * a half-page scroll repainting the entire screen, which is not what the
+ * user wanted. Second, if the line is larger than the screen, using physical
+ * lines can make it impossible to display parts of the line -- there aren't
+ * any commands that don't display the beginning of the line in historic vi,
+ * and if both the beginning and end of the line can't be on the screen at
+ * the same time, you lose. This is even worse in the case of the H, L, and
+ * M commands -- for large lines, they may all refer to the same line and
+ * will result in no movement at all.
+ *
+ * Another issue is that page and half-page scrolling commands historically
+ * moved to the first non-blank character in the new line. If the line is
+ * approximately the same size as the screen, this loses because the cursor
+ * before and after a ^D, may refer to the same location on the screen. In
+ * this implementation, scrolling commands set the cursor to the first non-
+ * blank character if the line changes because of the scroll. Otherwise,
+ * the cursor is left alone.
+ *
+ * This implementation does the scrolling (^B, ^D, ^F, ^U, ^Y, ^E), and the
+ * cursor positioning commands (H, L, M) commands using logical lines, not
+ * physical.
+ */
+
+/*
+ * v_lgoto -- [count]G
+ * Go to first non-blank character of the line count, the last line
+ * of the file by default.
+ *
+ * PUBLIC: int v_lgoto __P((SCR *, VICMD *));
+ */
+int
+v_lgoto(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t nlines;
+
+ if (F_ISSET(vp, VC_C1SET)) {
+ if (!db_exist(sp, vp->count)) {
+ /*
+ * !!!
+ * Historically, 1G was legal in an empty file.
+ */
+ if (vp->count == 1) {
+ if (db_last(sp, &nlines))
+ return (1);
+ if (nlines == 0)
+ return (0);
+ }
+ v_eof(sp, &vp->m_start);
+ return (1);
+ }
+ vp->m_stop.lno = vp->count;
+ } else {
+ if (db_last(sp, &nlines))
+ return (1);
+ vp->m_stop.lno = nlines ? nlines : 1;
+ }
+ goto_adjust(vp);
+ return (0);
+}
+
+/*
+ * v_home -- [count]H
+ * Move to the first non-blank character of the logical line
+ * count - 1 from the top of the screen, 0 by default.
+ *
+ * PUBLIC: int v_home __P((SCR *, VICMD *));
+ */
+int
+v_home(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ if (vs_sm_position(sp, &vp->m_stop,
+ F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_TOP))
+ return (1);
+ goto_adjust(vp);
+ return (0);
+}
+
+/*
+ * v_middle -- M
+ * Move to the first non-blank character of the logical line
+ * in the middle of the screen.
+ *
+ * PUBLIC: int v_middle __P((SCR *, VICMD *));
+ */
+int
+v_middle(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * Yielding to none in our quest for compatibility with every
+ * historical blemish of vi, no matter how strange it might be,
+ * we permit the user to enter a count and then ignore it.
+ */
+ if (vs_sm_position(sp, &vp->m_stop, 0, P_MIDDLE))
+ return (1);
+ goto_adjust(vp);
+ return (0);
+}
+
+/*
+ * v_bottom -- [count]L
+ * Move to the first non-blank character of the logical line
+ * count - 1 from the bottom of the screen, 0 by default.
+ *
+ * PUBLIC: int v_bottom __P((SCR *, VICMD *));
+ */
+int
+v_bottom(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ if (vs_sm_position(sp, &vp->m_stop,
+ F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_BOTTOM))
+ return (1);
+ goto_adjust(vp);
+ return (0);
+}
+
+static void
+goto_adjust(vp)
+ VICMD *vp;
+{
+ /* Guess that it's the end of the range. */
+ vp->m_final = vp->m_stop;
+
+ /*
+ * Non-motion commands move the cursor to the end of the range, and
+ * then to the NEXT nonblank of the line. Historic vi always moved
+ * to the first nonblank in the line; since the H, M, and L commands
+ * are logical motions in this implementation, we do the next nonblank
+ * so that it looks approximately the same to the user. To make this
+ * happen, the VM_RCM_SETNNB flag is set in the vcmd.c command table.
+ *
+ * If it's a motion, it's more complicated. The best possible solution
+ * is probably to display the first nonblank of the line the cursor
+ * will eventually rest on. This is tricky, particularly given that if
+ * the associated command is a delete, we don't yet know what line that
+ * will be. So, we clear the VM_RCM_SETNNB flag, and set the first
+ * nonblank flag (VM_RCM_SETFNB). Note, if the lines are sufficiently
+ * long, this can cause the cursor to warp out of the screen. It's too
+ * hard to fix.
+ *
+ * XXX
+ * The G command is always first nonblank, so it's okay to reset it.
+ */
+ if (ISMOTION(vp)) {
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SETFNB);
+ } else
+ return;
+
+ /*
+ * If moving backward in the file, delete and yank move to the end
+ * of the range, unless the line didn't change, in which case yank
+ * doesn't move. If moving forward in the file, delete and yank
+ * stay at the start of the range. Ignore others.
+ */
+ if (vp->m_stop.lno < vp->m_start.lno ||
+ vp->m_stop.lno == vp->m_start.lno &&
+ vp->m_stop.cno < vp->m_start.cno) {
+ if (ISCMD(vp->rkp, 'y') && vp->m_stop.lno == vp->m_start.lno)
+ vp->m_final = vp->m_start;
+ } else
+ vp->m_final = vp->m_start;
+}
+
+/*
+ * v_up -- [count]^P, [count]k, [count]-
+ * Move up by lines.
+ *
+ * PUBLIC: int v_up __P((SCR *, VICMD *));
+ */
+int
+v_up(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t lno;
+
+ lno = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ if (vp->m_start.lno <= lno) {
+ v_sof(sp, &vp->m_start);
+ return (1);
+ }
+ vp->m_stop.lno = vp->m_start.lno - lno;
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_cr -- [count]^M
+ * In a script window, send the line to the shell.
+ * In a regular window, move down by lines.
+ *
+ * PUBLIC: int v_cr __P((SCR *, VICMD *));
+ */
+int
+v_cr(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /* If it's a colon command-line edit window, it's an ex command. */
+ if (F_ISSET(sp, SC_COMEDIT))
+ return (v_ecl_exec(sp));
+
+ /* If it's a script window, exec the line. */
+ if (F_ISSET(sp, SC_SCRIPT))
+ return (sscr_exec(sp, vp->m_start.lno));
+
+ /* Otherwise, it's the same as v_down(). */
+ return (v_down(sp, vp));
+}
+
+/*
+ * v_down -- [count]^J, [count]^N, [count]j, [count]^M, [count]+
+ * Move down by lines.
+ *
+ * PUBLIC: int v_down __P((SCR *, VICMD *));
+ */
+int
+v_down(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t lno;
+
+ lno = vp->m_start.lno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1);
+ if (!db_exist(sp, lno)) {
+ v_eof(sp, &vp->m_start);
+ return (1);
+ }
+ vp->m_stop.lno = lno;
+ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_hpageup -- [count]^U
+ * Page up half screens.
+ *
+ * PUBLIC: int v_hpageup __P((SCR *, VICMD *));
+ */
+int
+v_hpageup(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * Half screens always succeed unless already at SOF.
+ *
+ * !!!
+ * Half screens set the scroll value, even if the command
+ * ultimately failed, in historic vi. Probably a don't care.
+ */
+ if (F_ISSET(vp, VC_C1SET))
+ sp->defscroll = vp->count;
+ if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_U))
+ return (1);
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_hpagedown -- [count]^D
+ * Page down half screens.
+ *
+ * PUBLIC: int v_hpagedown __P((SCR *, VICMD *));
+ */
+int
+v_hpagedown(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * Half screens always succeed unless already at EOF.
+ *
+ * !!!
+ * Half screens set the scroll value, even if the command
+ * ultimately failed, in historic vi. Probably a don't care.
+ */
+ if (F_ISSET(vp, VC_C1SET))
+ sp->defscroll = vp->count;
+ if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_D))
+ return (1);
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_pagedown -- [count]^F
+ * Page down full screens.
+ * !!!
+ * Historic vi did not move to the EOF if the screen couldn't move, i.e.
+ * if EOF was already displayed on the screen. This implementation does
+ * move to EOF in that case, making ^F more like the the historic ^D.
+ *
+ * PUBLIC: int v_pagedown __P((SCR *, VICMD *));
+ */
+int
+v_pagedown(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t offset;
+
+ /*
+ * !!!
+ * The calculation in IEEE Std 1003.2-1992 (POSIX) is:
+ *
+ * top_line = top_line + count * (window - 2);
+ *
+ * which was historically wrong. The correct one is:
+ *
+ * top_line = top_line + count * window - 2;
+ *
+ * i.e. the two line "overlap" was only subtracted once. Which
+ * makes no sense, but then again, an overlap makes no sense for
+ * any screen but the "next" one anyway. We do it the historical
+ * way as there's no good reason to change it.
+ *
+ * If the screen has been split, use the smaller of the current
+ * window size and the window option value.
+ *
+ * It possible for this calculation to be less than 1; move at
+ * least one line.
+ */
+ offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ?
+ MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW));
+ offset = offset <= 2 ? 1 : offset - 2;
+ if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_F))
+ return (1);
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_pageup -- [count]^B
+ * Page up full screens.
+ *
+ * !!!
+ * Historic vi did not move to the SOF if the screen couldn't move, i.e.
+ * if SOF was already displayed on the screen. This implementation does
+ * move to SOF in that case, making ^B more like the the historic ^U.
+ *
+ * PUBLIC: int v_pageup __P((SCR *, VICMD *));
+ */
+int
+v_pageup(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t offset;
+
+ /*
+ * !!!
+ * The calculation in IEEE Std 1003.2-1992 (POSIX) is:
+ *
+ * top_line = top_line - count * (window - 2);
+ *
+ * which was historically wrong. The correct one is:
+ *
+ * top_line = (top_line - count * window) + 2;
+ *
+ * A simpler expression is that, as with ^F, we scroll exactly:
+ *
+ * count * window - 2
+ *
+ * lines.
+ *
+ * Bizarre. As with ^F, an overlap makes no sense for anything
+ * but the first screen. We do it the historical way as there's
+ * no good reason to change it.
+ *
+ * If the screen has been split, use the smaller of the current
+ * window size and the window option value.
+ *
+ * It possible for this calculation to be less than 1; move at
+ * least one line.
+ */
+ offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ?
+ MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW));
+ offset = offset <= 2 ? 1 : offset - 2;
+ if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_B))
+ return (1);
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_lineup -- [count]^Y
+ * Page up by lines.
+ *
+ * PUBLIC: int v_lineup __P((SCR *, VICMD *));
+ */
+int
+v_lineup(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * The cursor moves down, staying with its original line, unless it
+ * reaches the bottom of the screen.
+ */
+ if (vs_sm_scroll(sp,
+ &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_Y))
+ return (1);
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_linedown -- [count]^E
+ * Page down by lines.
+ *
+ * PUBLIC: int v_linedown __P((SCR *, VICMD *));
+ */
+int
+v_linedown(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * The cursor moves up, staying with its original line, unless it
+ * reaches the top of the screen.
+ */
+ if (vs_sm_scroll(sp,
+ &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_E))
+ return (1);
+ vp->m_final = vp->m_stop;
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_search.c b/contrib/nvi/vi/v_search.c
new file mode 100644
index 0000000..4f7a267
--- /dev/null
+++ b/contrib/nvi/vi/v_search.c
@@ -0,0 +1,515 @@
+/*-
+ * 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[] = "@(#)v_search.c 10.18 (Berkeley) 9/19/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 "../common/common.h"
+#include "vi.h"
+
+static int v_exaddr __P((SCR *, VICMD *, dir_t));
+static int v_search __P((SCR *, VICMD *, char *, size_t, u_int, dir_t));
+
+/*
+ * v_srch -- [count]?RE[? offset]
+ * Ex address search backward.
+ *
+ * PUBLIC: int v_searchb __P((SCR *, VICMD *));
+ */
+int
+v_searchb(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (v_exaddr(sp, vp, BACKWARD));
+}
+
+/*
+ * v_searchf -- [count]/RE[/ offset]
+ * Ex address search forward.
+ *
+ * PUBLIC: int v_searchf __P((SCR *, VICMD *));
+ */
+int
+v_searchf(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (v_exaddr(sp, vp, FORWARD));
+}
+
+/*
+ * v_exaddr --
+ * Do a vi search (which is really an ex address).
+ */
+static int
+v_exaddr(sp, vp, dir)
+ SCR *sp;
+ VICMD *vp;
+ dir_t dir;
+{
+ static EXCMDLIST fake = { "search" };
+ EXCMD *cmdp;
+ GS *gp;
+ TEXT *tp;
+ recno_t s_lno;
+ size_t len, s_cno, tlen;
+ int err, nb, type;
+ char *cmd, *t, buf[20];
+
+ /*
+ * !!!
+ * If using the search command as a motion, any addressing components
+ * are lost, i.e. y/ptrn/+2, when repeated, is the same as y/ptrn/.
+ */
+ if (F_ISSET(vp, VC_ISDOT))
+ return (v_search(sp, vp,
+ NULL, 0, SEARCH_PARSE | SEARCH_MSG | SEARCH_SET, dir));
+
+ /* Get the search pattern. */
+ if (v_tcmd(sp, vp, dir == BACKWARD ? CH_BSEARCH : CH_FSEARCH,
+ TXT_BS | TXT_CR | TXT_ESCAPE | TXT_PROMPT |
+ (O_ISSET(sp, O_SEARCHINCR) ? TXT_SEARCHINCR : 0)))
+ return (1);
+
+ tp = sp->tiq.cqh_first;
+
+ /* If the user backspaced over the prompt, do nothing. */
+ if (tp->term == TERM_BS)
+ return (1);
+
+ /*
+ * If the user was doing an incremental search, then we've already
+ * updated the cursor and moved to the right location. Return the
+ * correct values, we're done.
+ */
+ if (tp->term == TERM_SEARCH) {
+ vp->m_stop.lno = sp->lno;
+ vp->m_stop.cno = sp->cno;
+ if (ISMOTION(vp))
+ return (v_correct(sp, vp, 0));
+ vp->m_final = vp->m_stop;
+ return (0);
+ }
+
+ /*
+ * If the user entered <escape> or <carriage-return>, the length is
+ * 1 and the right thing will happen, i.e. the prompt will be used
+ * as a command character.
+ *
+ * Build a fake ex command structure.
+ */
+ gp = sp->gp;
+ gp->excmd.cp = tp->lb;
+ gp->excmd.clen = tp->len;
+ F_INIT(&gp->excmd, E_VISEARCH);
+
+ /*
+ * XXX
+ * Warn if the search wraps. This is a pretty special case, but it's
+ * nice feature that wasn't in the original implementations of ex/vi.
+ * (It was added at some point to System V's version.) This message
+ * is only displayed if there are no keys in the queue. The problem is
+ * the command is going to succeed, and the message is informational,
+ * not an error. If a macro displays it repeatedly, e.g., the pattern
+ * only occurs once in the file and wrapscan is set, you lose big. For
+ * example, if the macro does something like:
+ *
+ * :map K /pattern/^MjK
+ *
+ * Each search will display the message, but the following "/pattern/"
+ * will immediately overwrite it, with strange results. The System V
+ * vi displays the "wrapped" message multiple times, but because it's
+ * overwritten each time, it's not as noticeable. As we don't discard
+ * messages, it's a real problem for us.
+ */
+ if (!KEYS_WAITING(sp))
+ F_SET(&gp->excmd, E_SEARCH_WMSG);
+
+ /* Save the current line/column. */
+ s_lno = sp->lno;
+ s_cno = sp->cno;
+
+ /*
+ * !!!
+ * Historically, vi / and ? commands were full-blown ex addresses,
+ * including ';' delimiters, trailing <blank>'s, multiple search
+ * strings (separated by semi-colons) and, finally, full-blown z
+ * commands after the / and ? search strings. (If the search was
+ * being used as a motion, the trailing z command was ignored.
+ * Also, we do some argument checking on the z command, to be sure
+ * that it's not some other random command.) For multiple search
+ * strings, leading <blank>'s at the second and subsequent strings
+ * were eaten as well. This has some (unintended?) side-effects:
+ * the command /ptrn/;3 is legal and results in moving to line 3.
+ * I suppose you could use it to optionally move to line 3...
+ *
+ * !!!
+ * Historically, if any part of the search command failed, the cursor
+ * remained unmodified (even if ; was used). We have to play games
+ * because the underlying ex parser thinks we're modifying the cursor
+ * as we go, but I think we're compatible with historic practice.
+ *
+ * !!!
+ * Historically, the command "/STRING/; " failed, apparently it
+ * confused the parser. We're not that compatible.
+ */
+ cmdp = &gp->excmd;
+ if (ex_range(sp, cmdp, &err))
+ return (1);
+
+ /*
+ * Remember where any remaining command information is, and clean
+ * up the fake ex command.
+ */
+ cmd = cmdp->cp;
+ len = cmdp->clen;
+ gp->excmd.clen = 0;
+
+ if (err)
+ goto err2;
+
+ /* Copy out the new cursor position and make sure it's okay. */
+ switch (cmdp->addrcnt) {
+ case 1:
+ vp->m_stop = cmdp->addr1;
+ break;
+ case 2:
+ vp->m_stop = cmdp->addr2;
+ break;
+ }
+ if (!db_exist(sp, vp->m_stop.lno)) {
+ ex_badaddr(sp, &fake,
+ vp->m_stop.lno == 0 ? A_ZERO : A_EOF, NUM_OK);
+ goto err2;
+ }
+
+ /*
+ * !!!
+ * Historic practice is that a trailing 'z' was ignored if it was a
+ * motion command. Should probably be an error, but not worth the
+ * effort.
+ */
+ if (ISMOTION(vp))
+ return (v_correct(sp, vp, F_ISSET(cmdp, E_DELTA)));
+
+ /*
+ * !!!
+ * Historically, if it wasn't a motion command, a delta in the search
+ * pattern turns it into a first nonblank movement.
+ */
+ nb = F_ISSET(cmdp, E_DELTA);
+
+ /* Check for the 'z' command. */
+ if (len != 0) {
+ if (*cmd != 'z')
+ goto err1;
+
+ /* No blanks, just like the z command. */
+ for (t = cmd + 1, tlen = len - 1; tlen > 0; ++t, --tlen)
+ if (!isdigit(*t))
+ break;
+ if (tlen &&
+ (*t == '-' || *t == '.' || *t == '+' || *t == '^')) {
+ ++t;
+ --tlen;
+ type = 1;
+ } else
+ type = 0;
+ if (tlen)
+ goto err1;
+
+ /* The z command will do the nonblank for us. */
+ nb = 0;
+
+ /* Default to z+. */
+ if (!type &&
+ v_event_push(sp, NULL, "+", 1, CH_NOMAP | CH_QUOTED))
+ return (1);
+
+ /* Push the user's command. */
+ if (v_event_push(sp, NULL, cmd, len, CH_NOMAP | CH_QUOTED))
+ return (1);
+
+ /* Push line number so get correct z display. */
+ tlen = snprintf(buf,
+ sizeof(buf), "%lu", (u_long)vp->m_stop.lno);
+ if (v_event_push(sp, NULL, buf, tlen, CH_NOMAP | CH_QUOTED))
+ return (1);
+
+ /* Don't refresh until after 'z' happens. */
+ F_SET(VIP(sp), VIP_S_REFRESH);
+ }
+
+ /* Non-motion commands move to the end of the range. */
+ vp->m_final = vp->m_stop;
+ if (nb) {
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SETFNB);
+ }
+ return (0);
+
+err1: msgq(sp, M_ERR,
+ "188|Characters after search string, line offset and/or z command");
+err2: vp->m_final.lno = s_lno;
+ vp->m_final.cno = s_cno;
+ return (1);
+}
+
+/*
+ * v_searchN -- N
+ * Reverse last search.
+ *
+ * PUBLIC: int v_searchN __P((SCR *, VICMD *));
+ */
+int
+v_searchN(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ dir_t dir;
+
+ switch (sp->searchdir) {
+ case BACKWARD:
+ dir = FORWARD;
+ break;
+ case FORWARD:
+ dir = BACKWARD;
+ break;
+ default:
+ dir = sp->searchdir;
+ break;
+ }
+ return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, dir));
+}
+
+/*
+ * v_searchn -- n
+ * Repeat last search.
+ *
+ * PUBLIC: int v_searchn __P((SCR *, VICMD *));
+ */
+int
+v_searchn(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, sp->searchdir));
+}
+
+/*
+ * v_searchw -- [count]^A
+ * Search for the word under the cursor.
+ *
+ * PUBLIC: int v_searchw __P((SCR *, VICMD *));
+ */
+int
+v_searchw(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t blen, len;
+ int rval;
+ char *bp;
+
+ len = VIP(sp)->klen + sizeof(RE_WSTART) + sizeof(RE_WSTOP);
+ GET_SPACE_RET(sp, bp, blen, len);
+ len = snprintf(bp, blen, "%s%s%s", RE_WSTART, VIP(sp)->keyw, RE_WSTOP);
+
+ rval = v_search(sp, vp, bp, len, SEARCH_SET, FORWARD);
+
+ FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
+
+/*
+ * v_search --
+ * The search commands.
+ */
+static int
+v_search(sp, vp, ptrn, plen, flags, dir)
+ SCR *sp;
+ VICMD *vp;
+ u_int flags;
+ char *ptrn;
+ size_t plen;
+ dir_t dir;
+{
+ /* Display messages. */
+ LF_SET(SEARCH_MSG);
+
+ /* If it's a motion search, offset past end-of-line is okay. */
+ if (ISMOTION(vp))
+ LF_SET(SEARCH_EOL);
+
+ /*
+ * XXX
+ * Warn if the search wraps. See the comment above, in v_exaddr().
+ */
+ if (!KEYS_WAITING(sp))
+ LF_SET(SEARCH_WMSG);
+
+ switch (dir) {
+ case BACKWARD:
+ if (b_search(sp,
+ &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags))
+ return (1);
+ break;
+ case FORWARD:
+ if (f_search(sp,
+ &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags))
+ return (1);
+ break;
+ case NOTSET:
+ msgq(sp, M_ERR, "189|No previous search pattern");
+ return (1);
+ default:
+ abort();
+ }
+
+ /* Correct motion commands, otherwise, simply move to the location. */
+ if (ISMOTION(vp)) {
+ if (v_correct(sp, vp, 0))
+ return(1);
+ } else
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_correct --
+ * Handle command with a search as the motion.
+ *
+ * !!!
+ * Historically, commands didn't affect the line searched to/from if the
+ * motion command was a search and the final position was the start/end
+ * of the line. There were some special cases and vi was not consistent;
+ * it was fairly easy to confuse it. For example, given the two lines:
+ *
+ * abcdefghi
+ * ABCDEFGHI
+ *
+ * placing the cursor on the 'A' and doing y?$ would so confuse it that 'h'
+ * 'k' and put would no longer work correctly. In any case, we try to do
+ * the right thing, but it's not going to exactly match historic practice.
+ *
+ * PUBLIC: int v_correct __P((SCR *, VICMD *, int));
+ */
+int
+v_correct(sp, vp, isdelta)
+ SCR *sp;
+ VICMD *vp;
+ int isdelta;
+{
+ dir_t dir;
+ MARK m;
+ size_t len;
+
+ /*
+ * !!!
+ * We may have wrapped if wrapscan was set, and we may have returned
+ * to the position where the cursor started. Historic vi didn't cope
+ * with this well. Yank wouldn't beep, but the first put after the
+ * yank would move the cursor right one column (without adding any
+ * text) and the second would put a copy of the current line. The
+ * change and delete commands would beep, but would leave the cursor
+ * on the colon command line. I believe that there are macros that
+ * depend on delete, at least, failing. For now, commands that use
+ * search as a motion component fail when the search returns to the
+ * original cursor position.
+ */
+ if (vp->m_start.lno == vp->m_stop.lno &&
+ vp->m_start.cno == vp->m_stop.cno) {
+ msgq(sp, M_BERR, "190|Search wrapped to original position");
+ return (1);
+ }
+
+ /*
+ * !!!
+ * Searches become line mode operations if there was a delta specified
+ * to the search pattern.
+ */
+ if (isdelta)
+ F_SET(vp, VM_LMODE);
+
+ /*
+ * If the motion is in the reverse direction, switch the start and
+ * stop MARK's so that it's in a forward direction. (There's no
+ * reason for this other than to make the tests below easier. The
+ * code in vi.c:vi() would have done the switch.) Both forward
+ * and backward motions can happen for any kind of search command
+ * because of the wrapscan option.
+ */
+ if (vp->m_start.lno > vp->m_stop.lno ||
+ vp->m_start.lno == vp->m_stop.lno &&
+ vp->m_start.cno > vp->m_stop.cno) {
+ m = vp->m_start;
+ vp->m_start = vp->m_stop;
+ vp->m_stop = m;
+ dir = BACKWARD;
+ } else
+ dir = FORWARD;
+
+ /*
+ * BACKWARD:
+ * Delete and yank commands move to the end of the range.
+ * Ignore others.
+ *
+ * FORWARD:
+ * Delete and yank commands don't move. Ignore others.
+ */
+ vp->m_final = vp->m_start;
+
+ /*
+ * !!!
+ * Delta'd searches don't correct based on column positions.
+ */
+ if (isdelta)
+ return (0);
+
+ /*
+ * !!!
+ * Backward searches starting at column 0, and forward searches ending
+ * at column 0 are corrected to the last column of the previous line.
+ * Otherwise, adjust the starting/ending point to the character before
+ * the current one (this is safe because we know the search had to move
+ * to succeed).
+ *
+ * Searches become line mode operations if they start at the first
+ * nonblank and end at column 0 of another line.
+ */
+ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) {
+ if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len))
+ return (1);
+ vp->m_stop.cno = len ? len - 1 : 0;
+ len = 0;
+ if (nonblank(sp, vp->m_start.lno, &len))
+ return (1);
+ if (vp->m_start.cno <= len)
+ F_SET(vp, VM_LMODE);
+ } else
+ --vp->m_stop.cno;
+
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_section.c b/contrib/nvi/vi/v_section.c
new file mode 100644
index 0000000..20e8ff2
--- /dev/null
+++ b/contrib/nvi/vi/v_section.c
@@ -0,0 +1,252 @@
+/*-
+ * 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[] = "@(#)v_section.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 <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * !!!
+ * In historic vi, the section commands ignored empty lines, unlike the
+ * paragraph commands, which was probably okay. However, they also moved
+ * to the start of the last line when there where no more sections instead
+ * of the end of the last line like the paragraph commands. I've changed
+ * the latter behavior to match the paragraph commands.
+ *
+ * In historic vi, a section was defined as the first character(s) of the
+ * line matching, which could be followed by anything. This implementation
+ * follows that historic practice.
+ *
+ * !!!
+ * The historic vi documentation (USD:15-10) claimed:
+ * The section commands interpret a preceding count as a different
+ * window size in which to redraw the screen at the new location,
+ * and this window size is the base size for newly drawn windows
+ * until another size is specified. This is very useful if you are
+ * on a slow terminal ...
+ *
+ * I can't get the 4BSD vi to do this, it just beeps at me. For now, a
+ * count to the section commands simply repeats the command.
+ */
+
+/*
+ * v_sectionf -- [count]]]
+ * Move forward count sections/functions.
+ *
+ * !!!
+ * Using ]] as a motion command was a bit special, historically. It could
+ * match } as well as the usual { and section values. If it matched a { or
+ * a section, it did NOT include the matched line. If it matched a }, it
+ * did include the line. No clue why.
+ *
+ * PUBLIC: int v_sectionf __P((SCR *, VICMD *));
+ */
+int
+v_sectionf(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t cnt, lno;
+ size_t len;
+ char *p, *list, *lp;
+
+ /* Get the macro list. */
+ if ((list = O_STR(sp, O_SECTIONS)) == NULL)
+ return (1);
+
+ /*
+ * !!!
+ * If the starting cursor position is at or before any non-blank
+ * characters in the line, i.e. the movement is cutting all of the
+ * line's text, the buffer is in line mode. It's a lot easier to
+ * check here, because we know that the end is going to be the start
+ * or end of a line.
+ */
+ if (ISMOTION(vp))
+ if (vp->m_start.cno == 0)
+ F_SET(vp, VM_LMODE);
+ else {
+ vp->m_stop = vp->m_start;
+ vp->m_stop.cno = 0;
+ if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno))
+ return (1);
+ if (vp->m_start.cno <= vp->m_stop.cno)
+ F_SET(vp, VM_LMODE);
+ }
+
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ for (lno = vp->m_start.lno; !db_get(sp, ++lno, 0, &p, &len);) {
+ if (len == 0)
+ continue;
+ if (p[0] == '{' || ISMOTION(vp) && p[0] == '}') {
+ if (!--cnt) {
+ if (p[0] == '{')
+ goto adjust1;
+ goto adjust2;
+ }
+ continue;
+ }
+ /*
+ * !!!
+ * Historic documentation (USD:15-11, 4.2) said that formfeed
+ * characters (^L) in the first column delimited sections.
+ * The historic code mentions formfeed characters, but never
+ * implements them. Seems reasonable, do it.
+ */
+ if (p[0] == '\014') {
+ if (!--cnt)
+ goto adjust1;
+ continue;
+ }
+ if (p[0] != '.' || len < 2)
+ continue;
+ for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp))
+ if (lp[0] == p[1] &&
+ (lp[1] == ' ' && len == 2 || lp[1] == p[2]) &&
+ !--cnt) {
+ /*
+ * !!!
+ * If not cutting this line, adjust to the end
+ * of the previous one. Otherwise, position to
+ * column 0.
+ */
+adjust1: if (ISMOTION(vp))
+ goto ret1;
+
+adjust2: vp->m_stop.lno = lno;
+ vp->m_stop.cno = 0;
+ goto ret2;
+ }
+ }
+
+ /* If moving forward, reached EOF, check to see if we started there. */
+ if (vp->m_start.lno == lno - 1) {
+ v_eof(sp, NULL);
+ return (1);
+ }
+
+ret1: if (db_get(sp, --lno, DBG_FATAL, NULL, &len))
+ return (1);
+ vp->m_stop.lno = lno;
+ vp->m_stop.cno = len ? len - 1 : 0;
+
+ /*
+ * Non-motion commands go to the end of the range. Delete and
+ * yank stay at the start of the range. Ignore others.
+ */
+ret2: if (ISMOTION(vp)) {
+ vp->m_final = vp->m_start;
+ if (F_ISSET(vp, VM_LMODE))
+ vp->m_final.cno = 0;
+ } else
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_sectionb -- [count][[
+ * Move backward count sections/functions.
+ *
+ * PUBLIC: int v_sectionb __P((SCR *, VICMD *));
+ */
+int
+v_sectionb(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ recno_t cnt, lno;
+ char *p, *list, *lp;
+
+ /* An empty file or starting from line 1 is always illegal. */
+ if (vp->m_start.lno <= 1) {
+ v_sof(sp, NULL);
+ return (1);
+ }
+
+ /* Get the macro list. */
+ if ((list = O_STR(sp, O_SECTIONS)) == NULL)
+ return (1);
+
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ for (lno = vp->m_start.lno; !db_get(sp, --lno, 0, &p, &len);) {
+ if (len == 0)
+ continue;
+ if (p[0] == '{') {
+ if (!--cnt)
+ goto adjust1;
+ continue;
+ }
+ /*
+ * !!!
+ * Historic documentation (USD:15-11, 4.2) said that formfeed
+ * characters (^L) in the first column delimited sections.
+ * The historic code mentions formfeed characters, but never
+ * implements them. Seems reasonable, do it.
+ */
+ if (p[0] == '\014') {
+ if (!--cnt)
+ goto adjust1;
+ continue;
+ }
+ if (p[0] != '.' || len < 2)
+ continue;
+ for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp))
+ if (lp[0] == p[1] &&
+ (lp[1] == ' ' && len == 2 || lp[1] == p[2]) &&
+ !--cnt) {
+adjust1: vp->m_stop.lno = lno;
+ vp->m_stop.cno = 0;
+ goto ret1;
+ }
+ }
+
+ /*
+ * If moving backward, reached SOF, which is a movement sink.
+ * We already checked for starting there.
+ */
+ vp->m_stop.lno = 1;
+ vp->m_stop.cno = 0;
+
+ /*
+ * All commands move to the end of the range.
+ *
+ * !!!
+ * Historic practice is the section cut was in line mode if it started
+ * from column 0 and was in the backward direction. Otherwise, left
+ * motion commands adjust the starting point to the character before
+ * the current one. What makes this worse is that if it cut to line
+ * mode it also went to the first non-<blank>.
+ */
+ret1: if (vp->m_start.cno == 0) {
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SETFNB);
+
+ --vp->m_start.lno;
+ F_SET(vp, VM_LMODE);
+ } else
+ --vp->m_start.cno;
+
+ vp->m_final = vp->m_stop;
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_sentence.c b/contrib/nvi/vi/v_sentence.c
new file mode 100644
index 0000000..a3d9376
--- /dev/null
+++ b/contrib/nvi/vi/v_sentence.c
@@ -0,0 +1,359 @@
+/*-
+ * 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[] = "@(#)v_sentence.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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * !!!
+ * In historic vi, a sentence was delimited by a '.', '?' or '!' character
+ * followed by TWO spaces or a newline. One or more empty lines was also
+ * treated as a separate sentence. The Berkeley documentation for historical
+ * vi states that any number of ')', ']', '"' and '\'' characters can be
+ * between the delimiter character and the spaces or end of line, however,
+ * the historical implementation did not handle additional '"' characters.
+ * We follow the documentation here, not the implementation.
+ *
+ * Once again, historical vi didn't do sentence movements associated with
+ * counts consistently, mostly in the presence of lines containing only
+ * white-space characters.
+ *
+ * This implementation also permits a single tab to delimit sentences, and
+ * treats lines containing only white-space characters as empty lines.
+ * Finally, tabs are eaten (along with spaces) when skipping to the start
+ * of the text following a "sentence".
+ */
+
+/*
+ * v_sentencef -- [count])
+ * Move forward count sentences.
+ *
+ * PUBLIC: int v_sentencef __P((SCR *, VICMD *));
+ */
+int
+v_sentencef(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ enum { BLANK, NONE, PERIOD } state;
+ VCS cs;
+ size_t len;
+ u_long cnt;
+
+ cs.cs_lno = vp->m_start.lno;
+ cs.cs_cno = vp->m_start.cno;
+ if (cs_init(sp, &cs))
+ return (1);
+
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+
+ /*
+ * !!!
+ * If in white-space, the next start of sentence counts as one.
+ * This may not handle " . " correctly, but it's real unclear
+ * what correctly means in that case.
+ */
+ if (cs.cs_flags == CS_EMP || cs.cs_flags == 0 && isblank(cs.cs_ch)) {
+ if (cs_fblank(sp, &cs))
+ return (1);
+ if (--cnt == 0) {
+ if (vp->m_start.lno != cs.cs_lno ||
+ vp->m_start.cno != cs.cs_cno)
+ goto okret;
+ return (1);
+ }
+ }
+
+ for (state = NONE;;) {
+ if (cs_next(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ break;
+ if (cs.cs_flags == CS_EOL) {
+ if ((state == PERIOD || state == BLANK) && --cnt == 0) {
+ if (cs_next(sp, &cs))
+ return (1);
+ if (cs.cs_flags == 0 &&
+ isblank(cs.cs_ch) && cs_fblank(sp, &cs))
+ return (1);
+ goto okret;
+ }
+ state = NONE;
+ continue;
+ }
+ if (cs.cs_flags == CS_EMP) { /* An EMP is two sentences. */
+ if (--cnt == 0)
+ goto okret;
+ if (cs_fblank(sp, &cs))
+ return (1);
+ if (--cnt == 0)
+ goto okret;
+ state = NONE;
+ continue;
+ }
+ switch (cs.cs_ch) {
+ case '.':
+ case '?':
+ case '!':
+ state = PERIOD;
+ break;
+ case ')':
+ case ']':
+ case '"':
+ case '\'':
+ if (state != PERIOD)
+ state = NONE;
+ break;
+ case '\t':
+ if (state == PERIOD)
+ state = BLANK;
+ /* FALLTHROUGH */
+ case ' ':
+ if (state == PERIOD) {
+ state = BLANK;
+ break;
+ }
+ if (state == BLANK && --cnt == 0) {
+ if (cs_fblank(sp, &cs))
+ return (1);
+ goto okret;
+ }
+ /* FALLTHROUGH */
+ default:
+ state = NONE;
+ break;
+ }
+ }
+
+ /* EOF is a movement sink, but it's an error not to have moved. */
+ if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) {
+ v_eof(sp, NULL);
+ return (1);
+ }
+
+okret: vp->m_stop.lno = cs.cs_lno;
+ vp->m_stop.cno = cs.cs_cno;
+
+ /*
+ * !!!
+ * Historic, uh, features, yeah, that's right, call 'em features.
+ * If the starting and ending cursor positions are at the first
+ * column in their lines, i.e. the movement is cutting entire lines,
+ * the buffer is in line mode, and the ending position is the last
+ * character of the previous line. Note check to make sure that
+ * it's not within a single line.
+ *
+ * Non-motion commands move to the end of the range. Delete and
+ * yank stay at the start. Ignore others. Adjust the end of the
+ * range for motion commands.
+ */
+ if (ISMOTION(vp)) {
+ if (vp->m_start.cno == 0 &&
+ (cs.cs_flags != 0 || vp->m_stop.cno == 0)) {
+ if (vp->m_start.lno < vp->m_stop.lno) {
+ if (db_get(sp,
+ --vp->m_stop.lno, DBG_FATAL, NULL, &len))
+ return (1);
+ vp->m_stop.cno = len ? len - 1 : 0;
+ }
+ F_SET(vp, VM_LMODE);
+ } else
+ --vp->m_stop.cno;
+ vp->m_final = vp->m_start;
+ } else
+ vp->m_final = vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_sentenceb -- [count](
+ * Move backward count sentences.
+ *
+ * PUBLIC: int v_sentenceb __P((SCR *, VICMD *));
+ */
+int
+v_sentenceb(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ VCS cs;
+ recno_t slno;
+ size_t len, scno;
+ u_long cnt;
+ int last;
+
+ /*
+ * !!!
+ * Historic vi permitted the user to hit SOF repeatedly.
+ */
+ if (vp->m_start.lno == 1 && vp->m_start.cno == 0)
+ return (0);
+
+ cs.cs_lno = vp->m_start.lno;
+ cs.cs_cno = vp->m_start.cno;
+ if (cs_init(sp, &cs))
+ return (1);
+
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+
+ /*
+ * !!!
+ * In empty lines, skip to the previous non-white-space character.
+ * If in text, skip to the prevous white-space character. Believe
+ * it or not, in the paragraph:
+ * ab cd.
+ * AB CD.
+ * if the cursor is on the 'A' or 'B', ( moves to the 'a'. If it
+ * is on the ' ', 'C' or 'D', it moves to the 'A'. Yes, Virginia,
+ * Berkeley was once a major center of drug activity.
+ */
+ if (cs.cs_flags == CS_EMP) {
+ if (cs_bblank(sp, &cs))
+ return (1);
+ for (;;) {
+ if (cs_prev(sp, &cs))
+ return (1);
+ if (cs.cs_flags != CS_EOL)
+ break;
+ }
+ } else if (cs.cs_flags == 0 && !isblank(cs.cs_ch))
+ for (;;) {
+ if (cs_prev(sp, &cs))
+ return (1);
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ break;
+ }
+
+ for (last = 0;;) {
+ if (cs_prev(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_SOF) /* SOF is a movement sink. */
+ break;
+ if (cs.cs_flags == CS_EOL) {
+ last = 1;
+ continue;
+ }
+ if (cs.cs_flags == CS_EMP) {
+ if (--cnt == 0)
+ goto ret;
+ if (cs_bblank(sp, &cs))
+ return (1);
+ last = 0;
+ continue;
+ }
+ switch (cs.cs_ch) {
+ case '.':
+ case '?':
+ case '!':
+ if (!last || --cnt != 0) {
+ last = 0;
+ continue;
+ }
+
+ret: slno = cs.cs_lno;
+ scno = cs.cs_cno;
+
+ /*
+ * Move to the start of the sentence, skipping blanks
+ * and special characters.
+ */
+ do {
+ if (cs_next(sp, &cs))
+ return (1);
+ } while (!cs.cs_flags &&
+ (cs.cs_ch == ')' || cs.cs_ch == ']' ||
+ cs.cs_ch == '"' || cs.cs_ch == '\''));
+ if ((cs.cs_flags || isblank(cs.cs_ch)) &&
+ cs_fblank(sp, &cs))
+ return (1);
+
+ /*
+ * If it was ". xyz", with the cursor on the 'x', or
+ * "end. ", with the cursor in the spaces, or the
+ * beginning of a sentence preceded by an empty line,
+ * we can end up where we started. Fix it.
+ */
+ if (vp->m_start.lno != cs.cs_lno ||
+ vp->m_start.cno != cs.cs_cno)
+ goto okret;
+
+ /*
+ * Well, if an empty line preceded possible blanks
+ * and the sentence, it could be a real sentence.
+ */
+ for (;;) {
+ if (cs_prev(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOL)
+ continue;
+ if (cs.cs_flags == 0 && isblank(cs.cs_ch))
+ continue;
+ break;
+ }
+ if (cs.cs_flags == CS_EMP)
+ goto okret;
+
+ /* But it wasn't; try again. */
+ ++cnt;
+ cs.cs_lno = slno;
+ cs.cs_cno = scno;
+ last = 0;
+ break;
+ case '\t':
+ last = 1;
+ break;
+ default:
+ last =
+ cs.cs_flags == CS_EOL || isblank(cs.cs_ch) ||
+ cs.cs_ch == ')' || cs.cs_ch == ']' ||
+ cs.cs_ch == '"' || cs.cs_ch == '\'' ? 1 : 0;
+ }
+ }
+
+okret: vp->m_stop.lno = cs.cs_lno;
+ vp->m_stop.cno = cs.cs_cno;
+
+ /*
+ * !!!
+ * If the starting and stopping cursor positions are at the first
+ * columns in the line, i.e. the movement is cutting an entire line,
+ * the buffer is in line mode, and the starting position is the last
+ * character of the previous line.
+ *
+ * All commands move to the end of the range. Adjust the start of
+ * the range for motion commands.
+ */
+ if (ISMOTION(vp))
+ if (vp->m_start.cno == 0 &&
+ (cs.cs_flags != 0 || vp->m_stop.cno == 0)) {
+ if (db_get(sp,
+ --vp->m_start.lno, DBG_FATAL, NULL, &len))
+ return (1);
+ vp->m_start.cno = len ? len - 1 : 0;
+ F_SET(vp, VM_LMODE);
+ } else
+ --vp->m_start.cno;
+ vp->m_final = vp->m_stop;
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_status.c b/contrib/nvi/vi/v_status.c
new file mode 100644
index 0000000..7095d78
--- /dev/null
+++ b/contrib/nvi/vi/v_status.c
@@ -0,0 +1,41 @@
+/*-
+ * 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[] = "@(#)v_status.c 10.9 (Berkeley) 5/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_status -- ^G
+ * Show the file status.
+ *
+ * PUBLIC: int v_status __P((SCR *, VICMD *));
+ */
+int
+v_status(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ (void)msgq_status(sp, vp->m_start.lno, MSTAT_SHOWLAST);
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_txt.c b/contrib/nvi/vi/v_txt.c
new file mode 100644
index 0000000..c47a485
--- /dev/null
+++ b/contrib/nvi/vi/v_txt.c
@@ -0,0 +1,2950 @@
+/*-
+ * 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[] = "@(#)v_txt.c 10.87 (Berkeley) 10/13/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 <unistd.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+static int txt_abbrev __P((SCR *, TEXT *, CHAR_T *, int, int *, int *));
+static void txt_ai_resolve __P((SCR *, TEXT *, int *));
+static TEXT *txt_backup __P((SCR *, TEXTH *, TEXT *, u_int32_t *));
+static int txt_dent __P((SCR *, TEXT *, int));
+static int txt_emark __P((SCR *, TEXT *, size_t));
+static void txt_err __P((SCR *, TEXTH *));
+static int txt_fc __P((SCR *, TEXT *, int *));
+static int txt_fc_col __P((SCR *, int, ARGS **));
+static int txt_hex __P((SCR *, TEXT *));
+static int txt_insch __P((SCR *, TEXT *, CHAR_T *, u_int));
+static int txt_isrch __P((SCR *, VICMD *, TEXT *, u_int8_t *));
+static int txt_map_end __P((SCR *));
+static int txt_map_init __P((SCR *));
+static int txt_margin __P((SCR *, TEXT *, TEXT *, int *, u_int32_t));
+static void txt_nomorech __P((SCR *));
+static void txt_Rresolve __P((SCR *, TEXTH *, TEXT *, const size_t));
+static int txt_resolve __P((SCR *, TEXTH *, u_int32_t));
+static int txt_showmatch __P((SCR *, TEXT *));
+static void txt_unmap __P((SCR *, TEXT *, u_int32_t *));
+
+/* Cursor character (space is hard to track on the screen). */
+#if defined(DEBUG) && 0
+#undef CH_CURSOR
+#define CH_CURSOR '+'
+#endif
+
+/*
+ * v_tcmd --
+ * Fill a buffer from the terminal for vi.
+ *
+ * PUBLIC: int v_tcmd __P((SCR *, VICMD *, ARG_CHAR_T, u_int));
+ */
+int
+v_tcmd(sp, vp, prompt, flags)
+ SCR *sp;
+ VICMD *vp;
+ ARG_CHAR_T prompt;
+ u_int flags;
+{
+ /* Normally, we end up where we started. */
+ vp->m_final.lno = sp->lno;
+ vp->m_final.cno = sp->cno;
+
+ /* Initialize the map. */
+ if (txt_map_init(sp))
+ return (1);
+
+ /* Move to the last line. */
+ sp->lno = TMAP[0].lno;
+ sp->cno = 0;
+
+ /* Don't update the modeline for now. */
+ F_SET(sp, SC_TINPUT_INFO);
+
+ /* Set the input flags. */
+ LF_SET(TXT_APPENDEOL |
+ TXT_CR | TXT_ESCAPE | TXT_INFOLINE | TXT_MAPINPUT);
+ if (O_ISSET(sp, O_ALTWERASE))
+ LF_SET(TXT_ALTWERASE);
+ if (O_ISSET(sp, O_TTYWERASE))
+ LF_SET(TXT_TTYWERASE);
+
+ /* Do the input thing. */
+ if (v_txt(sp, vp, NULL, NULL, 0, prompt, 0, 1, flags))
+ return (1);
+
+ /* Reenable the modeline updates. */
+ F_CLR(sp, SC_TINPUT_INFO);
+
+ /* Clean up the map. */
+ if (txt_map_end(sp))
+ return (1);
+
+ if (IS_ONELINE(sp))
+ F_SET(sp, SC_SCR_REDRAW); /* XXX */
+
+ /* Set the cursor to the resulting position. */
+ sp->lno = vp->m_final.lno;
+ sp->cno = vp->m_final.cno;
+
+ return (0);
+}
+
+/*
+ * txt_map_init
+ * Initialize the screen map for colon command-line input.
+ */
+static int
+txt_map_init(sp)
+ SCR *sp;
+{
+ SMAP *esmp;
+ VI_PRIVATE *vip;
+
+ vip = VIP(sp);
+ if (!IS_ONELINE(sp)) {
+ /*
+ * Fake like the user is doing input on the last line of the
+ * screen. This makes all of the scrolling work correctly,
+ * and allows us the use of the vi text editing routines, not
+ * to mention practically infinite length ex commands.
+ *
+ * Save the current location.
+ */
+ vip->sv_tm_lno = TMAP->lno;
+ vip->sv_tm_soff = TMAP->soff;
+ vip->sv_tm_coff = TMAP->coff;
+ vip->sv_t_maxrows = sp->t_maxrows;
+ vip->sv_t_minrows = sp->t_minrows;
+ vip->sv_t_rows = sp->t_rows;
+
+ /*
+ * If it's a small screen, TMAP may be small for the screen.
+ * Fix it, filling in fake lines as we go.
+ */
+ if (IS_SMALL(sp))
+ for (esmp =
+ HMAP + (sp->t_maxrows - 1); TMAP < esmp; ++TMAP) {
+ TMAP[1].lno = TMAP[0].lno + 1;
+ TMAP[1].coff = HMAP->coff;
+ TMAP[1].soff = 1;
+ }
+
+ /* Build the fake entry. */
+ TMAP[1].lno = TMAP[0].lno + 1;
+ TMAP[1].soff = 1;
+ TMAP[1].coff = 0;
+ SMAP_FLUSH(&TMAP[1]);
+ ++TMAP;
+
+ /* Reset the screen information. */
+ sp->t_rows = sp->t_minrows = ++sp->t_maxrows;
+ }
+ return (0);
+}
+
+/*
+ * txt_map_end
+ * Reset the screen map for colon command-line input.
+ */
+static int
+txt_map_end(sp)
+ SCR *sp;
+{
+ VI_PRIVATE *vip;
+ size_t cnt;
+
+ vip = VIP(sp);
+ if (!IS_ONELINE(sp)) {
+ /* Restore the screen information. */
+ sp->t_rows = vip->sv_t_rows;
+ sp->t_minrows = vip->sv_t_minrows;
+ sp->t_maxrows = vip->sv_t_maxrows;
+
+ /*
+ * If it's a small screen, TMAP may be wrong. Clear any
+ * lines that might have been overwritten.
+ */
+ if (IS_SMALL(sp)) {
+ for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {
+ (void)sp->gp->scr_move(sp, cnt, 0);
+ (void)sp->gp->scr_clrtoeol(sp);
+ }
+ TMAP = HMAP + (sp->t_rows - 1);
+ } else
+ --TMAP;
+
+ /*
+ * The map may be wrong if the user entered more than one
+ * (logical) line. Fix it. If the user entered a whole
+ * screen, this will be slow, but we probably don't care.
+ */
+ if (!O_ISSET(sp, O_LEFTRIGHT))
+ while (vip->sv_tm_lno != TMAP->lno ||
+ vip->sv_tm_soff != TMAP->soff)
+ if (vs_sm_1down(sp))
+ return (1);
+ }
+
+ /*
+ * Invalidate the cursor and the line size cache, the line never
+ * really existed. This fixes bugs where the user searches for
+ * the last line on the screen + 1 and the refresh routine thinks
+ * that's where we just were.
+ */
+ VI_SCR_CFLUSH(vip);
+ F_SET(vip, VIP_CUR_INVALID);
+
+ return (0);
+}
+
+/*
+ * If doing input mapping on the colon command line, may need to unmap
+ * based on the command.
+ */
+#define UNMAP_TST \
+ FL_ISSET(ec_flags, EC_MAPINPUT) && LF_ISSET(TXT_INFOLINE)
+
+/*
+ * Internally, we maintain tp->lno and tp->cno, externally, everyone uses
+ * sp->lno and sp->cno. Make them consistent as necessary.
+ */
+#define UPDATE_POSITION(sp, tp) { \
+ (sp)->lno = (tp)->lno; \
+ (sp)->cno = (tp)->cno; \
+}
+
+/*
+ * v_txt --
+ * Vi text input.
+ *
+ * PUBLIC: int v_txt __P((SCR *, VICMD *, MARK *,
+ * PUBLIC: const char *, size_t, ARG_CHAR_T, recno_t, u_long, u_int32_t));
+ */
+int
+v_txt(sp, vp, tm, lp, len, prompt, ai_line, rcount, flags)
+ SCR *sp;
+ VICMD *vp;
+ MARK *tm; /* To MARK. */
+ const char *lp; /* Input line. */
+ size_t len; /* Input line length. */
+ ARG_CHAR_T prompt; /* Prompt to display. */
+ recno_t ai_line; /* Line number to use for autoindent count. */
+ u_long rcount; /* Replay count. */
+ u_int32_t flags; /* TXT_* flags. */
+{
+ EVENT ev, *evp; /* Current event. */
+ EVENT fc; /* File name completion event. */
+ GS *gp;
+ TEXT *ntp, *tp; /* Input text structures. */
+ TEXT ait; /* Autoindent text structure. */
+ TEXT wmt; /* Wrapmargin text structure. */
+ TEXTH *tiqh;
+ VI_PRIVATE *vip;
+ abb_t abb; /* State of abbreviation checks. */
+ carat_t carat; /* State of the "[^0]^D" sequences. */
+ quote_t quote; /* State of quotation. */
+ size_t owrite, insert; /* Temporary copies of TEXT fields. */
+ size_t margin; /* Wrapmargin value. */
+ size_t rcol; /* 0-N: insert offset in the replay buffer. */
+ size_t tcol; /* Temporary column. */
+ u_int32_t ec_flags; /* Input mapping flags. */
+#define IS_RESTART 0x01 /* Reset the incremental search. */
+#define IS_RUNNING 0x02 /* Incremental search turned on. */
+ u_int8_t is_flags;
+ int abcnt, ab_turnoff; /* Abbreviation character count, switch. */
+ int filec_redraw; /* Redraw after the file completion routine. */
+ int hexcnt; /* Hex character count. */
+ int showmatch; /* Showmatch set on this character. */
+ int wm_set, wm_skip; /* Wrapmargin happened, blank skip flags. */
+ int max, tmp;
+ char *p;
+
+ gp = sp->gp;
+ vip = VIP(sp);
+
+ /*
+ * Set the input flag, so tabs get displayed correctly
+ * and everyone knows that the text buffer is in use.
+ */
+ F_SET(sp, SC_TINPUT);
+
+ /*
+ * Get one 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 changing a line,
+ * copy it into the TEXT buffer.
+ */
+ tiqh = &sp->tiq;
+ if (tiqh->cqh_first != (void *)tiqh) {
+ tp = tiqh->cqh_first;
+ if (tp->q.cqe_next != (void *)tiqh || tp->lb_len < len + 32) {
+ text_lfree(tiqh);
+ goto newtp;
+ }
+ tp->ai = tp->insert = tp->offset = tp->owrite = 0;
+ if (lp != NULL) {
+ tp->len = len;
+ memmove(tp->lb, lp, len);
+ } else
+ tp->len = 0;
+ } else {
+newtp: if ((tp = text_init(sp, lp, len, len + 32)) == NULL)
+ return (1);
+ CIRCLEQ_INSERT_HEAD(tiqh, tp, q);
+ }
+
+ /* Set default termination condition. */
+ tp->term = TERM_OK;
+
+ /* Set the starting line, column. */
+ tp->lno = sp->lno;
+ tp->cno = sp->cno;
+
+ /*
+ * Set the insert and overwrite counts. If overwriting characters,
+ * do insertion afterward. If not overwriting characters, assume
+ * doing insertion. If change is to a mark, emphasize it with an
+ * CH_ENDMARK character.
+ */
+ if (len) {
+ if (LF_ISSET(TXT_OVERWRITE)) {
+ tp->owrite = (tm->cno - tp->cno) + 1;
+ tp->insert = (len - tm->cno) - 1;
+ } else
+ tp->insert = len - tp->cno;
+
+ if (LF_ISSET(TXT_EMARK) && txt_emark(sp, tp, tm->cno))
+ return (1);
+ }
+
+ /*
+ * Many of the special cases in text input are to handle autoindent
+ * support. Somebody decided that it would be a good idea if "^^D"
+ * and "0^D" deleted all of the autoindented characters. In an editor
+ * that takes single character input from the user, this beggars the
+ * imagination. Note also, "^^D" resets the next lines' autoindent,
+ * but "0^D" doesn't.
+ *
+ * We assume that autoindent only happens on empty lines, so insert
+ * and overwrite will be zero. If doing autoindent, figure out how
+ * much indentation we need and fill it in. Update input column and
+ * screen cursor as necessary.
+ */
+ if (LF_ISSET(TXT_AUTOINDENT) && ai_line != OOBLNO) {
+ if (v_txt_auto(sp, ai_line, NULL, 0, tp))
+ return (1);
+ tp->cno = tp->ai;
+ } else {
+ /*
+ * The cc and S commands have a special feature -- leading
+ * <blank> characters are handled as autoindent characters.
+ * Beauty!
+ */
+ if (LF_ISSET(TXT_AICHARS)) {
+ tp->offset = 0;
+ tp->ai = tp->cno;
+ } else
+ tp->offset = tp->cno;
+ }
+
+ /* If getting a command buffer from the user, there may be a prompt. */
+ if (LF_ISSET(TXT_PROMPT)) {
+ tp->lb[tp->cno++] = prompt;
+ ++tp->len;
+ ++tp->offset;
+ }
+
+ /*
+ * If appending after the end-of-line, add a space into the buffer
+ * and move the cursor right. This space is inserted, i.e. pushed
+ * along, and then deleted when the line is resolved. Assumes that
+ * the cursor is already positioned at the end of the line. This
+ * avoids the nastiness of having the cursor reside on a magical
+ * column, i.e. a column that doesn't really exist. The only down
+ * side is that we may wrap lines or scroll the screen before it's
+ * strictly necessary. Not a big deal.
+ */
+ if (LF_ISSET(TXT_APPENDEOL)) {
+ tp->lb[tp->cno] = CH_CURSOR;
+ ++tp->len;
+ ++tp->insert;
+ (void)vs_change(sp, tp->lno, LINE_RESET);
+ }
+
+ /*
+ * Historic practice is that the wrapmargin value was a distance
+ * from the RIGHT-HAND margin, not the left. It's more useful to
+ * us as a distance from the left-hand margin, i.e. the same as
+ * the wraplen value. The wrapmargin option is historic practice.
+ * Nvi added the wraplen option so that it would be possible to
+ * edit files with consistent margins without knowing the number of
+ * columns in the window.
+ *
+ * XXX
+ * Setting margin causes a significant performance hit. Normally
+ * we don't update the screen if there are keys waiting, but we
+ * have to if margin is set, otherwise the screen routines don't
+ * know where the cursor is.
+ *
+ * !!!
+ * Abbreviated keys were affected by the wrapmargin option in the
+ * historic 4BSD vi. Mapped keys were usually, but sometimes not.
+ * See the comment in vi/v_text():set_txt_std for more information.
+ *
+ * !!!
+ * One more special case. If an inserted <blank> character causes
+ * wrapmargin to split the line, the next user entered character is
+ * discarded if it's a <space> character.
+ */
+ wm_set = wm_skip = 0;
+ if (LF_ISSET(TXT_WRAPMARGIN))
+ if ((margin = O_VAL(sp, O_WRAPMARGIN)) != 0)
+ margin = sp->cols - margin;
+ else
+ margin = O_VAL(sp, O_WRAPLEN);
+ else
+ margin = 0;
+
+ /* Initialize abbreviation checks. */
+ abcnt = ab_turnoff = 0;
+ abb = F_ISSET(gp, G_ABBREV) &&
+ LF_ISSET(TXT_MAPINPUT) ? AB_INWORD : AB_NOTSET;
+
+ /*
+ * Set up the dot command. Dot commands are done by saving the actual
+ * characters and then reevaluating them so that things like wrapmargin
+ * can change between the insert and the replay.
+ *
+ * !!!
+ * Historically, vi did not remap or reabbreviate replayed input. (It
+ * did beep at you if you changed an abbreviation and then replayed the
+ * input. We're not that compatible.) We don't have to do anything to
+ * avoid remapping, as we're not getting characters from the terminal
+ * routines. Turn the abbreviation check off.
+ *
+ * XXX
+ * It would be nice if we could swallow backspaces and such, but it's
+ * not all that easy to do. What we can do is turn off the common
+ * error messages during the replay. Otherwise, when the user enters
+ * an illegal command, e.g., "Ia<erase><erase><erase><erase>b<escape>",
+ * and then does a '.', they get a list of error messages after command
+ * completion.
+ */
+ rcol = 0;
+ if (LF_ISSET(TXT_REPLAY)) {
+ abb = AB_NOTSET;
+ LF_CLR(TXT_RECORD);
+ }
+
+ /* Other text input mode setup. */
+ quote = Q_NOTSET;
+ carat = C_NOTSET;
+ FL_INIT(is_flags,
+ LF_ISSET(TXT_SEARCHINCR) ? IS_RESTART | IS_RUNNING : 0);
+ filec_redraw = hexcnt = showmatch = 0;
+
+ /* Initialize input flags. */
+ ec_flags = LF_ISSET(TXT_MAPINPUT) ? EC_MAPINPUT : 0;
+
+ /* Refresh the screen. */
+ UPDATE_POSITION(sp, tp);
+ if (vs_refresh(sp, 1))
+ return (1);
+
+ /* If it's dot, just do it now. */
+ if (F_ISSET(vp, VC_ISDOT))
+ goto replay;
+
+ /* Get an event. */
+ evp = &ev;
+next: if (v_event_get(sp, evp, 0, ec_flags))
+ return (1);
+
+ /*
+ * If file completion overwrote part of the screen and nothing else has
+ * been displayed, clean up. We don't do this as part of the normal
+ * message resolution because we know the user is on the colon command
+ * line and there's no reason to enter explicit characters to continue.
+ */
+ if (filec_redraw && !F_ISSET(sp, SC_SCR_EXWROTE)) {
+ filec_redraw = 0;
+
+ fc.e_event = E_REPAINT;
+ fc.e_flno = vip->totalcount >=
+ sp->rows ? 1 : sp->rows - vip->totalcount;
+ fc.e_tlno = sp->rows;
+ vip->linecount = vip->lcontinue = vip->totalcount = 0;
+ (void)vs_repaint(sp, &fc);
+ (void)vs_refresh(sp, 1);
+ }
+
+ /* Deal with all non-character events. */
+ switch (evp->e_event) {
+ case E_CHARACTER:
+ break;
+ case E_ERR:
+ case E_EOF:
+ F_SET(sp, SC_EXIT_FORCE);
+ return (1);
+ case E_INTERRUPT:
+ /*
+ * !!!
+ * Historically, <interrupt> exited the user from text input
+ * mode or cancelled a colon command, and returned to command
+ * mode. It also beeped the terminal, but that seems a bit
+ * excessive.
+ */
+ goto k_escape;
+ case E_REPAINT:
+ if (vs_repaint(sp, &ev))
+ return (1);
+ goto next;
+ case E_WRESIZE:
+ /* <resize> interrupts the input mode. */
+ v_emsg(sp, NULL, VIM_WRESIZE);
+ goto k_escape;
+ default:
+ v_event_err(sp, evp);
+ goto k_escape;
+ }
+
+ /*
+ * !!!
+ * If the first character of the input is a nul, replay the previous
+ * input. (Historically, it's okay to replay non-existent input.)
+ * This was not documented as far as I know, and is a great test of vi
+ * clones.
+ */
+ if (rcol == 0 && !LF_ISSET(TXT_REPLAY) && evp->e_c == '\0') {
+ if (vip->rep == NULL)
+ goto done;
+
+ abb = AB_NOTSET;
+ LF_CLR(TXT_RECORD);
+ LF_SET(TXT_REPLAY);
+ goto replay;
+ }
+
+ /*
+ * File name completion and colon command-line editing. We don't
+ * have enough meta characters, so we expect people to overload
+ * them. If the two characters are the same, then we do file name
+ * completion if the cursor is past the first column, and do colon
+ * command-line editing if it's not.
+ */
+ if (quote == Q_NOTSET) {
+ int L__cedit, L__filec;
+
+ L__cedit = L__filec = 0;
+ if (LF_ISSET(TXT_CEDIT) && O_STR(sp, O_CEDIT) != NULL &&
+ O_STR(sp, O_CEDIT)[0] == evp->e_c)
+ L__cedit = 1;
+ if (LF_ISSET(TXT_FILEC) && O_STR(sp, O_FILEC) != NULL &&
+ O_STR(sp, O_FILEC)[0] == evp->e_c)
+ L__filec = 1;
+ if (L__cedit == 1 && (L__filec == 0 || tp->cno == tp->offset)) {
+ tp->term = TERM_CEDIT;
+ goto k_escape;
+ }
+ if (L__filec == 1) {
+ if (txt_fc(sp, tp, &filec_redraw))
+ goto err;
+ goto resolve;
+ }
+ }
+
+ /* Abbreviation overflow check. See comment in txt_abbrev(). */
+#define MAX_ABBREVIATION_EXPANSION 256
+ if (F_ISSET(&evp->e_ch, CH_ABBREVIATED)) {
+ if (++abcnt > MAX_ABBREVIATION_EXPANSION) {
+ if (v_event_flush(sp, CH_ABBREVIATED))
+ msgq(sp, M_ERR,
+"191|Abbreviation exceeded expansion limit: characters discarded");
+ abcnt = 0;
+ if (LF_ISSET(TXT_REPLAY))
+ goto done;
+ goto resolve;
+ }
+ } else
+ abcnt = 0;
+
+ /* Check to see if the character fits into the replay buffers. */
+ if (LF_ISSET(TXT_RECORD)) {
+ BINC_GOTO(sp, vip->rep,
+ vip->rep_len, (rcol + 1) * sizeof(EVENT));
+ vip->rep[rcol++] = *evp;
+ }
+
+replay: if (LF_ISSET(TXT_REPLAY))
+ evp = vip->rep + rcol++;
+
+ /* Wrapmargin check for leading space. */
+ if (wm_skip) {
+ wm_skip = 0;
+ if (evp->e_c == ' ')
+ goto resolve;
+ }
+
+ /* If quoted by someone else, simply insert the character. */
+ if (F_ISSET(&evp->e_ch, CH_QUOTED))
+ goto insq_ch;
+
+ /*
+ * !!!
+ * If this character was quoted by a K_VLNEXT or a backslash, replace
+ * the placeholder (a carat or a backslash) with the new character.
+ * If it was quoted by a K_VLNEXT, we've already adjusted the cursor
+ * because it has to appear on top of the placeholder character. If
+ * it was quoted by a backslash, adjust the cursor now, the cursor
+ * doesn't appear on top of it. Historic practice in both cases.
+ *
+ * Skip tests for abbreviations; ":ab xa XA" followed by "ixa^V<space>"
+ * doesn't perform an abbreviation. Special case, ^V^J (not ^V^M) is
+ * the same as ^J, historically.
+ */
+ if (quote == Q_BTHIS || quote == Q_VTHIS) {
+ FL_CLR(ec_flags, EC_QUOTED);
+ if (LF_ISSET(TXT_MAPINPUT))
+ FL_SET(ec_flags, EC_MAPINPUT);
+
+ if (quote == Q_BTHIS &&
+ (evp->e_value == K_VERASE || evp->e_value == K_VKILL)) {
+ quote = Q_NOTSET;
+ --tp->cno;
+ ++tp->owrite;
+ goto insl_ch;
+ }
+ if (quote == Q_VTHIS && evp->e_value != K_NL) {
+ quote = Q_NOTSET;
+ goto insl_ch;
+ }
+ quote = Q_NOTSET;
+ }
+
+ /*
+ * !!!
+ * Translate "<CH_HEX>[isxdigit()]*" to a character with a hex value:
+ * this test delimits the value by any non-hex character. Offset by
+ * one, we use 0 to mean that we've found <CH_HEX>.
+ */
+ if (hexcnt > 1 && !isxdigit(evp->e_c)) {
+ hexcnt = 0;
+ if (txt_hex(sp, tp))
+ goto err;
+ }
+
+ switch (evp->e_value) {
+ case K_CR: /* Carriage return. */
+ case K_NL: /* New line. */
+ /* Return in script windows and the command line. */
+k_cr: if (LF_ISSET(TXT_CR)) {
+ /*
+ * If this was a map, we may have not displayed
+ * the line. Display it, just in case.
+ *
+ * If a script window and not the colon line,
+ * push a <cr> so it gets executed.
+ */
+ if (LF_ISSET(TXT_INFOLINE)) {
+ if (vs_change(sp, tp->lno, LINE_RESET))
+ goto err;
+ } else if (F_ISSET(sp, SC_SCRIPT))
+ (void)v_event_push(sp, NULL, "\r", 1, CH_NOMAP);
+
+ /* Set term condition: if empty. */
+ if (tp->cno <= tp->offset)
+ tp->term = TERM_CR;
+ /*
+ * Set term condition: if searching incrementally and
+ * the user entered a pattern, return a completed
+ * search, regardless if the entire pattern was found.
+ */
+ if (FL_ISSET(is_flags, IS_RUNNING) &&
+ tp->cno >= tp->offset + 1)
+ tp->term = TERM_SEARCH;
+
+ goto k_escape;
+ }
+
+#define LINE_RESOLVE { \
+ /* \
+ * Handle abbreviations. If there was one, discard the \
+ * replay characters. \
+ */ \
+ if (abb == AB_INWORD && \
+ !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) { \
+ if (txt_abbrev(sp, tp, &evp->e_c, \
+ LF_ISSET(TXT_INFOLINE), &tmp, \
+ &ab_turnoff)) \
+ goto err; \
+ if (tmp) { \
+ if (LF_ISSET(TXT_RECORD)) \
+ rcol -= tmp + 1; \
+ goto resolve; \
+ } \
+ } \
+ if (abb != AB_NOTSET) \
+ abb = AB_NOTWORD; \
+ if (UNMAP_TST) \
+ txt_unmap(sp, tp, &ec_flags); \
+ /* \
+ * Delete any appended cursor. It's possible to get in \
+ * situations where TXT_APPENDEOL is set but tp->insert \
+ * is 0 when using the R command and all the characters \
+ * are tp->owrite characters. \
+ */ \
+ if (LF_ISSET(TXT_APPENDEOL) && tp->insert > 0) { \
+ --tp->len; \
+ --tp->insert; \
+ } \
+}
+ LINE_RESOLVE;
+
+ /*
+ * Save the current line information for restoration in
+ * txt_backup(), and set the line final length.
+ */
+ tp->sv_len = tp->len;
+ tp->sv_cno = tp->cno;
+ tp->len = tp->cno;
+
+ /* Update the old line. */
+ if (vs_change(sp, tp->lno, LINE_RESET))
+ goto err;
+
+ /*
+ * Historic practice, when the autoindent edit option was set,
+ * was to delete <blank> characters following the inserted
+ * newline. This affected the 'R', 'c', and 's' commands; 'c'
+ * and 's' retained the insert characters only, 'R' moved the
+ * overwrite and insert characters into the next TEXT structure.
+ * We keep track of the number of characters erased for the 'R'
+ * command so that the final resolution of the line is correct.
+ */
+ tp->R_erase = 0;
+ owrite = tp->owrite;
+ insert = tp->insert;
+ if (LF_ISSET(TXT_REPLACE) && owrite != 0) {
+ for (p = tp->lb + tp->cno; owrite > 0 && isblank(*p);
+ ++p, --owrite, ++tp->R_erase);
+ if (owrite == 0)
+ for (; insert > 0 && isblank(*p);
+ ++p, ++tp->R_erase, --insert);
+ } else {
+ p = tp->lb + tp->cno + owrite;
+ if (O_ISSET(sp, O_AUTOINDENT))
+ for (; insert > 0 &&
+ isblank(*p); ++p, --insert);
+ owrite = 0;
+ }
+
+ /*
+ * !!!
+ * Create a new line and insert the new TEXT into the queue.
+ * DON'T insert until the old line has been updated, or the
+ * inserted line count in line.c:db_get() will be wrong.
+ */
+ if ((ntp = text_init(sp, p,
+ insert + owrite, insert + owrite + 32)) == NULL)
+ goto err;
+ CIRCLEQ_INSERT_TAIL(&sp->tiq, ntp, q);
+
+ /* Set up bookkeeping for the new line. */
+ ntp->insert = insert;
+ ntp->owrite = owrite;
+ ntp->lno = tp->lno + 1;
+
+ /*
+ * Reset the autoindent line value. 0^D keeps the autoindent
+ * 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 == C_NOCHANGE) {
+ if (v_txt_auto(sp, OOBLNO, &ait, ait.ai, ntp))
+ goto err;
+ FREE_SPACE(sp, ait.lb, ait.lb_len);
+ } else
+ if (v_txt_auto(sp, OOBLNO, tp, tp->cno, ntp))
+ goto err;
+ carat = C_NOTSET;
+ }
+
+ /* Reset the cursor. */
+ ntp->cno = ntp->ai;
+
+ /*
+ * If we're here because wrapmargin was set and we've broken a
+ * line, there may be additional information (i.e. the start of
+ * a line) in the wmt structure.
+ */
+ if (wm_set) {
+ if (wmt.offset != 0 ||
+ wmt.owrite != 0 || wmt.insert != 0) {
+#define WMTSPACE wmt.offset + wmt.owrite + wmt.insert
+ BINC_GOTO(sp, ntp->lb,
+ ntp->lb_len, ntp->len + WMTSPACE + 32);
+ memmove(ntp->lb + ntp->cno, wmt.lb, WMTSPACE);
+ ntp->len += WMTSPACE;
+ ntp->cno += wmt.offset;
+ ntp->owrite = wmt.owrite;
+ ntp->insert = wmt.insert;
+ }
+ wm_set = 0;
+ }
+
+ /* New lines are TXT_APPENDEOL. */
+ if (ntp->owrite == 0 && ntp->insert == 0) {
+ BINC_GOTO(sp, ntp->lb, ntp->lb_len, ntp->len + 1);
+ LF_SET(TXT_APPENDEOL);
+ ntp->lb[ntp->cno] = CH_CURSOR;
+ ++ntp->insert;
+ ++ntp->len;
+ }
+
+ /* Swap old and new TEXT's, and update the new line. */
+ tp = ntp;
+ if (vs_change(sp, tp->lno, LINE_INSERT))
+ goto err;
+
+ goto resolve;
+ case K_ESCAPE: /* Escape. */
+ if (!LF_ISSET(TXT_ESCAPE))
+ goto ins_ch;
+
+ /* If we have a count, start replaying the input. */
+ if (rcount > 1) {
+ --rcount;
+
+ rcol = 0;
+ abb = AB_NOTSET;
+ LF_CLR(TXT_RECORD);
+ LF_SET(TXT_REPLAY);
+
+ /*
+ * Some commands (e.g. 'o') need a <newline> for each
+ * repetition.
+ */
+ if (LF_ISSET(TXT_ADDNEWLINE))
+ goto k_cr;
+
+ /*
+ * The R command turns into the 'a' command after the
+ * first repetition.
+ */
+ if (LF_ISSET(TXT_REPLACE)) {
+ tp->insert = tp->owrite;
+ tp->owrite = 0;
+ LF_CLR(TXT_REPLACE);
+ }
+ goto replay;
+ }
+
+ /* Set term condition: if empty. */
+ if (tp->cno <= tp->offset)
+ tp->term = TERM_ESC;
+ /*
+ * Set term condition: if searching incrementally and the user
+ * entered a pattern, return a completed search, regardless if
+ * the entire pattern was found.
+ */
+ if (FL_ISSET(is_flags, IS_RUNNING) && tp->cno >= tp->offset + 1)
+ tp->term = TERM_SEARCH;
+
+k_escape: LINE_RESOLVE;
+
+ /*
+ * Clean up for the 'R' command, restoring overwrite
+ * characters, and making them into insert characters.
+ */
+ if (LF_ISSET(TXT_REPLACE))
+ txt_Rresolve(sp, &sp->tiq, tp, len);
+
+ /*
+ * If there are any overwrite characters, copy down
+ * any insert characters, and decrement the length.
+ */
+ if (tp->owrite) {
+ if (tp->insert)
+ memmove(tp->lb + tp->cno,
+ tp->lb + tp->cno + tp->owrite, tp->insert);
+ tp->len -= tp->owrite;
+ }
+
+ /*
+ * Optionally resolve the lines into the file. If not
+ * resolving the lines into the file, end the line with
+ * a nul. If the line is empty, then set the length to
+ * 0, the termination condition has already been set.
+ *
+ * XXX
+ * This is wrong, should pass back a length.
+ */
+ if (LF_ISSET(TXT_RESOLVE)) {
+ if (txt_resolve(sp, &sp->tiq, flags))
+ goto err;
+ } else {
+ BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1);
+ tp->lb[tp->len] = '\0';
+ }
+
+ /*
+ * Set the return cursor position to rest on the last
+ * inserted character.
+ */
+ if (tp->cno != 0)
+ --tp->cno;
+
+ /* Update the last line. */
+ if (vs_change(sp, tp->lno, LINE_RESET))
+ return (1);
+ goto done;
+ case K_CARAT: /* Delete autoindent chars. */
+ if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT))
+ carat = C_CARATSET;
+ goto ins_ch;
+ case K_ZERO: /* Delete autoindent chars. */
+ if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT))
+ carat = C_ZEROSET;
+ goto ins_ch;
+ case K_CNTRLD: /* Delete autoindent char. */
+ /*
+ * If in the first column or no characters to erase, ignore
+ * the ^D (this matches historic practice). If not doing
+ * autoindent or already inserted non-ai characters, it's a
+ * literal. The latter test is done in the switch, as the
+ * CARAT forms are N + 1, not N.
+ */
+ if (!LF_ISSET(TXT_AUTOINDENT))
+ goto ins_ch;
+ if (tp->cno == 0)
+ goto resolve;
+
+ switch (carat) {
+ case C_CARATSET: /* ^^D */
+ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1)
+ goto ins_ch;
+
+ /* Save the ai string for later. */
+ ait.lb = NULL;
+ ait.lb_len = 0;
+ BINC_GOTO(sp, ait.lb, ait.lb_len, tp->ai);
+ memmove(ait.lb, tp->lb, tp->ai);
+ ait.ai = ait.len = tp->ai;
+
+ carat = C_NOCHANGE;
+ goto leftmargin;
+ case C_ZEROSET: /* 0^D */
+ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1)
+ goto ins_ch;
+
+ carat = C_NOTSET;
+leftmargin: tp->lb[tp->cno - 1] = ' ';
+ tp->owrite += tp->cno - tp->offset;
+ tp->ai = 0;
+ tp->cno = tp->offset;
+ break;
+ case C_NOTSET: /* ^D */
+ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset)
+ goto ins_ch;
+
+ (void)txt_dent(sp, tp, 0);
+ break;
+ default:
+ abort();
+ }
+ break;
+ case K_VERASE: /* Erase the last character. */
+ /* If can erase over the prompt, return. */
+ if (tp->cno <= tp->offset && LF_ISSET(TXT_BS)) {
+ tp->term = TERM_BS;
+ goto done;
+ }
+
+ /*
+ * If at the beginning of the line, try and drop back to a
+ * previously inserted line.
+ */
+ if (tp->cno == 0) {
+ if ((ntp =
+ txt_backup(sp, &sp->tiq, tp, &flags)) == NULL)
+ goto err;
+ tp = ntp;
+ break;
+ }
+
+ /* If nothing to erase, bell the user. */
+ if (tp->cno <= tp->offset) {
+ if (!LF_ISSET(TXT_REPLAY))
+ txt_nomorech(sp);
+ break;
+ }
+
+ /* Drop back one character. */
+ --tp->cno;
+
+ /*
+ * Historically, vi didn't replace the erased characters with
+ * <blank>s, presumably because it's easier to fix a minor
+ * typing mistake and continue on if the previous letters are
+ * already there. This is a problem for incremental searching,
+ * because the user can no longer tell where they are in the
+ * colon command line because the cursor is at the last search
+ * point in the screen. So, if incrementally searching, erase
+ * the erased characters from the screen.
+ */
+ if (FL_ISSET(is_flags, IS_RUNNING))
+ tp->lb[tp->cno] = ' ';
+
+ /*
+ * Increment overwrite, decrement ai if deleted.
+ *
+ * !!!
+ * Historic vi did not permit users to use erase characters
+ * to delete autoindent characters. We do. Eat hot death,
+ * POSIX.
+ */
+ ++tp->owrite;
+ if (tp->cno < tp->ai)
+ --tp->ai;
+
+ /* Reset if we deleted an incremental search character. */
+ if (FL_ISSET(is_flags, IS_RUNNING))
+ FL_SET(is_flags, IS_RESTART);
+ break;
+ case K_VWERASE: /* Skip back one word. */
+ /*
+ * If at the beginning of the line, try and drop back to a
+ * previously inserted line.
+ */
+ if (tp->cno == 0) {
+ if ((ntp =
+ txt_backup(sp, &sp->tiq, tp, &flags)) == NULL)
+ goto err;
+ tp = ntp;
+ }
+
+ /*
+ * If at offset, nothing to erase so bell the user.
+ */
+ if (tp->cno <= tp->offset) {
+ if (!LF_ISSET(TXT_REPLAY))
+ txt_nomorech(sp);
+ break;
+ }
+
+ /*
+ * The first werase goes back to any autoindent column and the
+ * second werase goes back to the offset.
+ *
+ * !!!
+ * Historic vi did not permit users to use erase characters to
+ * delete autoindent characters.
+ */
+ if (tp->ai && tp->cno > tp->ai)
+ max = tp->ai;
+ else {
+ tp->ai = 0;
+ max = tp->offset;
+ }
+
+ /* Skip over trailing space characters. */
+ while (tp->cno > max && isblank(tp->lb[tp->cno - 1])) {
+ --tp->cno;
+ ++tp->owrite;
+ }
+ if (tp->cno == max)
+ break;
+ /*
+ * There are three types of word erase found on UNIX systems.
+ * They can be identified by how the string /a/b/c is treated
+ * -- as 1, 3, or 6 words. Historic vi had two classes of
+ * characters, and strings were delimited by them and
+ * <blank>'s, so, 6 words. The historic tty interface used
+ * <blank>'s to delimit strings, so, 1 word. The algorithm
+ * offered in the 4.4BSD tty interface (as stty altwerase)
+ * treats it as 3 words -- there are two classes of
+ * characters, and strings are delimited by them and
+ * <blank>'s. The difference is that the type of the first
+ * erased character erased is ignored, which is exactly right
+ * when erasing pathname components. The edit options
+ * TXT_ALTWERASE and TXT_TTYWERASE specify the 4.4BSD tty
+ * interface and the historic tty driver behavior,
+ * respectively, and the default is the same as the historic
+ * vi behavior.
+ *
+ * Overwrite erased characters if doing incremental search;
+ * see comment above.
+ */
+ if (LF_ISSET(TXT_TTYWERASE))
+ while (tp->cno > max) {
+ --tp->cno;
+ ++tp->owrite;
+ if (FL_ISSET(is_flags, IS_RUNNING))
+ tp->lb[tp->cno] = ' ';
+ if (isblank(tp->lb[tp->cno - 1]))
+ break;
+ }
+ else {
+ if (LF_ISSET(TXT_ALTWERASE)) {
+ --tp->cno;
+ ++tp->owrite;
+ if (FL_ISSET(is_flags, IS_RUNNING))
+ tp->lb[tp->cno] = ' ';
+ if (isblank(tp->lb[tp->cno - 1]))
+ break;
+ }
+ if (tp->cno > max)
+ tmp = inword(tp->lb[tp->cno - 1]);
+ while (tp->cno > max) {
+ --tp->cno;
+ ++tp->owrite;
+ if (FL_ISSET(is_flags, IS_RUNNING))
+ tp->lb[tp->cno] = ' ';
+ if (tmp != inword(tp->lb[tp->cno - 1])
+ || isblank(tp->lb[tp->cno - 1]))
+ break;
+ }
+ }
+
+ /* Reset if we deleted an incremental search character. */
+ if (FL_ISSET(is_flags, IS_RUNNING))
+ FL_SET(is_flags, IS_RESTART);
+ break;
+ case K_VKILL: /* Restart this line. */
+ /*
+ * !!!
+ * If at the beginning of the line, try and drop back to a
+ * previously inserted line. Historic vi did not permit
+ * users to go back to previous lines.
+ */
+ if (tp->cno == 0) {
+ if ((ntp =
+ txt_backup(sp, &sp->tiq, tp, &flags)) == NULL)
+ goto err;
+ tp = ntp;
+ }
+
+ /* If at offset, nothing to erase so bell the user. */
+ if (tp->cno <= tp->offset) {
+ if (!LF_ISSET(TXT_REPLAY))
+ txt_nomorech(sp);
+ break;
+ }
+
+ /*
+ * First kill goes back to any autoindent and second kill goes
+ * back to the offset.
+ *
+ * !!!
+ * Historic vi did not permit users to use erase characters to
+ * delete autoindent characters.
+ */
+ if (tp->ai && tp->cno > tp->ai)
+ max = tp->ai;
+ else {
+ tp->ai = 0;
+ max = tp->offset;
+ }
+ tp->owrite += tp->cno - max;
+
+ /*
+ * Overwrite erased characters if doing incremental search;
+ * see comment above.
+ */
+ if (FL_ISSET(is_flags, IS_RUNNING))
+ do {
+ tp->lb[--tp->cno] = ' ';
+ } while (tp->cno > max);
+ else
+ tp->cno = max;
+
+ /* Reset if we deleted an incremental search character. */
+ if (FL_ISSET(is_flags, IS_RUNNING))
+ FL_SET(is_flags, IS_RESTART);
+ break;
+ case K_CNTRLT: /* Add autoindent characters. */
+ if (!LF_ISSET(TXT_CNTRLT))
+ goto ins_ch;
+ if (txt_dent(sp, tp, 1))
+ goto err;
+ goto ebuf_chk;
+ case K_RIGHTBRACE:
+ case K_RIGHTPAREN:
+ if (LF_ISSET(TXT_SHOWMATCH))
+ showmatch = 1;
+ goto ins_ch;
+ case K_BACKSLASH: /* Quote next erase/kill. */
+ /*
+ * !!!
+ * Historic vi tried to make abbreviations after a backslash
+ * escape work. If you did ":ab x y", and inserted "x\^H",
+ * (assuming the erase character was ^H) you got "x^H", and
+ * no abbreviation was done. If you inserted "x\z", however,
+ * it tried to back up and do the abbreviation, i.e. replace
+ * 'x' with 'y'. The problem was it got it wrong, and you
+ * ended up with "zy\".
+ *
+ * This is really hard to do (you have to remember the
+ * word/non-word state, for example), and doesn't make any
+ * sense to me. Both backslash and the characters it
+ * (usually) escapes will individually trigger the
+ * abbreviation, so I don't see why the combination of them
+ * wouldn't. I don't expect to get caught on this one,
+ * particularly since it never worked right, but I've been
+ * wrong before.
+ *
+ * Do the tests for abbreviations, so ":ab xa XA",
+ * "ixa\<K_VERASE>" performs the abbreviation.
+ */
+ quote = Q_BNEXT;
+ goto insq_ch;
+ case K_VLNEXT: /* Quote next character. */
+ evp->e_c = '^';
+ quote = Q_VNEXT;
+ /*
+ * Turn on the quote flag so that the underlying routines
+ * quote the next character where it's possible. Turn off
+ * the input mapbiting flag so that we don't remap the next
+ * character.
+ */
+ FL_SET(ec_flags, EC_QUOTED);
+ FL_CLR(ec_flags, EC_MAPINPUT);
+
+ /*
+ * !!!
+ * Skip the tests for abbreviations, so ":ab xa XA",
+ * "ixa^V<space>" doesn't perform the abbreviation.
+ */
+ goto insl_ch;
+ case K_HEXCHAR:
+ hexcnt = 1;
+ goto insq_ch;
+ default: /* Insert the character. */
+ins_ch: /*
+ * Historically, vi eliminated nul's out of hand. If the
+ * beautify option was set, it also deleted any unknown
+ * ASCII value less than space (040) and the del character
+ * (0177), except for tabs. Unknown is a key word here.
+ * Most vi documentation claims that it deleted everything
+ * but <tab>, <nl> and <ff>, as that's what the original
+ * 4BSD documentation said. This is obviously wrong,
+ * however, as <esc> would be included in that list. What
+ * we do is eliminate any unquoted, iscntrl() character that
+ * wasn't a replay and wasn't handled specially, except
+ * <tab> or <ff>.
+ */
+ if (LF_ISSET(TXT_BEAUTIFY) && iscntrl(evp->e_c) &&
+ evp->e_value != K_FORMFEED && evp->e_value != K_TAB) {
+ msgq(sp, M_BERR,
+ "192|Illegal character; quote to enter");
+ if (LF_ISSET(TXT_REPLAY))
+ goto done;
+ break;
+ }
+
+insq_ch: /*
+ * If entering a non-word character after a word, check for
+ * abbreviations. If there was one, discard replay characters.
+ * If entering a blank character, check for unmap commands,
+ * as well.
+ */
+ if (!inword(evp->e_c)) {
+ if (abb == AB_INWORD &&
+ !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) {
+ if (txt_abbrev(sp, tp, &evp->e_c,
+ LF_ISSET(TXT_INFOLINE), &tmp, &ab_turnoff))
+ goto err;
+ if (tmp) {
+ if (LF_ISSET(TXT_RECORD))
+ rcol -= tmp + 1;
+ goto resolve;
+ }
+ }
+ if (isblank(evp->e_c) && UNMAP_TST)
+ txt_unmap(sp, tp, &ec_flags);
+ }
+ if (abb != AB_NOTSET)
+ abb = inword(evp->e_c) ? AB_INWORD : AB_NOTWORD;
+
+insl_ch: if (txt_insch(sp, tp, &evp->e_c, flags))
+ goto err;
+
+ /*
+ * If we're using K_VLNEXT to quote the next character, then
+ * we want the cursor to position itself on the ^ placeholder
+ * we're displaying, to match historic practice.
+ */
+ if (quote == Q_VNEXT) {
+ --tp->cno;
+ ++tp->owrite;
+ }
+
+ /*
+ * !!!
+ * Translate "<CH_HEX>[isxdigit()]*" to a character with
+ * a hex value: this test delimits the value by the max
+ * number of hex bytes. Offset by one, we use 0 to mean
+ * that we've found <CH_HEX>.
+ */
+ if (hexcnt != 0 && hexcnt++ == sizeof(CHAR_T) * 2 + 1) {
+ hexcnt = 0;
+ if (txt_hex(sp, tp))
+ goto err;
+ }
+
+ /*
+ * Check to see if we've crossed the margin.
+ *
+ * !!!
+ * In the historic vi, the wrapmargin value was figured out
+ * using the display widths of the characters, i.e. <tab>
+ * characters were counted as two characters if the list edit
+ * option is set, but as the tabstop edit option number of
+ * characters otherwise. That's what the vs_column() function
+ * gives us, so we use it.
+ */
+ if (margin != 0) {
+ if (vs_column(sp, &tcol))
+ goto err;
+ if (tcol >= margin) {
+ if (txt_margin(sp, tp, &wmt, &tmp, flags))
+ goto err;
+ if (tmp) {
+ if (isblank(evp->e_c))
+ wm_skip = 1;
+ wm_set = 1;
+ goto k_cr;
+ }
+ }
+ }
+
+ /*
+ * If we've reached the end of the buffer, then we need to
+ * switch into insert mode. This happens when there's a
+ * change to a mark and the user puts in more characters than
+ * the length of the motion.
+ */
+ebuf_chk: if (tp->cno >= tp->len) {
+ BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1);
+ LF_SET(TXT_APPENDEOL);
+
+ tp->lb[tp->cno] = CH_CURSOR;
+ ++tp->insert;
+ ++tp->len;
+ }
+
+ /* Step the quote state forward. */
+ if (quote != Q_NOTSET) {
+ if (quote == Q_BNEXT)
+ quote = Q_BTHIS;
+ if (quote == Q_VNEXT)
+ quote = Q_VTHIS;
+ }
+ break;
+ }
+
+#ifdef DEBUG
+ if (tp->cno + tp->insert + tp->owrite != tp->len) {
+ msgq(sp, M_ERR,
+ "len %u != cno: %u ai: %u insert %u overwrite %u",
+ tp->len, tp->cno, tp->ai, tp->insert, tp->owrite);
+ if (LF_ISSET(TXT_REPLAY))
+ goto done;
+ tp->len = tp->cno + tp->insert + tp->owrite;
+ }
+#endif
+
+resolve:/*
+ * 1: If we don't need to know where the cursor really is and we're
+ * replaying text, keep going.
+ */
+ if (margin == 0 && LF_ISSET(TXT_REPLAY))
+ goto replay;
+
+ /*
+ * 2: Reset the line. Don't bother unless we're about to wait on
+ * a character or we need to know where the cursor really is.
+ * We have to do this before showing matching characters so the
+ * user can see what they're matching.
+ */
+ if ((margin != 0 || !KEYS_WAITING(sp)) &&
+ vs_change(sp, tp->lno, LINE_RESET))
+ return (1);
+
+ /*
+ * 3: If there aren't keys waiting, display the matching character.
+ * We have to do this before resolving any messages, otherwise
+ * the error message from a missing match won't appear correctly.
+ */
+ if (showmatch) {
+ if (!KEYS_WAITING(sp) && txt_showmatch(sp, tp))
+ return (1);
+ showmatch = 0;
+ }
+
+ /*
+ * 4: If there have been messages and we're not editing on the colon
+ * command line or doing file name completion, resolve them.
+ */
+ if ((vip->totalcount != 0 || F_ISSET(gp, G_BELLSCHED)) &&
+ !F_ISSET(sp, SC_TINPUT_INFO) && !filec_redraw &&
+ vs_resolve(sp, NULL, 0))
+ return (1);
+
+ /*
+ * 5: Refresh the screen if we're about to wait on a character or we
+ * need to know where the cursor really is.
+ */
+ if (margin != 0 || !KEYS_WAITING(sp)) {
+ UPDATE_POSITION(sp, tp);
+ if (vs_refresh(sp, margin != 0))
+ return (1);
+ }
+
+ /* 6: Proceed with the incremental search. */
+ if (FL_ISSET(is_flags, IS_RUNNING) && txt_isrch(sp, vp, tp, &is_flags))
+ return (1);
+
+ /* 7: Next character... */
+ if (LF_ISSET(TXT_REPLAY))
+ goto replay;
+ goto next;
+
+done: /* Leave input mode. */
+ F_CLR(sp, SC_TINPUT);
+
+ /* If recording for playback, save it. */
+ if (LF_ISSET(TXT_RECORD))
+ vip->rep_cnt = rcol;
+
+ /*
+ * If not working on the colon command line, set the final cursor
+ * position.
+ */
+ if (!F_ISSET(sp, SC_TINPUT_INFO)) {
+ vp->m_final.lno = tp->lno;
+ vp->m_final.cno = tp->cno;
+ }
+ return (0);
+
+err:
+alloc_err:
+ txt_err(sp, &sp->tiq);
+ return (1);
+}
+
+/*
+ * txt_abbrev --
+ * Handle abbreviations.
+ */
+static int
+txt_abbrev(sp, tp, pushcp, isinfoline, didsubp, turnoffp)
+ SCR *sp;
+ TEXT *tp;
+ CHAR_T *pushcp;
+ int isinfoline, *didsubp, *turnoffp;
+{
+ VI_PRIVATE *vip;
+ CHAR_T ch, *p;
+ SEQ *qp;
+ size_t len, off;
+
+ /* Check to make sure we're not at the start of an append. */
+ *didsubp = 0;
+ if (tp->cno == tp->offset)
+ return (0);
+
+ vip = VIP(sp);
+
+ /*
+ * Find the start of the "word".
+ *
+ * !!!
+ * We match historic practice, which, as far as I can tell, had an
+ * off-by-one error. The way this worked was that when the inserted
+ * text switched from a "word" character to a non-word character,
+ * vi would check for possible abbreviations. It would then take the
+ * type (i.e. word/non-word) of the character entered TWO characters
+ * ago, and move backward in the text until reaching a character that
+ * was not that type, or the beginning of the insert, the line, or
+ * the file. For example, in the string "abc<space>", when the <space>
+ * character triggered the abbreviation check, the type of the 'b'
+ * character was used for moving through the string. Maybe there's a
+ * reason for not using the first (i.e. 'c') character, but I can't
+ * think of one.
+ *
+ * Terminate at the beginning of the insert or the character after the
+ * offset character -- both can be tested for using tp->offset.
+ */
+ off = tp->cno - 1; /* Previous character. */
+ p = tp->lb + off;
+ len = 1; /* One character test. */
+ if (off == tp->offset || isblank(p[-1]))
+ goto search;
+ if (inword(p[-1])) /* Move backward to change. */
+ for (;;) {
+ --off; --p; ++len;
+ if (off == tp->offset || !inword(p[-1]))
+ break;
+ }
+ else
+ for (;;) {
+ --off; --p; ++len;
+ if (off == tp->offset ||
+ inword(p[-1]) || isblank(p[-1]))
+ break;
+ }
+
+ /*
+ * !!!
+ * Historic vi exploded abbreviations on the command line. This has
+ * obvious problems in that unabbreviating the string can be extremely
+ * tricky, particularly if the string has, say, an embedded escape
+ * character. Personally, I think it's a stunningly bad idea. Other
+ * examples of problems this caused in historic vi are:
+ * :ab foo bar
+ * :ab foo baz
+ * results in "bar" being abbreviated to "baz", which wasn't what the
+ * user had in mind at all. Also, the commands:
+ * :ab foo bar
+ * :unab foo<space>
+ * resulted in an error message that "bar" wasn't mapped. Finally,
+ * since the string was already exploded by the time the unabbreviate
+ * command got it, all it knew was that an abbreviation had occurred.
+ * Cleverly, it checked the replacement string for its unabbreviation
+ * match, which meant that the commands:
+ * :ab foo1 bar
+ * :ab foo2 bar
+ * :unab foo2
+ * unabbreviate "foo1", and the commands:
+ * :ab foo bar
+ * :ab bar baz
+ * unabbreviate "foo"!
+ *
+ * Anyway, people neglected to first ask my opinion before they wrote
+ * macros that depend on this stuff, so, we make this work as follows.
+ * When checking for an abbreviation on the command line, if we get a
+ * string which is <blank> terminated and which starts at the beginning
+ * of the line, we check to see it is the abbreviate or unabbreviate
+ * commands. If it is, turn abbreviations off and return as if no
+ * abbreviation was found. Note also, minor trickiness, so that if
+ * the user erases the line and starts another command, we turn the
+ * abbreviations back on.
+ *
+ * This makes the layering look like a Nachos Supreme.
+ */
+search: if (isinfoline)
+ if (off == tp->ai || off == tp->offset)
+ if (ex_is_abbrev(p, len)) {
+ *turnoffp = 1;
+ return (0);
+ } else
+ *turnoffp = 0;
+ else
+ if (*turnoffp)
+ return (0);
+
+ /* Check for any abbreviations. */
+ if ((qp = seq_find(sp, NULL, NULL, p, len, SEQ_ABBREV, NULL)) == NULL)
+ return (0);
+
+ /*
+ * Push the abbreviation onto the tty stack. Historically, characters
+ * resulting from an abbreviation expansion were themselves subject to
+ * map expansions, O_SHOWMATCH matching etc. This means the expanded
+ * characters will be re-tested for abbreviations. It's difficult to
+ * know what historic practice in this case was, since abbreviations
+ * were applied to :colon command lines, so entering abbreviations that
+ * looped was tricky, although possible. In addition, obvious loops
+ * didn't work as expected. (The command ':ab a b|ab b c|ab c a' will
+ * silently only implement and/or display the last abbreviation.)
+ *
+ * This implementation doesn't recover well from such abbreviations.
+ * The main input loop counts abbreviated characters, and, when it
+ * reaches a limit, discards any abbreviated characters on the queue.
+ * It's difficult to back up to the original position, as the replay
+ * queue would have to be adjusted, and the line state when an initial
+ * abbreviated character was received would have to be saved.
+ */
+ ch = *pushcp;
+ if (v_event_push(sp, NULL, &ch, 1, CH_ABBREVIATED))
+ return (1);
+ if (v_event_push(sp, NULL, qp->output, qp->olen, CH_ABBREVIATED))
+ return (1);
+
+ /*
+ * If the size of the abbreviation is larger than or equal to the size
+ * of the original text, move to the start of the replaced characters,
+ * and add their length to the overwrite count.
+ *
+ * If the abbreviation is smaller than the original text, we have to
+ * delete the additional overwrite characters and copy down any insert
+ * characters.
+ */
+ tp->cno -= len;
+ if (qp->olen >= len)
+ tp->owrite += len;
+ else {
+ if (tp->insert)
+ memmove(tp->lb + tp->cno + qp->olen,
+ tp->lb + tp->cno + tp->owrite + len, tp->insert);
+ tp->owrite += qp->olen;
+ tp->len -= len - qp->olen;
+ }
+
+ /*
+ * We return the length of the abbreviated characters. This is so
+ * the calling routine can replace the replay characters with the
+ * abbreviation. This means that subsequent '.' commands will produce
+ * the same text, regardless of intervening :[un]abbreviate commands.
+ * This is historic practice.
+ */
+ *didsubp = len;
+ return (0);
+}
+
+/*
+ * txt_unmap --
+ * Handle the unmap command.
+ */
+static void
+txt_unmap(sp, tp, ec_flagsp)
+ SCR *sp;
+ TEXT *tp;
+ u_int32_t *ec_flagsp;
+{
+ size_t len, off;
+ char *p;
+
+ /* Find the beginning of this "word". */
+ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) {
+ if (isblank(*p)) {
+ ++p;
+ break;
+ }
+ ++len;
+ if (off == tp->ai || off == tp->offset)
+ break;
+ }
+
+ /*
+ * !!!
+ * Historic vi exploded input mappings on the command line. See the
+ * txt_abbrev() routine for an explanation of the problems inherent
+ * in this.
+ *
+ * We make this work as follows. If we get a string which is <blank>
+ * terminated and which starts at the beginning of the line, we check
+ * to see it is the unmap command. If it is, we return that the input
+ * mapping should be turned off. Note also, minor trickiness, so that
+ * if the user erases the line and starts another command, we go ahead
+ * an turn mapping back on.
+ */
+ if ((off == tp->ai || off == tp->offset) && ex_is_unmap(p, len))
+ FL_CLR(*ec_flagsp, EC_MAPINPUT);
+ else
+ FL_SET(*ec_flagsp, EC_MAPINPUT);
+}
+
+/*
+ * txt_ai_resolve --
+ * When a line is resolved by <esc>, review autoindent characters.
+ */
+static void
+txt_ai_resolve(sp, tp, changedp)
+ SCR *sp;
+ TEXT *tp;
+ int *changedp;
+{
+ u_long ts;
+ int del;
+ size_t cno, len, new, old, scno, spaces, tab_after_sp, tabs;
+ char *p;
+
+ *changedp = 0;
+
+ /*
+ * If the line is empty, has an offset, or no autoindent
+ * characters, we're done.
+ */
+ if (!tp->len || tp->offset || !tp->ai)
+ return;
+
+ /*
+ * If the length is less than or equal to the autoindent
+ * characters, delete them.
+ */
+ if (tp->len <= tp->ai) {
+ tp->ai = tp->cno = tp->len = 0;
+ return;
+ }
+
+ /*
+ * The autoindent characters plus any leading <blank> characters
+ * in the line are resolved into the minimum number of characters.
+ * Historic practice.
+ */
+ ts = O_VAL(sp, O_TABSTOP);
+
+ /* Figure out the last <blank> screen column. */
+ for (p = tp->lb, scno = 0, len = tp->len,
+ spaces = tab_after_sp = 0; len-- && isblank(*p); ++p)
+ if (*p == '\t') {
+ if (spaces)
+ tab_after_sp = 1;
+ scno += COL_OFF(scno, ts);
+ } else {
+ ++spaces;
+ ++scno;
+ }
+
+ /*
+ * If there are no spaces, or no tabs after spaces and less than
+ * ts spaces, it's already minimal.
+ */
+ if (!spaces || !tab_after_sp && spaces < ts)
+ return;
+
+ /* 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;
+
+ /*
+ * Figure out how many characters we're dropping -- if we're not
+ * dropping any, it's already minimal, we're done.
+ */
+ old = p - tp->lb;
+ new = spaces + tabs;
+ if (old == new)
+ return;
+
+ /* Shift the rest of the characters down, adjust the counts. */
+ del = old - new;
+ memmove(p - del, p, tp->len - old);
+ tp->len -= del;
+ tp->cno -= del;
+
+ /* Fill in space/tab characters. */
+ for (p = tp->lb; tabs--;)
+ *p++ = '\t';
+ while (spaces--)
+ *p++ = ' ';
+ *changedp = 1;
+}
+
+/*
+ * v_txt_auto --
+ * Handle autoindent. If aitp isn't NULL, use it, otherwise,
+ * retrieve the line.
+ *
+ * PUBLIC: int v_txt_auto __P((SCR *, recno_t, TEXT *, size_t, TEXT *));
+ */
+int
+v_txt_auto(sp, lno, aitp, len, tp)
+ SCR *sp;
+ recno_t lno;
+ TEXT *aitp, *tp;
+ size_t len;
+{
+ size_t nlen;
+ char *p, *t;
+
+ if (aitp == NULL) {
+ /*
+ * If the ex append command is executed with an address of 0,
+ * it's possible to get here with a line number of 0. Return
+ * an indent of 0.
+ */
+ if (lno == 0) {
+ tp->ai = 0;
+ return (0);
+ }
+ if (db_get(sp, lno, DBG_FATAL, &t, &len))
+ return (1);
+ } else
+ t = aitp->lb;
+
+ /* Count whitespace characters. */
+ for (p = t; len > 0; ++p, --len)
+ if (!isblank(*p))
+ break;
+
+ /* Set count, check for no indentation. */
+ if ((nlen = (p - t)) == 0)
+ return (0);
+
+ /* Make sure the buffer's big enough. */
+ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + nlen);
+
+ /* Copy the buffer's current contents up. */
+ if (tp->len != 0)
+ memmove(tp->lb + nlen, tp->lb, tp->len);
+ tp->len += nlen;
+
+ /* Copy the indentation into the new buffer. */
+ memmove(tp->lb, t, nlen);
+
+ /* Set the autoindent count. */
+ tp->ai = nlen;
+ return (0);
+}
+
+/*
+ * txt_backup --
+ * Back up to the previously edited line.
+ */
+static TEXT *
+txt_backup(sp, tiqh, tp, flagsp)
+ SCR *sp;
+ TEXTH *tiqh;
+ TEXT *tp;
+ u_int32_t *flagsp;
+{
+ VI_PRIVATE *vip;
+ TEXT *ntp;
+
+ /* Get a handle on the previous TEXT structure. */
+ if ((ntp = tp->q.cqe_prev) == (void *)tiqh) {
+ if (!FL_ISSET(*flagsp, TXT_REPLAY))
+ msgq(sp, M_BERR,
+ "193|Already at the beginning of the insert");
+ return (tp);
+ }
+
+ /* Bookkeeping. */
+ ntp->len = ntp->sv_len;
+
+ /* Handle appending to the line. */
+ vip = VIP(sp);
+ if (ntp->owrite == 0 && ntp->insert == 0) {
+ ntp->lb[ntp->len] = CH_CURSOR;
+ ++ntp->insert;
+ ++ntp->len;
+ FL_SET(*flagsp, TXT_APPENDEOL);
+ } else
+ FL_CLR(*flagsp, TXT_APPENDEOL);
+
+ /* Release the current TEXT. */
+ CIRCLEQ_REMOVE(tiqh, tp, q);
+ text_free(tp);
+
+ /* Update the old line on the screen. */
+ if (vs_change(sp, ntp->lno + 1, LINE_DELETE))
+ return (NULL);
+
+ /* Return the new/current TEXT. */
+ return (ntp);
+}
+
+/*
+ * Text indentation is truly strange. ^T and ^D do movements to the next or
+ * previous shiftwidth value, i.e. for a 1-based numbering, with shiftwidth=3,
+ * ^T moves a cursor on the 7th, 8th or 9th column to the 10th column, and ^D
+ * moves it back.
+ *
+ * !!!
+ * The ^T and ^D characters in historical vi had special meaning only when they
+ * were the first characters entered after entering text input mode. As normal
+ * erase characters couldn't erase autoindent characters (^T in this case), it
+ * meant that inserting text into previously existing text was strange -- ^T
+ * only worked if it was the first keystroke(s), and then could only be erased
+ * using ^D. This implementation treats ^T specially anywhere it occurs in the
+ * input, and permits the standard erase characters to erase the characters it
+ * inserts.
+ *
+ * !!!
+ * A fun test is to try:
+ * :se sw=4 ai list
+ * i<CR>^Tx<CR>^Tx<CR>^Tx<CR>^Dx<CR>^Dx<CR>^Dx<esc>
+ * Historic vi loses some of the '$' marks on the line ends, but otherwise gets
+ * it right.
+ *
+ * XXX
+ * Technically, txt_dent should be part of the screen interface, as it requires
+ * knowledge of character sizes, including <space>s, on the screen. It's here
+ * because it's a complicated little beast, and I didn't want to shove it down
+ * into the screen. It's probable that KEY_LEN will call into the screen once
+ * there are screens with different character representations.
+ *
+ * txt_dent --
+ * Handle ^T indents, ^D outdents.
+ *
+ * If anything changes here, check the ex version to see if it needs similar
+ * changes.
+ */
+static int
+txt_dent(sp, tp, isindent)
+ SCR *sp;
+ TEXT *tp;
+ int isindent;
+{
+ CHAR_T ch;
+ u_long sw, ts;
+ size_t cno, current, spaces, target, tabs, off;
+ int ai_reset;
+
+ ts = O_VAL(sp, O_TABSTOP);
+ sw = O_VAL(sp, O_SHIFTWIDTH);
+
+ /*
+ * Since we don't know what precedes the character(s) being inserted
+ * (or deleted), the preceding whitespace characters must be resolved.
+ * An example is a <tab>, which doesn't need 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 (or worse,
+ * something for which tabstop isn't a multiple) and then uses ^T to
+ * indent, and ^D to outdent.
+ *
+ * Figure out the current and target screen columns. In the historic
+ * vi, the autoindent column was NOT determined using display widths
+ * of characters as was the wrapmargin column. For that reason, we
+ * can't use the vs_column() function, but have to calculate it here.
+ * This is slow, but it's normally only on the first few characters of
+ * a line.
+ */
+ for (current = cno = 0; cno < tp->cno; ++cno)
+ current += tp->lb[cno] == '\t' ?
+ COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]);
+
+ target = current;
+ if (isindent)
+ target += COL_OFF(target, sw);
+ else
+ target -= --target % sw;
+
+ /*
+ * The AI characters will be turned into overwrite characters if the
+ * cursor immediately follows them. We test both the cursor position
+ * and the indent flag because there's no single test. (^T can only
+ * be detected by the cursor position, and while we know that the test
+ * is always true for ^D, the cursor can be in more than one place, as
+ * "0^D" and "^D" are different.)
+ */
+ ai_reset = !isindent || tp->cno == tp->ai + tp->offset;
+
+ /*
+ * Back up over any previous <blank> characters, changing them into
+ * overwrite characters (including any ai characters). Then figure
+ * out the current screen column.
+ */
+ for (; tp->cno > tp->offset &&
+ (tp->lb[tp->cno - 1] == ' ' || tp->lb[tp->cno - 1] == '\t');
+ --tp->cno, ++tp->owrite);
+ for (current = cno = 0; cno < tp->cno; ++cno)
+ current += tp->lb[cno] == '\t' ?
+ COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]);
+
+ /*
+ * If we didn't move up to or past the target, it's because there
+ * weren't enough characters to delete, e.g. the first character
+ * of the line was a tp->offset character, and the user entered
+ * ^D to move to the beginning of a line. An example of this is:
+ *
+ * :set ai sw=4<cr>i<space>a<esc>i^T^D
+ *
+ * Otherwise, count up the total spaces/tabs needed to get from the
+ * beginning of the line (or the last non-<blank> character) to the
+ * target.
+ */
+ if (current >= target)
+ spaces = tabs = 0;
+ else {
+ for (cno = current,
+ tabs = 0; cno + COL_OFF(cno, ts) <= target; ++tabs)
+ cno += COL_OFF(cno, ts);
+ spaces = target - cno;
+ }
+
+ /* If we overwrote ai characters, reset the ai count. */
+ if (ai_reset)
+ tp->ai = tabs + spaces;
+
+ /*
+ * Call txt_insch() to insert each character, so that we get the
+ * correct effect when we add a <tab> to replace N <spaces>.
+ */
+ for (ch = '\t'; tabs > 0; --tabs)
+ (void)txt_insch(sp, tp, &ch, 0);
+ for (ch = ' '; spaces > 0; --spaces)
+ (void)txt_insch(sp, tp, &ch, 0);
+ return (0);
+}
+
+/*
+ * txt_fc --
+ * File name completion.
+ */
+static int
+txt_fc(sp, tp, redrawp)
+ SCR *sp;
+ TEXT *tp;
+ int *redrawp;
+{
+ struct stat sb;
+ ARGS **argv;
+ CHAR_T s_ch;
+ EXCMD cmd;
+ size_t indx, len, nlen, off;
+ int argc, trydir;
+ char *p, *t;
+
+ trydir = 0;
+ *redrawp = 0;
+
+ /*
+ * Find the beginning of this "word" -- if we're at the beginning
+ * of the line, it's a special case.
+ */
+ if (tp->cno == 1) {
+ len = 0;
+ p = tp->lb;
+ } else
+retry: for (len = 0,
+ off = tp->cno - 1, p = tp->lb + off;; --off, --p) {
+ if (isblank(*p)) {
+ ++p;
+ break;
+ }
+ ++len;
+ if (off == tp->ai || off == tp->offset)
+ break;
+ }
+
+ /*
+ * Get enough space for a wildcard character.
+ *
+ * XXX
+ * This won't work for "foo\", since the \ will escape the expansion
+ * character. I'm not sure if that's a bug or not...
+ */
+ off = p - tp->lb;
+ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1);
+ p = tp->lb + off;
+
+ s_ch = p[len];
+ p[len] = '*';
+
+ /* Build an ex command, and call the ex expansion routines. */
+ ex_cinit(&cmd, 0, 0, OOBLNO, OOBLNO, 0, NULL);
+ if (argv_init(sp, &cmd))
+ return (1);
+ if (argv_exp2(sp, &cmd, p, len + 1)) {
+ p[len] = s_ch;
+ return (0);
+ }
+ argc = cmd.argc;
+ argv = cmd.argv;
+
+ p[len] = s_ch;
+
+ switch (argc) {
+ case 0: /* No matches. */
+ if (!trydir)
+ (void)sp->gp->scr_bell(sp);
+ return (0);
+ case 1: /* One match. */
+ /* If something changed, do the exchange. */
+ nlen = strlen(cmd.argv[0]->bp);
+ if (len != nlen || memcmp(cmd.argv[0]->bp, p, len))
+ break;
+
+ /* If haven't done a directory test, do it now. */
+ if (!trydir &&
+ !stat(cmd.argv[0]->bp, &sb) && S_ISDIR(sb.st_mode)) {
+ p += len;
+ goto isdir;
+ }
+
+ /* If nothing changed, period, ring the bell. */
+ if (!trydir)
+ (void)sp->gp->scr_bell(sp);
+ return (0);
+ default: /* Multiple matches. */
+ *redrawp = 1;
+ if (txt_fc_col(sp, argc, argv))
+ return (1);
+
+ /* Find the length of the shortest match. */
+ for (nlen = cmd.argv[0]->len; --argc > 0;) {
+ if (cmd.argv[argc]->len < nlen)
+ nlen = cmd.argv[argc]->len;
+ for (indx = 0; indx < nlen &&
+ cmd.argv[argc]->bp[indx] == cmd.argv[0]->bp[indx];
+ ++indx);
+ nlen = indx;
+ }
+ break;
+ }
+
+ /* Overwrite the expanded text first. */
+ for (t = cmd.argv[0]->bp; len > 0 && nlen > 0; --len, --nlen)
+ *p++ = *t++;
+
+ /* If lost text, make the remaining old text overwrite characters. */
+ if (len) {
+ tp->cno -= len;
+ tp->owrite += len;
+ }
+
+ /* Overwrite any overwrite characters next. */
+ for (; nlen > 0 && tp->owrite > 0; --nlen, --tp->owrite, ++tp->cno)
+ *p++ = *t++;
+
+ /* Shift remaining text up, and move the cursor to the end. */
+ if (nlen) {
+ off = p - tp->lb;
+ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + nlen);
+ p = tp->lb + off;
+
+ tp->cno += nlen;
+ tp->len += nlen;
+
+ if (tp->insert != 0)
+ (void)memmove(p + nlen, p, tp->insert);
+ while (nlen--)
+ *p++ = *t++;
+ }
+
+ /* If a single match and it's a directory, retry it. */
+ if (argc == 1 && !stat(cmd.argv[0]->bp, &sb) && S_ISDIR(sb.st_mode)) {
+isdir: if (tp->owrite == 0) {
+ off = p - tp->lb;
+ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1);
+ p = tp->lb + off;
+ if (tp->insert != 0)
+ (void)memmove(p + 1, p, tp->insert);
+ ++tp->len;
+ } else
+ --tp->owrite;
+
+ ++tp->cno;
+ *p++ = '/';
+
+ trydir = 1;
+ goto retry;
+ }
+ return (0);
+}
+
+/*
+ * txt_fc_col --
+ * Display file names for file name completion.
+ */
+static int
+txt_fc_col(sp, argc, argv)
+ SCR *sp;
+ int argc;
+ ARGS **argv;
+{
+ ARGS **av;
+ CHAR_T *p;
+ GS *gp;
+ size_t base, cnt, col, colwidth, numrows, numcols, prefix, row;
+ int ac, nf, reset;
+
+ gp = sp->gp;
+
+ /* Trim any directory prefix common to all of the files. */
+ if ((p = strrchr(argv[0]->bp, '/')) == NULL)
+ prefix = 0;
+ else {
+ prefix = (p - argv[0]->bp) + 1;
+ for (ac = argc - 1, av = argv + 1; ac > 0; --ac, ++av)
+ if (av[0]->len < prefix ||
+ memcmp(av[0]->bp, argv[0]->bp, prefix)) {
+ prefix = 0;
+ break;
+ }
+ }
+
+ /*
+ * Figure out the column width for the longest name. Output is done on
+ * 6 character "tab" boundaries for no particular reason. (Since we
+ * don't output tab characters, we ignore the terminal's tab settings.)
+ * Ignore the user's tab setting because we have no idea how reasonable
+ * it is.
+ */
+ for (ac = argc, av = argv, colwidth = 0; ac > 0; --ac, ++av) {
+ for (col = 0, p = av[0]->bp + prefix; *p != '\0'; ++p)
+ col += KEY_LEN(sp, *p);
+ if (col > colwidth)
+ colwidth = col;
+ }
+ colwidth += COL_OFF(colwidth, 6);
+
+ /*
+ * Writing to the bottom line of the screen is always turned off when
+ * SC_TINPUT_INFO is set. Turn it back on, we know what we're doing.
+ */
+ if (F_ISSET(sp, SC_TINPUT_INFO)) {
+ reset = 1;
+ F_CLR(sp, SC_TINPUT_INFO);
+ } else
+ reset = 0;
+
+#define CHK_INTR \
+ if (F_ISSET(gp, G_INTERRUPTED)) \
+ goto intr;
+
+ /* If the largest file name is too large, just print them. */
+ if (colwidth > sp->cols) {
+ p = msg_print(sp, av[0]->bp + prefix, &nf);
+ for (ac = argc, av = argv; ac > 0; --ac, ++av) {
+ (void)ex_printf(sp, "%s\n", p);
+ if (F_ISSET(gp, G_INTERRUPTED))
+ break;
+ }
+ if (nf)
+ FREE_SPACE(sp, p, 0);
+ CHK_INTR;
+ } else {
+ /* Figure out the number of columns. */
+ numcols = (sp->cols - 1) / colwidth;
+ if (argc > numcols) {
+ numrows = argc / numcols;
+ if (argc % numcols)
+ ++numrows;
+ } else
+ numrows = 1;
+
+ /* Display the files in sorted order. */
+ for (row = 0; row < numrows; ++row) {
+ for (base = row, col = 0; col < numcols; ++col) {
+ p = msg_print(sp, argv[base]->bp + prefix, &nf);
+ cnt = ex_printf(sp, "%s", p);
+ if (nf)
+ FREE_SPACE(sp, p, 0);
+ CHK_INTR;
+ if ((base += numrows) >= argc)
+ break;
+ (void)ex_printf(sp,
+ "%*s", (int)(colwidth - cnt), "");
+ CHK_INTR;
+ }
+ (void)ex_puts(sp, "\n");
+ CHK_INTR;
+ }
+ (void)ex_puts(sp, "\n");
+ CHK_INTR;
+ }
+ (void)ex_fflush(sp);
+
+ if (0) {
+intr: F_CLR(gp, G_INTERRUPTED);
+ }
+ if (reset)
+ F_SET(sp, SC_TINPUT_INFO);
+
+ return (0);
+}
+
+/*
+ * txt_emark --
+ * Set the end mark on the line.
+ */
+static int
+txt_emark(sp, tp, cno)
+ SCR *sp;
+ TEXT *tp;
+ size_t cno;
+{
+ CHAR_T ch, *kp;
+ size_t chlen, nlen, olen;
+ char *p;
+
+ ch = CH_ENDMARK;
+
+ /*
+ * The end mark may not be the same size as the current character.
+ * Don't let the line shift.
+ */
+ nlen = KEY_LEN(sp, ch);
+ if (tp->lb[cno] == '\t')
+ (void)vs_columns(sp, tp->lb, tp->lno, &cno, &olen);
+ else
+ olen = KEY_LEN(sp, tp->lb[cno]);
+
+ /*
+ * If the line got longer, well, it's weird, but it's easy. If
+ * it's the same length, it's easy. If it got shorter, we have
+ * to fix it up.
+ */
+ if (olen > nlen) {
+ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + olen);
+ chlen = olen - nlen;
+ if (tp->insert != 0)
+ memmove(tp->lb + cno + 1 + chlen,
+ tp->lb + cno + 1, tp->insert);
+
+ tp->len += chlen;
+ tp->owrite += chlen;
+ p = tp->lb + cno;
+ if (tp->lb[cno] == '\t')
+ for (cno += chlen; chlen--;)
+ *p++ = ' ';
+ else
+ for (kp = KEY_NAME(sp, tp->lb[cno]),
+ cno += chlen; chlen--;)
+ *p++ = *kp++;
+ }
+ tp->lb[cno] = ch;
+ return (vs_change(sp, tp->lno, LINE_RESET));
+}
+
+/*
+ * txt_err --
+ * Handle an error during input processing.
+ */
+static void
+txt_err(sp, tiqh)
+ SCR *sp;
+ TEXTH *tiqh;
+{
+ recno_t lno;
+
+ /*
+ * The problem with input processing is that the cursor is at an
+ * indeterminate position since some input may have been lost due
+ * to a malloc error. So, try to go back to the place from which
+ * the cursor started, knowing that it may no longer be available.
+ *
+ * We depend on at least one line number being set in the text
+ * chain.
+ */
+ for (lno = tiqh->cqh_first->lno;
+ !db_exist(sp, lno) && lno > 0; --lno);
+
+ sp->lno = lno == 0 ? 1 : lno;
+ sp->cno = 0;
+
+ /* Redraw the screen, just in case. */
+ F_SET(sp, SC_SCR_REDRAW);
+}
+
+/*
+ * txt_hex --
+ * Let the user insert any character value they want.
+ *
+ * !!!
+ * This is an extension. The pattern "^X[0-9a-fA-F]*" is a way
+ * for the user to specify a character value which their keyboard
+ * may not be able to enter.
+ */
+static int
+txt_hex(sp, tp)
+ SCR *sp;
+ TEXT *tp;
+{
+ CHAR_T savec;
+ size_t len, off;
+ u_long value;
+ char *p, *wp;
+
+ /*
+ * Null-terminate the string. Since nul isn't a legal hex value,
+ * this should be okay, and lets us use a local routine, which
+ * presumably understands the character set, to convert the value.
+ */
+ savec = tp->lb[tp->cno];
+ tp->lb[tp->cno] = 0;
+
+ /* Find the previous CH_HEX character. */
+ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off, ++len) {
+ if (*p == CH_HEX) {
+ wp = p + 1;
+ break;
+ }
+ /* Not on this line? Shouldn't happen. */
+ if (off == tp->ai || off == tp->offset)
+ goto nothex;
+ }
+
+ /* If length of 0, then it wasn't a hex value. */
+ if (len == 0)
+ goto nothex;
+
+ /* Get the value. */
+ errno = 0;
+ value = strtol(wp, NULL, 16);
+ if (errno || value > MAX_CHAR_T) {
+nothex: tp->lb[tp->cno] = savec;
+ return (0);
+ }
+
+ /* Restore the original character. */
+ tp->lb[tp->cno] = savec;
+
+ /* Adjust the bookkeeping. */
+ tp->cno -= len;
+ tp->len -= len;
+ tp->lb[tp->cno - 1] = value;
+
+ /* Copy down any overwrite characters. */
+ if (tp->owrite)
+ memmove(tp->lb + tp->cno, tp->lb + tp->cno + len, tp->owrite);
+
+ /* Copy down any insert characters. */
+ if (tp->insert)
+ memmove(tp->lb + tp->cno + tp->owrite,
+ tp->lb + tp->cno + tp->owrite + len, tp->insert);
+
+ return (0);
+}
+
+/*
+ * txt_insch --
+ *
+ * !!!
+ * Historic vi did a special screen optimization for tab characters. As an
+ * example, for the keystrokes "iabcd<esc>0C<tab>", the tab overwrote the
+ * rest of the string when it was displayed.
+ *
+ * Because early versions of this implementation redisplayed the entire line
+ * on each keystroke, the "bcd" was pushed to the right as it ignored that
+ * the user had "promised" to change the rest of the characters. However,
+ * the historic vi implementation had an even worse bug: given the keystrokes
+ * "iabcd<esc>0R<tab><esc>", the "bcd" disappears, and magically reappears
+ * on the second <esc> key.
+ *
+ * POSIX 1003.2 requires (will require) that this be fixed, specifying that
+ * vi overwrite characters the user has committed to changing, on the basis
+ * of the screen space they require, but that it not overwrite other characters.
+ */
+static int
+txt_insch(sp, tp, chp, flags)
+ SCR *sp;
+ TEXT *tp;
+ CHAR_T *chp;
+ u_int flags;
+{
+ CHAR_T *kp, savech;
+ size_t chlen, cno, copydown, olen, nlen;
+ char *p;
+
+ /*
+ * The 'R' command does one-for-one replacement, because there's
+ * no way to know how many characters the user intends to replace.
+ */
+ if (LF_ISSET(TXT_REPLACE)) {
+ if (tp->owrite) {
+ --tp->owrite;
+ tp->lb[tp->cno++] = *chp;
+ return (0);
+ }
+ } else if (tp->owrite) { /* Overwrite a character. */
+ cno = tp->cno;
+
+ /*
+ * If the old or new characters are tabs, then the length of the
+ * display depends on the character position in the display. We
+ * don't even try to handle this here, just ask the screen.
+ */
+ if (*chp == '\t') {
+ savech = tp->lb[cno];
+ tp->lb[cno] = '\t';
+ (void)vs_columns(sp, tp->lb, tp->lno, &cno, &nlen);
+ tp->lb[cno] = savech;
+ } else
+ nlen = KEY_LEN(sp, *chp);
+
+ /*
+ * Eat overwrite characters until we run out of them or we've
+ * handled the length of the new character. If we only eat
+ * part of an overwrite character, break it into its component
+ * elements and display the remaining components.
+ */
+ for (copydown = 0; nlen != 0 && tp->owrite != 0;) {
+ --tp->owrite;
+
+ if (tp->lb[cno] == '\t')
+ (void)vs_columns(sp,
+ tp->lb, tp->lno, &cno, &olen);
+ else
+ olen = KEY_LEN(sp, tp->lb[cno]);
+
+ if (olen == nlen) {
+ nlen = 0;
+ break;
+ }
+ if (olen < nlen) {
+ ++copydown;
+ nlen -= olen;
+ } else {
+ BINC_RET(sp,
+ tp->lb, tp->lb_len, tp->len + olen);
+ chlen = olen - nlen;
+ memmove(tp->lb + cno + 1 + chlen,
+ tp->lb + cno + 1, tp->owrite + tp->insert);
+
+ tp->len += chlen;
+ tp->owrite += chlen;
+ if (tp->lb[cno] == '\t')
+ for (p = tp->lb + cno + 1; chlen--;)
+ *p++ = ' ';
+ else
+ for (kp =
+ KEY_NAME(sp, tp->lb[cno]) + nlen,
+ p = tp->lb + cno + 1; chlen--;)
+ *p++ = *kp++;
+ nlen = 0;
+ break;
+ }
+ }
+
+ /*
+ * If had to erase several characters, we adjust the total
+ * count, and if there are any characters left, shift them
+ * into position.
+ */
+ if (copydown != 0 && (tp->len -= copydown) != 0)
+ memmove(tp->lb + cno, tp->lb + cno + copydown,
+ tp->owrite + tp->insert + copydown);
+
+ /* If we had enough overwrite characters, we're done. */
+ if (nlen == 0) {
+ tp->lb[tp->cno++] = *chp;
+ return (0);
+ }
+ }
+
+ /* Check to see if the character fits into the input buffer. */
+ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1);
+
+ ++tp->len;
+ if (tp->insert) { /* Insert a character. */
+ if (tp->insert == 1)
+ tp->lb[tp->cno + 1] = tp->lb[tp->cno];
+ else
+ memmove(tp->lb + tp->cno + 1,
+ tp->lb + tp->cno, tp->owrite + tp->insert);
+ }
+ tp->lb[tp->cno++] = *chp;
+ return (0);
+}
+
+/*
+ * txt_isrch --
+ * Do an incremental search.
+ */
+static int
+txt_isrch(sp, vp, tp, is_flagsp)
+ SCR *sp;
+ VICMD *vp;
+ TEXT *tp;
+ u_int8_t *is_flagsp;
+{
+ MARK start;
+ recno_t lno;
+ u_int sf;
+
+ /* If it's a one-line screen, we don't do incrementals. */
+ if (IS_ONELINE(sp)) {
+ FL_CLR(*is_flagsp, IS_RUNNING);
+ return (0);
+ }
+
+ /*
+ * If the user erases back to the beginning of the buffer, there's
+ * nothing to search for. Reset the cursor to the starting point.
+ */
+ if (tp->cno <= 1) {
+ vp->m_final = vp->m_start;
+ return (0);
+ }
+
+ /*
+ * If it's an RE quote character, and not quoted, ignore it until
+ * we get another character.
+ */
+ if (tp->lb[tp->cno - 1] == '\\' &&
+ (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\'))
+ return (0);
+
+ /*
+ * If it's a magic shell character, and not quoted, reset the cursor
+ * to the starting point.
+ */
+ if (strchr(O_STR(sp, O_SHELLMETA), tp->lb[tp->cno - 1]) != NULL &&
+ (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\'))
+ vp->m_final = vp->m_start;
+
+ /*
+ * If we see the search pattern termination character, then quit doing
+ * an incremental search. There may be more, e.g., ":/foo/;/bar/",
+ * and we can't handle that incrementally. Also, reset the cursor to
+ * the original location, the ex search routines don't know anything
+ * about incremental searches.
+ */
+ if (tp->lb[0] == tp->lb[tp->cno - 1] &&
+ (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) {
+ vp->m_final = vp->m_start;
+ FL_CLR(*is_flagsp, IS_RUNNING);
+ return (0);
+ }
+
+ /*
+ * Remember the input line and discard the special input map,
+ * but don't overwrite the input line on the screen.
+ */
+ lno = tp->lno;
+ F_SET(VIP(sp), VIP_S_MODELINE);
+ F_CLR(sp, SC_TINPUT | SC_TINPUT_INFO);
+ if (txt_map_end(sp))
+ return (1);
+
+ /*
+ * Specify a starting point and search. If we find a match, move to
+ * it and refresh the screen. If we didn't find the match, then we
+ * beep the screen. When searching from the original cursor position,
+ * we have to move the cursor, otherwise, we don't want to move the
+ * cursor in case the text at the current position continues to match.
+ */
+ if (FL_ISSET(*is_flagsp, IS_RESTART)) {
+ start = vp->m_start;
+ sf = SEARCH_SET;
+ } else {
+ start = vp->m_final;
+ sf = SEARCH_INCR | SEARCH_SET;
+ }
+
+ if (tp->lb[0] == '/' ?
+ !f_search(sp,
+ &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf) :
+ !b_search(sp,
+ &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf)) {
+ sp->lno = vp->m_final.lno;
+ sp->cno = vp->m_final.cno;
+ FL_CLR(*is_flagsp, IS_RESTART);
+
+ if (!KEYS_WAITING(sp) && vs_refresh(sp, 0))
+ return (1);
+ } else
+ FL_SET(*is_flagsp, IS_RESTART);
+
+ /* Reinstantiate the special input map. */
+ if (txt_map_init(sp))
+ return (1);
+ F_CLR(VIP(sp), VIP_S_MODELINE);
+ F_SET(sp, SC_TINPUT | SC_TINPUT_INFO);
+
+ /* Reset the line number of the input line. */
+ tp->lno = TMAP[0].lno;
+
+ /*
+ * If the colon command-line moved, i.e. the screen scrolled,
+ * refresh the input line.
+ *
+ * XXX
+ * We shouldn't be calling vs_line, here -- we need dirty bits
+ * on entries in the SMAP array.
+ */
+ if (lno != TMAP[0].lno) {
+ if (vs_line(sp, &TMAP[0], NULL, NULL))
+ return (1);
+ (void)sp->gp->scr_refresh(sp, 0);
+ }
+ return (0);
+}
+
+/*
+ * txt_resolve --
+ * Resolve the input text chain into the file.
+ */
+static int
+txt_resolve(sp, tiqh, flags)
+ SCR *sp;
+ TEXTH *tiqh;
+ u_int32_t flags;
+{
+ VI_PRIVATE *vip;
+ TEXT *tp;
+ recno_t lno;
+ int changed;
+
+ /*
+ * The first line replaces a current line, and all subsequent lines
+ * are appended into the file. Resolve autoindented characters for
+ * each line before committing it. If the latter causes the line to
+ * change, we have to redisplay it, otherwise the information cached
+ * about the line will be wrong.
+ */
+ vip = VIP(sp);
+ tp = tiqh->cqh_first;
+
+ if (LF_ISSET(TXT_AUTOINDENT))
+ txt_ai_resolve(sp, tp, &changed);
+ else
+ changed = 0;
+ if (db_set(sp, tp->lno, tp->lb, tp->len) ||
+ changed && vs_change(sp, tp->lno, LINE_RESET))
+ return (1);
+
+ for (lno = tp->lno; (tp = tp->q.cqe_next) != (void *)&sp->tiq; ++lno) {
+ if (LF_ISSET(TXT_AUTOINDENT))
+ txt_ai_resolve(sp, tp, &changed);
+ else
+ changed = 0;
+ if (db_append(sp, 0, lno, tp->lb, tp->len) ||
+ changed && vs_change(sp, tp->lno, LINE_RESET))
+ return (1);
+ }
+
+ /*
+ * Clear the input flag, the look-aside buffer is no longer valid.
+ * Has to be done as part of text resolution, or upon return we'll
+ * be looking at incorrect data.
+ */
+ F_CLR(sp, SC_TINPUT);
+
+ return (0);
+}
+
+/*
+ * txt_showmatch --
+ * Show a character match.
+ *
+ * !!!
+ * Historic vi tried to display matches even in the :colon command line.
+ * I think not.
+ */
+static int
+txt_showmatch(sp, tp)
+ SCR *sp;
+ TEXT *tp;
+{
+ GS *gp;
+ VCS cs;
+ MARK m;
+ int cnt, endc, startc;
+
+ gp = sp->gp;
+
+ /*
+ * Do a refresh first, in case we haven't done one in awhile,
+ * so the user can see what we're complaining about.
+ */
+ UPDATE_POSITION(sp, tp);
+ if (vs_refresh(sp, 1))
+ return (1);
+
+ /*
+ * We don't display the match if it's not on the screen. Find
+ * out what the first character on the screen is.
+ */
+ if (vs_sm_position(sp, &m, 0, P_TOP))
+ return (1);
+
+ /* Initialize the getc() interface. */
+ cs.cs_lno = tp->lno;
+ cs.cs_cno = tp->cno - 1;
+ if (cs_init(sp, &cs))
+ return (1);
+ startc = (endc = cs.cs_ch) == ')' ? '(' : '{';
+
+ /* Search for the match. */
+ for (cnt = 1;;) {
+ if (cs_prev(sp, &cs))
+ return (1);
+ if (cs.cs_flags != 0) {
+ if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) {
+ msgq(sp, M_BERR,
+ "Unmatched %s", KEY_NAME(sp, endc));
+ return (0);
+ }
+ continue;
+ }
+ if (cs.cs_ch == endc)
+ ++cnt;
+ else if (cs.cs_ch == startc && --cnt == 0)
+ break;
+ }
+
+ /* If the match is on the screen, move to it. */
+ if (cs.cs_lno < m.lno || cs.cs_lno == m.lno && cs.cs_cno < m.cno)
+ return (0);
+ sp->lno = cs.cs_lno;
+ sp->cno = cs.cs_cno;
+ if (vs_refresh(sp, 1))
+ return (1);
+
+ /* Wait for timeout or character arrival. */
+ return (v_event_get(sp,
+ NULL, O_VAL(sp, O_MATCHTIME) * 100, EC_TIMEOUT));
+}
+
+/*
+ * txt_margin --
+ * Handle margin wrap.
+ */
+static int
+txt_margin(sp, tp, wmtp, didbreak, flags)
+ SCR *sp;
+ TEXT *tp, *wmtp;
+ int *didbreak;
+ u_int32_t flags;
+{
+ VI_PRIVATE *vip;
+ size_t len, off;
+ char *p, *wp;
+
+ /* Find the nearest previous blank. */
+ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --off, --p, ++len) {
+ if (isblank(*p)) {
+ wp = p + 1;
+ break;
+ }
+
+ /*
+ * If reach the start of the line, there's nowhere to break.
+ *
+ * !!!
+ * Historic vi belled each time a character was entered after
+ * crossing the margin until a space was entered which could
+ * be used to break the line. I don't as it tends to wake the
+ * cats.
+ */
+ if (off == tp->ai || off == tp->offset) {
+ *didbreak = 0;
+ return (0);
+ }
+ }
+
+ /*
+ * Store saved information about the rest of the line in the
+ * wrapmargin TEXT structure.
+ *
+ * !!!
+ * The offset field holds the length of the current characters
+ * that the user entered, but which are getting split to the new
+ * line -- it's going to be used to set the cursor value when we
+ * move to the new line.
+ */
+ vip = VIP(sp);
+ wmtp->lb = p + 1;
+ wmtp->offset = len;
+ wmtp->insert = LF_ISSET(TXT_APPENDEOL) ? tp->insert - 1 : tp->insert;
+ wmtp->owrite = tp->owrite;
+
+ /* Correct current bookkeeping information. */
+ tp->cno -= len;
+ if (LF_ISSET(TXT_APPENDEOL)) {
+ tp->len -= len + tp->owrite + (tp->insert - 1);
+ tp->insert = 1;
+ } else {
+ tp->len -= len + tp->owrite + tp->insert;
+ tp->insert = 0;
+ }
+ tp->owrite = 0;
+
+ /*
+ * !!!
+ * Delete any trailing whitespace from the current line.
+ */
+ for (;; --p, --off) {
+ if (!isblank(*p))
+ break;
+ --tp->cno;
+ --tp->len;
+ if (off == tp->ai || off == tp->offset)
+ break;
+ }
+ *didbreak = 1;
+ return (0);
+}
+
+/*
+ * txt_Rresolve --
+ * Resolve the input line for the 'R' command.
+ */
+static void
+txt_Rresolve(sp, tiqh, tp, orig_len)
+ SCR *sp;
+ TEXTH *tiqh;
+ TEXT *tp;
+ const size_t orig_len;
+{
+ TEXT *ttp;
+ size_t input_len, retain;
+ char *p;
+
+ /*
+ * Check to make sure that the cursor hasn't moved beyond
+ * the end of the line.
+ */
+ if (tp->owrite == 0)
+ return;
+
+ /*
+ * Calculate how many characters the user has entered,
+ * plus the blanks erased by <carriage-return>/<newline>s.
+ */
+ for (ttp = tiqh->cqh_first, input_len = 0;;) {
+ input_len += ttp == tp ? tp->cno : ttp->len + ttp->R_erase;
+ if ((ttp = ttp->q.cqe_next) == (void *)&sp->tiq)
+ break;
+ }
+
+ /*
+ * If the user has entered less characters than the original line
+ * was long, restore any overwriteable characters to the original
+ * characters. These characters are entered as "insert characters",
+ * because they're after the cursor and we don't want to lose them.
+ * (This is okay because the R command has no insert characters.)
+ * We set owrite to 0 so that the insert characters don't get copied
+ * to somewhere else, which means that the line and the length have
+ * to be adjusted here as well.
+ *
+ * We have to retrieve the original line because the original pinned
+ * page has long since been discarded. If it doesn't exist, that's
+ * okay, the user just extended the file.
+ */
+ if (input_len < orig_len) {
+ retain = MIN(tp->owrite, orig_len - input_len);
+ if (db_get(sp,
+ tiqh->cqh_first->lno, DBG_FATAL | DBG_NOCACHE, &p, NULL))
+ return;
+ memcpy(tp->lb + tp->cno, p + input_len, retain);
+ tp->len -= tp->owrite - retain;
+ tp->owrite = 0;
+ tp->insert += retain;
+ }
+}
+
+/*
+ * txt_nomorech --
+ * No more characters message.
+ */
+static void
+txt_nomorech(sp)
+ SCR *sp;
+{
+ msgq(sp, M_BERR, "194|No more characters to erase");
+}
diff --git a/contrib/nvi/vi/v_ulcase.c b/contrib/nvi/vi/v_ulcase.c
new file mode 100644
index 0000000..179beba
--- /dev/null
+++ b/contrib/nvi/vi/v_ulcase.c
@@ -0,0 +1,179 @@
+/*-
+ * 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[] = "@(#)v_ulcase.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 <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+static int ulcase __P((SCR *, recno_t, CHAR_T *, size_t, size_t, size_t));
+
+/*
+ * v_ulcase -- [count]~
+ * Toggle upper & lower case letters.
+ *
+ * !!!
+ * Historic vi didn't permit ~ to cross newline boundaries. I can
+ * think of no reason why it shouldn't, which at least lets the user
+ * auto-repeat through a paragraph.
+ *
+ * !!!
+ * In historic vi, the count was ignored. It would have been better
+ * if there had been an associated motion, but it's too late to make
+ * that the default now.
+ *
+ * PUBLIC: int v_ulcase __P((SCR *, VICMD *));
+ */
+int
+v_ulcase(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t lno;
+ size_t cno, lcnt, len;
+ u_long cnt;
+ char *p;
+
+ lno = vp->m_start.lno;
+ cno = vp->m_start.cno;
+
+ for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt > 0; cno = 0) {
+ /* SOF is an error, EOF is an infinite count sink. */
+ if (db_get(sp, lno, 0, &p, &len)) {
+ if (lno == 1) {
+ v_emsg(sp, NULL, VIM_EMPTY);
+ return (1);
+ }
+ --lno;
+ break;
+ }
+
+ /* Empty lines decrement the count by one. */
+ if (len == 0) {
+ --cnt;
+ vp->m_final.cno = 0;
+ continue;
+ }
+
+ if (cno + cnt >= len) {
+ lcnt = len - 1;
+ cnt -= len - cno;
+
+ vp->m_final.cno = len - 1;
+ } else {
+ lcnt = cno + cnt - 1;
+ cnt = 0;
+
+ vp->m_final.cno = lcnt + 1;
+ }
+
+ if (ulcase(sp, lno, p, len, cno, lcnt))
+ return (1);
+
+ if (cnt > 0)
+ ++lno;
+ }
+
+ vp->m_final.lno = lno;
+ return (0);
+}
+
+/*
+ * v_mulcase -- [count]~[count]motion
+ * Toggle upper & lower case letters over a range.
+ *
+ * PUBLIC: int v_mulcase __P((SCR *, VICMD *));
+ */
+int
+v_mulcase(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ CHAR_T *p;
+ size_t len;
+ recno_t lno;
+
+ for (lno = vp->m_start.lno;;) {
+ if (db_get(sp, lno, DBG_FATAL, &p, &len))
+ return (1);
+ if (len != 0 && ulcase(sp, lno, p, len,
+ lno == vp->m_start.lno ? vp->m_start.cno : 0,
+ !F_ISSET(vp, VM_LMODE) &&
+ lno == vp->m_stop.lno ? vp->m_stop.cno : len))
+ return (1);
+
+ if (++lno > vp->m_stop.lno)
+ break;
+ }
+
+ /*
+ * XXX
+ * I didn't create a new motion command when I added motion semantics
+ * for ~. While that's the correct way to do it, that choice would
+ * have required changes all over the vi directory for little gain.
+ * Instead, we pretend it's a yank command. Note, this means that we
+ * follow the cursor motion rules for yank commands, but that seems
+ * reasonable to me.
+ */
+ return (0);
+}
+
+/*
+ * ulcase --
+ * Change part of a line's case.
+ */
+static int
+ulcase(sp, lno, lp, len, scno, ecno)
+ SCR *sp;
+ recno_t lno;
+ CHAR_T *lp;
+ size_t len, scno, ecno;
+{
+ size_t blen;
+ int change, rval;
+ CHAR_T ch, *p, *t;
+ char *bp;
+
+ GET_SPACE_RET(sp, bp, blen, len);
+ memmove(bp, lp, len);
+
+ change = rval = 0;
+ for (p = bp + scno, t = bp + ecno + 1; p < t; ++p) {
+ ch = *(u_char *)p;
+ if (islower(ch)) {
+ *p = toupper(ch);
+ change = 1;
+ } else if (isupper(ch)) {
+ *p = tolower(ch);
+ change = 1;
+ }
+ }
+
+ if (change && db_set(sp, lno, bp, len))
+ rval = 1;
+
+ FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
diff --git a/contrib/nvi/vi/v_undo.c b/contrib/nvi/vi/v_undo.c
new file mode 100644
index 0000000..d04a8a1
--- /dev/null
+++ b/contrib/nvi/vi/v_undo.c
@@ -0,0 +1,139 @@
+/*-
+ * 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[] = "@(#)v_undo.c 10.5 (Berkeley) 3/6/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.h"
+
+/*
+ * v_Undo -- U
+ * Undo changes to this line.
+ *
+ * PUBLIC: int v_Undo __P((SCR *, VICMD *));
+ */
+int
+v_Undo(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /*
+ * Historically, U reset the cursor to the first column in the line
+ * (not the first non-blank). This seems a bit non-intuitive, but,
+ * considering that we may have undone multiple changes, anything
+ * else (including the cursor position stored in the logging records)
+ * is going to appear random.
+ */
+ vp->m_final.cno = 0;
+
+ /*
+ * !!!
+ * Set up the flags so that an immediately subsequent 'u' will roll
+ * forward, instead of backward. In historic vi, a 'u' following a
+ * 'U' redid all of the changes to the line. Given that the user has
+ * explicitly discarded those changes by entering 'U', it seems likely
+ * that the user wants something between the original and end forms of
+ * the line, so starting to replay the changes seems the best way to
+ * get to there.
+ */
+ F_SET(sp->ep, F_UNDO);
+ sp->ep->lundo = BACKWARD;
+
+ return (log_setline(sp));
+}
+
+/*
+ * v_undo -- u
+ * Undo the last change.
+ *
+ * PUBLIC: int v_undo __P((SCR *, VICMD *));
+ */
+int
+v_undo(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ EXF *ep;
+
+ /* Set the command count. */
+ VIP(sp)->u_ccnt = sp->ccnt;
+
+ /*
+ * !!!
+ * In historic vi, 'u' toggled between "undo" and "redo", i.e. 'u'
+ * undid the last undo. However, if there has been a change since
+ * the last undo/redo, we always do an undo. To make this work when
+ * the user can undo multiple operations, we leave the old semantic
+ * unchanged, but make '.' after a 'u' do another undo/redo operation.
+ * This has two problems.
+ *
+ * The first is that 'u' didn't set '.' in historic vi. So, if a
+ * user made a change, realized it was in the wrong place, does a
+ * 'u' to undo it, moves to the right place and then does '.', the
+ * change was reapplied. To make this work, we only apply the '.'
+ * to the undo command if it's the command immediately following an
+ * undo command. See vi/vi.c:getcmd() for the details.
+ *
+ * The second is that the traditional way to view the numbered cut
+ * buffers in vi was to enter the commands "1pu.u.u.u. which will
+ * no longer work because the '.' immediately follows the 'u' command.
+ * Since we provide a much better method of viewing buffers, and
+ * nobody can think of a better way of adding in multiple undo, this
+ * remains broken.
+ *
+ * !!!
+ * There is change to historic practice for the final cursor position
+ * in this implementation. In historic vi, if an undo was isolated to
+ * a single line, the cursor moved to the start of the change, and
+ * then, subsequent 'u' commands would not move it again. (It has been
+ * pointed out that users used multiple undo commands to get the cursor
+ * to the start of the changed text.) Nvi toggles between the cursor
+ * position before and after the change was made. One final issue is
+ * that historic vi only did this if the user had not moved off of the
+ * line before entering the undo command; otherwise, vi would move the
+ * cursor to the most attractive position on the changed line.
+ *
+ * It would be difficult to match historic practice in this area. You
+ * not only have to know that the changes were isolated to one line,
+ * but whether it was the first or second undo command as well. And,
+ * to completely match historic practice, we'd have to track users line
+ * changes, too. This isn't worth the effort.
+ */
+ ep = sp->ep;
+ if (!F_ISSET(ep, F_UNDO)) {
+ F_SET(ep, F_UNDO);
+ ep->lundo = BACKWARD;
+ } else if (!F_ISSET(vp, VC_ISDOT))
+ ep->lundo = ep->lundo == BACKWARD ? FORWARD : BACKWARD;
+
+ switch (ep->lundo) {
+ case BACKWARD:
+ return (log_backward(sp, &vp->m_final));
+ case FORWARD:
+ return (log_forward(sp, &vp->m_final));
+ default:
+ abort();
+ }
+ /* NOTREACHED */
+}
diff --git a/contrib/nvi/vi/v_util.c b/contrib/nvi/vi/v_util.c
new file mode 100644
index 0000000..c4c0daa
--- /dev/null
+++ b/contrib/nvi/vi/v_util.c
@@ -0,0 +1,180 @@
+/*-
+ * 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[] = "@(#)v_util.c 10.11 (Berkeley) 6/30/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 <unistd.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_eof --
+ * Vi end-of-file error.
+ *
+ * PUBLIC: void v_eof __P((SCR *, MARK *));
+ */
+void
+v_eof(sp, mp)
+ SCR *sp;
+ MARK *mp;
+{
+ recno_t lno;
+
+ if (mp == NULL)
+ v_emsg(sp, NULL, VIM_EOF);
+ else {
+ if (db_last(sp, &lno))
+ return;
+ if (mp->lno >= lno)
+ v_emsg(sp, NULL, VIM_EOF);
+ else
+ msgq(sp, M_BERR, "195|Movement past the end-of-file");
+ }
+}
+
+/*
+ * v_eol --
+ * Vi end-of-line error.
+ *
+ * PUBLIC: void v_eol __P((SCR *, MARK *));
+ */
+void
+v_eol(sp, mp)
+ SCR *sp;
+ MARK *mp;
+{
+ size_t len;
+
+ if (mp == NULL)
+ v_emsg(sp, NULL, VIM_EOL);
+ else {
+ if (db_get(sp, mp->lno, DBG_FATAL, NULL, &len))
+ return;
+ if (mp->cno == len - 1)
+ v_emsg(sp, NULL, VIM_EOL);
+ else
+ msgq(sp, M_BERR, "196|Movement past the end-of-line");
+ }
+}
+
+/*
+ * v_nomove --
+ * Vi no cursor movement error.
+ *
+ * PUBLIC: void v_nomove __P((SCR *));
+ */
+void
+v_nomove(sp)
+ SCR *sp;
+{
+ msgq(sp, M_BERR, "197|No cursor movement made");
+}
+
+/*
+ * v_sof --
+ * Vi start-of-file error.
+ *
+ * PUBLIC: void v_sof __P((SCR *, MARK *));
+ */
+void
+v_sof(sp, mp)
+ SCR *sp;
+ MARK *mp;
+{
+ if (mp == NULL || mp->lno == 1)
+ msgq(sp, M_BERR, "198|Already at the beginning of the file");
+ else
+ msgq(sp, M_BERR, "199|Movement past the beginning of the file");
+}
+
+/*
+ * v_sol --
+ * Vi start-of-line error.
+ *
+ * PUBLIC: void v_sol __P((SCR *));
+ */
+void
+v_sol(sp)
+ SCR *sp;
+{
+ msgq(sp, M_BERR, "200|Already in the first column");
+}
+
+/*
+ * v_isempty --
+ * Return if the line contains nothing but white-space characters.
+ *
+ * PUBLIC: int v_isempty __P((char *, size_t));
+ */
+int
+v_isempty(p, len)
+ char *p;
+ size_t len;
+{
+ for (; len--; ++p)
+ if (!isblank(*p))
+ return (0);
+ return (1);
+}
+
+/*
+ * v_emsg --
+ * Display a few common vi messages.
+ *
+ * PUBLIC: void v_emsg __P((SCR *, char *, vim_t));
+ */
+void
+v_emsg(sp, p, which)
+ SCR *sp;
+ char *p;
+ vim_t which;
+{
+ switch (which) {
+ case VIM_COMBUF:
+ msgq(sp, M_ERR,
+ "201|Buffers should be specified before the command");
+ break;
+ case VIM_EMPTY:
+ msgq(sp, M_BERR, "209|The file is empty");
+ break;
+ case VIM_EOF:
+ msgq(sp, M_BERR, "202|Already at end-of-file");
+ break;
+ case VIM_EOL:
+ msgq(sp, M_BERR, "203|Already at end-of-line");
+ break;
+ case VIM_NOCOM:
+ case VIM_NOCOM_B:
+ msgq(sp,
+ which == VIM_NOCOM_B ? M_BERR : M_ERR,
+ "204|%s isn't a vi command", p);
+ break;
+ case VIM_WRESIZE:
+ msgq(sp, M_ERR, "Window resize interrupted text input mode");
+ break;
+ case VIM_USAGE:
+ msgq(sp, M_ERR, "205|Usage: %s", p);
+ break;
+ }
+}
diff --git a/contrib/nvi/vi/v_word.c b/contrib/nvi/vi/v_word.c
new file mode 100644
index 0000000..e6e4a63
--- /dev/null
+++ b/contrib/nvi/vi/v_word.c
@@ -0,0 +1,547 @@
+/*-
+ * 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[] = "@(#)v_word.c 10.5 (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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * There are two types of "words". Bigwords are easy -- groups of anything
+ * delimited by whitespace. Normal words are trickier. They are either a
+ * group of characters, numbers and underscores, or a group of anything but,
+ * delimited by whitespace. When for a word, if you're in whitespace, it's
+ * easy, just remove the whitespace and go to the beginning or end of the
+ * word. Otherwise, figure out if the next character is in a different group.
+ * If it is, go to the beginning or end of that group, otherwise, go to the
+ * beginning or end of the current group. The historic version of vi didn't
+ * get this right, so, for example, there were cases where "4e" was not the
+ * same as "eeee" -- in particular, single character words, and commands that
+ * began in whitespace were almost always handled incorrectly. To get it right
+ * you have to resolve the cursor after each search so that the look-ahead to
+ * figure out what type of "word" the cursor is in will be correct.
+ *
+ * Empty lines, and lines that consist of only white-space characters count
+ * as a single word, and the beginning and end of the file counts as an
+ * infinite number of words.
+ *
+ * Movements associated with commands are different than movement commands.
+ * For example, in "abc def", with the cursor on the 'a', "cw" is from
+ * 'a' to 'c', while "w" is from 'a' to 'd'. In general, trailing white
+ * space is discarded from the change movement. Another example is that,
+ * in the same string, a "cw" on any white space character replaces that
+ * single character, and nothing else. Ain't nothin' in here that's easy.
+ *
+ * One historic note -- in the original vi, the 'w', 'W' and 'B' commands
+ * would treat groups of empty lines as individual words, i.e. the command
+ * would move the cursor to each new empty line. The 'e' and 'E' commands
+ * would treat groups of empty lines as a single word, i.e. the first use
+ * would move past the group of lines. The 'b' command would just beep at
+ * you, or, if you did it from the start of the line as part of a motion
+ * command, go absolutely nuts. If the lines contained only white-space
+ * characters, the 'w' and 'W' commands would just beep at you, and the 'B',
+ * 'b', 'E' and 'e' commands would treat the group as a single word, and
+ * the 'B' and 'b' commands will treat the lines as individual words. This
+ * implementation treats all of these cases as a single white-space word.
+ */
+
+enum which {BIGWORD, LITTLEWORD};
+
+static int bword __P((SCR *, VICMD *, enum which));
+static int eword __P((SCR *, VICMD *, enum which));
+static int fword __P((SCR *, VICMD *, enum which));
+
+/*
+ * v_wordW -- [count]W
+ * Move forward a bigword at a time.
+ *
+ * PUBLIC: int v_wordW __P((SCR *, VICMD *));
+ */
+int
+v_wordW(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (fword(sp, vp, BIGWORD));
+}
+
+/*
+ * v_wordw -- [count]w
+ * Move forward a word at a time.
+ *
+ * PUBLIC: int v_wordw __P((SCR *, VICMD *));
+ */
+int
+v_wordw(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (fword(sp, vp, LITTLEWORD));
+}
+
+/*
+ * fword --
+ * Move forward by words.
+ */
+static int
+fword(sp, vp, type)
+ SCR *sp;
+ VICMD *vp;
+ enum which type;
+{
+ enum { INWORD, NOTWORD } state;
+ VCS cs;
+ u_long cnt;
+
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ cs.cs_lno = vp->m_start.lno;
+ cs.cs_cno = vp->m_start.cno;
+ if (cs_init(sp, &cs))
+ return (1);
+
+ /*
+ * If in white-space:
+ * If the count is 1, and it's a change command, we're done.
+ * Else, move to the first non-white-space character, which
+ * counts as a single word move. If it's a motion command,
+ * don't move off the end of the line.
+ */
+ if (cs.cs_flags == CS_EMP || cs.cs_flags == 0 && isblank(cs.cs_ch)) {
+ if (ISMOTION(vp) && cs.cs_flags != CS_EMP && cnt == 1) {
+ if (ISCMD(vp->rkp, 'c'))
+ return (0);
+ if (ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) {
+ if (cs_fspace(sp, &cs))
+ return (1);
+ goto ret;
+ }
+ }
+ if (cs_fblank(sp, &cs))
+ return (1);
+ --cnt;
+ }
+
+ /*
+ * Cyclically move to the next word -- this involves skipping
+ * over word characters and then any trailing non-word characters.
+ * Note, for the 'w' command, the definition of a word keeps
+ * switching.
+ */
+ if (type == BIGWORD)
+ while (cnt--) {
+ for (;;) {
+ if (cs_next(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ goto ret;
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ break;
+ }
+ /*
+ * If a motion command and we're at the end of the
+ * last word, we're done. Delete and yank eat any
+ * trailing blanks, but we don't move off the end
+ * of the line regardless.
+ */
+ if (cnt == 0 && ISMOTION(vp)) {
+ if ((ISCMD(vp->rkp, 'd') ||
+ ISCMD(vp->rkp, 'y')) &&
+ cs_fspace(sp, &cs))
+ return (1);
+ break;
+ }
+
+ /* Eat whitespace characters. */
+ if (cs_fblank(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ goto ret;
+ }
+ else
+ while (cnt--) {
+ state = cs.cs_flags == 0 &&
+ inword(cs.cs_ch) ? INWORD : NOTWORD;
+ for (;;) {
+ if (cs_next(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ goto ret;
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ break;
+ if (state == INWORD) {
+ if (!inword(cs.cs_ch))
+ break;
+ } else
+ if (inword(cs.cs_ch))
+ break;
+ }
+ /* See comment above. */
+ if (cnt == 0 && ISMOTION(vp)) {
+ if ((ISCMD(vp->rkp, 'd') ||
+ ISCMD(vp->rkp, 'y')) &&
+ cs_fspace(sp, &cs))
+ return (1);
+ break;
+ }
+
+ /* Eat whitespace characters. */
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ if (cs_fblank(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ goto ret;
+ }
+
+ /*
+ * If we didn't move, we must be at EOF.
+ *
+ * !!!
+ * That's okay for motion commands, however.
+ */
+ret: if (!ISMOTION(vp) &&
+ cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {
+ v_eof(sp, &vp->m_start);
+ return (1);
+ }
+
+ /* Adjust the end of the range for motion commands. */
+ vp->m_stop.lno = cs.cs_lno;
+ vp->m_stop.cno = cs.cs_cno;
+ if (ISMOTION(vp) && cs.cs_flags == 0)
+ --vp->m_stop.cno;
+
+ /*
+ * Non-motion commands move to the end of the range. Delete
+ * and yank stay at the start, ignore others.
+ */
+ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_wordE -- [count]E
+ * Move forward to the end of the bigword.
+ *
+ * PUBLIC: int v_wordE __P((SCR *, VICMD *));
+ */
+int
+v_wordE(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (eword(sp, vp, BIGWORD));
+}
+
+/*
+ * v_worde -- [count]e
+ * Move forward to the end of the word.
+ *
+ * PUBLIC: int v_worde __P((SCR *, VICMD *));
+ */
+int
+v_worde(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (eword(sp, vp, LITTLEWORD));
+}
+
+/*
+ * eword --
+ * Move forward to the end of the word.
+ */
+static int
+eword(sp, vp, type)
+ SCR *sp;
+ VICMD *vp;
+ enum which type;
+{
+ enum { INWORD, NOTWORD } state;
+ VCS cs;
+ u_long cnt;
+
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ cs.cs_lno = vp->m_start.lno;
+ cs.cs_cno = vp->m_start.cno;
+ if (cs_init(sp, &cs))
+ return (1);
+
+ /*
+ * !!!
+ * If in whitespace, or the next character is whitespace, move past
+ * it. (This doesn't count as a word move.) Stay at the character
+ * past the current one, it sets word "state" for the 'e' command.
+ */
+ if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) {
+ if (cs_next(sp, &cs))
+ return (1);
+ if (cs.cs_flags == 0 && !isblank(cs.cs_ch))
+ goto start;
+ }
+ if (cs_fblank(sp, &cs))
+ return (1);
+
+ /*
+ * Cyclically move to the next word -- this involves skipping
+ * over word characters and then any trailing non-word characters.
+ * Note, for the 'e' command, the definition of a word keeps
+ * switching.
+ */
+start: if (type == BIGWORD)
+ while (cnt--) {
+ for (;;) {
+ if (cs_next(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ goto ret;
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ break;
+ }
+ /*
+ * When we reach the start of the word after the last
+ * word, we're done. If we changed state, back up one
+ * to the end of the previous word.
+ */
+ if (cnt == 0) {
+ if (cs.cs_flags == 0 && cs_prev(sp, &cs))
+ return (1);
+ break;
+ }
+
+ /* Eat whitespace characters. */
+ if (cs_fblank(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ goto ret;
+ }
+ else
+ while (cnt--) {
+ state = cs.cs_flags == 0 &&
+ inword(cs.cs_ch) ? INWORD : NOTWORD;
+ for (;;) {
+ if (cs_next(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ goto ret;
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ break;
+ if (state == INWORD) {
+ if (!inword(cs.cs_ch))
+ break;
+ } else
+ if (inword(cs.cs_ch))
+ break;
+ }
+ /* See comment above. */
+ if (cnt == 0) {
+ if (cs.cs_flags == 0 && cs_prev(sp, &cs))
+ return (1);
+ break;
+ }
+
+ /* Eat whitespace characters. */
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ if (cs_fblank(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_EOF)
+ goto ret;
+ }
+
+ /*
+ * If we didn't move, we must be at EOF.
+ *
+ * !!!
+ * That's okay for motion commands, however.
+ */
+ret: if (!ISMOTION(vp) &&
+ cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {
+ v_eof(sp, &vp->m_start);
+ return (1);
+ }
+
+ /* Set the end of the range for motion commands. */
+ vp->m_stop.lno = cs.cs_lno;
+ vp->m_stop.cno = cs.cs_cno;
+
+ /*
+ * Non-motion commands move to the end of the range.
+ * Delete and yank stay at the start, ignore others.
+ */
+ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
+ return (0);
+}
+
+/*
+ * v_WordB -- [count]B
+ * Move backward a bigword at a time.
+ *
+ * PUBLIC: int v_wordB __P((SCR *, VICMD *));
+ */
+int
+v_wordB(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (bword(sp, vp, BIGWORD));
+}
+
+/*
+ * v_wordb -- [count]b
+ * Move backward a word at a time.
+ *
+ * PUBLIC: int v_wordb __P((SCR *, VICMD *));
+ */
+int
+v_wordb(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ return (bword(sp, vp, LITTLEWORD));
+}
+
+/*
+ * bword --
+ * Move backward by words.
+ */
+static int
+bword(sp, vp, type)
+ SCR *sp;
+ VICMD *vp;
+ enum which type;
+{
+ enum { INWORD, NOTWORD } state;
+ VCS cs;
+ u_long cnt;
+
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ cs.cs_lno = vp->m_start.lno;
+ cs.cs_cno = vp->m_start.cno;
+ if (cs_init(sp, &cs))
+ return (1);
+
+ /*
+ * !!!
+ * If in whitespace, or the previous character is whitespace, move
+ * past it. (This doesn't count as a word move.) Stay at the
+ * character before the current one, it sets word "state" for the
+ * 'b' command.
+ */
+ if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) {
+ if (cs_prev(sp, &cs))
+ return (1);
+ if (cs.cs_flags == 0 && !isblank(cs.cs_ch))
+ goto start;
+ }
+ if (cs_bblank(sp, &cs))
+ return (1);
+
+ /*
+ * Cyclically move to the beginning of the previous word -- this
+ * involves skipping over word characters and then any trailing
+ * non-word characters. Note, for the 'b' command, the definition
+ * of a word keeps switching.
+ */
+start: if (type == BIGWORD)
+ while (cnt--) {
+ for (;;) {
+ if (cs_prev(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_SOF)
+ goto ret;
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ break;
+ }
+ /*
+ * When we reach the end of the word before the last
+ * word, we're done. If we changed state, move forward
+ * one to the end of the next word.
+ */
+ if (cnt == 0) {
+ if (cs.cs_flags == 0 && cs_next(sp, &cs))
+ return (1);
+ break;
+ }
+
+ /* Eat whitespace characters. */
+ if (cs_bblank(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_SOF)
+ goto ret;
+ }
+ else
+ while (cnt--) {
+ state = cs.cs_flags == 0 &&
+ inword(cs.cs_ch) ? INWORD : NOTWORD;
+ for (;;) {
+ if (cs_prev(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_SOF)
+ goto ret;
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ break;
+ if (state == INWORD) {
+ if (!inword(cs.cs_ch))
+ break;
+ } else
+ if (inword(cs.cs_ch))
+ break;
+ }
+ /* See comment above. */
+ if (cnt == 0) {
+ if (cs.cs_flags == 0 && cs_next(sp, &cs))
+ return (1);
+ break;
+ }
+
+ /* Eat whitespace characters. */
+ if (cs.cs_flags != 0 || isblank(cs.cs_ch))
+ if (cs_bblank(sp, &cs))
+ return (1);
+ if (cs.cs_flags == CS_SOF)
+ goto ret;
+ }
+
+ /* If we didn't move, we must be at SOF. */
+ret: if (cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) {
+ v_sof(sp, &vp->m_start);
+ return (1);
+ }
+
+ /* Set the end of the range for motion commands. */
+ vp->m_stop.lno = cs.cs_lno;
+ vp->m_stop.cno = cs.cs_cno;
+
+ /*
+ * All commands move to the end of the range. Motion commands
+ * adjust the starting point to the character before the current
+ * one.
+ *
+ * !!!
+ * The historic vi didn't get this right -- the `yb' command yanked
+ * the right stuff and even updated the cursor value, but the cursor
+ * was not actually updated on the screen.
+ */
+ vp->m_final = vp->m_stop;
+ if (ISMOTION(vp))
+ --vp->m_start.cno;
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_xchar.c b/contrib/nvi/vi/v_xchar.c
new file mode 100644
index 0000000..15f155f
--- /dev/null
+++ b/contrib/nvi/vi/v_xchar.c
@@ -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.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)v_xchar.c 10.9 (Berkeley) 10/23/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_xchar -- [buffer] [count]x
+ * Deletes the character(s) on which the cursor sits.
+ *
+ * PUBLIC: int v_xchar __P((SCR *, VICMD *));
+ */
+int
+v_xchar(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+ int isempty;
+
+ if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) {
+ if (isempty)
+ goto nodel;
+ return (1);
+ }
+ if (len == 0) {
+nodel: msgq(sp, M_BERR, "206|No characters to delete");
+ return (1);
+ }
+
+ /*
+ * Delete from the cursor toward the end of line, w/o moving the
+ * cursor.
+ *
+ * !!!
+ * Note, "2x" at EOL isn't the same as "xx" because the left movement
+ * of the cursor as part of the 'x' command isn't taken into account.
+ * Historically correct.
+ */
+ if (F_ISSET(vp, VC_C1SET))
+ vp->m_stop.cno += vp->count - 1;
+ if (vp->m_stop.cno >= len - 1) {
+ vp->m_stop.cno = len - 1;
+ vp->m_final.cno = vp->m_start.cno ? vp->m_start.cno - 1 : 0;
+ } else
+ vp->m_final.cno = vp->m_start.cno;
+
+ if (cut(sp,
+ F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
+ &vp->m_start, &vp->m_stop, 0))
+ return (1);
+ return (del(sp, &vp->m_start, &vp->m_stop, 0));
+}
+
+/*
+ * v_Xchar -- [buffer] [count]X
+ * Deletes the character(s) immediately before the current cursor
+ * position.
+ *
+ * PUBLIC: int v_Xchar __P((SCR *, VICMD *));
+ */
+int
+v_Xchar(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ u_long cnt;
+
+ if (vp->m_start.cno == 0) {
+ v_sol(sp);
+ return (1);
+ }
+
+ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
+ if (cnt >= vp->m_start.cno)
+ vp->m_start.cno = 0;
+ else
+ vp->m_start.cno -= cnt;
+ --vp->m_stop.cno;
+ vp->m_final.cno = vp->m_start.cno;
+
+ if (cut(sp,
+ F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
+ &vp->m_start, &vp->m_stop, 0))
+ return (1);
+ return (del(sp, &vp->m_start, &vp->m_stop, 0));
+}
diff --git a/contrib/nvi/vi/v_yank.c b/contrib/nvi/vi/v_yank.c
new file mode 100644
index 0000000..4708f19
--- /dev/null
+++ b/contrib/nvi/vi/v_yank.c
@@ -0,0 +1,82 @@
+/*-
+ * 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[] = "@(#)v_yank.c 10.9 (Berkeley) 5/19/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_yank -- [buffer][count]y[count][motion]
+ * [buffer][count]Y
+ * Yank text (or lines of text) into a cut buffer.
+ *
+ * !!!
+ * Historic vi moved the cursor to the from MARK if it was before the current
+ * cursor and on a different line, e.g., "yk" moves the cursor but "yj" and
+ * "yl" do not. Unfortunately, it's too late to change this now. Matching
+ * the historic semantics isn't easy. The line number was always changed and
+ * column movement was usually relative. However, "y'a" moved the cursor to
+ * the first non-blank of the line marked by a, while "y`a" moved the cursor
+ * to the line and column marked by a. Hopefully, the motion component code
+ * got it right... Unlike delete, we make no adjustments here.
+ *
+ * PUBLIC: int v_yank __P((SCR *, VICMD *));
+ */
+int
+v_yank(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ size_t len;
+
+ if (cut(sp,
+ F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start,
+ &vp->m_stop, F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0))
+ return (1);
+ sp->rptlines[L_YANKED] += (vp->m_stop.lno - vp->m_start.lno) + 1;
+
+ /*
+ * One special correction, in case we've deleted the current line or
+ * character. We check it here instead of checking in every command
+ * that can be a motion component.
+ */
+ if (db_get(sp, vp->m_final.lno, DBG_FATAL, NULL, &len))
+ return (1);
+
+ /*
+ * !!!
+ * Cursor movements, other than those caused by a line mode command
+ * moving to another line, historically reset the relative position.
+ *
+ * This currently matches the check made in v_delete(), I'm hoping
+ * that they should be consistent...
+ */
+ if (!F_ISSET(vp, VM_LMODE)) {
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(vp, VM_RCM_SET);
+
+ /* Make sure the set cursor position exists. */
+ if (vp->m_final.cno >= len)
+ vp->m_final.cno = len ? len - 1 : 0;
+ }
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_z.c b/contrib/nvi/vi/v_z.c
new file mode 100644
index 0000000..5f02ddf
--- /dev/null
+++ b/contrib/nvi/vi/v_z.c
@@ -0,0 +1,149 @@
+/*-
+ * 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[] = "@(#)v_z.c 10.10 (Berkeley) 5/16/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 "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_z -- [count]z[count][-.+^<CR>]
+ * Move the screen.
+ *
+ * PUBLIC: int v_z __P((SCR *, VICMD *));
+ */
+int
+v_z(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ recno_t lno;
+ u_int value;
+
+ /*
+ * The first count is the line to use. If the value doesn't
+ * exist, use the last line.
+ */
+ if (F_ISSET(vp, VC_C1SET)) {
+ lno = vp->count;
+ if (!db_exist(sp, lno) && db_last(sp, &lno))
+ return (1);
+ } else
+ lno = vp->m_start.lno;
+
+ /* Set default return cursor line. */
+ vp->m_final.lno = lno;
+ vp->m_final.cno = vp->m_start.cno;
+
+ /*
+ * The second count is the displayed window size, i.e. the 'z' command
+ * is another way to get artificially small windows. Note, you can't
+ * grow beyond the size of the window.
+ *
+ * !!!
+ * A window size of 0 was historically allowed, and simply ignored.
+ * This could be much more simply done by modifying the value of the
+ * O_WINDOW option, but that's not how it worked historically.
+ */
+ if (F_ISSET(vp, VC_C2SET) && vp->count2 != 0) {
+ if (vp->count2 > O_VAL(sp, O_WINDOW))
+ vp->count2 = O_VAL(sp, O_WINDOW);
+ if (vs_crel(sp, vp->count2))
+ return (1);
+ }
+
+ switch (vp->character) {
+ case '-': /* Put the line at the bottom. */
+ if (vs_sm_fill(sp, lno, P_BOTTOM))
+ return (1);
+ break;
+ case '.': /* Put the line in the middle. */
+ if (vs_sm_fill(sp, lno, P_MIDDLE))
+ return (1);
+ break;
+ case '+':
+ /*
+ * If the user specified a line number, put that line at the
+ * top and move the cursor to it. Otherwise, scroll forward
+ * a screen from the current screen.
+ */
+ if (F_ISSET(vp, VC_C1SET)) {
+ if (vs_sm_fill(sp, lno, P_TOP))
+ return (1);
+ if (vs_sm_position(sp, &vp->m_final, 0, P_TOP))
+ return (1);
+ } else
+ if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_PLUS))
+ return (1);
+ break;
+ case '^':
+ /*
+ * If the user specified a line number, put that line at the
+ * bottom, move the cursor to it, and then display the screen
+ * before that one. Otherwise, scroll backward a screen from
+ * the current screen.
+ *
+ * !!!
+ * Note, we match the off-by-one characteristics of historic
+ * vi, here.
+ */
+ if (F_ISSET(vp, VC_C1SET)) {
+ if (vs_sm_fill(sp, lno, P_BOTTOM))
+ return (1);
+ if (vs_sm_position(sp, &vp->m_final, 0, P_TOP))
+ return (1);
+ if (vs_sm_fill(sp, vp->m_final.lno, P_BOTTOM))
+ return (1);
+ } else
+ if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_CARAT))
+ return (1);
+ break;
+ default: /* Put the line at the top for <cr>. */
+ value = KEY_VAL(sp, vp->character);
+ if (value != K_CR && value != K_NL) {
+ v_emsg(sp, vp->kp->usage, VIM_USAGE);
+ return (1);
+ }
+ if (vs_sm_fill(sp, lno, P_TOP))
+ return (1);
+ break;
+ }
+ return (0);
+}
+
+/*
+ * vs_crel --
+ * Change the relative size of the current screen.
+ *
+ * PUBLIC: int vs_crel __P((SCR *, long));
+ */
+int
+vs_crel(sp, count)
+ SCR *sp;
+ long count;
+{
+ sp->t_minrows = sp->t_rows = count;
+ if (sp->t_rows > sp->rows - 1)
+ sp->t_minrows = sp->t_rows = sp->rows - 1;
+ TMAP = HMAP + (sp->t_rows - 1);
+ F_SET(sp, SC_SCR_REDRAW);
+ return (0);
+}
diff --git a/contrib/nvi/vi/v_zexit.c b/contrib/nvi/vi/v_zexit.c
new file mode 100644
index 0000000..3e454ca
--- /dev/null
+++ b/contrib/nvi/vi/v_zexit.c
@@ -0,0 +1,54 @@
+/*-
+ * 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[] = "@(#)v_zexit.c 10.6 (Berkeley) 4/27/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 <string.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * v_zexit -- ZZ
+ * Save the file and exit.
+ *
+ * PUBLIC: int v_zexit __P((SCR *, VICMD *));
+ */
+int
+v_zexit(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ /* Write back any modifications. */
+ if (F_ISSET(sp->ep, F_MODIFIED) &&
+ file_write(sp, NULL, NULL, NULL, FS_ALL))
+ return (1);
+
+ /* Check to make sure it's not a temporary file. */
+ if (file_m3(sp, 0))
+ return (1);
+
+ /* Check for more files to edit. */
+ if (ex_ncheck(sp, 0))
+ return (1);
+
+ F_SET(sp, SC_EXIT);
+ return (0);
+}
diff --git a/contrib/nvi/vi/vi.c b/contrib/nvi/vi/vi.c
new file mode 100644
index 0000000..d20f7f2
--- /dev/null
+++ b/contrib/nvi/vi/vi.c
@@ -0,0 +1,1251 @@
+/*-
+ * 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[] = "@(#)vi.c 10.57 (Berkeley) 10/13/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.h"
+
+typedef enum {
+ GC_ERR, GC_ERR_NOFLUSH, GC_EVENT, GC_FATAL, GC_INTERRUPT, GC_OK
+} gcret_t;
+
+static VIKEYS const
+ *v_alias __P((SCR *, VICMD *, VIKEYS const *));
+static gcret_t v_cmd __P((SCR *, VICMD *, VICMD *, VICMD *, int *, int *));
+static int v_count __P((SCR *, ARG_CHAR_T, u_long *));
+static void v_dtoh __P((SCR *));
+static int v_init __P((SCR *));
+static gcret_t v_key __P((SCR *, int, EVENT *, u_int32_t));
+static int v_keyword __P((SCR *));
+static int v_motion __P((SCR *, VICMD *, VICMD *, int *));
+
+#if defined(DEBUG) && defined(COMLOG)
+static void v_comlog __P((SCR *, VICMD *));
+#endif
+
+/*
+ * Side-effect:
+ * The dot structure can be set by the underlying vi functions,
+ * see v_Put() and v_put().
+ */
+#define DOT (&VIP(sp)->sdot)
+#define DOTMOTION (&VIP(sp)->sdotmotion)
+
+/*
+ * vi --
+ * Main vi command loop.
+ *
+ * PUBLIC: int vi __P((SCR **));
+ */
+int
+vi(spp)
+ SCR **spp;
+{
+ GS *gp;
+ MARK abs;
+ SCR *next, *sp;
+ VICMD cmd, *vp;
+ VI_PRIVATE *vip;
+ int comcount, mapped, rval;
+
+ /* Get the first screen. */
+ sp = *spp;
+ gp = sp->gp;
+
+ /* Initialize the command structure. */
+ vp = &cmd;
+ memset(vp, 0, sizeof(VICMD));
+
+ /* Reset strange attraction. */
+ F_SET(vp, VM_RCM_SET);
+
+ /* Initialize the vi screen. */
+ if (v_init(sp))
+ return (1);
+
+ /* Set the focus. */
+ (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
+
+ for (vip = VIP(sp), rval = 0;;) {
+ /* Resolve messages. */
+ if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0))
+ goto ret;
+
+ /*
+ * If not skipping a refresh, return to command mode and
+ * refresh the screen.
+ */
+ if (F_ISSET(vip, VIP_S_REFRESH))
+ F_CLR(vip, VIP_S_REFRESH);
+ else {
+ sp->showmode = SM_COMMAND;
+ if (vs_refresh(sp, 0))
+ goto ret;
+ }
+
+ /* Set the new favorite position. */
+ if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) {
+ F_CLR(vip, VIP_RCM_LAST);
+ (void)vs_column(sp, &sp->rcm);
+ }
+
+ /*
+ * If not currently in a map, log the cursor position,
+ * and set a flag so that this command can become the
+ * DOT command.
+ */
+ if (MAPPED_KEYS_WAITING(sp))
+ mapped = 1;
+ else {
+ if (log_cursor(sp))
+ goto err;
+ mapped = 0;
+ }
+
+ /*
+ * There may be an ex command waiting, and we returned here
+ * only because we exited a screen or file. In this case,
+ * we simply go back into the ex parser.
+ */
+ if (EXCMD_RUNNING(gp)) {
+ vp->kp = &vikeys[':'];
+ goto ex_continue;
+ }
+
+ /* Refresh the command structure. */
+ memset(vp, 0, sizeof(VICMD));
+
+ /*
+ * We get a command, which may or may not have an associated
+ * motion. If it does, we get it too, calling its underlying
+ * function to get the resulting mark. We then call the
+ * command setting the cursor to the resulting mark.
+ *
+ * !!!
+ * Vi historically flushed mapped characters on error, but
+ * entering extra <escape> characters at the beginning of
+ * a map wasn't considered an error -- in fact, users would
+ * put leading <escape> characters in maps to clean up vi
+ * state before the map was interpreted. Beauty!
+ */
+ switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) {
+ case GC_ERR:
+ goto err;
+ case GC_ERR_NOFLUSH:
+ goto gc_err_noflush;
+ case GC_EVENT:
+ if (v_event_exec(sp, vp))
+ goto err;
+ goto gc_event;
+ case GC_FATAL:
+ goto ret;
+ case GC_INTERRUPT:
+ goto intr;
+ case GC_OK:
+ break;
+ }
+
+ /* Check for security setting. */
+ if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) {
+ ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE);
+ goto err;
+ }
+
+ /*
+ * Historical practice: if a dot command gets a new count,
+ * any motion component goes away, i.e. "d3w2." deletes a
+ * total of 5 words.
+ */
+ if (F_ISSET(vp, VC_ISDOT) && comcount)
+ DOTMOTION->count = 1;
+
+ /* Copy the key flags into the local structure. */
+ F_SET(vp, vp->kp->flags);
+
+ /* Prepare to set the previous context. */
+ if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) {
+ abs.lno = sp->lno;
+ abs.cno = sp->cno;
+ }
+
+ /*
+ * Set the three cursor locations to the current cursor. The
+ * underlying routines don't bother if the cursor doesn't move.
+ * This also handles line commands (e.g. Y) defaulting to the
+ * current line.
+ */
+ vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno;
+ vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno;
+
+ /*
+ * Do any required motion; v_motion sets the from MARK and the
+ * line mode flag, as well as the VM_RCM flags.
+ */
+ if (F_ISSET(vp, V_MOTION) &&
+ v_motion(sp, DOTMOTION, vp, &mapped)) {
+ if (INTERRUPTED(sp))
+ goto intr;
+ goto err;
+ }
+
+ /*
+ * If a count is set and the command is line oriented, set the
+ * to MARK here relative to the cursor/from MARK. This is for
+ * commands that take both counts and motions, i.e. "4yy" and
+ * "y%". As there's no way the command can know which the user
+ * did, we have to do it here. (There are commands that are
+ * line oriented and that take counts ("#G", "#H"), for which
+ * this calculation is either completely meaningless or wrong.
+ * Each command must validate the value for itself.
+ */
+ if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE))
+ vp->m_stop.lno += vp->count - 1;
+
+ /* Increment the command count. */
+ ++sp->ccnt;
+
+#if defined(DEBUG) && defined(COMLOG)
+ v_comlog(sp, vp);
+#endif
+ /* Call the function. */
+ex_continue: if (vp->kp->func(sp, vp))
+ goto err;
+gc_event:
+#ifdef DEBUG
+ /* Make sure no function left the temporary space locked. */
+ if (F_ISSET(gp, G_TMP_INUSE)) {
+ F_CLR(gp, G_TMP_INUSE);
+ msgq(sp, M_ERR,
+ "232|vi: temporary buffer not released");
+ }
+#endif
+ /*
+ * If we're exiting this screen, move to the next one, or, if
+ * there aren't any more, 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)))
+ goto ret;
+ if (vs_discard(sp, &next))
+ goto ret;
+ if (next == NULL && vs_swap(sp, &next, NULL))
+ goto ret;
+ *spp = next;
+ if (screen_end(sp))
+ goto ret;
+ if (next == NULL)
+ break;
+
+ /* Switch screens, change focus. */
+ sp = next;
+ vip = VIP(sp);
+ (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
+
+ /* Don't trust the cursor. */
+ F_SET(vip, VIP_CUR_INVALID);
+
+ continue;
+ }
+
+ /*
+ * Set the dot command structure.
+ *
+ * !!!
+ * Historically, commands which used mapped keys did not
+ * set the dot command, with the exception of the text
+ * input commands.
+ */
+ if (F_ISSET(vp, V_DOT) && !mapped) {
+ *DOT = cmd;
+ F_SET(DOT, VC_ISDOT);
+
+ /*
+ * If a count was supplied for both the command and
+ * its motion, the count was used only for the motion.
+ * Turn the count back on for the dot structure.
+ */
+ if (F_ISSET(vp, VC_C1RESET))
+ F_SET(DOT, VC_C1SET);
+
+ /* VM flags aren't retained. */
+ F_CLR(DOT, VM_COMMASK | VM_RCM_MASK);
+ }
+
+ /*
+ * Some vi row movements are "attracted" to the last position
+ * set, i.e. the VM_RCM commands are moths to the VM_RCM_SET
+ * commands' candle. If the movement is to the EOL the vi
+ * command handles it. If it's to the beginning, we handle it
+ * here.
+ *
+ * Note, some commands (e.g. _, ^) don't set the VM_RCM_SETFNB
+ * flag, but do the work themselves. The reason is that they
+ * have to modify the column in case they're being used as a
+ * motion component. Other similar commands (e.g. +, -) don't
+ * have to modify the column because they are always line mode
+ * operations when used as motions, so the column number isn't
+ * of any interest.
+ *
+ * Does this totally violate the screen and editor layering?
+ * You betcha. As they say, if you think you understand it,
+ * you don't.
+ */
+ switch (F_ISSET(vp, VM_RCM_MASK)) {
+ case 0:
+ case VM_RCM_SET:
+ break;
+ case VM_RCM:
+ vp->m_final.cno = vs_rcm(sp,
+ vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST));
+ break;
+ case VM_RCM_SETLAST:
+ F_SET(vip, VIP_RCM_LAST);
+ break;
+ case VM_RCM_SETFNB:
+ vp->m_final.cno = 0;
+ /* FALLTHROUGH */
+ case VM_RCM_SETNNB:
+ if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno))
+ goto err;
+ break;
+ default:
+ abort();
+ }
+
+ /* Update the cursor. */
+ sp->lno = vp->m_final.lno;
+ sp->cno = vp->m_final.cno;
+
+ /*
+ * Set the absolute mark -- set even if a tags or similar
+ * command, since the tag may be moving to the same file.
+ */
+ if ((F_ISSET(vp, V_ABS) ||
+ F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno ||
+ F_ISSET(vp, V_ABS_C) &&
+ (sp->lno != abs.lno || sp->cno != abs.cno)) &&
+ mark_set(sp, ABSMARK1, &abs, 1))
+ goto err;
+
+ if (0) {
+err: if (v_event_flush(sp, CH_MAPPED))
+ msgq(sp, M_BERR,
+ "110|Vi command failed: mapped keys discarded");
+ }
+
+ /*
+ * Check and clear interrupts. There's an obvious race, but
+ * it's not worth fixing.
+ */
+gc_err_noflush: if (INTERRUPTED(sp)) {
+intr: CLR_INTERRUPT(sp);
+ if (v_event_flush(sp, CH_MAPPED))
+ msgq(sp, M_ERR,
+ "231|Interrupted: mapped keys discarded");
+ else
+ msgq(sp, M_ERR, "236|Interrupted");
+ }
+
+ /* If the last command switched screens, update. */
+ if (F_ISSET(sp, SC_SSWITCH)) {
+ F_CLR(sp, SC_SSWITCH);
+
+ /*
+ * If the current screen is still displayed, it will
+ * need a new status line.
+ */
+ F_SET(sp, SC_STATUS);
+
+ /* Switch screens, change focus. */
+ sp = sp->nextdisp;
+ vip = VIP(sp);
+ (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
+
+ /* Don't trust the cursor. */
+ F_SET(vip, VIP_CUR_INVALID);
+
+ /* Refresh so we can display messages. */
+ if (vs_refresh(sp, 1))
+ return (1);
+ }
+
+ /* If the last command switched files, change focus. */
+ if (F_ISSET(sp, SC_FSWITCH)) {
+ F_CLR(sp, SC_FSWITCH);
+ (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
+ }
+
+ /* If leaving vi, return to the main editor loop. */
+ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) {
+ *spp = sp;
+ v_dtoh(sp);
+ break;
+ }
+ }
+ if (0)
+ret: rval = 1;
+ return (rval);
+}
+
+#define KEY(key, ec_flags) { \
+ if ((gcret = v_key(sp, 0, &ev, ec_flags)) != GC_OK) \
+ return (gcret); \
+ if (ev.e_value == K_ESCAPE) \
+ goto esc; \
+ if (F_ISSET(&ev.e_ch, CH_MAPPED)) \
+ *mappedp = 1; \
+ key = ev.e_c; \
+}
+
+/*
+ * The O_TILDEOP option makes the ~ command take a motion instead
+ * of a straight count. This is the replacement structure we use
+ * instead of the one currently in the VIKEYS table.
+ *
+ * XXX
+ * This should probably be deleted -- it's not all that useful, and
+ * we get help messages wrong.
+ */
+VIKEYS const tmotion = {
+ v_mulcase, V_CNT|V_DOT|V_MOTION|VM_RCM_SET,
+ "[count]~[count]motion",
+ " ~ change case to motion"
+};
+
+/*
+ * v_cmd --
+ *
+ * The command structure for vi is less complex than ex (and don't think
+ * I'm not grateful!) The command syntax is:
+ *
+ * [count] [buffer] [count] key [[motion] | [buffer] [character]]
+ *
+ * and there are several special cases. The motion value is itself a vi
+ * command, with the syntax:
+ *
+ * [count] key [character]
+ */
+static gcret_t
+v_cmd(sp, dp, vp, ismotion, comcountp, mappedp)
+ SCR *sp;
+ VICMD *dp, *vp;
+ VICMD *ismotion; /* Previous key if getting motion component. */
+ int *comcountp, *mappedp;
+{
+ enum { COMMANDMODE, ISPARTIAL, NOTPARTIAL } cpart;
+ EVENT ev;
+ VIKEYS const *kp;
+ gcret_t gcret;
+ u_int flags;
+ CHAR_T key;
+ char *s;
+
+ /*
+ * Get a key.
+ *
+ * <escape> cancels partial commands, i.e. a command where at least
+ * one non-numeric character has been entered. Otherwise, it beeps
+ * the terminal.
+ *
+ * !!!
+ * POSIX 1003.2-1992 explicitly disallows cancelling commands where
+ * all that's been entered is a number, requiring that the terminal
+ * be alerted.
+ */
+ cpart = ismotion == NULL ? COMMANDMODE : ISPARTIAL;
+ if ((gcret =
+ v_key(sp, ismotion == NULL, &ev, EC_MAPCOMMAND)) != GC_OK) {
+ if (gcret == GC_EVENT)
+ vp->ev = ev;
+ return (gcret);
+ }
+ if (ev.e_value == K_ESCAPE)
+ goto esc;
+ if (F_ISSET(&ev.e_ch, CH_MAPPED))
+ *mappedp = 1;
+ key = ev.e_c;
+
+ if (ismotion == NULL)
+ cpart = NOTPARTIAL;
+
+ /* Pick up optional buffer. */
+ if (key == '"') {
+ cpart = ISPARTIAL;
+ if (ismotion != NULL) {
+ v_emsg(sp, NULL, VIM_COMBUF);
+ return (GC_ERR);
+ }
+ KEY(vp->buffer, 0);
+ F_SET(vp, VC_BUFFER);
+
+ KEY(key, EC_MAPCOMMAND);
+ }
+
+ /*
+ * Pick up optional count, where a leading 0 is not a count,
+ * it's a command.
+ */
+ if (isdigit(key) && key != '0') {
+ if (v_count(sp, key, &vp->count))
+ return (GC_ERR);
+ F_SET(vp, VC_C1SET);
+ *comcountp = 1;
+
+ KEY(key, EC_MAPCOMMAND);
+ } else
+ *comcountp = 0;
+
+ /* Pick up optional buffer. */
+ if (key == '"') {
+ cpart = ISPARTIAL;
+ if (F_ISSET(vp, VC_BUFFER)) {
+ msgq(sp, M_ERR, "234|Only one buffer may be specified");
+ return (GC_ERR);
+ }
+ if (ismotion != NULL) {
+ v_emsg(sp, NULL, VIM_COMBUF);
+ return (GC_ERR);
+ }
+ KEY(vp->buffer, 0);
+ F_SET(vp, VC_BUFFER);
+
+ KEY(key, EC_MAPCOMMAND);
+ }
+
+ /* Check for an OOB command key. */
+ cpart = ISPARTIAL;
+ if (key > MAXVIKEY) {
+ v_emsg(sp, KEY_NAME(sp, key), VIM_NOCOM);
+ return (GC_ERR);
+ }
+ kp = &vikeys[vp->key = key];
+
+ /*
+ * !!!
+ * Historically, D accepted and then ignored a count. Match it.
+ */
+ if (vp->key == 'D' && F_ISSET(vp, VC_C1SET)) {
+ *comcountp = 0;
+ vp->count = 0;
+ F_CLR(vp, VC_C1SET);
+ }
+
+ /* Check for command aliases. */
+ if (kp->func == NULL && (kp = v_alias(sp, vp, kp)) == NULL)
+ return (GC_ERR);
+
+ /* The tildeop option makes the ~ command take a motion. */
+ if (key == '~' && O_ISSET(sp, O_TILDEOP))
+ kp = &tmotion;
+
+ vp->kp = kp;
+
+ /*
+ * Find the command. The only legal command with no underlying
+ * function is dot. It's historic practice that <escape> doesn't
+ * just erase the preceding number, it beeps the terminal as well.
+ * It's a common problem, so just beep the terminal unless verbose
+ * was set.
+ */
+ if (kp->func == NULL) {
+ if (key != '.') {
+ v_emsg(sp, KEY_NAME(sp, key),
+ ev.e_value == K_ESCAPE ? VIM_NOCOM_B : VIM_NOCOM);
+ return (GC_ERR);
+ }
+
+ /* If called for a motion command, stop now. */
+ if (dp == NULL)
+ goto usage;
+
+ /*
+ * !!!
+ * If a '.' is immediately entered after an undo command, we
+ * replay the log instead of redoing the last command. This
+ * is necessary because 'u' can't set the dot command -- see
+ * vi/v_undo.c:v_undo for details.
+ */
+ if (VIP(sp)->u_ccnt == sp->ccnt) {
+ vp->kp = &vikeys['u'];
+ F_SET(vp, VC_ISDOT);
+ return (GC_OK);
+ }
+
+ /* Otherwise, a repeatable command must have been executed. */
+ if (!F_ISSET(dp, VC_ISDOT)) {
+ msgq(sp, M_ERR, "208|No command to repeat");
+ return (GC_ERR);
+ }
+
+ /* Set new count/buffer, if any, and return. */
+ if (F_ISSET(vp, VC_C1SET)) {
+ F_SET(dp, VC_C1SET);
+ dp->count = vp->count;
+ }
+ if (F_ISSET(vp, VC_BUFFER))
+ dp->buffer = vp->buffer;
+
+ *vp = *dp;
+ return (GC_OK);
+ }
+
+ /* Set the flags based on the command flags. */
+ flags = kp->flags;
+
+ /* Check for illegal count. */
+ if (F_ISSET(vp, VC_C1SET) && !LF_ISSET(V_CNT))
+ goto usage;
+
+ /* Illegal motion command. */
+ if (ismotion == NULL) {
+ /* Illegal buffer. */
+ if (!LF_ISSET(V_OBUF) && F_ISSET(vp, VC_BUFFER))
+ goto usage;
+
+ /* Required buffer. */
+ if (LF_ISSET(V_RBUF)) {
+ KEY(vp->buffer, 0);
+ F_SET(vp, VC_BUFFER);
+ }
+ }
+
+ /*
+ * Special case: '[', ']' and 'Z' commands. Doesn't the fact that
+ * the *single* characters don't mean anything but the *doubled*
+ * characters do, just frost your shorts?
+ */
+ if (vp->key == '[' || vp->key == ']' || vp->key == 'Z') {
+ /*
+ * Historically, half entered [[, ]] or Z commands weren't
+ * cancelled by <escape>, the terminal was beeped instead.
+ * POSIX.2-1992 probably didn't notice, and requires that
+ * they be cancelled instead of beeping. Seems fine to me.
+ *
+ * Don't set the EC_MAPCOMMAND flag, apparently ] is a popular
+ * vi meta-character, and we don't want the user to wait while
+ * we time out a possible mapping. This *appears* to match
+ * historic vi practice, but with mapping characters, you Just
+ * Never Know.
+ */
+ KEY(key, 0);
+
+ if (vp->key != key) {
+usage: if (ismotion == NULL)
+ s = kp->usage;
+ else if (ismotion->key == '~' && O_ISSET(sp, O_TILDEOP))
+ s = tmotion.usage;
+ else
+ s = vikeys[ismotion->key].usage;
+ v_emsg(sp, s, VIM_USAGE);
+ return (GC_ERR);
+ }
+ }
+ /* Special case: 'z' command. */
+ if (vp->key == 'z') {
+ KEY(vp->character, 0);
+ if (isdigit(vp->character)) {
+ if (v_count(sp, vp->character, &vp->count2))
+ return (GC_ERR);
+ F_SET(vp, VC_C2SET);
+ KEY(vp->character, 0);
+ }
+ }
+
+ /*
+ * Commands that have motion components can be doubled to
+ * imply the current line.
+ */
+ if (ismotion != NULL && ismotion->key != key && !LF_ISSET(V_MOVE)) {
+ msgq(sp, M_ERR, "210|%s may not be used as a motion command",
+ KEY_NAME(sp, key));
+ return (GC_ERR);
+ }
+
+ /* Required character. */
+ if (LF_ISSET(V_CHAR))
+ KEY(vp->character, 0);
+
+ /* Get any associated cursor word. */
+ if (F_ISSET(kp, V_KEYW) && v_keyword(sp))
+ return (GC_ERR);
+
+ return (GC_OK);
+
+esc: switch (cpart) {
+ case COMMANDMODE:
+ msgq(sp, M_BERR, "211|Already in command mode");
+ return (GC_ERR_NOFLUSH);
+ case ISPARTIAL:
+ break;
+ case NOTPARTIAL:
+ (void)sp->gp->scr_bell(sp);
+ break;
+ }
+ return (GC_ERR);
+}
+
+/*
+ * v_motion --
+ *
+ * Get resulting motion mark.
+ */
+static int
+v_motion(sp, dm, vp, mappedp)
+ SCR *sp;
+ VICMD *dm, *vp;
+ int *mappedp;
+{
+ VICMD motion;
+ size_t len;
+ u_long cnt;
+ u_int flags;
+ int tilde_reset, notused;
+
+ /*
+ * If '.' command, use the dot motion, else get the motion command.
+ * Clear any line motion flags, the subsequent motion isn't always
+ * the same, i.e. "/aaa" may or may not be a line motion.
+ */
+ if (F_ISSET(vp, VC_ISDOT)) {
+ motion = *dm;
+ F_SET(&motion, VC_ISDOT);
+ F_CLR(&motion, VM_COMMASK);
+ } else {
+ memset(&motion, 0, sizeof(VICMD));
+ if (v_cmd(sp, NULL, &motion, vp, &notused, mappedp) != GC_OK)
+ return (1);
+ }
+
+ /*
+ * A count may be provided both to the command and to the motion, in
+ * which case the count is multiplicative. For example, "3y4y" is the
+ * same as "12yy". This count is provided to the motion command and
+ * not to the regular function.
+ */
+ cnt = motion.count = F_ISSET(&motion, VC_C1SET) ? motion.count : 1;
+ if (F_ISSET(vp, VC_C1SET)) {
+ motion.count *= vp->count;
+ F_SET(&motion, VC_C1SET);
+
+ /*
+ * Set flags to restore the original values of the command
+ * structure so dot commands can change the count values,
+ * e.g. "2dw" "3." deletes a total of five words.
+ */
+ F_CLR(vp, VC_C1SET);
+ F_SET(vp, VC_C1RESET);
+ }
+
+ /*
+ * Some commands can be repeated to indicate the current line. In
+ * this case, or if the command is a "line command", set the flags
+ * appropriately. If not a doubled command, run the function to get
+ * the resulting mark.
+ */
+ if (vp->key == motion.key) {
+ F_SET(vp, VM_LDOUBLE | VM_LMODE);
+
+ /* Set the origin of the command. */
+ vp->m_start.lno = sp->lno;
+ vp->m_start.cno = 0;
+
+ /*
+ * Set the end of the command.
+ *
+ * If the current line is missing, i.e. the file is empty,
+ * historic vi permitted a "cc" or "!!" command to insert
+ * text.
+ */
+ vp->m_stop.lno = sp->lno + motion.count - 1;
+ if (db_get(sp, vp->m_stop.lno, 0, NULL, &len)) {
+ if (vp->m_stop.lno != 1 ||
+ vp->key != 'c' && vp->key != '!') {
+ v_emsg(sp, NULL, VIM_EMPTY);
+ return (1);
+ }
+ vp->m_stop.cno = 0;
+ } else
+ vp->m_stop.cno = len ? len - 1 : 0;
+ } else {
+ /*
+ * Motion commands change the underlying movement (*snarl*).
+ * For example, "l" is illegal at the end of a line, but "dl"
+ * is not. Set flags so the function knows the situation.
+ */
+ motion.rkp = vp->kp;
+
+ /*
+ * XXX
+ * Use yank instead of creating a new motion command, it's a
+ * lot easier for now.
+ */
+ if (vp->kp == &tmotion) {
+ tilde_reset = 1;
+ vp->kp = &vikeys['y'];
+ } else
+ tilde_reset = 0;
+
+ /*
+ * Copy the key flags into the local structure, except for the
+ * RCM flags -- the motion command will set the RCM flags in
+ * the vp structure if necessary. This means that the motion
+ * command is expected to determine where the cursor ends up!
+ * However, we save off the current RCM mask and restore it if
+ * it no RCM flags are set by the motion command, with a small
+ * modification.
+ *
+ * We replace the VM_RCM_SET flag with the VM_RCM flag. This
+ * is so that cursor movement doesn't set the relative position
+ * unless the motion command explicitly specified it. This
+ * appears to match historic practice, but I've never been able
+ * to develop a hard-and-fast rule.
+ */
+ flags = F_ISSET(vp, VM_RCM_MASK);
+ if (LF_ISSET(VM_RCM_SET)) {
+ LF_SET(VM_RCM);
+ LF_CLR(VM_RCM_SET);
+ }
+ F_CLR(vp, VM_RCM_MASK);
+ F_SET(&motion, motion.kp->flags & ~VM_RCM_MASK);
+
+ /*
+ * Set the three cursor locations to the current cursor. This
+ * permits commands like 'j' and 'k', that are line oriented
+ * motions and have special cursor suck semantics when they are
+ * used as standalone commands, to ignore column positioning.
+ */
+ motion.m_final.lno =
+ motion.m_stop.lno = motion.m_start.lno = sp->lno;
+ motion.m_final.cno =
+ motion.m_stop.cno = motion.m_start.cno = sp->cno;
+
+ /* Run the function. */
+ if ((motion.kp->func)(sp, &motion))
+ return (1);
+
+ /*
+ * If the current line is missing, i.e. the file is empty,
+ * historic vi allowed "c<motion>" or "!<motion>" to insert
+ * text. Otherwise fail -- most motion commands will have
+ * already failed, but some, e.g. G, succeed in empty files.
+ */
+ if (!db_exist(sp, vp->m_stop.lno)) {
+ if (vp->m_stop.lno != 1 ||
+ vp->key != 'c' && vp->key != '!') {
+ v_emsg(sp, NULL, VIM_EMPTY);
+ return (1);
+ }
+ vp->m_stop.cno = 0;
+ }
+
+ /*
+ * XXX
+ * See above.
+ */
+ if (tilde_reset)
+ vp->kp = &tmotion;
+
+ /*
+ * Copy cut buffer, line mode and cursor position information
+ * from the motion command structure, i.e. anything that the
+ * motion command can set for us. The commands can flag the
+ * movement as a line motion (see v_sentence) as well as set
+ * the VM_RCM_* flags explicitly.
+ */
+ F_SET(vp, F_ISSET(&motion, VM_COMMASK | VM_RCM_MASK));
+
+ /*
+ * If the motion command set no relative motion flags, use
+ * the (slightly) modified previous values.
+ */
+ if (!F_ISSET(vp, VM_RCM_MASK))
+ F_SET(vp, flags);
+
+ /*
+ * Commands can change behaviors based on the motion command
+ * used, for example, the ! command repeated the last bang
+ * command if N or n was used as the motion.
+ */
+ vp->rkp = motion.kp;
+
+ /*
+ * Motion commands can reset all of the cursor information.
+ * If the motion is in the reverse direction, switch the
+ * from and to MARK's so that it's in a forward direction.
+ * Motions are from the from MARK to the to MARK (inclusive).
+ */
+ if (motion.m_start.lno > motion.m_stop.lno ||
+ motion.m_start.lno == motion.m_stop.lno &&
+ motion.m_start.cno > motion.m_stop.cno) {
+ vp->m_start = motion.m_stop;
+ vp->m_stop = motion.m_start;
+ } else {
+ vp->m_start = motion.m_start;
+ vp->m_stop = motion.m_stop;
+ }
+ vp->m_final = motion.m_final;
+ }
+
+ /*
+ * If the command sets dot, save the motion structure. The motion
+ * count was changed above and needs to be reset, that's why this
+ * is done here, and not in the calling routine.
+ */
+ if (F_ISSET(vp->kp, V_DOT)) {
+ *dm = motion;
+ dm->count = cnt;
+ }
+ return (0);
+}
+
+/*
+ * v_init --
+ * Initialize the vi screen.
+ */
+static int
+v_init(sp)
+ SCR *sp;
+{
+ GS *gp;
+ VI_PRIVATE *vip;
+
+ gp = sp->gp;
+ vip = VIP(sp);
+
+ /* Switch into vi. */
+ if (gp->scr_screen(sp, SC_VI))
+ return (1);
+ (void)gp->scr_attr(sp, SA_ALTERNATE, 1);
+
+ F_CLR(sp, SC_EX | SC_SCR_EX);
+ F_SET(sp, SC_VI);
+
+ /*
+ * Initialize screen values.
+ *
+ * Small windows: see vs_refresh(), section 6a.
+ *
+ * Setup:
+ * t_minrows is the minimum rows to display
+ * t_maxrows is the maximum rows to display (rows - 1)
+ * t_rows is the rows currently being displayed
+ */
+ sp->rows = vip->srows = O_VAL(sp, O_LINES);
+ sp->cols = O_VAL(sp, O_COLUMNS);
+ sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW);
+ if (sp->rows != 1) {
+ if (sp->t_rows > sp->rows - 1) {
+ sp->t_minrows = sp->t_rows = sp->rows - 1;
+ msgq(sp, M_INFO,
+ "214|Windows option value is too large, max is %u",
+ sp->t_rows);
+ }
+ sp->t_maxrows = sp->rows - 1;
+ } else
+ sp->t_maxrows = 1;
+ sp->woff = 0;
+
+ /* Create a screen map. */
+ CALLOC_RET(sp, HMAP, SMAP *, SIZE_HMAP(sp), sizeof(SMAP));
+ TMAP = HMAP + (sp->t_rows - 1);
+ HMAP->lno = sp->lno;
+ HMAP->coff = 0;
+ HMAP->soff = 1;
+
+ /*
+ * Fill the screen map from scratch -- try and center the line. That
+ * way if we're starting with a file we've seen before, we'll put the
+ * line in the middle, otherwise, it won't work and we'll end up with
+ * the line at the top.
+ */
+ F_SET(sp, SC_SCR_REFORMAT | SC_SCR_CENTER);
+
+ /* Invalidate the cursor. */
+ F_SET(vip, VIP_CUR_INVALID);
+
+ /* Paint the screen image from scratch. */
+ F_SET(vip, VIP_N_EX_PAINT);
+
+ return (0);
+}
+
+/*
+ * v_dtoh --
+ * Move all but the current screen to the hidden queue.
+ */
+static void
+v_dtoh(sp)
+ SCR *sp;
+{
+ GS *gp;
+ SCR *tsp;
+ int hidden;
+
+ /* Move all screens to the hidden queue, tossing screen maps. */
+ for (hidden = 0, gp = sp->gp;
+ (tsp = gp->dq.cqh_first) != (void *)&gp->dq; ++hidden) {
+ if (_HMAP(tsp) != NULL) {
+ free(_HMAP(tsp));
+ _HMAP(tsp) = NULL;
+ }
+ CIRCLEQ_REMOVE(&gp->dq, tsp, q);
+ CIRCLEQ_INSERT_TAIL(&gp->hq, tsp, q);
+ }
+
+ /* Move current screen back to the display queue. */
+ CIRCLEQ_REMOVE(&gp->hq, sp, q);
+ CIRCLEQ_INSERT_TAIL(&gp->dq, sp, q);
+
+ /*
+ * XXX
+ * Don't bother internationalizing this message, it's going to
+ * go away as soon as we have one-line screens. --TK
+ */
+ if (hidden > 1)
+ msgq(sp, M_INFO,
+ "%d screens backgrounded; use :display to list them",
+ hidden - 1);
+}
+
+/*
+ * v_keyword --
+ * Get the word (or non-word) the cursor is on.
+ */
+static int
+v_keyword(sp)
+ SCR *sp;
+{
+ VI_PRIVATE *vip;
+ size_t beg, end, len;
+ int moved, state;
+ char *p;
+
+ if (db_get(sp, sp->lno, DBG_FATAL, &p, &len))
+ return (1);
+
+ /*
+ * !!!
+ * Historically, tag commands skipped over any leading whitespace
+ * characters. Make this true in general when using cursor words.
+ * If movement, getting a cursor word implies moving the cursor to
+ * its beginning. Refresh now.
+ *
+ * !!!
+ * Find the beginning/end of the keyword. Keywords are currently
+ * used for cursor-word searching and for tags. Historical vi
+ * only used the word in a tag search from the cursor to the end
+ * of the word, i.e. if the cursor was on the 'b' in " abc ", the
+ * tag was "bc". For consistency, we make cursor word searches
+ * follow the same rule.
+ */
+ for (moved = 0,
+ beg = sp->cno; beg < len && isspace(p[beg]); moved = 1, ++beg);
+ if (beg >= len) {
+ msgq(sp, M_BERR, "212|Cursor not in a word");
+ return (1);
+ }
+ if (moved) {
+ sp->cno = beg;
+ (void)vs_refresh(sp, 0);
+ }
+
+ /* Find the end of the word. */
+ for (state = inword(p[beg]),
+ end = beg; ++end < len && state == inword(p[end]););
+
+ vip = VIP(sp);
+ len = (end - beg);
+ BINC_RET(sp, vip->keyw, vip->klen, len);
+ memmove(vip->keyw, p + beg, len);
+ vip->keyw[len] = '\0'; /* XXX */
+ return (0);
+}
+
+/*
+ * v_alias --
+ * Check for a command alias.
+ */
+static VIKEYS const *
+v_alias(sp, vp, kp)
+ SCR *sp;
+ VICMD *vp;
+ VIKEYS const *kp;
+{
+ CHAR_T push;
+
+ switch (vp->key) {
+ case 'C': /* C -> c$ */
+ push = '$';
+ vp->key = 'c';
+ break;
+ case 'D': /* D -> d$ */
+ push = '$';
+ vp->key = 'd';
+ break;
+ case 'S': /* S -> c_ */
+ push = '_';
+ vp->key = 'c';
+ break;
+ case 'Y': /* Y -> y_ */
+ push = '_';
+ vp->key = 'y';
+ break;
+ default:
+ return (kp);
+ }
+ return (v_event_push(sp,
+ NULL, &push, 1, CH_NOMAP | CH_QUOTED) ? NULL : &vikeys[vp->key]);
+}
+
+/*
+ * v_count --
+ * Return the next count.
+ */
+static int
+v_count(sp, fkey, countp)
+ SCR *sp;
+ ARG_CHAR_T fkey;
+ u_long *countp;
+{
+ EVENT ev;
+ u_long count, tc;
+
+ ev.e_c = fkey;
+ count = tc = 0;
+ do {
+ /*
+ * XXX
+ * Assume that overflow results in a smaller number.
+ */
+ tc = count * 10 + ev.e_c - '0';
+ if (count > tc) {
+ /* Toss to the next non-digit. */
+ do {
+ if (v_key(sp, 0, &ev,
+ EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK)
+ return (1);
+ } while (isdigit(ev.e_c));
+ msgq(sp, M_ERR,
+ "235|Number larger than %lu", ULONG_MAX);
+ return (1);
+ }
+ count = tc;
+ if (v_key(sp, 0, &ev, EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK)
+ return (1);
+ } while (isdigit(ev.e_c));
+ *countp = count;
+ return (0);
+}
+
+/*
+ * v_key --
+ * Return the next event.
+ */
+static gcret_t
+v_key(sp, command_events, evp, ec_flags)
+ SCR *sp;
+ int command_events;
+ EVENT *evp;
+ u_int32_t ec_flags;
+{
+ u_int32_t quote;
+
+ for (quote = 0;;) {
+ if (v_event_get(sp, evp, 0, ec_flags | quote))
+ return (GC_FATAL);
+ quote = 0;
+
+ switch (evp->e_event) {
+ case E_CHARACTER:
+ /*
+ * !!!
+ * Historically, ^V was ignored in the command stream,
+ * although it had a useful side-effect of interrupting
+ * mappings. Adding a quoting bit to the call probably
+ * extends historic practice, but it feels right.
+ */
+ if (evp->e_value == K_VLNEXT) {
+ quote = EC_QUOTED;
+ break;
+ }
+ return (GC_OK);
+ case E_ERR:
+ case E_EOF:
+ return (GC_FATAL);
+ case E_INTERRUPT:
+ /*
+ * !!!
+ * Historically, vi beeped on command level interrupts.
+ *
+ * Historically, vi exited to ex mode if no file was
+ * named on the command line, and two interrupts were
+ * generated in a row. (Just figured you might want
+ * to know that.)
+ */
+ (void)sp->gp->scr_bell(sp);
+ return (GC_INTERRUPT);
+ case E_REPAINT:
+ if (vs_repaint(sp, evp))
+ return (GC_FATAL);
+ break;
+ case E_WRESIZE:
+ return (GC_ERR);
+ case E_QUIT:
+ case E_WRITE:
+ if (command_events)
+ return (GC_EVENT);
+ /* FALLTHROUGH */
+ default:
+ v_event_err(sp, evp);
+ return (GC_ERR);
+ }
+ }
+ /* NOTREACHED */
+}
+
+#if defined(DEBUG) && defined(COMLOG)
+/*
+ * v_comlog --
+ * Log the contents of the command structure.
+ */
+static void
+v_comlog(sp, vp)
+ SCR *sp;
+ VICMD *vp;
+{
+ TRACE(sp, "vcmd: %c", vp->key);
+ if (F_ISSET(vp, VC_BUFFER))
+ TRACE(sp, " buffer: %c", vp->buffer);
+ if (F_ISSET(vp, VC_C1SET))
+ TRACE(sp, " c1: %lu", vp->count);
+ if (F_ISSET(vp, VC_C2SET))
+ TRACE(sp, " c2: %lu", vp->count2);
+ TRACE(sp, " flags: 0x%x\n", vp->flags);
+}
+#endif
diff --git a/contrib/nvi/vi/vi.h b/contrib/nvi/vi/vi.h
new file mode 100644
index 0000000..bede3a6
--- /dev/null
+++ b/contrib/nvi/vi/vi.h
@@ -0,0 +1,377 @@
+/*-
+ * 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.
+ *
+ * @(#)vi.h 10.19 (Berkeley) 6/30/96
+ */
+
+/* Definition of a vi "word". */
+#define inword(ch) (isalnum(ch) || (ch) == '_')
+
+typedef struct _vikeys VIKEYS;
+
+/* Structure passed around to functions implementing vi commands. */
+typedef struct _vicmd {
+ CHAR_T key; /* Command key. */
+ CHAR_T buffer; /* Buffer. */
+ CHAR_T character; /* Character. */
+ u_long count; /* Count. */
+ u_long count2; /* Second count (only used by z). */
+ EVENT ev; /* Associated event. */
+
+#define ISCMD(p, key) ((p) == &vikeys[key])
+ VIKEYS const *kp; /* Command/Motion VIKEYS entry. */
+#define ISMOTION(vp) (vp->rkp != NULL && F_ISSET(vp->rkp, V_MOTION))
+ VIKEYS const *rkp; /* Related C/M VIKEYS entry. */
+
+ /*
+ * Historic vi allowed "dl" when the cursor was on the last column,
+ * deleting the last character, and similarly allowed "dw" when
+ * the cursor was on the last column of the file. It didn't allow
+ * "dh" when the cursor was on column 1, although these cases are
+ * not strictly analogous. The point is that some movements would
+ * succeed if they were associated with a motion command, and fail
+ * otherwise. This is part of the off-by-1 schizophrenia that
+ * plagued vi. Other examples are that "dfb" deleted everything
+ * up to and including the next 'b' character, while "d/b" deleted
+ * everything up to the next 'b' character. While this implementation
+ * regularizes the interface to the extent possible, there are many
+ * special cases that can't be fixed. The special cases are handled
+ * by setting flags per command so that the underlying command and
+ * motion routines know what's really going on.
+ *
+ * The VM_* flags are set in the vikeys array and by the underlying
+ * functions (motion component or command) as well. For this reason,
+ * the flags in the VICMD and VIKEYS structures live in the same name
+ * space.
+ */
+#define VM_CMDFAILED 0x00000001 /* Command failed. */
+#define VM_CUTREQ 0x00000002 /* Always cut into numeric buffers. */
+#define VM_LDOUBLE 0x00000004 /* Doubled command for line mode. */
+#define VM_LMODE 0x00000008 /* Motion is line oriented. */
+#define VM_COMMASK 0x0000000f /* Mask for VM flags. */
+
+ /*
+ * The VM_RCM_* flags are single usage, i.e. if you set one, you have
+ * to clear the others.
+ */
+#define VM_RCM 0x00000010 /* Use relative cursor movment (RCM). */
+#define VM_RCM_SET 0x00000020 /* RCM: set to current position. */
+#define VM_RCM_SETFNB 0x00000040 /* RCM: set to first non-blank (FNB). */
+#define VM_RCM_SETLAST 0x00000080 /* RCM: set to last character. */
+#define VM_RCM_SETNNB 0x00000100 /* RCM: set to next non-blank. */
+#define VM_RCM_MASK 0x000001f0 /* Mask for RCM flags. */
+
+ /* Flags for the underlying function. */
+#define VC_BUFFER 0x00000200 /* The buffer was set. */
+#define VC_C1RESET 0x00000400 /* Reset C1SET flag for dot commands. */
+#define VC_C1SET 0x00000800 /* Count 1 was set. */
+#define VC_C2SET 0x00001000 /* Count 2 was set. */
+#define VC_ISDOT 0x00002000 /* Command was the dot command. */
+ u_int32_t flags;
+
+ /*
+ * There are four cursor locations that we worry about: the initial
+ * cursor position, the start of the range, the end of the range,
+ * and the final cursor position. The initial cursor position and
+ * the start of the range are both m_start, and are always the same.
+ * All locations are initialized to the starting cursor position by
+ * the main vi routines, and the underlying functions depend on this.
+ *
+ * Commands that can be motion components set the end of the range
+ * cursor position, m_stop. All commands must set the ending cursor
+ * position, m_final. The reason that m_stop isn't the same as m_final
+ * is that there are situations where the final position of the cursor
+ * is outside of the cut/delete range (e.g. 'd[[' from the first column
+ * of a line). The final cursor position often varies based on the
+ * direction of the movement, as well as the command. The only special
+ * case that the delete code handles is that it will make adjustments
+ * if the final cursor position is deleted.
+ *
+ * The reason for all of this is that the historic vi semantics were
+ * defined command-by-command. Every function has to roll its own
+ * starting and stopping positions, and adjust them if it's being used
+ * as a motion component. The general rules are as follows:
+ *
+ * 1: If not a motion component, the final cursor is at the end
+ * of the range.
+ * 2: If moving backward in the file, delete and yank move the
+ * final cursor to the end of the range.
+ * 3: If moving forward in the file, delete and yank leave the
+ * final cursor at the start of the range.
+ *
+ * Usually, if moving backward in the file and it's a motion component,
+ * the starting cursor is decremented by a single character (or, in a
+ * few cases, to the end of the previous line) so that the starting
+ * cursor character isn't cut or deleted. No cursor adjustment is
+ * needed for moving forward, because the cut/delete routines handle
+ * m_stop inclusively, i.e. the last character in the range is cut or
+ * deleted. This makes cutting to the EOF/EOL reasonable.
+ *
+ * The 'c', '<', '>', and '!' commands are special cases. We ignore
+ * the final cursor position for all of them: for 'c', the text input
+ * routines set the cursor to the last character inserted; for '<',
+ * '>' and '!', the underlying ex commands that do the operation will
+ * set the cursor for us, usually to something related to the first
+ * <nonblank>.
+ */
+ MARK m_start; /* mark: initial cursor, range start. */
+ MARK m_stop; /* mark: range end. */
+ MARK m_final; /* mark: final cursor position. */
+} VICMD;
+
+/* Vi command table structure. */
+struct _vikeys { /* Underlying function. */
+ int (*func) __P((SCR *, VICMD *));
+#define V_ABS 0x00004000 /* Absolute movement, set '' mark. */
+#define V_ABS_C 0x00008000 /* V_ABS: if the line/column changed. */
+#define V_ABS_L 0x00010000 /* V_ABS: if the line changed. */
+#define V_CHAR 0x00020000 /* Character (required, trailing). */
+#define V_CNT 0x00040000 /* Count (optional, leading). */
+#define V_DOT 0x00080000 /* On success, sets dot command. */
+#define V_KEYW 0x00100000 /* Cursor referenced word. */
+#define V_MOTION 0x00200000 /* Motion (required, trailing). */
+#define V_MOVE 0x00400000 /* Command defines movement. */
+#define V_OBUF 0x00800000 /* Buffer (optional, leading). */
+#define V_RBUF 0x01000000 /* Buffer (required, trailing). */
+#define V_SECURE 0x02000000 /* Permission denied if O_SECURE set. */
+ u_int32_t flags;
+ char *usage; /* Usage line. */
+ char *help; /* Help line. */
+};
+#define MAXVIKEY 126 /* List of vi commands. */
+extern VIKEYS const vikeys[MAXVIKEY + 1];
+extern VIKEYS const tmotion; /* XXX Hacked ~ command. */
+
+/* Character stream structure, prototypes. */
+typedef struct _vcs {
+ recno_t cs_lno; /* Line. */
+ size_t cs_cno; /* Column. */
+ CHAR_T *cs_bp; /* Buffer. */
+ size_t cs_len; /* Length. */
+ CHAR_T cs_ch; /* Character. */
+#define CS_EMP 1 /* Empty line. */
+#define CS_EOF 2 /* End-of-file. */
+#define CS_EOL 3 /* End-of-line. */
+#define CS_SOF 4 /* Start-of-file. */
+ int cs_flags; /* Return flags. */
+} VCS;
+
+int cs_bblank __P((SCR *, VCS *));
+int cs_fblank __P((SCR *, VCS *));
+int cs_fspace __P((SCR *, VCS *));
+int cs_init __P((SCR *, VCS *));
+int cs_next __P((SCR *, VCS *));
+int cs_prev __P((SCR *, VCS *));
+
+/*
+ * We use a single "window" for each set of vi screens. The model would be
+ * simpler with two windows (one for the text, and one for the modeline)
+ * because scrolling the text window down would work correctly then, not
+ * affecting the mode line. As it is we have to play games to make it look
+ * right. The reason for this choice is that it would be difficult for
+ * curses to optimize the movement, i.e. detect that the downward scroll
+ * isn't going to change the modeline, set the scrolling region on the
+ * terminal and only scroll the first part of the text window.
+ *
+ * Structure for mapping lines to the screen. An SMAP is an array, with one
+ * structure element per screen line, which holds information describing the
+ * physical line which is displayed in the screen line. The first two fields
+ * (lno and off) are all that are necessary to describe a line. The rest of
+ * the information is useful to keep information from being re-calculated.
+ *
+ * The SMAP always has an entry for each line of the physical screen, plus a
+ * slot for the colon command line, so there is room to add any screen into
+ * another one at screen exit.
+ *
+ * Lno is the line number. If doing the historic vi long line folding, off
+ * is the screen offset into the line. For example, the pair 2:1 would be
+ * the first screen of line 2, and 2:2 would be the second. In the case of
+ * long lines, the screen map will tend to be staggered, e.g., 1:1, 1:2, 1:3,
+ * 2:1, 3:1, etc. If doing left-right scrolling, the off field is the screen
+ * column offset into the lines, and can take on any value, as it's adjusted
+ * by the user set value O_SIDESCROLL.
+ */
+typedef struct _smap {
+ recno_t lno; /* 1-N: Physical file line number. */
+ size_t coff; /* 0-N: Column offset in the line. */
+ size_t soff; /* 1-N: Screen offset in the line. */
+
+ /* vs_line() cache information. */
+ size_t c_sboff; /* 0-N: offset of first character byte. */
+ size_t c_eboff; /* 0-N: offset of last character byte. */
+ u_int8_t c_scoff; /* 0-N: offset into the first character. */
+ u_int8_t c_eclen; /* 1-N: columns from the last character. */
+ u_int8_t c_ecsize; /* 1-N: size of the last character. */
+} SMAP;
+ /* Macros to flush/test cached information. */
+#define SMAP_CACHE(smp) ((smp)->c_ecsize != 0)
+#define SMAP_FLUSH(smp) ((smp)->c_ecsize = 0)
+
+ /* Character search information. */
+typedef enum { CNOTSET, FSEARCH, fSEARCH, TSEARCH, tSEARCH } cdir_t;
+
+typedef enum { AB_NOTSET, AB_NOTWORD, AB_INWORD } abb_t;
+typedef enum { Q_NOTSET, Q_BNEXT, Q_BTHIS, Q_VNEXT, Q_VTHIS } quote_t;
+
+/* Vi private, per-screen memory. */
+typedef struct _vi_private {
+ VICMD cmd; /* Current command, motion. */
+ VICMD motion;
+
+ /*
+ * !!!
+ * The saved command structure can be modified by the underlying
+ * vi functions, see v_Put() and v_put().
+ */
+ VICMD sdot; /* Saved dot, motion command. */
+ VICMD sdotmotion;
+
+ CHAR_T *keyw; /* Keyword buffer. */
+ size_t klen; /* Keyword length. */
+ size_t keywlen; /* Keyword buffer length. */
+
+ CHAR_T rlast; /* Last 'r' replacement character. */
+ e_key_t rvalue; /* Value of last replacement character. */
+
+ EVENT *rep; /* Input replay buffer. */
+ size_t rep_len; /* Input replay buffer length. */
+ size_t rep_cnt; /* Input replay buffer characters. */
+
+ mtype_t mtype; /* Last displayed message type. */
+ size_t linecount; /* 1-N: Output overwrite count. */
+ size_t lcontinue; /* 1-N: Output line continue value. */
+ size_t totalcount; /* 1-N: Output overwrite count. */
+
+ /* Busy state. */
+ int busy_ref; /* Busy reference count. */
+ int busy_ch; /* Busy character. */
+ size_t busy_fx; /* Busy character x coordinate. */
+ size_t busy_oldy; /* Saved y coordinate. */
+ size_t busy_oldx; /* Saved x coordinate. */
+ struct timeval busy_tv; /* Busy timer. */
+
+ char *ps; /* Paragraph plus section list. */
+
+ u_long u_ccnt; /* Undo command count. */
+
+ CHAR_T lastckey; /* Last search character. */
+ cdir_t csearchdir; /* Character search direction. */
+
+ SMAP *h_smap; /* First slot of the line map. */
+ SMAP *t_smap; /* Last slot of the line map. */
+
+ /*
+ * One extra slot is always allocated for the map so that we can use
+ * it to do vi :colon command input; see v_tcmd().
+ */
+ recno_t sv_tm_lno; /* tcmd: saved TMAP lno field. */
+ size_t sv_tm_coff; /* tcmd: saved TMAP coff field. */
+ size_t sv_tm_soff; /* tcmd: saved TMAP soff field. */
+ size_t sv_t_maxrows; /* tcmd: saved t_maxrows. */
+ size_t sv_t_minrows; /* tcmd: saved t_minrows. */
+ size_t sv_t_rows; /* tcmd: saved t_rows. */
+#define SIZE_HMAP(sp) (VIP(sp)->srows + 1)
+
+ /*
+ * Macros to get to the head/tail of the smap. If the screen only has
+ * one line, HMAP can be equal to TMAP, so the code has to understand
+ * the off-by-one errors that can result. If stepping through an SMAP
+ * and operating on each entry, use sp->t_rows as the count of slots,
+ * don't use a loop that compares <= TMAP.
+ */
+#define _HMAP(sp) (VIP(sp)->h_smap)
+#define HMAP _HMAP(sp)
+#define _TMAP(sp) (VIP(sp)->t_smap)
+#define TMAP _TMAP(sp)
+
+ recno_t ss_lno; /* 1-N: vi_opt_screens cached line number. */
+ size_t ss_screens; /* vi_opt_screens cached return value. */
+#define VI_SCR_CFLUSH(vip) vip->ss_lno = OOBLNO
+
+ size_t srows; /* 1-N: rows in the terminal/window. */
+ recno_t olno; /* 1-N: old cursor file line. */
+ size_t ocno; /* 0-N: old file cursor column. */
+ size_t sc_col; /* 0-N: LOGICAL screen column. */
+ SMAP *sc_smap; /* SMAP entry where sc_col occurs. */
+
+#define VIP_CUR_INVALID 0x0001 /* Cursor position is unknown. */
+#define VIP_DIVIDER 0x0002 /* Divider line was displayed. */
+#define VIP_N_EX_PAINT 0x0004 /* Clear and repaint when ex finishes. */
+#define VIP_N_EX_REDRAW 0x0008 /* Schedule SC_SCR_REDRAW when ex finishes. */
+#define VIP_N_REFRESH 0x0010 /* Repaint (from SMAP) on the next refresh. */
+#define VIP_N_RENUMBER 0x0020 /* Renumber screen on the next refresh. */
+#define VIP_RCM_LAST 0x0040 /* Cursor drawn to the last column. */
+#define VIP_S_MODELINE 0x0080 /* Skip next modeline refresh. */
+#define VIP_S_REFRESH 0x0100 /* Skip next refresh. */
+ u_int16_t flags;
+} VI_PRIVATE;
+
+/* Vi private area. */
+#define VIP(sp) ((VI_PRIVATE *)((sp)->vi_private))
+
+#define O_NUMBER_FMT "%7lu " /* O_NUMBER format, length. */
+#define O_NUMBER_LENGTH 8
+#define SCREEN_COLS(sp) /* Screen columns. */ \
+ ((O_ISSET(sp, O_NUMBER) ? (sp)->cols - O_NUMBER_LENGTH : (sp)->cols))
+
+/*
+ * LASTLINE is the zero-based, last line in the screen. Note that it is correct
+ * regardless of the changes in the screen to permit text input on the last line
+ * of the screen, or the existence of small screens.
+ */
+#define LASTLINE(sp) \
+ ((sp)->t_maxrows < (sp)->rows ? (sp)->t_maxrows : (sp)->rows - 1)
+
+/*
+ * Small screen (see vs_refresh.c, section 6a) and one-line screen test.
+ * Note, both cannot be true for the same screen.
+ */
+#define IS_SMALL(sp) ((sp)->t_minrows != (sp)->t_maxrows)
+#define IS_ONELINE(sp) ((sp)->rows == 1)
+
+#define HALFTEXT(sp) /* Half text. */ \
+ ((sp)->t_rows == 1 ? 1 : (sp)->t_rows / 2)
+#define HALFSCREEN(sp) /* Half text screen. */ \
+ ((sp)->t_maxrows == 1 ? 1 : (sp)->t_maxrows / 2)
+
+/*
+ * Next tab offset.
+ *
+ * !!!
+ * There are problems with how the historical vi handled tabs. For example,
+ * by doing "set ts=3" and building lines that fold, you can get it to step
+ * through tabs as if they were spaces and move inserted characters to new
+ * positions when <esc> is entered. I believe that nvi does tabs correctly,
+ * but there are some historical incompatibilities.
+ */
+#define TAB_OFF(c) COL_OFF((c), O_VAL(sp, O_TABSTOP))
+
+/* If more than one screen being shown. */
+#define IS_SPLIT(sp) \
+ ((sp)->q.cqe_next != (void *)&(sp)->gp->dq || \
+ (sp)->q.cqe_prev != (void *)&(sp)->gp->dq)
+
+/* Screen adjustment operations. */
+typedef enum { A_DECREASE, A_INCREASE, A_SET } adj_t;
+
+/* Screen position operations. */
+typedef enum { P_BOTTOM, P_FILL, P_MIDDLE, P_TOP } pos_t;
+
+/* Scrolling operations. */
+typedef enum {
+ CNTRL_B, CNTRL_D, CNTRL_E, CNTRL_F,
+ CNTRL_U, CNTRL_Y, Z_CARAT, Z_PLUS
+} scroll_t;
+
+/* Vi common error messages. */
+typedef enum {
+ VIM_COMBUF, VIM_EMPTY, VIM_EOF, VIM_EOL,
+ VIM_NOCOM, VIM_NOCOM_B, VIM_USAGE, VIM_WRESIZE
+} vim_t;
+
+#include "vi_extern.h"
diff --git a/contrib/nvi/vi/vs_line.c b/contrib/nvi/vi/vs_line.c
new file mode 100644
index 0000000..b439de9
--- /dev/null
+++ b/contrib/nvi/vi/vs_line.c
@@ -0,0 +1,514 @@
+/*-
+ * 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[] = "@(#)vs_line.c 10.19 (Berkeley) 9/26/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 <string.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+#ifdef VISIBLE_TAB_CHARS
+#define TABCH '-'
+#else
+#define TABCH ' '
+#endif
+
+/*
+ * vs_line --
+ * Update one line on the screen.
+ *
+ * PUBLIC: int vs_line __P((SCR *, SMAP *, size_t *, size_t *));
+ */
+int
+vs_line(sp, smp, yp, xp)
+ SCR *sp;
+ SMAP *smp;
+ size_t *xp, *yp;
+{
+ CHAR_T *kp;
+ GS *gp;
+ SMAP *tsmp;
+ size_t chlen, cno_cnt, cols_per_screen, len, nlen;
+ size_t offset_in_char, offset_in_line, oldx, oldy;
+ size_t scno, skip_cols, skip_screens;
+ int ch, dne, is_cached, is_partial, is_tab;
+ int list_tab, list_dollar;
+ char *p, *cbp, *ecbp, cbuf[128];
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "vs_line: row %u: line: %u off: %u\n",
+ smp - HMAP, smp->lno, smp->off);
+#endif
+ /*
+ * If ex modifies the screen after ex output is already on the screen,
+ * don't touch it -- we'll get scrolling wrong, at best.
+ */
+ if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1)
+ return (0);
+ if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp))
+ return (0);
+
+ /*
+ * Assume that, if the cache entry for the line is filled in, the
+ * line is already on the screen, and all we need to do is return
+ * the cursor position. If the calling routine doesn't need the
+ * cursor position, we can just return.
+ */
+ is_cached = SMAP_CACHE(smp);
+ if (yp == NULL && is_cached)
+ return (0);
+
+ /*
+ * A nasty side effect of this routine is that it returns the screen
+ * position for the "current" character. Not pretty, but this is the
+ * only routine that really knows what's out there.
+ *
+ * Move to the line. This routine can be called by vs_sm_position(),
+ * which uses it to fill in the cache entry so it can figure out what
+ * the real contents of the screen are. Because of this, we have to
+ * return to whereever we started from.
+ */
+ gp = sp->gp;
+ (void)gp->scr_cursor(sp, &oldy, &oldx);
+ (void)gp->scr_move(sp, smp - HMAP, 0);
+
+ /* Get the line. */
+ dne = db_get(sp, smp->lno, 0, &p, &len);
+
+ /*
+ * Special case if we're printing the info/mode line. Skip printing
+ * the leading number, as well as other minor setup. The only time
+ * this code paints the mode line is when the user is entering text
+ * for a ":" command, so we can put the code here instead of dealing
+ * with the empty line logic below. This is a kludge, but it's pretty
+ * much confined to this module.
+ *
+ * Set the number of columns for this screen.
+ * Set the number of chars or screens to skip until a character is to
+ * be displayed.
+ */
+ cols_per_screen = sp->cols;
+ if (O_ISSET(sp, O_LEFTRIGHT)) {
+ skip_screens = 0;
+ skip_cols = smp->coff;
+ } else {
+ skip_screens = smp->soff - 1;
+ skip_cols = skip_screens * cols_per_screen;
+ }
+
+ list_tab = O_ISSET(sp, O_LIST);
+ if (F_ISSET(sp, SC_TINPUT_INFO))
+ list_dollar = 0;
+ else {
+ list_dollar = list_tab;
+
+ /*
+ * If O_NUMBER is set, the line doesn't exist and it's line
+ * number 1, i.e., an empty file, display the line number.
+ *
+ * If O_NUMBER is set, the line exists and the first character
+ * on the screen is the first character in the line, display
+ * the line number.
+ *
+ * !!!
+ * If O_NUMBER set, decrement the number of columns in the
+ * first screen. DO NOT CHANGE THIS -- IT'S RIGHT! The
+ * rest of the code expects this to reflect the number of
+ * columns in the first screen, regardless of the number of
+ * columns we're going to skip.
+ */
+ if (O_ISSET(sp, O_NUMBER)) {
+ cols_per_screen -= O_NUMBER_LENGTH;
+ if ((!dne || smp->lno == 1) && skip_cols == 0) {
+ nlen = snprintf(cbuf,
+ sizeof(cbuf), O_NUMBER_FMT, smp->lno);
+ (void)gp->scr_addstr(sp, cbuf, nlen);
+ }
+ }
+ }
+
+ /*
+ * Special case non-existent lines and the first line of an empty
+ * file. In both cases, the cursor position is 0, but corrected
+ * as necessary for the O_NUMBER field, if it was displayed.
+ */
+ if (dne || len == 0) {
+ /* Fill in the cursor. */
+ if (yp != NULL && smp->lno == sp->lno) {
+ *yp = smp - HMAP;
+ *xp = sp->cols - cols_per_screen;
+ }
+
+ /* If the line is on the screen, quit. */
+ if (is_cached)
+ goto ret1;
+
+ /* Set line cache information. */
+ smp->c_sboff = smp->c_eboff = 0;
+ smp->c_scoff = smp->c_eclen = 0;
+
+ /*
+ * Lots of special cases for empty lines, but they only apply
+ * if we're displaying the first screen of the line.
+ */
+ if (skip_cols == 0)
+ if (dne) {
+ if (smp->lno == 1) {
+ if (list_dollar) {
+ ch = '$';
+ goto empty;
+ }
+ } else {
+ ch = '~';
+ goto empty;
+ }
+ } else
+ if (list_dollar) {
+ ch = '$';
+empty: (void)gp->scr_addstr(sp,
+ KEY_NAME(sp, ch), KEY_LEN(sp, ch));
+ }
+
+ (void)gp->scr_clrtoeol(sp);
+ (void)gp->scr_move(sp, oldy, oldx);
+ return (0);
+ }
+
+ /*
+ * If we just wrote this or a previous line, we cached the starting
+ * and ending positions of that line. The way it works is we keep
+ * information about the lines displayed in the SMAP. If we're
+ * painting the screen in the forward direction, this saves us from
+ * reformatting the physical line for every line on the screen. This
+ * wins big on binary files with 10K lines.
+ *
+ * Test for the first screen of the line, then the current screen line,
+ * then the line behind us, then do the hard work. Note, it doesn't
+ * do us any good to have a line in front of us -- it would be really
+ * hard to try and figure out tabs in the reverse direction, i.e. how
+ * many spaces a tab takes up in the reverse direction depends on
+ * what characters preceded it.
+ *
+ * Test for the first screen of the line.
+ */
+ if (skip_cols == 0) {
+ smp->c_sboff = offset_in_line = 0;
+ smp->c_scoff = offset_in_char = 0;
+ p = &p[offset_in_line];
+ goto display;
+ }
+
+ /* Test to see if we've seen this exact line before. */
+ if (is_cached) {
+ offset_in_line = smp->c_sboff;
+ offset_in_char = smp->c_scoff;
+ p = &p[offset_in_line];
+
+ /* Set cols_per_screen to 2nd and later line length. */
+ if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)
+ cols_per_screen = sp->cols;
+ goto display;
+ }
+
+ /* Test to see if we saw an earlier part of this line before. */
+ if (smp != HMAP &&
+ SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) {
+ if (tsmp->c_eclen != tsmp->c_ecsize) {
+ offset_in_line = tsmp->c_eboff;
+ offset_in_char = tsmp->c_eclen;
+ } else {
+ offset_in_line = tsmp->c_eboff + 1;
+ offset_in_char = 0;
+ }
+
+ /* Put starting info for this line in the cache. */
+ smp->c_sboff = offset_in_line;
+ smp->c_scoff = offset_in_char;
+ p = &p[offset_in_line];
+
+ /* Set cols_per_screen to 2nd and later line length. */
+ if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)
+ cols_per_screen = sp->cols;
+ goto display;
+ }
+
+ scno = 0;
+ offset_in_line = 0;
+ offset_in_char = 0;
+
+ /* Do it the hard way, for leftright scrolling screens. */
+ if (O_ISSET(sp, O_LEFTRIGHT)) {
+ for (; offset_in_line < len; ++offset_in_line) {
+ chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ?
+ TAB_OFF(scno) : KEY_LEN(sp, ch);
+ if ((scno += chlen) >= skip_cols)
+ break;
+ }
+
+ /* Set cols_per_screen to 2nd and later line length. */
+ cols_per_screen = sp->cols;
+
+ /* Put starting info for this line in the cache. */
+ if (scno != skip_cols) {
+ smp->c_sboff = offset_in_line;
+ smp->c_scoff =
+ offset_in_char = chlen - (scno - skip_cols);
+ --p;
+ } else {
+ smp->c_sboff = ++offset_in_line;
+ smp->c_scoff = 0;
+ }
+ }
+
+ /* Do it the hard way, for historic line-folding screens. */
+ else {
+ for (; offset_in_line < len; ++offset_in_line) {
+ chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ?
+ TAB_OFF(scno) : KEY_LEN(sp, ch);
+ if ((scno += chlen) < cols_per_screen)
+ continue;
+ scno -= cols_per_screen;
+
+ /* Set cols_per_screen to 2nd and later line length. */
+ cols_per_screen = sp->cols;
+
+ /*
+ * If crossed the last skipped screen boundary, start
+ * displaying the characters.
+ */
+ if (--skip_screens == 0)
+ break;
+ }
+
+ /* Put starting info for this line in the cache. */
+ if (scno != 0) {
+ smp->c_sboff = offset_in_line;
+ smp->c_scoff = offset_in_char = chlen - scno;
+ --p;
+ } else {
+ smp->c_sboff = ++offset_in_line;
+ smp->c_scoff = 0;
+ }
+ }
+
+display:
+ /*
+ * Set the number of characters to skip before reaching the cursor
+ * character. Offset by 1 and use 0 as a flag value. Vs_line is
+ * called repeatedly with a valid pointer to a cursor position.
+ * Don't fill anything in unless it's the right line and the right
+ * character, and the right part of the character...
+ */
+ if (yp == NULL ||
+ smp->lno != sp->lno || sp->cno < offset_in_line ||
+ offset_in_line + cols_per_screen < sp->cno) {
+ cno_cnt = 0;
+ /* If the line is on the screen, quit. */
+ if (is_cached)
+ goto ret1;
+ } else
+ cno_cnt = (sp->cno - offset_in_line) + 1;
+
+ /* This is the loop that actually displays characters. */
+ ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;
+ for (is_partial = 0, scno = 0;
+ offset_in_line < len; ++offset_in_line, offset_in_char = 0) {
+ if ((ch = *(u_char *)p++) == '\t' && !list_tab) {
+ scno += chlen = TAB_OFF(scno) - offset_in_char;
+ is_tab = 1;
+ } else {
+ scno += chlen = KEY_LEN(sp, ch) - offset_in_char;
+ is_tab = 0;
+ }
+
+ /*
+ * Only display up to the right-hand column. Set a flag if
+ * the entire character wasn't displayed for use in setting
+ * the cursor. If reached the end of the line, set the cache
+ * info for the screen. Don't worry about there not being
+ * characters to display on the next screen, its lno/off won't
+ * match up in that case.
+ */
+ if (scno >= cols_per_screen) {
+ if (is_tab == 1) {
+ chlen -= scno - cols_per_screen;
+ smp->c_ecsize = smp->c_eclen = chlen;
+ scno = cols_per_screen;
+ } else {
+ smp->c_ecsize = chlen;
+ chlen -= scno - cols_per_screen;
+ smp->c_eclen = chlen;
+
+ if (scno > cols_per_screen)
+ is_partial = 1;
+ }
+ smp->c_eboff = offset_in_line;
+
+ /* Terminate the loop. */
+ offset_in_line = len;
+ }
+
+ /*
+ * If the caller wants the cursor value, and this was the
+ * cursor character, set the value. There are two ways to
+ * put the cursor on a character -- if it's normal display
+ * mode, it goes on the last column of the character. If
+ * it's input mode, it goes on the first. In normal mode,
+ * set the cursor only if the entire character was displayed.
+ */
+ if (cno_cnt &&
+ --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) {
+ *yp = smp - HMAP;
+ if (F_ISSET(sp, SC_TINPUT))
+ *xp = scno - chlen;
+ else
+ *xp = scno - 1;
+ if (O_ISSET(sp, O_NUMBER) &&
+ !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0)
+ *xp += O_NUMBER_LENGTH;
+
+ /* If the line is on the screen, quit. */
+ if (is_cached)
+ goto ret1;
+ }
+
+ /* If the line is on the screen, don't display anything. */
+ if (is_cached)
+ continue;
+
+#define FLUSH { \
+ *cbp = '\0'; \
+ (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \
+ cbp = cbuf; \
+}
+ /*
+ * Display the character. We do tab expansion here because
+ * the screen interface doesn't have any way to set the tab
+ * length. Note, it's theoretically possible for chlen to
+ * be larger than cbuf, if the user set a impossibly large
+ * tabstop.
+ */
+ if (is_tab)
+ while (chlen--) {
+ if (cbp >= ecbp)
+ FLUSH;
+ *cbp++ = TABCH;
+ }
+ else {
+ if (cbp + chlen >= ecbp)
+ FLUSH;
+ for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;)
+ *cbp++ = *kp++;
+ }
+ }
+
+ if (scno < cols_per_screen) {
+ /* If didn't paint the whole line, update the cache. */
+ smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch);
+ smp->c_eboff = len - 1;
+
+ /*
+ * If not the info/mode line, and O_LIST set, and at the
+ * end of the line, and the line ended on this screen,
+ * add a trailing $.
+ */
+ if (list_dollar) {
+ ++scno;
+
+ chlen = KEY_LEN(sp, '$');
+ if (cbp + chlen >= ecbp)
+ FLUSH;
+ for (kp = KEY_NAME(sp, '$'); chlen--;)
+ *cbp++ = *kp++;
+ }
+
+ /* If still didn't paint the whole line, clear the rest. */
+ if (scno < cols_per_screen)
+ (void)gp->scr_clrtoeol(sp);
+ }
+
+ /* Flush any buffered characters. */
+ if (cbp > cbuf)
+ FLUSH;
+
+ret1: (void)gp->scr_move(sp, oldy, oldx);
+ return (0);
+}
+
+/*
+ * vs_number --
+ * Repaint the numbers on all the lines.
+ *
+ * PUBLIC: int vs_number __P((SCR *));
+ */
+int
+vs_number(sp)
+ SCR *sp;
+{
+ GS *gp;
+ SMAP *smp;
+ VI_PRIVATE *vip;
+ size_t len, oldy, oldx;
+ int exist;
+ char nbuf[10];
+
+ gp = sp->gp;
+ vip = VIP(sp);
+
+ /* No reason to do anything if we're in input mode on the info line. */
+ if (F_ISSET(sp, SC_TINPUT_INFO))
+ return (0);
+
+ /*
+ * Try and avoid getting the last line in the file, by getting the
+ * line after the last line in the screen -- if it exists, we know
+ * we have to to number all the lines in the screen. Get the one
+ * after the last instead of the last, so that the info line doesn't
+ * fool us. (The problem is that file_lline will lie, and tell us
+ * that the info line is the last line in the file.) If that test
+ * fails, we have to check each line for existence.
+ */
+ exist = db_exist(sp, TMAP->lno + 1);
+
+ (void)gp->scr_cursor(sp, &oldy, &oldx);
+ for (smp = HMAP; smp <= TMAP; ++smp) {
+ /* Numbers are only displayed for the first screen line. */
+ if (O_ISSET(sp, O_LEFTRIGHT)) {
+ if (smp->coff != 0)
+ continue;
+ } else
+ if (smp->soff != 1)
+ continue;
+
+ /*
+ * The first line of an empty file gets numbered, otherwise
+ * number any existing line.
+ */
+ if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno))
+ break;
+
+ (void)gp->scr_move(sp, smp - HMAP, 0);
+ len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, smp->lno);
+ (void)gp->scr_addstr(sp, nbuf, len);
+ }
+ (void)gp->scr_move(sp, oldy, oldx);
+ return (0);
+}
diff --git a/contrib/nvi/vi/vs_msg.c b/contrib/nvi/vi/vs_msg.c
new file mode 100644
index 0000000..7ef8f53
--- /dev/null
+++ b/contrib/nvi/vi/vs_msg.c
@@ -0,0 +1,927 @@
+/*-
+ * 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[] = "@(#)vs_msg.c 10.77 (Berkeley) 10/13/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+typedef enum {
+ SCROLL_W, /* User wait. */
+ SCROLL_W_EX, /* User wait, or enter : to continue. */
+ SCROLL_W_QUIT /* User wait, or enter q to quit. */
+ /*
+ * SCROLL_W_QUIT has another semantic
+ * -- only wait if the screen is full
+ */
+} sw_t;
+
+static void vs_divider __P((SCR *));
+static void vs_msgsave __P((SCR *, mtype_t, char *, size_t));
+static void vs_output __P((SCR *, mtype_t, const char *, int));
+static void vs_scroll __P((SCR *, int *, sw_t));
+static void vs_wait __P((SCR *, int *, sw_t));
+
+/*
+ * vs_busy --
+ * Display, update or clear a busy message.
+ *
+ * This routine is the default editor interface for vi busy messages. It
+ * implements a standard strategy of stealing lines from the bottom of the
+ * vi text screen. Screens using an alternate method of displaying busy
+ * messages, e.g. X11 clock icons, should set their scr_busy function to the
+ * correct function before calling the main editor routine.
+ *
+ * PUBLIC: void vs_busy __P((SCR *, const char *, busy_t));
+ */
+void
+vs_busy(sp, msg, btype)
+ SCR *sp;
+ const char *msg;
+ busy_t btype;
+{
+ GS *gp;
+ VI_PRIVATE *vip;
+ static const char flagc[] = "|/-\\";
+ struct timeval tv;
+ size_t len, notused;
+ const char *p;
+
+ /* Ex doesn't display busy messages. */
+ if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE))
+ return;
+
+ gp = sp->gp;
+ vip = VIP(sp);
+
+ /*
+ * Most of this routine is to deal with the screen sharing real estate
+ * between the normal edit messages and the busy messages. Logically,
+ * all that's needed is something that puts up a message, periodically
+ * updates it, and then goes away.
+ */
+ switch (btype) {
+ case BUSY_ON:
+ ++vip->busy_ref;
+ if (vip->totalcount != 0 || vip->busy_ref != 1)
+ break;
+
+ /* Initialize state for updates. */
+ vip->busy_ch = 0;
+ (void)gettimeofday(&vip->busy_tv, NULL);
+
+ /* Save the current cursor. */
+ (void)gp->scr_cursor(sp, &vip->busy_oldy, &vip->busy_oldx);
+
+ /* Display the busy message. */
+ p = msg_cat(sp, msg, &len);
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)gp->scr_addstr(sp, p, len);
+ (void)gp->scr_cursor(sp, &notused, &vip->busy_fx);
+ (void)gp->scr_clrtoeol(sp);
+ (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);
+ break;
+ case BUSY_OFF:
+ if (vip->busy_ref == 0)
+ break;
+ --vip->busy_ref;
+
+ /*
+ * If the line isn't in use for another purpose, clear it.
+ * Always return to the original position.
+ */
+ if (vip->totalcount == 0 && vip->busy_ref == 0) {
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)gp->scr_clrtoeol(sp);
+ }
+ (void)gp->scr_move(sp, vip->busy_oldy, vip->busy_oldx);
+ break;
+ case BUSY_UPDATE:
+ if (vip->totalcount != 0 || vip->busy_ref == 0)
+ break;
+
+ /* Update no more than every 1/8 of a second. */
+ (void)gettimeofday(&tv, NULL);
+ if (((tv.tv_sec - vip->busy_tv.tv_sec) * 1000000 +
+ (tv.tv_usec - vip->busy_tv.tv_usec)) < 125000)
+ return;
+ vip->busy_tv = tv;
+
+ /* Display the update. */
+ if (vip->busy_ch == sizeof(flagc) - 1)
+ vip->busy_ch = 0;
+ (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);
+ (void)gp->scr_addstr(sp, flagc + vip->busy_ch++, 1);
+ (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);
+ break;
+ }
+ (void)gp->scr_refresh(sp, 0);
+}
+
+/*
+ * vs_home --
+ * Home the cursor to the bottom row, left-most column.
+ *
+ * PUBLIC: void vs_home __P((SCR *));
+ */
+void
+vs_home(sp)
+ SCR *sp;
+{
+ (void)sp->gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)sp->gp->scr_refresh(sp, 0);
+}
+
+/*
+ * vs_update --
+ * Update a command.
+ *
+ * PUBLIC: void vs_update __P((SCR *, const char *, const char *));
+ */
+void
+vs_update(sp, m1, m2)
+ SCR *sp;
+ const char *m1, *m2;
+{
+ GS *gp;
+ size_t len, mlen, oldx, oldy;
+
+ gp = sp->gp;
+
+ /*
+ * This routine displays a message on the bottom line of the screen,
+ * without updating any of the command structures that would keep it
+ * there for any period of time, i.e. it is overwritten immediately.
+ *
+ * It's used by the ex read and ! commands when the user's command is
+ * expanded, and by the ex substitution confirmation prompt.
+ */
+ if (F_ISSET(sp, SC_SCR_EXWROTE)) {
+ (void)ex_printf(sp,
+ "%s\n", m1 == NULL? "" : m1, m2 == NULL ? "" : m2);
+ (void)ex_fflush(sp);
+ }
+
+ /*
+ * Save the cursor position, the substitute-with-confirmation code
+ * will have already set it correctly.
+ */
+ (void)gp->scr_cursor(sp, &oldy, &oldx);
+
+ /* Clear the bottom line. */
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)gp->scr_clrtoeol(sp);
+
+ /*
+ * XXX
+ * Don't let long file names screw up the screen.
+ */
+ if (m1 != NULL) {
+ mlen = len = strlen(m1);
+ if (len > sp->cols - 2)
+ mlen = len = sp->cols - 2;
+ (void)gp->scr_addstr(sp, m1, mlen);
+ } else
+ len = 0;
+ if (m2 != NULL) {
+ mlen = strlen(m2);
+ if (len + mlen > sp->cols - 2)
+ mlen = (sp->cols - 2) - len;
+ (void)gp->scr_addstr(sp, m2, mlen);
+ }
+
+ (void)gp->scr_move(sp, oldy, oldx);
+ (void)gp->scr_refresh(sp, 0);
+}
+
+/*
+ * vs_msg --
+ * Display ex output or error messages for the screen.
+ *
+ * This routine is the default editor interface for all ex output, and all ex
+ * and vi error/informational messages. It implements the standard strategy
+ * of stealing lines from the bottom of the vi text screen. Screens using an
+ * alternate method of displaying messages, e.g. dialog boxes, should set their
+ * scr_msg function to the correct function before calling the editor.
+ *
+ * PUBLIC: void vs_msg __P((SCR *, mtype_t, char *, size_t));
+ */
+void
+vs_msg(sp, mtype, line, len)
+ SCR *sp;
+ mtype_t mtype;
+ char *line;
+ size_t len;
+{
+ GS *gp;
+ VI_PRIVATE *vip;
+ size_t maxcols, oldx, oldy, padding;
+ const char *e, *s, *t;
+
+ gp = sp->gp;
+ vip = VIP(sp);
+
+ /*
+ * Ring the bell if it's scheduled.
+ *
+ * XXX
+ * Shouldn't we save this, too?
+ */
+ if (F_ISSET(sp, SC_TINPUT_INFO) || F_ISSET(gp, G_BELLSCHED))
+ if (F_ISSET(sp, SC_SCR_VI)) {
+ F_CLR(gp, G_BELLSCHED);
+ (void)gp->scr_bell(sp);
+ } else
+ F_SET(gp, G_BELLSCHED);
+
+ /*
+ * If vi is using the error line for text input, there's no screen
+ * real-estate for the error message. Nothing to do without some
+ * information as to how important the error message is.
+ */
+ if (F_ISSET(sp, SC_TINPUT_INFO))
+ return;
+
+ /*
+ * Ex or ex controlled screen output.
+ *
+ * If output happens during startup, e.g., a .exrc file, we may be
+ * in ex mode but haven't initialized the screen. Initialize here,
+ * and in this case, stay in ex mode.
+ *
+ * If the SC_SCR_EXWROTE bit is set, then we're switching back and
+ * forth between ex and vi, but the screen is trashed and we have
+ * to respect that. Switch to ex mode long enough to put out the
+ * message.
+ *
+ * If the SC_EX_WAIT_NO bit is set, turn it off -- we're writing to
+ * the screen, so previous opinions are ignored.
+ */
+ if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) {
+ if (!F_ISSET(sp, SC_SCR_EX))
+ if (F_ISSET(sp, SC_SCR_EXWROTE)) {
+ if (sp->gp->scr_screen(sp, SC_EX))
+ return;
+ } else
+ if (ex_init(sp))
+ return;
+
+ if (mtype == M_ERR)
+ (void)gp->scr_attr(sp, SA_INVERSE, 1);
+ (void)printf("%.*s", (int)len, line);
+ if (mtype == M_ERR)
+ (void)gp->scr_attr(sp, SA_INVERSE, 0);
+ (void)fflush(stdout);
+
+ F_CLR(sp, SC_EX_WAIT_NO);
+
+ if (!F_ISSET(sp, SC_SCR_EX))
+ (void)sp->gp->scr_screen(sp, SC_VI);
+ return;
+ }
+
+ /* If the vi screen isn't ready, save the message. */
+ if (!F_ISSET(sp, SC_SCR_VI)) {
+ (void)vs_msgsave(sp, mtype, line, len);
+ return;
+ }
+
+ /* Save the cursor position. */
+ (void)gp->scr_cursor(sp, &oldy, &oldx);
+
+ /* If it's an ex output message, just write it out. */
+ if (mtype == M_NONE) {
+ vs_output(sp, mtype, line, len);
+ goto ret;
+ }
+
+ /*
+ * If it's a vi message, strip the trailing <newline> so we can
+ * try and paste messages together.
+ */
+ if (line[len - 1] == '\n')
+ --len;
+
+ /*
+ * If a message won't fit on a single line, try to split on a <blank>.
+ * If a subsequent message fits on the same line, write a separator
+ * and output it. Otherwise, put out a newline.
+ *
+ * Need up to two padding characters normally; a semi-colon and a
+ * separating space. If only a single line on the screen, add some
+ * more for the trailing continuation message.
+ *
+ * XXX
+ * Assume that periods and semi-colons take up a single column on the
+ * screen.
+ *
+ * XXX
+ * There are almost certainly pathological cases that will break this
+ * code.
+ */
+ if (IS_ONELINE(sp))
+ (void)msg_cmsg(sp, CMSG_CONT_S, &padding);
+ else
+ padding = 0;
+ padding += 2;
+
+ maxcols = sp->cols - 1;
+ if (vip->lcontinue != 0)
+ if (len + vip->lcontinue + padding > maxcols)
+ vs_output(sp, vip->mtype, ".\n", 2);
+ else {
+ vs_output(sp, vip->mtype, ";", 1);
+ vs_output(sp, M_NONE, " ", 1);
+ }
+ vip->mtype = mtype;
+ for (s = line;; s = t) {
+ for (; len > 0 && isblank(*s); --len, ++s);
+ if (len == 0)
+ break;
+ if (len + vip->lcontinue > maxcols) {
+ for (e = s + (maxcols - vip->lcontinue);
+ e > s && !isblank(*e); --e);
+ if (e == s)
+ e = t = s + (maxcols - vip->lcontinue);
+ else
+ for (t = e; isblank(e[-1]); --e);
+ } else
+ e = t = s + len;
+
+ /*
+ * If the message ends in a period, discard it, we want to
+ * gang messages where possible.
+ */
+ len -= t - s;
+ if (len == 0 && (e - s) > 1 && s[(e - s) - 1] == '.')
+ --e;
+ vs_output(sp, mtype, s, e - s);
+
+ if (len != 0)
+ vs_output(sp, M_NONE, "\n", 1);
+
+ if (INTERRUPTED(sp))
+ break;
+ }
+
+ret: (void)gp->scr_move(sp, oldy, oldx);
+ (void)gp->scr_refresh(sp, 0);
+}
+
+/*
+ * vs_output --
+ * Output the text to the screen.
+ */
+static void
+vs_output(sp, mtype, line, llen)
+ SCR *sp;
+ mtype_t mtype;
+ const char *line;
+ int llen;
+{
+ CHAR_T *kp;
+ GS *gp;
+ VI_PRIVATE *vip;
+ size_t chlen, notused;
+ int ch, len, rlen, tlen;
+ const char *p, *t;
+ char *cbp, *ecbp, cbuf[128];
+
+ gp = sp->gp;
+ vip = VIP(sp);
+ for (p = line, rlen = llen; llen > 0;) {
+ /* Get the next physical line. */
+ if ((p = memchr(line, '\n', llen)) == NULL)
+ len = llen;
+ else
+ len = p - line;
+
+ /*
+ * The max is sp->cols characters, and we may have already
+ * written part of the line.
+ */
+ if (len + vip->lcontinue > sp->cols)
+ len = sp->cols - vip->lcontinue;
+
+ /*
+ * If the first line output, do nothing. If the second line
+ * output, draw the divider line. If drew a full screen, we
+ * remove the divider line. If it's a continuation line, move
+ * to the continuation point, else, move the screen up.
+ */
+ if (vip->lcontinue == 0) {
+ if (!IS_ONELINE(sp)) {
+ if (vip->totalcount == 1) {
+ (void)gp->scr_move(sp,
+ LASTLINE(sp) - 1, 0);
+ (void)gp->scr_clrtoeol(sp);
+ (void)vs_divider(sp);
+ F_SET(vip, VIP_DIVIDER);
+ ++vip->totalcount;
+ ++vip->linecount;
+ }
+ if (vip->totalcount == sp->t_maxrows &&
+ F_ISSET(vip, VIP_DIVIDER)) {
+ --vip->totalcount;
+ --vip->linecount;
+ F_CLR(vip, VIP_DIVIDER);
+ }
+ }
+ if (vip->totalcount != 0)
+ vs_scroll(sp, NULL, SCROLL_W_QUIT);
+
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ ++vip->totalcount;
+ ++vip->linecount;
+
+ if (INTERRUPTED(sp))
+ break;
+ } else
+ (void)gp->scr_move(sp, LASTLINE(sp), vip->lcontinue);
+
+ /* Error messages are in inverse video. */
+ if (mtype == M_ERR)
+ (void)gp->scr_attr(sp, SA_INVERSE, 1);
+
+ /* Display the line, doing character translation. */
+#define FLUSH { \
+ *cbp = '\0'; \
+ (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \
+ cbp = cbuf; \
+}
+ ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;
+ for (t = line, tlen = len; tlen--; ++t) {
+ ch = *t;
+ /*
+ * Replace tabs with spaces, there are places in
+ * ex that do column calculations without looking
+ * at <tabs> -- and all routines that care about
+ * <tabs> do their own expansions. This catches
+ * <tabs> in things like tag search strings.
+ */
+ if (ch == '\t')
+ ch = ' ';
+ chlen = KEY_LEN(sp, ch);
+ if (cbp + chlen >= ecbp)
+ FLUSH;
+ for (kp = KEY_NAME(sp, ch); chlen--;)
+ *cbp++ = *kp++;
+ }
+ if (cbp > cbuf)
+ FLUSH;
+ if (mtype == M_ERR)
+ (void)gp->scr_attr(sp, SA_INVERSE, 0);
+
+ /* Clear the rest of the line. */
+ (void)gp->scr_clrtoeol(sp);
+
+ /* If we loop, it's a new line. */
+ vip->lcontinue = 0;
+
+ /* Reset for the next line. */
+ line += len;
+ llen -= len;
+ if (p != NULL) {
+ ++line;
+ --llen;
+ }
+ }
+
+ /* Set up next continuation line. */
+ if (p == NULL)
+ gp->scr_cursor(sp, &notused, &vip->lcontinue);
+}
+
+/*
+ * vs_ex_resolve --
+ * Deal with ex message output.
+ *
+ * This routine is called when exiting a colon command to resolve any ex
+ * output that may have occurred.
+ *
+ * PUBLIC: int vs_ex_resolve __P((SCR *, int *));
+ */
+int
+vs_ex_resolve(sp, continuep)
+ SCR *sp;
+ int *continuep;
+{
+ EVENT ev;
+ GS *gp;
+ VI_PRIVATE *vip;
+ sw_t wtype;
+
+ gp = sp->gp;
+ vip = VIP(sp);
+ *continuep = 0;
+
+ /* If we ran any ex command, we can't trust the cursor position. */
+ F_SET(vip, VIP_CUR_INVALID);
+
+ /* Terminate any partially written message. */
+ if (vip->lcontinue != 0) {
+ vs_output(sp, vip->mtype, ".", 1);
+ vip->lcontinue = 0;
+
+ vip->mtype = M_NONE;
+ }
+
+ /*
+ * If we switched out of the vi screen into ex, switch back while we
+ * figure out what to do with the screen and potentially get another
+ * command to execute.
+ *
+ * If we didn't switch into ex, we're not required to wait, and less
+ * than 2 lines of output, we can continue without waiting for the
+ * wait.
+ *
+ * Note, all other code paths require waiting, so we leave the report
+ * of modified lines until later, so that we won't wait for no other
+ * reason than a threshold number of lines were modified. This means
+ * we display cumulative line modification reports for groups of ex
+ * commands. That seems right to me (well, at least not wrong).
+ */
+ if (F_ISSET(sp, SC_SCR_EXWROTE)) {
+ if (sp->gp->scr_screen(sp, SC_VI))
+ return (1);
+ } else
+ if (!F_ISSET(sp, SC_EX_WAIT_YES) && vip->totalcount < 2) {
+ F_CLR(sp, SC_EX_WAIT_NO);
+ return (0);
+ }
+
+ /* Clear the required wait flag, it's no longer needed. */
+ F_CLR(sp, SC_EX_WAIT_YES);
+
+ /*
+ * Wait, unless explicitly told not to wait or the user interrupted
+ * the command. If the user is leaving the screen, for any reason,
+ * they can't continue with further ex commands.
+ */
+ if (!F_ISSET(sp, SC_EX_WAIT_NO) && !INTERRUPTED(sp)) {
+ wtype = F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE |
+ SC_FSWITCH | SC_SSWITCH) ? SCROLL_W : SCROLL_W_EX;
+ if (F_ISSET(sp, SC_SCR_EXWROTE))
+ vs_wait(sp, continuep, wtype);
+ else
+ vs_scroll(sp, continuep, wtype);
+ if (*continuep)
+ return (0);
+ }
+
+ /* If ex wrote on the screen, refresh the screen image. */
+ if (F_ISSET(sp, SC_SCR_EXWROTE))
+ F_SET(vip, VIP_N_EX_PAINT);
+
+ /*
+ * If we're not the bottom of the split screen stack, the screen
+ * image itself is wrong, so redraw everything.
+ */
+ if (sp->q.cqe_next != (void *)&sp->gp->dq)
+ F_SET(sp, SC_SCR_REDRAW);
+
+ /* If ex changed the underlying file, the map itself is wrong. */
+ if (F_ISSET(vip, VIP_N_EX_REDRAW))
+ F_SET(sp, SC_SCR_REFORMAT);
+
+ /* Ex may have switched out of the alternate screen, return. */
+ (void)gp->scr_attr(sp, SA_ALTERNATE, 1);
+
+ /*
+ * Whew. We're finally back home, after what feels like years.
+ * Kiss the ground.
+ */
+ F_CLR(sp, SC_SCR_EXWROTE | SC_EX_WAIT_NO);
+
+ /*
+ * We may need to repaint some of the screen, e.g.:
+ *
+ * :set
+ * :!ls
+ *
+ * gives us a combination of some lines that are "wrong", and a need
+ * for a full refresh.
+ */
+ if (vip->totalcount > 1) {
+ /* Set up the redraw of the overwritten lines. */
+ ev.e_event = E_REPAINT;
+ ev.e_flno = vip->totalcount >=
+ sp->rows ? 1 : sp->rows - vip->totalcount;
+ ev.e_tlno = sp->rows;
+
+ /* Reset the count of overwriting lines. */
+ vip->linecount = vip->lcontinue = vip->totalcount = 0;
+
+ /* Redraw. */
+ (void)vs_repaint(sp, &ev);
+ } else
+ /* Reset the count of overwriting lines. */
+ vip->linecount = vip->lcontinue = vip->totalcount = 0;
+
+ return (0);
+}
+
+/*
+ * vs_resolve --
+ * Deal with message output.
+ *
+ * PUBLIC: int vs_resolve __P((SCR *, SCR *, int));
+ */
+int
+vs_resolve(sp, csp, forcewait)
+ SCR *sp, *csp;
+ int forcewait;
+{
+ EVENT ev;
+ GS *gp;
+ MSGS *mp;
+ VI_PRIVATE *vip;
+ size_t oldy, oldx;
+ int redraw;
+
+ /*
+ * Vs_resolve is called from the main vi loop and the refresh function
+ * to periodically ensure that the user has seen any messages that have
+ * been displayed and that any status lines are correct. The sp screen
+ * is the screen we're checking, usually the current screen. When it's
+ * not, csp is the current screen, used for final cursor positioning.
+ */
+ gp = sp->gp;
+ vip = VIP(sp);
+ if (csp == NULL)
+ csp = sp;
+
+ /* Save the cursor position. */
+ (void)gp->scr_cursor(csp, &oldy, &oldx);
+
+ /* Ring the bell if it's scheduled. */
+ if (F_ISSET(gp, G_BELLSCHED)) {
+ F_CLR(gp, G_BELLSCHED);
+ (void)gp->scr_bell(sp);
+ }
+
+ /* Display new file status line. */
+ if (F_ISSET(sp, SC_STATUS)) {
+ F_CLR(sp, SC_STATUS);
+ msgq_status(sp, sp->lno, MSTAT_TRUNCATE);
+ }
+
+ /* Report on line modifications. */
+ mod_rpt(sp);
+
+ /*
+ * Flush any saved messages. If the screen isn't ready, refresh
+ * it. (A side-effect of screen refresh is that we can display
+ * messages.) Once this is done, don't trust the cursor. That
+ * extra refresh screwed the pooch.
+ */
+ if (gp->msgq.lh_first != NULL) {
+ if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 1))
+ return (1);
+ 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);
+ }
+ F_SET(vip, VIP_CUR_INVALID);
+ }
+
+ switch (vip->totalcount) {
+ case 0:
+ redraw = 0;
+ break;
+ case 1:
+ /*
+ * If we're switching screens, we have to wait for messages,
+ * regardless. If we don't wait, skip updating the modeline.
+ */
+ if (forcewait)
+ vs_scroll(sp, NULL, SCROLL_W);
+ else
+ F_SET(vip, VIP_S_MODELINE);
+
+ redraw = 0;
+ break;
+ default:
+ /*
+ * If >1 message line in use, prompt the user to continue and
+ * repaint overwritten lines.
+ */
+ vs_scroll(sp, NULL, SCROLL_W);
+
+ ev.e_event = E_REPAINT;
+ ev.e_flno = vip->totalcount >=
+ sp->rows ? 1 : sp->rows - vip->totalcount;
+ ev.e_tlno = sp->rows;
+
+ redraw = 1;
+ break;
+ }
+
+ /* Reset the count of overwriting lines. */
+ vip->linecount = vip->lcontinue = vip->totalcount = 0;
+
+ /* Redraw. */
+ if (redraw)
+ (void)vs_repaint(sp, &ev);
+
+ /* Restore the cursor position. */
+ (void)gp->scr_move(csp, oldy, oldx);
+
+ return (0);
+}
+
+/*
+ * vs_scroll --
+ * Scroll the screen for output.
+ */
+static void
+vs_scroll(sp, continuep, wtype)
+ SCR *sp;
+ int *continuep;
+ sw_t wtype;
+{
+ GS *gp;
+ VI_PRIVATE *vip;
+
+ gp = sp->gp;
+ vip = VIP(sp);
+ if (!IS_ONELINE(sp)) {
+ /*
+ * Scroll the screen. Instead of scrolling the entire screen,
+ * delete the line above the first line output so preserve the
+ * maximum amount of the screen.
+ */
+ (void)gp->scr_move(sp, vip->totalcount <
+ sp->rows ? LASTLINE(sp) - vip->totalcount : 0, 0);
+ (void)gp->scr_deleteln(sp);
+
+ /* If there are screens below us, push them back into place. */
+ if (sp->q.cqe_next != (void *)&sp->gp->dq) {
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)gp->scr_insertln(sp);
+ }
+ }
+ if (wtype == SCROLL_W_QUIT && vip->linecount < sp->t_maxrows)
+ return;
+ vs_wait(sp, continuep, wtype);
+}
+
+/*
+ * vs_wait --
+ * Prompt the user to continue.
+ */
+static void
+vs_wait(sp, continuep, wtype)
+ SCR *sp;
+ int *continuep;
+ sw_t wtype;
+{
+ EVENT ev;
+ VI_PRIVATE *vip;
+ const char *p;
+ GS *gp;
+ size_t len;
+
+ gp = sp->gp;
+ vip = VIP(sp);
+
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ if (IS_ONELINE(sp))
+ p = msg_cmsg(sp, CMSG_CONT_S, &len);
+ else
+ switch (wtype) {
+ case SCROLL_W_QUIT:
+ p = msg_cmsg(sp, CMSG_CONT_Q, &len);
+ break;
+ case SCROLL_W_EX:
+ p = msg_cmsg(sp, CMSG_CONT_EX, &len);
+ break;
+ case SCROLL_W:
+ p = msg_cmsg(sp, CMSG_CONT, &len);
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+ (void)gp->scr_addstr(sp, p, len);
+
+ ++vip->totalcount;
+ vip->linecount = 0;
+
+ (void)gp->scr_clrtoeol(sp);
+ (void)gp->scr_refresh(sp, 0);
+
+ /* Get a single character from the terminal. */
+ if (continuep != NULL)
+ *continuep = 0;
+ for (;;) {
+ if (v_event_get(sp, &ev, 0, 0))
+ return;
+ if (ev.e_event == E_CHARACTER)
+ break;
+ if (ev.e_event == E_INTERRUPT) {
+ ev.e_c = CH_QUIT;
+ F_SET(gp, G_INTERRUPTED);
+ break;
+ }
+ (void)gp->scr_bell(sp);
+ }
+ switch (wtype) {
+ case SCROLL_W_QUIT:
+ if (ev.e_c == CH_QUIT)
+ F_SET(gp, G_INTERRUPTED);
+ break;
+ case SCROLL_W_EX:
+ if (ev.e_c == ':' && continuep != NULL)
+ *continuep = 1;
+ break;
+ case SCROLL_W:
+ break;
+ }
+}
+
+/*
+ * vs_divider --
+ * Draw a dividing line between the screen and the output.
+ */
+static void
+vs_divider(sp)
+ SCR *sp;
+{
+ GS *gp;
+ size_t len;
+
+#define DIVIDESTR "+=+=+=+=+=+=+=+"
+ len =
+ sizeof(DIVIDESTR) - 1 > sp->cols ? sp->cols : sizeof(DIVIDESTR) - 1;
+ gp = sp->gp;
+ (void)gp->scr_attr(sp, SA_INVERSE, 1);
+ (void)gp->scr_addstr(sp, DIVIDESTR, len);
+ (void)gp->scr_attr(sp, SA_INVERSE, 0);
+}
+
+/*
+ * vs_msgsave --
+ * Save a message for later display.
+ */
+static void
+vs_msgsave(sp, mt, p, len)
+ SCR *sp;
+ mtype_t mt;
+ char *p;
+ size_t len;
+{
+ GS *gp;
+ MSGS *mp_c, *mp_n;
+
+ /*
+ * We have to handle messages before we have any place to put them.
+ * If there's no screen support yet, allocate a msg structure, copy
+ * in the message, and queue it on the global structure. If we can't
+ * allocate memory here, we're genuinely screwed, dump the message
+ * to stderr in the (probably) vain hope that someone will see it.
+ */
+ CALLOC_GOTO(sp, mp_n, MSGS *, 1, sizeof(MSGS));
+ MALLOC_GOTO(sp, mp_n->buf, char *, len);
+
+ memmove(mp_n->buf, p, len);
+ mp_n->len = len;
+ mp_n->mtype = mt;
+
+ gp = sp->gp;
+ if ((mp_c = gp->msgq.lh_first) == NULL) {
+ LIST_INSERT_HEAD(&gp->msgq, mp_n, q);
+ } else {
+ for (; mp_c->q.le_next != NULL; mp_c = mp_c->q.le_next);
+ LIST_INSERT_AFTER(mp_c, mp_n, q);
+ }
+ return;
+
+alloc_err:
+ if (mp_n != NULL)
+ free(mp_n);
+ (void)fprintf(stderr, "%.*s\n", (int)len, p);
+}
diff --git a/contrib/nvi/vi/vs_refresh.c b/contrib/nvi/vi/vs_refresh.c
new file mode 100644
index 0000000..8158760
--- /dev/null
+++ b/contrib/nvi/vi/vs_refresh.c
@@ -0,0 +1,885 @@
+/*-
+ * 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[] = "@(#)vs_refresh.c 10.44 (Berkeley) 10/13/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.h"
+
+#define UPDATE_CURSOR 0x01 /* Update the cursor. */
+#define UPDATE_SCREEN 0x02 /* Flush to screen. */
+
+static void vs_modeline __P((SCR *));
+static int vs_paint __P((SCR *, u_int));
+
+/*
+ * v_repaint --
+ * Repaint selected lines from the screen.
+ *
+ * PUBLIC: int vs_repaint __P((SCR *, EVENT *));
+ */
+int
+vs_repaint(sp, evp)
+ SCR *sp;
+ EVENT *evp;
+{
+ SMAP *smp;
+
+ for (; evp->e_flno <= evp->e_tlno; ++evp->e_flno) {
+ smp = HMAP + evp->e_flno - 1;
+ SMAP_FLUSH(smp);
+ if (vs_line(sp, smp, NULL, NULL))
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * vs_refresh --
+ * Refresh all screens.
+ *
+ * PUBLIC: int vs_refresh __P((SCR *, int));
+ */
+int
+vs_refresh(sp, forcepaint)
+ SCR *sp;
+ int forcepaint;
+{
+ GS *gp;
+ SCR *tsp;
+ int need_refresh;
+ u_int priv_paint, pub_paint;
+
+ gp = sp->gp;
+
+ /*
+ * 1: Refresh the screen.
+ *
+ * If SC_SCR_REDRAW is set in the current screen, repaint everything
+ * that we can find, including status lines.
+ */
+ if (F_ISSET(sp, SC_SCR_REDRAW))
+ for (tsp = gp->dq.cqh_first;
+ tsp != (void *)&gp->dq; tsp = tsp->q.cqe_next)
+ if (tsp != sp)
+ F_SET(tsp, SC_SCR_REDRAW | SC_STATUS);
+
+ /*
+ * 2: Related or dirtied screens, or screens with messages.
+ *
+ * If related screens share a view into a file, they may have been
+ * modified as well. Refresh any screens that aren't exiting that
+ * have paint or dirty bits set. Always update their screens, we
+ * are not likely to get another chance. Finally, if we refresh any
+ * screens other than the current one, the cursor will be trashed.
+ */
+ pub_paint = SC_SCR_REFORMAT | SC_SCR_REDRAW;
+ priv_paint = VIP_CUR_INVALID | VIP_N_REFRESH;
+ if (O_ISSET(sp, O_NUMBER))
+ priv_paint |= VIP_N_RENUMBER;
+ for (tsp = gp->dq.cqh_first;
+ tsp != (void *)&gp->dq; tsp = tsp->q.cqe_next)
+ if (tsp != sp && !F_ISSET(tsp, SC_EXIT | SC_EXIT_FORCE) &&
+ (F_ISSET(tsp, pub_paint) ||
+ F_ISSET(VIP(tsp), priv_paint))) {
+ (void)vs_paint(tsp,
+ (F_ISSET(VIP(tsp), VIP_CUR_INVALID) ?
+ UPDATE_CURSOR : 0) | UPDATE_SCREEN);
+ F_SET(VIP(sp), VIP_CUR_INVALID);
+ }
+
+ /*
+ * 3: Refresh the current screen.
+ *
+ * Always refresh the current screen, it may be a cursor movement.
+ * Also, always do it last -- that way, SC_SCR_REDRAW can be set
+ * in the current screen only, and the screen won't flash.
+ */
+ if (vs_paint(sp, UPDATE_CURSOR | (!forcepaint &&
+ F_ISSET(sp, SC_SCR_VI) && KEYS_WAITING(sp) ? 0 : UPDATE_SCREEN)))
+ return (1);
+
+ /*
+ * 4: Paint any missing status lines.
+ *
+ * XXX
+ * This is fairly evil. Status lines are written using the vi message
+ * mechanism, since we have no idea how long they are. Since we may be
+ * painting screens other than the current one, we don't want to make
+ * the user wait. We depend heavily on there not being any other lines
+ * currently waiting to be displayed and the message truncation code in
+ * the msgq_status routine working.
+ *
+ * And, finally, if we updated any status lines, make sure the cursor
+ * gets back to where it belongs.
+ */
+ for (need_refresh = 0, tsp = gp->dq.cqh_first;
+ tsp != (void *)&gp->dq; tsp = tsp->q.cqe_next)
+ if (F_ISSET(tsp, SC_STATUS)) {
+ need_refresh = 1;
+ vs_resolve(tsp, sp, 0);
+ }
+ if (need_refresh)
+ (void)gp->scr_refresh(sp, 0);
+
+ /*
+ * A side-effect of refreshing the screen is that it's now ready
+ * for everything else, i.e. messages.
+ */
+ F_SET(sp, SC_SCR_VI);
+ return (0);
+}
+
+/*
+ * vs_paint --
+ * This is the guts of the vi curses screen code. The idea is that
+ * the SCR structure passed in contains the new coordinates of the
+ * screen. What makes this hard is that we don't know how big
+ * characters are, doing input can put the cursor in illegal places,
+ * and we're frantically trying to avoid repainting unless it's
+ * absolutely necessary. If you change this code, you'd better know
+ * what you're doing. It's subtle and quick to anger.
+ */
+static int
+vs_paint(sp, flags)
+ SCR *sp;
+ u_int flags;
+{
+ GS *gp;
+ SMAP *smp, tmp;
+ VI_PRIVATE *vip;
+ recno_t lastline, lcnt;
+ size_t cwtotal, cnt, len, notused, off, y;
+ int ch, didpaint, isempty, leftright_warp;
+ char *p;
+
+#define LNO sp->lno /* Current file line. */
+#define OLNO vip->olno /* Remembered file line. */
+#define CNO sp->cno /* Current file column. */
+#define OCNO vip->ocno /* Remembered file column. */
+#define SCNO vip->sc_col /* Current screen column. */
+
+ gp = sp->gp;
+ vip = VIP(sp);
+ didpaint = leftright_warp = 0;
+
+ /*
+ * 5: Reformat the lines.
+ *
+ * If the lines themselves have changed (:set list, for example),
+ * fill in the map from scratch. Adjust the screen that's being
+ * displayed if the leftright flag is set.
+ */
+ if (F_ISSET(sp, SC_SCR_REFORMAT)) {
+ /* Invalidate the line size cache. */
+ VI_SCR_CFLUSH(vip);
+
+ /* Toss vs_line() cached information. */
+ if (F_ISSET(sp, SC_SCR_TOP)) {
+ if (vs_sm_fill(sp, LNO, P_TOP))
+ return (1);
+ }
+ else if (F_ISSET(sp, SC_SCR_CENTER)) {
+ if (vs_sm_fill(sp, LNO, P_MIDDLE))
+ return (1);
+ } else
+ if (vs_sm_fill(sp, OOBLNO, P_TOP))
+ return (1);
+ F_SET(sp, SC_SCR_REDRAW);
+ }
+
+ /*
+ * 6: Line movement.
+ *
+ * Line changes can cause the top line to change as well. As
+ * before, if the movement is large, the screen is repainted.
+ *
+ * 6a: Small screens.
+ *
+ * Users can use the window, w300, w1200 and w9600 options to make
+ * the screen artificially small. The behavior of these options
+ * in the historic vi wasn't all that consistent, and, in fact, it
+ * was never documented how various screen movements affected the
+ * screen size. Generally, one of three things would happen:
+ * 1: The screen would expand in size, showing the line
+ * 2: The screen would scroll, showing the line
+ * 3: The screen would compress to its smallest size and
+ * repaint.
+ * In general, scrolling didn't cause compression (200^D was handled
+ * the same as ^D), movement to a specific line would (:N where N
+ * was 1 line below the screen caused a screen compress), and cursor
+ * movement would scroll if it was 11 lines or less, and compress if
+ * it was more than 11 lines. (And, no, I have no idea where the 11
+ * comes from.)
+ *
+ * What we do is try and figure out if the line is less than half of
+ * a full screen away. If it is, we expand the screen if there's
+ * room, and then scroll as necessary. The alternative is to compress
+ * and repaint.
+ *
+ * !!!
+ * This code is a special case from beginning to end. Unfortunately,
+ * home modems are still slow enough that it's worth having.
+ *
+ * XXX
+ * If the line a really long one, i.e. part of the line is on the
+ * screen but the column offset is not, we'll end up in the adjust
+ * code, when we should probably have compressed the screen.
+ */
+ if (IS_SMALL(sp))
+ if (LNO < HMAP->lno) {
+ lcnt = vs_sm_nlines(sp, HMAP, LNO, sp->t_maxrows);
+ if (lcnt <= HALFSCREEN(sp))
+ for (; lcnt && sp->t_rows != sp->t_maxrows;
+ --lcnt, ++sp->t_rows) {
+ ++TMAP;
+ if (vs_sm_1down(sp))
+ return (1);
+ }
+ else
+ goto small_fill;
+ } else if (LNO > TMAP->lno) {
+ lcnt = vs_sm_nlines(sp, TMAP, LNO, sp->t_maxrows);
+ if (lcnt <= HALFSCREEN(sp))
+ for (; lcnt && sp->t_rows != sp->t_maxrows;
+ --lcnt, ++sp->t_rows) {
+ if (vs_sm_next(sp, TMAP, TMAP + 1))
+ return (1);
+ ++TMAP;
+ if (vs_line(sp, TMAP, NULL, NULL))
+ return (1);
+ }
+ else {
+small_fill: (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)gp->scr_clrtoeol(sp);
+ for (; sp->t_rows > sp->t_minrows;
+ --sp->t_rows, --TMAP) {
+ (void)gp->scr_move(sp, TMAP - HMAP, 0);
+ (void)gp->scr_clrtoeol(sp);
+ }
+ if (vs_sm_fill(sp, LNO, P_FILL))
+ return (1);
+ F_SET(sp, SC_SCR_REDRAW);
+ goto adjust;
+ }
+ }
+
+ /*
+ * 6b: Line down, or current screen.
+ */
+ if (LNO >= HMAP->lno) {
+ /* Current screen. */
+ if (LNO <= TMAP->lno)
+ goto adjust;
+ if (F_ISSET(sp, SC_SCR_TOP))
+ goto top;
+ if (F_ISSET(sp, SC_SCR_CENTER))
+ goto middle;
+
+ /*
+ * If less than half a screen above the line, scroll down
+ * until the line is on the screen.
+ */
+ lcnt = vs_sm_nlines(sp, TMAP, LNO, HALFTEXT(sp));
+ if (lcnt < HALFTEXT(sp)) {
+ while (lcnt--)
+ if (vs_sm_1up(sp))
+ return (1);
+ goto adjust;
+ }
+ goto bottom;
+ }
+
+ /*
+ * 6c: If not on the current screen, may request center or top.
+ */
+ if (F_ISSET(sp, SC_SCR_TOP))
+ goto top;
+ if (F_ISSET(sp, SC_SCR_CENTER))
+ goto middle;
+
+ /*
+ * 6d: Line up.
+ */
+ lcnt = vs_sm_nlines(sp, HMAP, LNO, HALFTEXT(sp));
+ if (lcnt < HALFTEXT(sp)) {
+ /*
+ * If less than half a screen below the line, scroll up until
+ * the line is the first line on the screen. Special check so
+ * that if the screen has been emptied, we refill it.
+ */
+ if (db_exist(sp, HMAP->lno)) {
+ while (lcnt--)
+ if (vs_sm_1down(sp))
+ return (1);
+ goto adjust;
+ }
+
+ /*
+ * If less than a half screen from the bottom of the file,
+ * put the last line of the file on the bottom of the screen.
+ */
+bottom: if (db_last(sp, &lastline))
+ return (1);
+ tmp.lno = LNO;
+ tmp.coff = HMAP->coff;
+ tmp.soff = 1;
+ lcnt = vs_sm_nlines(sp, &tmp, lastline, sp->t_rows);
+ if (lcnt < HALFTEXT(sp)) {
+ if (vs_sm_fill(sp, lastline, P_BOTTOM))
+ return (1);
+ F_SET(sp, SC_SCR_REDRAW);
+ goto adjust;
+ }
+ /* It's not close, just put the line in the middle. */
+ goto middle;
+ }
+
+ /*
+ * If less than half a screen from the top of the file, put the first
+ * line of the file at the top of the screen. Otherwise, put the line
+ * in the middle of the screen.
+ */
+ tmp.lno = 1;
+ tmp.coff = HMAP->coff;
+ tmp.soff = 1;
+ lcnt = vs_sm_nlines(sp, &tmp, LNO, HALFTEXT(sp));
+ if (lcnt < HALFTEXT(sp)) {
+ if (vs_sm_fill(sp, 1, P_TOP))
+ return (1);
+ } else
+middle: if (vs_sm_fill(sp, LNO, P_MIDDLE))
+ return (1);
+ if (0) {
+top: if (vs_sm_fill(sp, LNO, P_TOP))
+ return (1);
+ }
+ F_SET(sp, SC_SCR_REDRAW);
+
+ /*
+ * At this point we know part of the line is on the screen. Since
+ * scrolling is done using logical lines, not physical, all of the
+ * line may not be on the screen. While that's not necessarily bad,
+ * if the part the cursor is on isn't there, we're going to lose.
+ * This can be tricky; if the line covers the entire screen, lno
+ * may be the same as both ends of the map, that's why we test BOTH
+ * the top and the bottom of the map. This isn't a problem for
+ * left-right scrolling, the cursor movement code handles the problem.
+ *
+ * There's a performance issue here if editing *really* long lines.
+ * This gets to the right spot by scrolling, and, in a binary, by
+ * scrolling hundreds of lines. If the adjustment looks like it's
+ * going to be a serious problem, refill the screen and repaint.
+ */
+adjust: if (!O_ISSET(sp, O_LEFTRIGHT) &&
+ (LNO == HMAP->lno || LNO == TMAP->lno)) {
+ cnt = vs_screens(sp, LNO, &CNO);
+ if (LNO == HMAP->lno && cnt < HMAP->soff)
+ if ((HMAP->soff - cnt) > HALFTEXT(sp)) {
+ HMAP->soff = cnt;
+ vs_sm_fill(sp, OOBLNO, P_TOP);
+ F_SET(sp, SC_SCR_REDRAW);
+ } else
+ while (cnt < HMAP->soff)
+ if (vs_sm_1down(sp))
+ return (1);
+ if (LNO == TMAP->lno && cnt > TMAP->soff)
+ if ((cnt - TMAP->soff) > HALFTEXT(sp)) {
+ TMAP->soff = cnt;
+ vs_sm_fill(sp, OOBLNO, P_BOTTOM);
+ F_SET(sp, SC_SCR_REDRAW);
+ } else
+ while (cnt > TMAP->soff)
+ if (vs_sm_1up(sp))
+ return (1);
+ }
+
+ /*
+ * If the screen needs to be repainted, skip cursor optimization.
+ * However, in the code above we skipped leftright scrolling on
+ * the grounds that the cursor code would handle it. Make sure
+ * the right screen is up.
+ */
+ if (F_ISSET(sp, SC_SCR_REDRAW)) {
+ if (O_ISSET(sp, O_LEFTRIGHT))
+ goto slow;
+ goto paint;
+ }
+
+ /*
+ * 7: Cursor movements (current screen only).
+ */
+ if (!LF_ISSET(UPDATE_CURSOR))
+ goto number;
+
+ /*
+ * Decide cursor position. If the line has changed, the cursor has
+ * moved over a tab, or don't know where the cursor was, reparse the
+ * line. Otherwise, we've just moved over fixed-width characters,
+ * and can calculate the left/right scrolling and cursor movement
+ * without reparsing the line. Note that we don't know which (if any)
+ * of the characters between the old and new cursor positions changed.
+ *
+ * XXX
+ * With some work, it should be possible to handle tabs quickly, at
+ * least in obvious situations, like moving right and encountering
+ * a tab, without reparsing the whole line.
+ *
+ * If the line we're working with has changed, reread it..
+ */
+ if (F_ISSET(vip, VIP_CUR_INVALID) || LNO != OLNO)
+ goto slow;
+
+ /* Otherwise, if nothing's changed, ignore the cursor. */
+ if (CNO == OCNO)
+ goto fast;
+
+ /*
+ * Get the current line. If this fails, we either have an empty
+ * file and can just repaint, or there's a real problem. This
+ * isn't a performance issue because there aren't any ways to get
+ * here repeatedly.
+ */
+ if (db_eget(sp, LNO, &p, &len, &isempty)) {
+ if (isempty)
+ goto slow;
+ return (1);
+ }
+
+#ifdef DEBUG
+ /* Sanity checking. */
+ if (CNO >= len && len != 0) {
+ msgq(sp, M_ERR, "Error: %s/%d: cno (%u) >= len (%u)",
+ tail(__FILE__), __LINE__, CNO, len);
+ return (1);
+ }
+#endif
+ /*
+ * The basic scheme here is to look at the characters in between
+ * the old and new positions and decide how big they are on the
+ * screen, and therefore, how many screen positions to move.
+ */
+ if (CNO < OCNO) {
+ /*
+ * 7a: Cursor moved left.
+ *
+ * Point to the old character. The old cursor position can
+ * be past EOL if, for example, we just deleted the rest of
+ * the line. In this case, since we don't know the width of
+ * the characters we traversed, we have to do it slowly.
+ */
+ p += OCNO;
+ cnt = (OCNO - CNO) + 1;
+ if (OCNO >= len)
+ goto slow;
+
+ /*
+ * Quick sanity check -- it's hard to figure out exactly when
+ * we cross a screen boundary as we do in the cursor right
+ * movement. If cnt is so large that we're going to cross the
+ * boundary no matter what, stop now.
+ */
+ if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt)
+ goto slow;
+
+ /*
+ * Count up the widths of the characters. If it's a tab
+ * character, go do it the the slow way.
+ */
+ for (cwtotal = 0; cnt--; cwtotal += KEY_LEN(sp, ch))
+ if ((ch = *(u_char *)p--) == '\t')
+ goto slow;
+
+ /*
+ * Decrement the screen cursor by the total width of the
+ * characters minus 1.
+ */
+ cwtotal -= 1;
+
+ /*
+ * If we're moving left, and there's a wide character in the
+ * current position, go to the end of the character.
+ */
+ if (KEY_LEN(sp, ch) > 1)
+ cwtotal -= KEY_LEN(sp, ch) - 1;
+
+ /*
+ * If the new column moved us off of the current logical line,
+ * calculate a new one. If doing leftright scrolling, we've
+ * moved off of the current screen, as well.
+ */
+ if (SCNO < cwtotal)
+ goto slow;
+ SCNO -= cwtotal;
+ } else {
+ /*
+ * 7b: Cursor moved right.
+ *
+ * Point to the first character to the right.
+ */
+ p += OCNO + 1;
+ cnt = CNO - OCNO;
+
+ /*
+ * Count up the widths of the characters. If it's a tab
+ * character, go do it the the slow way. If we cross a
+ * screen boundary, we can quit.
+ */
+ for (cwtotal = SCNO; cnt--;) {
+ if ((ch = *(u_char *)p++) == '\t')
+ goto slow;
+ if ((cwtotal += KEY_LEN(sp, ch)) >= SCREEN_COLS(sp))
+ break;
+ }
+
+ /*
+ * Increment the screen cursor by the total width of the
+ * characters.
+ */
+ SCNO = cwtotal;
+
+ /* See screen change comment in section 6a. */
+ if (SCNO >= SCREEN_COLS(sp))
+ goto slow;
+ }
+
+ /*
+ * 7c: Fast cursor update.
+ *
+ * We have the current column, retrieve the current row.
+ */
+fast: (void)gp->scr_cursor(sp, &y, &notused);
+ goto done_cursor;
+
+ /*
+ * 7d: Slow cursor update.
+ *
+ * Walk through the map and find the current line.
+ */
+slow: for (smp = HMAP; smp->lno != LNO; ++smp);
+
+ /*
+ * 7e: Leftright scrolling adjustment.
+ *
+ * If doing left-right scrolling and the cursor movement has changed
+ * the displayed screen, scroll the screen left or right, unless we're
+ * updating the info line in which case we just scroll that one line.
+ * We adjust the offset up or down until we have a window that covers
+ * the current column, making sure that we adjust differently for the
+ * first screen as compared to subsequent ones.
+ */
+ if (O_ISSET(sp, O_LEFTRIGHT)) {
+ /*
+ * Get the screen column for this character, and correct
+ * for the number option offset.
+ */
+ cnt = vs_columns(sp, NULL, LNO, &CNO, NULL);
+ if (O_ISSET(sp, O_NUMBER))
+ cnt -= O_NUMBER_LENGTH;
+
+ /* Adjust the window towards the beginning of the line. */
+ off = smp->coff;
+ if (off >= cnt) {
+ do {
+ if (off >= O_VAL(sp, O_SIDESCROLL))
+ off -= O_VAL(sp, O_SIDESCROLL);
+ else {
+ off = 0;
+ break;
+ }
+ } while (off >= cnt);
+ goto shifted;
+ }
+
+ /* Adjust the window towards the end of the line. */
+ if (off == 0 && off + SCREEN_COLS(sp) < cnt ||
+ off != 0 && off + sp->cols < cnt) {
+ do {
+ off += O_VAL(sp, O_SIDESCROLL);
+ } while (off + sp->cols < cnt);
+
+shifted: /* Fill in screen map with the new offset. */
+ if (F_ISSET(sp, SC_TINPUT_INFO))
+ smp->coff = off;
+ else {
+ for (smp = HMAP; smp <= TMAP; ++smp)
+ smp->coff = off;
+ leftright_warp = 1;
+ }
+ goto paint;
+ }
+
+ /*
+ * We may have jumped here to adjust a leftright screen because
+ * redraw was set. If so, we have to paint the entire screen.
+ */
+ if (F_ISSET(sp, SC_SCR_REDRAW))
+ goto paint;
+ }
+
+ /*
+ * Update the screen lines for this particular file line until we
+ * have a new screen cursor position.
+ */
+ for (y = -1,
+ vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) {
+ if (vs_line(sp, smp, &y, &SCNO))
+ return (1);
+ if (y != -1) {
+ vip->sc_smap = smp;
+ break;
+ }
+ }
+ goto done_cursor;
+
+ /*
+ * 8: Repaint the entire screen.
+ *
+ * Lost big, do what you have to do. We flush the cache, since
+ * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and
+ * it's simpler to repaint. So, don't trust anything that we
+ * think we know about it.
+ */
+paint: for (smp = HMAP; smp <= TMAP; ++smp)
+ SMAP_FLUSH(smp);
+ for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) {
+ if (vs_line(sp, smp, &y, &SCNO))
+ return (1);
+ if (y != -1 && vip->sc_smap == NULL)
+ vip->sc_smap = smp;
+ }
+ /*
+ * If it's a small screen and we're redrawing, clear the unused lines,
+ * ex may have overwritten them.
+ */
+ if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp))
+ for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {
+ (void)gp->scr_move(sp, cnt, 0);
+ (void)gp->scr_clrtoeol(sp);
+ }
+
+ didpaint = 1;
+
+done_cursor:
+ /*
+ * Sanity checking. When the repainting code messes up, the usual
+ * result is we don't repaint the cursor and so sc_smap will be
+ * NULL. If we're debugging, die, otherwise restart from scratch.
+ */
+#ifdef DEBUG
+ if (vip->sc_smap == NULL)
+ abort();
+#else
+ if (vip->sc_smap == NULL) {
+ F_SET(sp, SC_SCR_REFORMAT);
+ return (vs_paint(sp, flags));
+ }
+#endif
+
+ /*
+ * 9: Set the remembered cursor values.
+ */
+ OCNO = CNO;
+ OLNO = LNO;
+
+ /*
+ * 10: Repaint the line numbers.
+ *
+ * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we
+ * didn't repaint the screen, repaint all of the line numbers,
+ * they've changed.
+ */
+number: if (O_ISSET(sp, O_NUMBER) &&
+ F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp))
+ return (1);
+
+ /*
+ * 11: Update the mode line, position the cursor, and flush changes.
+ *
+ * If we warped the screen, we have to refresh everything.
+ */
+ if (leftright_warp)
+ LF_SET(UPDATE_CURSOR | UPDATE_SCREEN);
+
+ if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) &&
+ !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO))
+ vs_modeline(sp);
+
+ if (LF_ISSET(UPDATE_CURSOR)) {
+ (void)gp->scr_move(sp, y, SCNO);
+
+ /*
+ * XXX
+ * If the screen shifted, we recalculate the "most favorite"
+ * cursor position. Vi won't know that we've warped the
+ * screen, so it's going to have a wrong idea about where the
+ * cursor should be. This is vi's problem, and fixing it here
+ * is a gross layering violation.
+ */
+ if (leftright_warp)
+ (void)vs_column(sp, &sp->rcm);
+ }
+
+ if (LF_ISSET(UPDATE_SCREEN))
+ (void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT));
+
+ /* 12: Clear the flags that are handled by this routine. */
+ F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP);
+ F_CLR(vip, VIP_CUR_INVALID |
+ VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE);
+
+ return (0);
+
+#undef LNO
+#undef OLNO
+#undef CNO
+#undef OCNO
+#undef SCNO
+}
+
+/*
+ * vs_modeline --
+ * Update the mode line.
+ */
+static void
+vs_modeline(sp)
+ SCR *sp;
+{
+ static char * const modes[] = {
+ "215|Append", /* SM_APPEND */
+ "216|Change", /* SM_CHANGE */
+ "217|Command", /* SM_COMMAND */
+ "218|Insert", /* SM_INSERT */
+ "219|Replace", /* SM_REPLACE */
+ };
+ GS *gp;
+ size_t cols, curcol, curlen, endpoint, len, midpoint;
+ const char *t;
+ int ellipsis;
+ char *p, buf[20];
+
+ gp = sp->gp;
+
+ /*
+ * We put down the file name, the ruler, the mode and the dirty flag.
+ * If there's not enough room, there's not enough room, we don't play
+ * any special games. We try to put the ruler in the middle and the
+ * mode and dirty flag at the end.
+ *
+ * !!!
+ * Leave the last character blank, in case it's a really dumb terminal
+ * with hardware scroll. Second, don't paint the last character in the
+ * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you.
+ *
+ * Move to the last line on the screen.
+ */
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+
+ /* If more than one screen in the display, show the file name. */
+ curlen = 0;
+ if (IS_SPLIT(sp)) {
+ for (p = sp->frp->name; *p != '\0'; ++p);
+ for (ellipsis = 0, cols = sp->cols / 2; --p > sp->frp->name;) {
+ if (*p == '/') {
+ ++p;
+ break;
+ }
+ if ((curlen += KEY_LEN(sp, *p)) > cols) {
+ ellipsis = 3;
+ curlen +=
+ KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' ');
+ while (curlen > cols) {
+ ++p;
+ curlen -= KEY_LEN(sp, *p);
+ }
+ break;
+ }
+ }
+ if (ellipsis) {
+ while (ellipsis--)
+ (void)gp->scr_addstr(sp,
+ KEY_NAME(sp, '.'), KEY_LEN(sp, '.'));
+ (void)gp->scr_addstr(sp,
+ KEY_NAME(sp, ' '), KEY_LEN(sp, ' '));
+ }
+ for (; *p != '\0'; ++p)
+ (void)gp->scr_addstr(sp,
+ KEY_NAME(sp, *p), KEY_LEN(sp, *p));
+ }
+
+ /* Clear the rest of the line. */
+ (void)gp->scr_clrtoeol(sp);
+
+ /*
+ * Display the ruler. If we're not at the midpoint yet, move there.
+ * Otherwise, add in two extra spaces.
+ *
+ * Adjust the current column for the fact that the editor uses it as
+ * a zero-based number.
+ *
+ * XXX
+ * Assume that numbers, commas, and spaces only take up a single
+ * column on the screen.
+ */
+ cols = sp->cols - 1;
+ if (O_ISSET(sp, O_RULER)) {
+ vs_column(sp, &curcol);
+ len =
+ snprintf(buf, sizeof(buf), "%lu,%lu", sp->lno, curcol + 1);
+
+ midpoint = (cols - ((len + 1) / 2)) / 2;
+ if (curlen < midpoint) {
+ (void)gp->scr_move(sp, LASTLINE(sp), midpoint);
+ curlen += len;
+ } else if (curlen + 2 + len < cols) {
+ (void)gp->scr_addstr(sp, " ", 2);
+ curlen += 2 + len;
+ }
+ (void)gp->scr_addstr(sp, buf, len);
+ }
+
+ /*
+ * Display the mode and the modified flag, as close to the end of the
+ * line as possible, but guaranteeing at least two spaces between the
+ * ruler and the modified flag.
+ */
+#define MODESIZE 9
+ endpoint = cols;
+ if (O_ISSET(sp, O_SHOWMODE)) {
+ if (F_ISSET(sp->ep, F_MODIFIED))
+ --endpoint;
+ t = msg_cat(sp, modes[sp->showmode], &len);
+ endpoint -= len;
+ }
+
+ if (endpoint > curlen + 2) {
+ (void)gp->scr_move(sp, LASTLINE(sp), endpoint);
+ if (O_ISSET(sp, O_SHOWMODE)) {
+ if (F_ISSET(sp->ep, F_MODIFIED))
+ (void)gp->scr_addstr(sp,
+ KEY_NAME(sp, '*'), KEY_LEN(sp, '*'));
+ (void)gp->scr_addstr(sp, t, len);
+ }
+ }
+}
diff --git a/contrib/nvi/vi/vs_relative.c b/contrib/nvi/vi/vs_relative.c
new file mode 100644
index 0000000..c92c10c
--- /dev/null
+++ b/contrib/nvi/vi/vs_relative.c
@@ -0,0 +1,305 @@
+/*-
+ * 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[] = "@(#)vs_relative.c 10.11 (Berkeley) 5/13/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 <string.h>
+
+#include "../common/common.h"
+#include "vi.h"
+
+/*
+ * vs_column --
+ * Return the logical column of the cursor in the line.
+ *
+ * PUBLIC: int vs_column __P((SCR *, size_t *));
+ */
+int
+vs_column(sp, colp)
+ SCR *sp;
+ size_t *colp;
+{
+ VI_PRIVATE *vip;
+
+ vip = VIP(sp);
+
+ *colp = (O_ISSET(sp, O_LEFTRIGHT) ?
+ vip->sc_smap->coff : (vip->sc_smap->soff - 1) * sp->cols) +
+ vip->sc_col - (O_ISSET(sp, O_NUMBER) ? O_NUMBER_LENGTH : 0);
+ return (0);
+}
+
+/*
+ * vs_screens --
+ * Return the screens necessary to display the line, or if specified,
+ * the physical character column within the line, including space
+ * required for the O_NUMBER and O_LIST options.
+ *
+ * PUBLIC: size_t vs_screens __P((SCR *, recno_t, size_t *));
+ */
+size_t
+vs_screens(sp, lno, cnop)
+ SCR *sp;
+ recno_t lno;
+ size_t *cnop;
+{
+ size_t cols, screens;
+
+ /* Left-right screens are simple, it's always 1. */
+ if (O_ISSET(sp, O_LEFTRIGHT))
+ return (1);
+
+ /*
+ * Check for a cached value. We maintain a cache because, if the
+ * line is large, this routine gets called repeatedly. One other
+ * hack, lots of time the cursor is on column one, which is an easy
+ * one.
+ */
+ if (cnop == NULL) {
+ if (VIP(sp)->ss_lno == lno)
+ return (VIP(sp)->ss_screens);
+ } else if (*cnop == 0)
+ return (1);
+
+ /* Figure out how many columns the line/column needs. */
+ cols = vs_columns(sp, NULL, lno, cnop, NULL);
+
+ screens = (cols / sp->cols + (cols % sp->cols ? 1 : 0));
+ if (screens == 0)
+ screens = 1;
+
+ /* Cache the value. */
+ if (cnop == NULL) {
+ VIP(sp)->ss_lno = lno;
+ VIP(sp)->ss_screens = screens;
+ }
+ return (screens);
+}
+
+/*
+ * vs_columns --
+ * Return the screen columns necessary to display the line, or,
+ * if specified, the physical character column within the line.
+ *
+ * PUBLIC: size_t vs_columns __P((SCR *, char *, recno_t, size_t *, size_t *));
+ */
+size_t
+vs_columns(sp, lp, lno, cnop, diffp)
+ SCR *sp;
+ char *lp;
+ recno_t lno;
+ size_t *cnop, *diffp;
+{
+ size_t chlen, cno, curoff, last, len, scno;
+ int ch, leftright, listset;
+ char *p;
+
+ /* Need the line to go any further. */
+ if (lp == NULL) {
+ (void)db_get(sp, lno, 0, &lp, &len);
+ if (len == 0)
+ goto done;
+ }
+
+ /* Missing or empty lines are easy. */
+ if (lp == NULL) {
+done: if (diffp != NULL) /* XXX */
+ *diffp = 0;
+ return (0);
+ }
+
+ /* Store away the values of the list and leftright edit options. */
+ listset = O_ISSET(sp, O_LIST);
+ leftright = O_ISSET(sp, O_LEFTRIGHT);
+
+ /*
+ * Initialize the pointer into the buffer and screen and current
+ * offsets.
+ */
+ p = lp;
+ curoff = scno = 0;
+
+ /* Leading number if O_NUMBER option set. */
+ if (O_ISSET(sp, O_NUMBER))
+ scno += O_NUMBER_LENGTH;
+
+ /* Macro to return the display length of any signal character. */
+#define CHLEN(val) (ch = *(u_char *)p++) == '\t' && \
+ !listset ? TAB_OFF(val) : KEY_LEN(sp, ch);
+
+ /*
+ * If folding screens (the historic vi screen format), past the end
+ * of the current screen, and the character was a tab, reset the
+ * current screen column to 0, and the total screen columns to the
+ * last column of the screen. Otherwise, display the rest of the
+ * character in the next screen.
+ */
+#define TAB_RESET { \
+ curoff += chlen; \
+ if (!leftright && curoff >= sp->cols) \
+ if (ch == '\t') { \
+ curoff = 0; \
+ scno -= scno % sp->cols; \
+ } else \
+ curoff -= sp->cols; \
+}
+ if (cnop == NULL)
+ while (len--) {
+ chlen = CHLEN(curoff);
+ last = scno;
+ scno += chlen;
+ TAB_RESET;
+ }
+ else
+ for (cno = *cnop;; --cno) {
+ chlen = CHLEN(curoff);
+ last = scno;
+ scno += chlen;
+ TAB_RESET;
+ if (cno == 0)
+ break;
+ }
+
+ /* Add the trailing '$' if the O_LIST option set. */
+ if (listset && cnop == NULL)
+ scno += KEY_LEN(sp, '$');
+
+ /*
+ * The text input screen code needs to know how much additional
+ * room the last two characters required, so that it can handle
+ * tab character displays correctly.
+ */
+ if (diffp != NULL)
+ *diffp = scno - last;
+ return (scno);
+}
+
+/*
+ * vs_rcm --
+ * Return the physical column from the line that will display a
+ * character closest to the currently most attractive character
+ * position (which is stored as a screen column).
+ *
+ * PUBLIC: size_t vs_rcm __P((SCR *, recno_t, int));
+ */
+size_t
+vs_rcm(sp, lno, islast)
+ SCR *sp;
+ recno_t lno;
+ int islast;
+{
+ size_t len;
+
+ /* Last character is easy, and common. */
+ if (islast) {
+ if (db_get(sp, lno, 0, NULL, &len) || len == 0)
+ return (0);
+ return (len - 1);
+ }
+
+ /* First character is easy, and common. */
+ if (sp->rcm == 0)
+ return (0);
+
+ return (vs_colpos(sp, lno, sp->rcm));
+}
+
+/*
+ * vs_colpos --
+ * Return the physical column from the line that will display a
+ * character closest to the specified screen column.
+ *
+ * PUBLIC: size_t vs_colpos __P((SCR *, recno_t, size_t));
+ */
+size_t
+vs_colpos(sp, lno, cno)
+ SCR *sp;
+ recno_t lno;
+ size_t cno;
+{
+ size_t chlen, curoff, len, llen, off, scno;
+ int ch, leftright, listset;
+ char *lp, *p;
+
+ /* Need the line to go any further. */
+ (void)db_get(sp, lno, 0, &lp, &llen);
+
+ /* Missing or empty lines are easy. */
+ if (lp == NULL || llen == 0)
+ return (0);
+
+ /* Store away the values of the list and leftright edit options. */
+ listset = O_ISSET(sp, O_LIST);
+ leftright = O_ISSET(sp, O_LEFTRIGHT);
+
+ /* Discard screen (logical) lines. */
+ off = cno / sp->cols;
+ cno %= sp->cols;
+ for (scno = 0, p = lp, len = llen; off--;) {
+ for (; len && scno < sp->cols; --len)
+ scno += CHLEN(scno);
+
+ /*
+ * If reached the end of the physical line, return the last
+ * physical character in the line.
+ */
+ if (len == 0)
+ return (llen - 1);
+
+ /*
+ * If folding screens (the historic vi screen format), past
+ * the end of the current screen, and the character was a tab,
+ * reset the current screen column to 0. Otherwise, the rest
+ * of the character is displayed in the next screen.
+ */
+ if (leftright && ch == '\t')
+ scno = 0;
+ else
+ scno -= sp->cols;
+ }
+
+ /* Step through the line until reach the right character or EOL. */
+ for (curoff = scno; len--;) {
+ chlen = CHLEN(curoff);
+
+ /*
+ * If we've reached the specific character, there are three
+ * cases.
+ *
+ * 1: scno == cno, i.e. the current character ends at the
+ * screen character we care about.
+ * a: off < llen - 1, i.e. not the last character in
+ * the line, return the offset of the next character.
+ * b: else return the offset of the last character.
+ * 2: scno != cno, i.e. this character overruns the character
+ * we care about, return the offset of this character.
+ */
+ if ((scno += chlen) >= cno) {
+ off = p - lp;
+ return (scno == cno ?
+ (off < llen - 1 ? off : llen - 1) : off - 1);
+ }
+
+ TAB_RESET;
+ }
+
+ /* No such character; return the start of the last character. */
+ return (llen - 1);
+}
diff --git a/contrib/nvi/vi/vs_smap.c b/contrib/nvi/vi/vs_smap.c
new file mode 100644
index 0000000..af38057
--- /dev/null
+++ b/contrib/nvi/vi/vs_smap.c
@@ -0,0 +1,1260 @@
+/*-
+ * 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[] = "@(#)vs_smap.c 10.25 (Berkeley) 7/12/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.h"
+
+static int vs_deleteln __P((SCR *, int));
+static int vs_insertln __P((SCR *, int));
+static int vs_sm_delete __P((SCR *, recno_t));
+static int vs_sm_down __P((SCR *, MARK *, recno_t, scroll_t, SMAP *));
+static int vs_sm_erase __P((SCR *));
+static int vs_sm_insert __P((SCR *, recno_t));
+static int vs_sm_reset __P((SCR *, recno_t));
+static int vs_sm_up __P((SCR *, MARK *, recno_t, scroll_t, SMAP *));
+
+/*
+ * vs_change --
+ * Make a change to the screen.
+ *
+ * PUBLIC: int vs_change __P((SCR *, recno_t, lnop_t));
+ */
+int
+vs_change(sp, lno, op)
+ SCR *sp;
+ recno_t lno;
+ lnop_t op;
+{
+ VI_PRIVATE *vip;
+ SMAP *p;
+ size_t cnt, oldy, oldx;
+
+ vip = VIP(sp);
+
+ /*
+ * XXX
+ * Very nasty special case. The historic vi code displays a single
+ * space (or a '$' if the list option is set) for the first line in
+ * an "empty" file. If we "insert" a line, that line gets scrolled
+ * down, not repainted, so it's incorrect when we refresh the screen.
+ * The vi text input functions detect it explicitly and don't insert
+ * a new line.
+ *
+ * Check for line #2 before going to the end of the file.
+ */
+ if ((op == LINE_APPEND && lno == 0 || op == LINE_INSERT && lno == 1) &&
+ !db_exist(sp, 2)) {
+ lno = 1;
+ op = LINE_RESET;
+ }
+
+ /* Appending is the same as inserting, if the line is incremented. */
+ if (op == LINE_APPEND) {
+ ++lno;
+ op = LINE_INSERT;
+ }
+
+ /* Ignore the change if the line is after the map. */
+ if (lno > TMAP->lno)
+ return (0);
+
+ /*
+ * If the line is before the map, and it's a decrement, decrement
+ * the map. If it's an increment, increment the map. Otherwise,
+ * ignore it.
+ */
+ if (lno < HMAP->lno) {
+ switch (op) {
+ case LINE_APPEND:
+ abort();
+ /* NOTREACHED */
+ case LINE_DELETE:
+ for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
+ --p->lno;
+ if (sp->lno >= lno)
+ --sp->lno;
+ F_SET(vip, VIP_N_RENUMBER);
+ break;
+ case LINE_INSERT:
+ for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
+ ++p->lno;
+ if (sp->lno >= lno)
+ ++sp->lno;
+ F_SET(vip, VIP_N_RENUMBER);
+ break;
+ case LINE_RESET:
+ break;
+ }
+ return (0);
+ }
+
+ F_SET(vip, VIP_N_REFRESH);
+
+ /*
+ * Invalidate the line size cache, and invalidate the cursor if it's
+ * on this line,
+ */
+ VI_SCR_CFLUSH(vip);
+ if (sp->lno == lno)
+ F_SET(vip, VIP_CUR_INVALID);
+
+ /*
+ * If ex modifies the screen after ex output is already on the screen
+ * or if we've switched into ex canonical mode, don't touch it -- we'll
+ * get scrolling wrong, at best.
+ */
+ if (!F_ISSET(sp, SC_TINPUT_INFO) &&
+ (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) {
+ F_SET(vip, VIP_N_EX_REDRAW);
+ return (0);
+ }
+
+ /* Save and restore the cursor for these routines. */
+ (void)sp->gp->scr_cursor(sp, &oldy, &oldx);
+
+ switch (op) {
+ case LINE_DELETE:
+ if (vs_sm_delete(sp, lno))
+ return (1);
+ F_SET(vip, VIP_N_RENUMBER);
+ break;
+ case LINE_INSERT:
+ if (vs_sm_insert(sp, lno))
+ return (1);
+ F_SET(vip, VIP_N_RENUMBER);
+ break;
+ case LINE_RESET:
+ if (vs_sm_reset(sp, lno))
+ return (1);
+ break;
+ default:
+ abort();
+ }
+
+ (void)sp->gp->scr_move(sp, oldy, oldx);
+ return (0);
+}
+
+/*
+ * vs_sm_fill --
+ * Fill in the screen map, placing the specified line at the
+ * right position. There isn't any way to tell if an SMAP
+ * entry has been filled in, so this routine had better be
+ * called with P_FILL set before anything else is done.
+ *
+ * !!!
+ * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP
+ * slot is already filled in, P_BOTTOM means that the TMAP slot is
+ * already filled in, and we just finish up the job.
+ *
+ * PUBLIC: int vs_sm_fill __P((SCR *, recno_t, pos_t));
+ */
+int
+vs_sm_fill(sp, lno, pos)
+ SCR *sp;
+ recno_t lno;
+ pos_t pos;
+{
+ SMAP *p, tmp;
+ size_t cnt;
+
+ /* Flush all cached information from the SMAP. */
+ for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
+ SMAP_FLUSH(p);
+
+ /*
+ * If the map is filled, the screen must be redrawn.
+ *
+ * XXX
+ * This is a bug. We should try and figure out if the desired line
+ * is already in the map or close by -- scrolling the screen would
+ * be a lot better than redrawing.
+ */
+ F_SET(sp, SC_SCR_REDRAW);
+
+ switch (pos) {
+ case P_FILL:
+ tmp.lno = 1;
+ tmp.coff = 0;
+ tmp.soff = 1;
+
+ /* See if less than half a screen from the top. */
+ if (vs_sm_nlines(sp,
+ &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
+ lno = 1;
+ goto top;
+ }
+
+ /* See if less than half a screen from the bottom. */
+ if (db_last(sp, &tmp.lno))
+ return (1);
+ tmp.coff = 0;
+ tmp.soff = vs_screens(sp, tmp.lno, NULL);
+ if (vs_sm_nlines(sp,
+ &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
+ TMAP->lno = tmp.lno;
+ TMAP->coff = tmp.coff;
+ TMAP->soff = tmp.soff;
+ goto bottom;
+ }
+ goto middle;
+ case P_TOP:
+ if (lno != OOBLNO) {
+top: HMAP->lno = lno;
+ HMAP->coff = 0;
+ HMAP->soff = 1;
+ }
+ /* If we fail, just punt. */
+ for (p = HMAP, cnt = sp->t_rows; --cnt; ++p)
+ if (vs_sm_next(sp, p, p + 1))
+ goto err;
+ break;
+ case P_MIDDLE:
+ /* If we fail, guess that the file is too small. */
+middle: p = HMAP + sp->t_rows / 2;
+ p->lno = lno;
+ p->coff = 0;
+ p->soff = 1;
+ for (; p > HMAP; --p)
+ if (vs_sm_prev(sp, p, p - 1)) {
+ lno = 1;
+ goto top;
+ }
+
+ /* If we fail, just punt. */
+ p = HMAP + sp->t_rows / 2;
+ for (; p < TMAP; ++p)
+ if (vs_sm_next(sp, p, p + 1))
+ goto err;
+ break;
+ case P_BOTTOM:
+ if (lno != OOBLNO) {
+ TMAP->lno = lno;
+ TMAP->coff = 0;
+ TMAP->soff = vs_screens(sp, lno, NULL);
+ }
+ /* If we fail, guess that the file is too small. */
+bottom: for (p = TMAP; p > HMAP; --p)
+ if (vs_sm_prev(sp, p, p - 1)) {
+ lno = 1;
+ goto top;
+ }
+ break;
+ default:
+ abort();
+ }
+ return (0);
+
+ /*
+ * Try and put *something* on the screen. If this fails, we have a
+ * serious hard error.
+ */
+err: HMAP->lno = 1;
+ HMAP->coff = 0;
+ HMAP->soff = 1;
+ for (p = HMAP; p < TMAP; ++p)
+ if (vs_sm_next(sp, p, p + 1))
+ return (1);
+ return (0);
+}
+
+/*
+ * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the
+ * screen contains only a single line (whether because the screen is small
+ * or the line large), it gets fairly exciting. Skip the fun, set a flag
+ * so the screen map is refilled and the screen redrawn, and return. This
+ * is amazingly slow, but it's not clear that anyone will care.
+ */
+#define HANDLE_WEIRDNESS(cnt) { \
+ if (cnt >= sp->t_rows) { \
+ F_SET(sp, SC_SCR_REFORMAT); \
+ return (0); \
+ } \
+}
+
+/*
+ * vs_sm_delete --
+ * Delete a line out of the SMAP.
+ */
+static int
+vs_sm_delete(sp, lno)
+ SCR *sp;
+ recno_t lno;
+{
+ SMAP *p, *t;
+ size_t cnt_orig;
+
+ /*
+ * Find the line in the map, and count the number of screen lines
+ * which display any part of the deleted line.
+ */
+ for (p = HMAP; p->lno != lno; ++p);
+ if (O_ISSET(sp, O_LEFTRIGHT))
+ cnt_orig = 1;
+ else
+ for (cnt_orig = 1, t = p + 1;
+ t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
+
+ HANDLE_WEIRDNESS(cnt_orig);
+
+ /* Delete that many lines from the screen. */
+ (void)sp->gp->scr_move(sp, p - HMAP, 0);
+ if (vs_deleteln(sp, cnt_orig))
+ return (1);
+
+ /* Shift the screen map up. */
+ memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
+
+ /* Decrement the line numbers for the rest of the map. */
+ for (t = TMAP - cnt_orig; p <= t; ++p)
+ --p->lno;
+
+ /* Display the new lines. */
+ for (p = TMAP - cnt_orig;;) {
+ if (p < TMAP && vs_sm_next(sp, p, p + 1))
+ return (1);
+ /* vs_sm_next() flushed the cache. */
+ if (vs_line(sp, ++p, NULL, NULL))
+ return (1);
+ if (p == TMAP)
+ break;
+ }
+ return (0);
+}
+
+/*
+ * vs_sm_insert --
+ * Insert a line into the SMAP.
+ */
+static int
+vs_sm_insert(sp, lno)
+ SCR *sp;
+ recno_t lno;
+{
+ SMAP *p, *t;
+ size_t cnt_orig, cnt, coff;
+
+ /* Save the offset. */
+ coff = HMAP->coff;
+
+ /*
+ * Find the line in the map, find out how many screen lines
+ * needed to display the line.
+ */
+ for (p = HMAP; p->lno != lno; ++p);
+
+ cnt_orig = vs_screens(sp, lno, NULL);
+ HANDLE_WEIRDNESS(cnt_orig);
+
+ /*
+ * The lines left in the screen override the number of screen
+ * lines in the inserted line.
+ */
+ cnt = (TMAP - p) + 1;
+ if (cnt_orig > cnt)
+ cnt_orig = cnt;
+
+ /* Push down that many lines. */
+ (void)sp->gp->scr_move(sp, p - HMAP, 0);
+ if (vs_insertln(sp, cnt_orig))
+ return (1);
+
+ /* Shift the screen map down. */
+ memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
+
+ /* Increment the line numbers for the rest of the map. */
+ for (t = p + cnt_orig; t <= TMAP; ++t)
+ ++t->lno;
+
+ /* Fill in the SMAP for the new lines, and display. */
+ for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) {
+ t->lno = lno;
+ t->coff = coff;
+ t->soff = cnt;
+ SMAP_FLUSH(t);
+ if (vs_line(sp, t, NULL, NULL))
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * vs_sm_reset --
+ * Reset a line in the SMAP.
+ */
+static int
+vs_sm_reset(sp, lno)
+ SCR *sp;
+ recno_t lno;
+{
+ SMAP *p, *t;
+ size_t cnt_orig, cnt_new, cnt, diff;
+
+ /*
+ * See if the number of on-screen rows taken up by the old display
+ * for the line is the same as the number needed for the new one.
+ * If so, repaint, otherwise do it the hard way.
+ */
+ for (p = HMAP; p->lno != lno; ++p);
+ if (O_ISSET(sp, O_LEFTRIGHT)) {
+ t = p;
+ cnt_orig = cnt_new = 1;
+ } else {
+ for (cnt_orig = 0,
+ t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
+ cnt_new = vs_screens(sp, lno, NULL);
+ }
+
+ HANDLE_WEIRDNESS(cnt_orig);
+
+ if (cnt_orig == cnt_new) {
+ do {
+ SMAP_FLUSH(p);
+ if (vs_line(sp, p, NULL, NULL))
+ return (1);
+ } while (++p < t);
+ return (0);
+ }
+
+ if (cnt_orig < cnt_new) {
+ /* Get the difference. */
+ diff = cnt_new - cnt_orig;
+
+ /*
+ * The lines left in the screen override the number of screen
+ * lines in the inserted line.
+ */
+ cnt = (TMAP - p) + 1;
+ if (diff > cnt)
+ diff = cnt;
+
+ /* If there are any following lines, push them down. */
+ if (cnt > 1) {
+ (void)sp->gp->scr_move(sp, p - HMAP, 0);
+ if (vs_insertln(sp, diff))
+ return (1);
+
+ /* Shift the screen map down. */
+ memmove(p + diff, p,
+ (((TMAP - p) - diff) + 1) * sizeof(SMAP));
+ }
+
+ /* Fill in the SMAP for the replaced line, and display. */
+ for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) {
+ t->lno = lno;
+ t->soff = cnt;
+ SMAP_FLUSH(t);
+ if (vs_line(sp, t, NULL, NULL))
+ return (1);
+ }
+ } else {
+ /* Get the difference. */
+ diff = cnt_orig - cnt_new;
+
+ /* Delete that many lines from the screen. */
+ (void)sp->gp->scr_move(sp, p - HMAP, 0);
+ if (vs_deleteln(sp, diff))
+ return (1);
+
+ /* Shift the screen map up. */
+ memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP));
+
+ /* Fill in the SMAP for the replaced line, and display. */
+ for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) {
+ t->lno = lno;
+ t->soff = cnt;
+ SMAP_FLUSH(t);
+ if (vs_line(sp, t, NULL, NULL))
+ return (1);
+ }
+
+ /* Display the new lines at the bottom of the screen. */
+ for (t = TMAP - diff;;) {
+ if (t < TMAP && vs_sm_next(sp, t, t + 1))
+ return (1);
+ /* vs_sm_next() flushed the cache. */
+ if (vs_line(sp, ++t, NULL, NULL))
+ return (1);
+ if (t == TMAP)
+ break;
+ }
+ }
+ return (0);
+}
+
+/*
+ * vs_sm_scroll
+ * Scroll the SMAP up/down count logical lines. Different
+ * semantics based on the vi command, *sigh*.
+ *
+ * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, recno_t, scroll_t));
+ */
+int
+vs_sm_scroll(sp, rp, count, scmd)
+ SCR *sp;
+ MARK *rp;
+ recno_t count;
+ scroll_t scmd;
+{
+ SMAP *smp;
+
+ /*
+ * Invalidate the cursor. The line is probably going to change,
+ * (although for ^E and ^Y it may not). In any case, the scroll
+ * routines move the cursor to draw things.
+ */
+ F_SET(VIP(sp), VIP_CUR_INVALID);
+
+ /* Find the cursor in the screen. */
+ if (vs_sm_cursor(sp, &smp))
+ return (1);
+
+ switch (scmd) {
+ case CNTRL_B:
+ case CNTRL_U:
+ case CNTRL_Y:
+ case Z_CARAT:
+ if (vs_sm_down(sp, rp, count, scmd, smp))
+ return (1);
+ break;
+ case CNTRL_D:
+ case CNTRL_E:
+ case CNTRL_F:
+ case Z_PLUS:
+ if (vs_sm_up(sp, rp, count, scmd, smp))
+ return (1);
+ break;
+ default:
+ abort();
+ }
+
+ /*
+ * !!!
+ * If we're at the start of a line, go for the first non-blank.
+ * This makes it look like the old vi, even though we're moving
+ * around by logical lines, not physical ones.
+ *
+ * XXX
+ * In the presence of a long line, which has more than a screen
+ * width of leading spaces, this code can cause a cursor warp.
+ * Live with it.
+ */
+ if (scmd != CNTRL_E && scmd != CNTRL_Y &&
+ rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno))
+ return (1);
+
+ return (0);
+}
+
+/*
+ * vs_sm_up --
+ * Scroll the SMAP up count logical lines.
+ */
+static int
+vs_sm_up(sp, rp, count, scmd, smp)
+ SCR *sp;
+ MARK *rp;
+ scroll_t scmd;
+ recno_t count;
+ SMAP *smp;
+{
+ int cursor_set, echanged, zset;
+ SMAP *ssmp, s1, s2;
+
+ /*
+ * Check to see if movement is possible.
+ *
+ * Get the line after the map. If that line is a new one (and if
+ * O_LEFTRIGHT option is set, this has to be true), and the next
+ * line doesn't exist, and the cursor doesn't move, or the cursor
+ * isn't even on the screen, or the cursor is already at the last
+ * line in the map, it's an error. If that test succeeded because
+ * the cursor wasn't at the end of the map, test to see if the map
+ * is mostly empty.
+ */
+ if (vs_sm_next(sp, TMAP, &s1))
+ return (1);
+ if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) {
+ if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) {
+ v_eof(sp, NULL);
+ return (1);
+ }
+ if (vs_sm_next(sp, smp, &s1))
+ return (1);
+ if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) {
+ v_eof(sp, NULL);
+ return (1);
+ }
+ }
+
+ /*
+ * Small screens: see vs_refresh.c section 6a.
+ *
+ * If it's a small screen, and the movement isn't larger than a
+ * screen, i.e some context will remain, open up the screen and
+ * display by scrolling. In this case, the cursor moves down one
+ * line for each line displayed. Otherwise, erase/compress and
+ * repaint, and move the cursor to the first line in the screen.
+ * Note, the ^F command is always in the latter case, for historical
+ * reasons.
+ */
+ cursor_set = 0;
+ if (IS_SMALL(sp)) {
+ if (count >= sp->t_maxrows || scmd == CNTRL_F) {
+ s1 = TMAP[0];
+ if (vs_sm_erase(sp))
+ return (1);
+ for (; count--; s1 = s2) {
+ if (vs_sm_next(sp, &s1, &s2))
+ return (1);
+ if (s2.lno != s1.lno && !db_exist(sp, s2.lno))
+ break;
+ }
+ TMAP[0] = s2;
+ if (vs_sm_fill(sp, OOBLNO, P_BOTTOM))
+ return (1);
+ return (vs_sm_position(sp, rp, 0, P_TOP));
+ }
+ cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp);
+ for (; count &&
+ sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
+ if (vs_sm_next(sp, TMAP, &s1))
+ return (1);
+ if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
+ break;
+ *++TMAP = s1;
+ /* vs_sm_next() flushed the cache. */
+ if (vs_line(sp, TMAP, NULL, NULL))
+ return (1);
+
+ if (!cursor_set)
+ ++ssmp;
+ }
+ if (!cursor_set) {
+ rp->lno = ssmp->lno;
+ rp->cno = ssmp->c_sboff;
+ }
+ if (count == 0)
+ return (0);
+ }
+
+ for (echanged = zset = 0; count; --count) {
+ /* Decide what would show up on the screen. */
+ if (vs_sm_next(sp, TMAP, &s1))
+ return (1);
+
+ /* If the line doesn't exist, we're done. */
+ if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
+ break;
+
+ /* Scroll the screen cursor up one logical line. */
+ if (vs_sm_1up(sp))
+ return (1);
+ switch (scmd) {
+ case CNTRL_E:
+ if (smp > HMAP)
+ --smp;
+ else
+ echanged = 1;
+ break;
+ case Z_PLUS:
+ if (zset) {
+ if (smp > HMAP)
+ --smp;
+ } else {
+ smp = TMAP;
+ zset = 1;
+ }
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ }
+
+ if (cursor_set)
+ return(0);
+
+ switch (scmd) {
+ case CNTRL_E:
+ /*
+ * On a ^E that was forced to change lines, try and keep the
+ * cursor as close as possible to the last position, but also
+ * set it up so that the next "real" movement will return the
+ * cursor to the closest position to the last real movement.
+ */
+ if (echanged) {
+ rp->lno = smp->lno;
+ rp->cno = vs_colpos(sp, smp->lno,
+ (O_ISSET(sp, O_LEFTRIGHT) ?
+ smp->coff : (smp->soff - 1) * sp->cols) +
+ sp->rcm % sp->cols);
+ }
+ return (0);
+ case CNTRL_F:
+ /*
+ * If there are more lines, the ^F command is positioned at
+ * the first line of the screen.
+ */
+ if (!count) {
+ smp = HMAP;
+ break;
+ }
+ /* FALLTHROUGH */
+ case CNTRL_D:
+ /*
+ * The ^D and ^F commands move the cursor towards EOF
+ * if there are more lines to move. Check to be sure
+ * the lines actually exist. (They may not if the
+ * file is smaller than the screen.)
+ */
+ for (; count; --count, ++smp)
+ if (smp == TMAP || !db_exist(sp, smp[1].lno))
+ break;
+ break;
+ case Z_PLUS:
+ /* The z+ command moves the cursor to the first new line. */
+ break;
+ default:
+ abort();
+ }
+
+ if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
+ return (1);
+ rp->lno = smp->lno;
+ rp->cno = smp->c_sboff;
+ return (0);
+}
+
+/*
+ * vs_sm_1up --
+ * Scroll the SMAP up one.
+ *
+ * PUBLIC: int vs_sm_1up __P((SCR *));
+ */
+int
+vs_sm_1up(sp)
+ SCR *sp;
+{
+ /*
+ * Delete the top line of the screen. Shift the screen map
+ * up and display a new line at the bottom of the screen.
+ */
+ (void)sp->gp->scr_move(sp, 0, 0);
+ if (vs_deleteln(sp, 1))
+ return (1);
+
+ /* One-line screens can fail. */
+ if (IS_ONELINE(sp)) {
+ if (vs_sm_next(sp, TMAP, TMAP))
+ return (1);
+ } else {
+ memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));
+ if (vs_sm_next(sp, TMAP - 1, TMAP))
+ return (1);
+ }
+ /* vs_sm_next() flushed the cache. */
+ return (vs_line(sp, TMAP, NULL, NULL));
+}
+
+/*
+ * vs_deleteln --
+ * Delete a line a la curses, make sure to put the information
+ * line and other screens back.
+ */
+static int
+vs_deleteln(sp, cnt)
+ SCR *sp;
+ int cnt;
+{
+ GS *gp;
+ size_t oldy, oldx;
+
+ gp = sp->gp;
+ if (IS_ONELINE(sp))
+ (void)gp->scr_clrtoeol(sp);
+ else {
+ (void)gp->scr_cursor(sp, &oldy, &oldx);
+ while (cnt--) {
+ (void)gp->scr_deleteln(sp);
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)gp->scr_insertln(sp);
+ (void)gp->scr_move(sp, oldy, oldx);
+ }
+ }
+ return (0);
+}
+
+/*
+ * vs_sm_down --
+ * Scroll the SMAP down count logical lines.
+ */
+static int
+vs_sm_down(sp, rp, count, scmd, smp)
+ SCR *sp;
+ MARK *rp;
+ recno_t count;
+ SMAP *smp;
+ scroll_t scmd;
+{
+ SMAP *ssmp, s1, s2;
+ int cursor_set, ychanged, zset;
+
+ /* Check to see if movement is possible. */
+ if (HMAP->lno == 1 &&
+ (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&
+ (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {
+ v_sof(sp, NULL);
+ return (1);
+ }
+
+ /*
+ * Small screens: see vs_refresh.c section 6a.
+ *
+ * If it's a small screen, and the movement isn't larger than a
+ * screen, i.e some context will remain, open up the screen and
+ * display by scrolling. In this case, the cursor moves up one
+ * line for each line displayed. Otherwise, erase/compress and
+ * repaint, and move the cursor to the first line in the screen.
+ * Note, the ^B command is always in the latter case, for historical
+ * reasons.
+ */
+ cursor_set = scmd == CNTRL_Y;
+ if (IS_SMALL(sp)) {
+ if (count >= sp->t_maxrows || scmd == CNTRL_B) {
+ s1 = HMAP[0];
+ if (vs_sm_erase(sp))
+ return (1);
+ for (; count--; s1 = s2) {
+ if (vs_sm_prev(sp, &s1, &s2))
+ return (1);
+ if (s2.lno == 1 &&
+ (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))
+ break;
+ }
+ HMAP[0] = s2;
+ if (vs_sm_fill(sp, OOBLNO, P_TOP))
+ return (1);
+ return (vs_sm_position(sp, rp, 0, P_BOTTOM));
+ }
+ cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);
+ for (; count &&
+ sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
+ if (HMAP->lno == 1 &&
+ (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
+ break;
+ ++TMAP;
+ if (vs_sm_1down(sp))
+ return (1);
+ }
+ if (!cursor_set) {
+ rp->lno = ssmp->lno;
+ rp->cno = ssmp->c_sboff;
+ }
+ if (count == 0)
+ return (0);
+ }
+
+ for (ychanged = zset = 0; count; --count) {
+ /* If the line doesn't exist, we're done. */
+ if (HMAP->lno == 1 &&
+ (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
+ break;
+
+ /* Scroll the screen and cursor down one logical line. */
+ if (vs_sm_1down(sp))
+ return (1);
+ switch (scmd) {
+ case CNTRL_Y:
+ if (smp < TMAP)
+ ++smp;
+ else
+ ychanged = 1;
+ break;
+ case Z_CARAT:
+ if (zset) {
+ if (smp < TMAP)
+ ++smp;
+ } else {
+ smp = HMAP;
+ zset = 1;
+ }
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ }
+
+ if (scmd != CNTRL_Y && cursor_set)
+ return(0);
+
+ switch (scmd) {
+ case CNTRL_B:
+ /*
+ * If there are more lines, the ^B command is positioned at
+ * the last line of the screen. However, the line may not
+ * exist.
+ */
+ if (!count) {
+ for (smp = TMAP; smp > HMAP; --smp)
+ if (db_exist(sp, smp->lno))
+ break;
+ break;
+ }
+ /* FALLTHROUGH */
+ case CNTRL_U:
+ /*
+ * The ^B and ^U commands move the cursor towards SOF
+ * if there are more lines to move.
+ */
+ if (count < smp - HMAP)
+ smp -= count;
+ else
+ smp = HMAP;
+ break;
+ case CNTRL_Y:
+ /*
+ * On a ^Y that was forced to change lines, try and keep the
+ * cursor as close as possible to the last position, but also
+ * set it up so that the next "real" movement will return the
+ * cursor to the closest position to the last real movement.
+ */
+ if (ychanged) {
+ rp->lno = smp->lno;
+ rp->cno = vs_colpos(sp, smp->lno,
+ (O_ISSET(sp, O_LEFTRIGHT) ?
+ smp->coff : (smp->soff - 1) * sp->cols) +
+ sp->rcm % sp->cols);
+ }
+ return (0);
+ case Z_CARAT:
+ /* The z^ command moves the cursor to the first new line. */
+ break;
+ default:
+ abort();
+ }
+
+ if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
+ return (1);
+ rp->lno = smp->lno;
+ rp->cno = smp->c_sboff;
+ return (0);
+}
+
+/*
+ * vs_sm_erase --
+ * Erase the small screen area for the scrolling functions.
+ */
+static int
+vs_sm_erase(sp)
+ SCR *sp;
+{
+ GS *gp;
+
+ gp = sp->gp;
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)gp->scr_clrtoeol(sp);
+ for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {
+ (void)gp->scr_move(sp, TMAP - HMAP, 0);
+ (void)gp->scr_clrtoeol(sp);
+ }
+ return (0);
+}
+
+/*
+ * vs_sm_1down --
+ * Scroll the SMAP down one.
+ *
+ * PUBLIC: int vs_sm_1down __P((SCR *));
+ */
+int
+vs_sm_1down(sp)
+ SCR *sp;
+{
+ /*
+ * Insert a line at the top of the screen. Shift the screen map
+ * down and display a new line at the top of the screen.
+ */
+ (void)sp->gp->scr_move(sp, 0, 0);
+ if (vs_insertln(sp, 1))
+ return (1);
+
+ /* One-line screens can fail. */
+ if (IS_ONELINE(sp)) {
+ if (vs_sm_prev(sp, HMAP, HMAP))
+ return (1);
+ } else {
+ memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));
+ if (vs_sm_prev(sp, HMAP + 1, HMAP))
+ return (1);
+ }
+ /* vs_sm_prev() flushed the cache. */
+ return (vs_line(sp, HMAP, NULL, NULL));
+}
+
+/*
+ * vs_insertln --
+ * Insert a line a la curses, make sure to put the information
+ * line and other screens back.
+ */
+static int
+vs_insertln(sp, cnt)
+ SCR *sp;
+ int cnt;
+{
+ GS *gp;
+ size_t oldy, oldx;
+
+ gp = sp->gp;
+ if (IS_ONELINE(sp)) {
+ (void)gp->scr_move(sp, LASTLINE(sp), 0);
+ (void)gp->scr_clrtoeol(sp);
+ } else {
+ (void)gp->scr_cursor(sp, &oldy, &oldx);
+ while (cnt--) {
+ (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);
+ (void)gp->scr_deleteln(sp);
+ (void)gp->scr_move(sp, oldy, oldx);
+ (void)gp->scr_insertln(sp);
+ }
+ }
+ return (0);
+}
+
+/*
+ * vs_sm_next --
+ * Fill in the next entry in the SMAP.
+ *
+ * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *));
+ */
+int
+vs_sm_next(sp, p, t)
+ SCR *sp;
+ SMAP *p, *t;
+{
+ size_t lcnt;
+
+ SMAP_FLUSH(t);
+ if (O_ISSET(sp, O_LEFTRIGHT)) {
+ t->lno = p->lno + 1;
+ t->coff = p->coff;
+ } else {
+ lcnt = vs_screens(sp, p->lno, NULL);
+ if (lcnt == p->soff) {
+ t->lno = p->lno + 1;
+ t->soff = 1;
+ } else {
+ t->lno = p->lno;
+ t->soff = p->soff + 1;
+ }
+ }
+ return (0);
+}
+
+/*
+ * vs_sm_prev --
+ * Fill in the previous entry in the SMAP.
+ *
+ * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *));
+ */
+int
+vs_sm_prev(sp, p, t)
+ SCR *sp;
+ SMAP *p, *t;
+{
+ SMAP_FLUSH(t);
+ if (O_ISSET(sp, O_LEFTRIGHT)) {
+ t->lno = p->lno - 1;
+ t->coff = p->coff;
+ } else {
+ if (p->soff != 1) {
+ t->lno = p->lno;
+ t->soff = p->soff - 1;
+ } else {
+ t->lno = p->lno - 1;
+ t->soff = vs_screens(sp, t->lno, NULL);
+ }
+ }
+ return (t->lno == 0);
+}
+
+/*
+ * vs_sm_cursor --
+ * Return the SMAP entry referenced by the cursor.
+ *
+ * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **));
+ */
+int
+vs_sm_cursor(sp, smpp)
+ SCR *sp;
+ SMAP **smpp;
+{
+ SMAP *p;
+
+ /* See if the cursor is not in the map. */
+ if (sp->lno < HMAP->lno || sp->lno > TMAP->lno)
+ return (1);
+
+ /* Find the first occurence of the line. */
+ for (p = HMAP; p->lno != sp->lno; ++p);
+
+ /* Fill in the map information until we find the right line. */
+ for (; p <= TMAP; ++p) {
+ /* Short lines are common and easy to detect. */
+ if (p != TMAP && (p + 1)->lno != p->lno) {
+ *smpp = p;
+ return (0);
+ }
+ if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL))
+ return (1);
+ if (p->c_eboff >= sp->cno) {
+ *smpp = p;
+ return (0);
+ }
+ }
+
+ /* It was past the end of the map after all. */
+ return (1);
+}
+
+/*
+ * vs_sm_position --
+ * Return the line/column of the top, middle or last line on the screen.
+ * (The vi H, M and L commands.) Here because only the screen routines
+ * know what's really out there.
+ *
+ * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t));
+ */
+int
+vs_sm_position(sp, rp, cnt, pos)
+ SCR *sp;
+ MARK *rp;
+ u_long cnt;
+ pos_t pos;
+{
+ SMAP *smp;
+ recno_t last;
+
+ switch (pos) {
+ case P_TOP:
+ /*
+ * !!!
+ * Historically, an invalid count to the H command failed.
+ * We do nothing special here, just making sure that H in
+ * an empty screen works.
+ */
+ if (cnt > TMAP - HMAP)
+ goto sof;
+ smp = HMAP + cnt;
+ if (cnt && !db_exist(sp, smp->lno)) {
+sof: msgq(sp, M_BERR, "220|Movement past the end-of-screen");
+ return (1);
+ }
+ break;
+ case P_MIDDLE:
+ /*
+ * !!!
+ * Historically, a count to the M command was ignored.
+ * If the screen isn't filled, find the middle of what's
+ * real and move there.
+ */
+ if (!db_exist(sp, TMAP->lno)) {
+ if (db_last(sp, &last))
+ return (1);
+ for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);
+ if (smp > HMAP)
+ smp -= (smp - HMAP) / 2;
+ } else
+ smp = (HMAP + (TMAP - HMAP) / 2) + cnt;
+ break;
+ case P_BOTTOM:
+ /*
+ * !!!
+ * Historically, an invalid count to the L command failed.
+ * If the screen isn't filled, find the bottom of what's
+ * real and try to offset from there.
+ */
+ if (cnt > TMAP - HMAP)
+ goto eof;
+ smp = TMAP - cnt;
+ if (!db_exist(sp, smp->lno)) {
+ if (db_last(sp, &last))
+ return (1);
+ for (; smp->lno > last && smp > HMAP; --smp);
+ if (cnt > smp - HMAP) {
+eof: msgq(sp, M_BERR,
+ "221|Movement past the beginning-of-screen");
+ return (1);
+ }
+ smp -= cnt;
+ }
+ break;
+ default:
+ abort();
+ }
+
+ /* Make sure that the cached information is valid. */
+ if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
+ return (1);
+ rp->lno = smp->lno;
+ rp->cno = smp->c_sboff;
+
+ return (0);
+}
+
+/*
+ * vs_sm_nlines --
+ * Return the number of screen lines from an SMAP entry to the
+ * start of some file line, less than a maximum value.
+ *
+ * PUBLIC: recno_t vs_sm_nlines __P((SCR *, SMAP *, recno_t, size_t));
+ */
+recno_t
+vs_sm_nlines(sp, from_sp, to_lno, max)
+ SCR *sp;
+ SMAP *from_sp;
+ recno_t to_lno;
+ size_t max;
+{
+ recno_t lno, lcnt;
+
+ if (O_ISSET(sp, O_LEFTRIGHT))
+ return (from_sp->lno > to_lno ?
+ from_sp->lno - to_lno : to_lno - from_sp->lno);
+
+ if (from_sp->lno == to_lno)
+ return (from_sp->soff - 1);
+
+ if (from_sp->lno > to_lno) {
+ lcnt = from_sp->soff - 1; /* Correct for off-by-one. */
+ for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
+ lcnt += vs_screens(sp, lno, NULL);
+ } else {
+ lno = from_sp->lno;
+ lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;
+ for (; ++lno < to_lno && lcnt <= max;)
+ lcnt += vs_screens(sp, lno, NULL);
+ }
+ return (lcnt);
+}
diff --git a/contrib/nvi/vi/vs_split.c b/contrib/nvi/vi/vs_split.c
new file mode 100644
index 0000000..d017354
--- /dev/null
+++ b/contrib/nvi/vi/vs_split.c
@@ -0,0 +1,607 @@
+/*-
+ * 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[] = "@(#)vs_split.c 10.31 (Berkeley) 10/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.h"
+
+static SCR *vs_getbg __P((SCR *, char *));
+
+/*
+ * vs_split --
+ * Create a new screen.
+ *
+ * PUBLIC: int vs_split __P((SCR *, SCR *, int));
+ */
+int
+vs_split(sp, new, ccl)
+ SCR *sp, *new;
+ int ccl; /* Colon-command line split. */
+{
+ GS *gp;
+ SMAP *smp;
+ size_t half;
+ int issmallscreen, splitup;
+
+ gp = sp->gp;
+
+ /* Check to see if it's possible. */
+ /* XXX: The IS_ONELINE fix will change this, too. */
+ if (sp->rows < 4) {
+ msgq(sp, M_ERR,
+ "222|Screen must be larger than %d lines to split", 4 - 1);
+ return (1);
+ }
+
+ /* Wait for any messages in the screen. */
+ vs_resolve(sp, NULL, 1);
+
+ half = sp->rows / 2;
+ if (ccl && half > 6)
+ half = 6;
+
+ /* Get a new screen map. */
+ CALLOC(sp, _HMAP(new), SMAP *, SIZE_HMAP(sp), sizeof(SMAP));
+ if (_HMAP(new) == NULL)
+ return (1);
+ _HMAP(new)->lno = sp->lno;
+ _HMAP(new)->coff = 0;
+ _HMAP(new)->soff = 1;
+
+ /*
+ * Small screens: see vs_refresh.c section 6a. Set a flag so
+ * we know to fix the screen up later.
+ */
+ issmallscreen = IS_SMALL(sp);
+
+ /* The columns in the screen don't change. */
+ new->cols = sp->cols;
+
+ /*
+ * Split the screen, and link the screens together. If creating a
+ * screen to edit the colon command line or the cursor is in the top
+ * half of the current screen, the new screen goes under the current
+ * screen. Else, it goes above the current screen.
+ *
+ * Recalculate current cursor position based on sp->lno, we're called
+ * with the cursor on the colon command line. Then split the screen
+ * in half and update the shared information.
+ */
+ splitup =
+ !ccl && (vs_sm_cursor(sp, &smp) ? 0 : (smp - HMAP) + 1) >= half;
+ if (splitup) { /* Old is bottom half. */
+ new->rows = sp->rows - half; /* New. */
+ new->woff = sp->woff;
+ sp->rows = half; /* Old. */
+ sp->woff += new->rows;
+ /* Link in before old. */
+ CIRCLEQ_INSERT_BEFORE(&gp->dq, sp, new, q);
+
+ /*
+ * If the parent is the bottom half of the screen, shift
+ * the map down to match on-screen text.
+ */
+ memmove(_HMAP(sp), _HMAP(sp) + new->rows,
+ (sp->t_maxrows - new->rows) * sizeof(SMAP));
+ } else { /* Old is top half. */
+ new->rows = half; /* New. */
+ sp->rows -= half; /* Old. */
+ new->woff = sp->woff + sp->rows;
+ /* Link in after old. */
+ CIRCLEQ_INSERT_AFTER(&gp->dq, sp, new, q);
+ }
+
+ /* Adjust maximum text count. */
+ sp->t_maxrows = IS_ONELINE(sp) ? 1 : sp->rows - 1;
+ new->t_maxrows = IS_ONELINE(new) ? 1 : new->rows - 1;
+
+ /*
+ * Small screens: see vs_refresh.c, section 6a.
+ *
+ * The child may have different screen options sizes than the parent,
+ * so use them. Guarantee that text counts aren't larger than the
+ * new screen sizes.
+ */
+ if (issmallscreen) {
+ /* Fix the text line count for the parent. */
+ if (splitup)
+ sp->t_rows -= new->rows;
+
+ /* Fix the parent screen. */
+ if (sp->t_rows > sp->t_maxrows)
+ sp->t_rows = sp->t_maxrows;
+ if (sp->t_minrows > sp->t_maxrows)
+ sp->t_minrows = sp->t_maxrows;
+
+ /* Fix the child screen. */
+ new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);
+ if (new->t_rows > new->t_maxrows)
+ new->t_rows = new->t_maxrows;
+ if (new->t_minrows > new->t_maxrows)
+ new->t_minrows = new->t_maxrows;
+ } else {
+ sp->t_minrows = sp->t_rows = IS_ONELINE(sp) ? 1 : sp->rows - 1;
+
+ /*
+ * The new screen may be a small screen, even if the parent
+ * was not. Don't complain if O_WINDOW is too large, we're
+ * splitting the screen so the screen is much smaller than
+ * normal.
+ */
+ new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);
+ if (new->t_rows > new->rows - 1)
+ new->t_minrows = new->t_rows =
+ IS_ONELINE(new) ? 1 : new->rows - 1;
+ }
+
+ /* Adjust the ends of the new and old maps. */
+ _TMAP(sp) = IS_ONELINE(sp) ?
+ _HMAP(sp) : _HMAP(sp) + (sp->t_rows - 1);
+ _TMAP(new) = IS_ONELINE(new) ?
+ _HMAP(new) : _HMAP(new) + (new->t_rows - 1);
+
+ /* Reset the length of the default scroll. */
+ if ((sp->defscroll = sp->t_maxrows / 2) == 0)
+ sp->defscroll = 1;
+ if ((new->defscroll = new->t_maxrows / 2) == 0)
+ new->defscroll = 1;
+
+ /*
+ * Initialize the screen flags:
+ *
+ * If we're in vi mode in one screen, we don't have to reinitialize.
+ * This isn't just a cosmetic fix. The path goes like this:
+ *
+ * return into vi(), SC_SSWITCH set
+ * call vs_refresh() with SC_STATUS set
+ * call vs_resolve to display the status message
+ * call vs_refresh() because the SC_SCR_VI bit isn't set
+ *
+ * Things go downhill at this point.
+ *
+ * Draw the new screen from scratch, and add a status line.
+ */
+ F_SET(new,
+ SC_SCR_REFORMAT | SC_STATUS |
+ F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX));
+ return (0);
+}
+
+/*
+ * vs_discard --
+ * Discard the screen, folding the real-estate into a related screen,
+ * if one exists, and return that screen.
+ *
+ * PUBLIC: int vs_discard __P((SCR *, SCR **));
+ */
+int
+vs_discard(sp, spp)
+ SCR *sp, **spp;
+{
+ SCR *nsp;
+ dir_t dir;
+
+ /*
+ * Save the old screen's cursor information.
+ *
+ * XXX
+ * If called after file_end(), and the underlying file was a tmp
+ * file, it may have gone away.
+ */
+ if (sp->frp != NULL) {
+ sp->frp->lno = sp->lno;
+ sp->frp->cno = sp->cno;
+ F_SET(sp->frp, FR_CURSORSET);
+ }
+
+ /*
+ * Add into a previous screen and then into a subsequent screen, as
+ * they're the closest to the current screen. If that doesn't work,
+ * there was no screen to join.
+ */
+ if ((nsp = sp->q.cqe_prev) != (void *)&sp->gp->dq) {
+ nsp->rows += sp->rows;
+ sp = nsp;
+ dir = FORWARD;
+ } else if ((nsp = sp->q.cqe_next) != (void *)&sp->gp->dq) {
+ nsp->woff = sp->woff;
+ nsp->rows += sp->rows;
+ sp = nsp;
+ dir = BACKWARD;
+ } else
+ sp = NULL;
+
+ if (spp != NULL)
+ *spp = sp;
+ if (sp == NULL)
+ return (0);
+
+ /*
+ * Make no effort to clean up the discarded screen's information. If
+ * it's not exiting, we'll do the work when the user redisplays it.
+ *
+ * Small screens: see vs_refresh.c section 6a. Adjust text line info,
+ * unless it's a small screen.
+ *
+ * Reset the length of the default scroll.
+ */
+ if (!IS_SMALL(sp))
+ sp->t_rows = sp->t_minrows = sp->rows - 1;
+ sp->t_maxrows = sp->rows - 1;
+ sp->defscroll = sp->t_maxrows / 2;
+ *(HMAP + (sp->t_rows - 1)) = *TMAP;
+ TMAP = HMAP + (sp->t_rows - 1);
+
+ /*
+ * Draw the new screen from scratch, and add a status line.
+ *
+ * XXX
+ * We could play games with the map, if this were ever to be a
+ * performance problem, but I wrote the code a few times and it
+ * was never clean or easy.
+ */
+ switch (dir) {
+ case FORWARD:
+ vs_sm_fill(sp, OOBLNO, P_TOP);
+ break;
+ case BACKWARD:
+ vs_sm_fill(sp, OOBLNO, P_BOTTOM);
+ break;
+ default:
+ abort();
+ }
+
+ F_SET(sp, SC_STATUS);
+ return (0);
+}
+
+/*
+ * vs_fg --
+ * Background the current screen, and foreground a new one.
+ *
+ * PUBLIC: int vs_fg __P((SCR *, SCR **, CHAR_T *, int));
+ */
+int
+vs_fg(sp, nspp, name, newscreen)
+ SCR *sp, **nspp;
+ CHAR_T *name;
+ int newscreen;
+{
+ GS *gp;
+ SCR *nsp;
+
+ gp = sp->gp;
+
+ if (newscreen)
+ /* Get the specified background screen. */
+ nsp = vs_getbg(sp, name);
+ else
+ /* Swap screens. */
+ if (vs_swap(sp, &nsp, name))
+ return (1);
+
+ if ((*nspp = nsp) == NULL) {
+ msgq_str(sp, M_ERR, name,
+ name == NULL ?
+ "223|There are no background screens" :
+ "224|There's no background screen editing a file named %s");
+ return (1);
+ }
+
+ if (newscreen) {
+ /* Remove the new screen from the background queue. */
+ CIRCLEQ_REMOVE(&gp->hq, nsp, q);
+
+ /* Split the screen; if we fail, hook the screen back in. */
+ if (vs_split(sp, nsp, 0)) {
+ CIRCLEQ_INSERT_TAIL(&gp->hq, nsp, q);
+ return (1);
+ }
+ } else {
+ /* Move the old screen to the background queue. */
+ CIRCLEQ_REMOVE(&gp->dq, sp, q);
+ CIRCLEQ_INSERT_TAIL(&gp->hq, sp, q);
+ }
+ return (0);
+}
+
+/*
+ * vs_bg --
+ * Background the screen, and switch to the next one.
+ *
+ * PUBLIC: int vs_bg __P((SCR *));
+ */
+int
+vs_bg(sp)
+ SCR *sp;
+{
+ GS *gp;
+ SCR *nsp;
+
+ gp = sp->gp;
+
+ /* Try and join with another screen. */
+ if (vs_discard(sp, &nsp))
+ return (1);
+ if (nsp == NULL) {
+ msgq(sp, M_ERR,
+ "225|You may not background your only displayed screen");
+ return (1);
+ }
+
+ /* Move the old screen to the background queue. */
+ CIRCLEQ_REMOVE(&gp->dq, sp, q);
+ CIRCLEQ_INSERT_TAIL(&gp->hq, sp, q);
+
+ /* Toss the screen map. */
+ free(_HMAP(sp));
+ _HMAP(sp) = NULL;
+
+ /* Switch screens. */
+ sp->nextdisp = nsp;
+ F_SET(sp, SC_SSWITCH);
+
+ return (0);
+}
+
+/*
+ * vs_swap --
+ * Swap the current screen with a backgrounded one.
+ *
+ * PUBLIC: int vs_swap __P((SCR *, SCR **, char *));
+ */
+int
+vs_swap(sp, nspp, name)
+ SCR *sp, **nspp;
+ char *name;
+{
+ GS *gp;
+ SCR *nsp;
+
+ gp = sp->gp;
+
+ /* Get the specified background screen. */
+ if ((*nspp = nsp = vs_getbg(sp, name)) == NULL)
+ return (0);
+
+ /*
+ * Save the old screen's cursor information.
+ *
+ * XXX
+ * If called after file_end(), and the underlying file was a tmp
+ * file, it may have gone away.
+ */
+ if (sp->frp != NULL) {
+ sp->frp->lno = sp->lno;
+ sp->frp->cno = sp->cno;
+ F_SET(sp->frp, FR_CURSORSET);
+ }
+
+ /* Switch screens. */
+ sp->nextdisp = nsp;
+ F_SET(sp, SC_SSWITCH);
+
+ /* Initialize terminal information. */
+ VIP(nsp)->srows = VIP(sp)->srows;
+
+ /* Initialize screen information. */
+ nsp->cols = sp->cols;
+ nsp->rows = sp->rows; /* XXX: Only place in vi that sets rows. */
+ nsp->woff = sp->woff;
+
+ /*
+ * Small screens: see vs_refresh.c, section 6a.
+ *
+ * The new screens may have different screen options sizes than the
+ * old one, so use them. Make sure that text counts aren't larger
+ * than the new screen sizes.
+ */
+ if (IS_SMALL(nsp)) {
+ nsp->t_minrows = nsp->t_rows = O_VAL(nsp, O_WINDOW);
+ if (nsp->t_rows > sp->t_maxrows)
+ nsp->t_rows = nsp->t_maxrows;
+ if (nsp->t_minrows > sp->t_maxrows)
+ nsp->t_minrows = nsp->t_maxrows;
+ } else
+ nsp->t_rows = nsp->t_maxrows = nsp->t_minrows = nsp->rows - 1;
+
+ /* Reset the length of the default scroll. */
+ nsp->defscroll = nsp->t_maxrows / 2;
+
+ /* Allocate a new screen map. */
+ CALLOC_RET(nsp, _HMAP(nsp), SMAP *, SIZE_HMAP(nsp), sizeof(SMAP));
+ _TMAP(nsp) = _HMAP(nsp) + (nsp->t_rows - 1);
+
+ /* Fill the map. */
+ if (vs_sm_fill(nsp, nsp->lno, P_FILL))
+ return (1);
+
+ /*
+ * The new screen replaces the old screen in the parent/child list.
+ * We insert the new screen after the old one. If we're exiting,
+ * the exit will delete the old one, if we're foregrounding, the fg
+ * code will move the old one to the background queue.
+ */
+ CIRCLEQ_REMOVE(&gp->hq, nsp, q);
+ CIRCLEQ_INSERT_AFTER(&gp->dq, sp, nsp, q);
+
+ /*
+ * Don't change the screen's cursor information other than to
+ * note that the cursor is wrong.
+ */
+ F_SET(VIP(nsp), VIP_CUR_INVALID);
+
+ /* Draw the new screen from scratch, and add a status line. */
+ F_SET(nsp, SC_SCR_REDRAW | SC_STATUS);
+ return (0);
+}
+
+/*
+ * vs_resize --
+ * Change the absolute size of the current screen.
+ *
+ * PUBLIC: int vs_resize __P((SCR *, long, adj_t));
+ */
+int
+vs_resize(sp, count, adj)
+ SCR *sp;
+ long count;
+ adj_t adj;
+{
+ GS *gp;
+ SCR *g, *s;
+ size_t g_off, s_off;
+
+ gp = sp->gp;
+
+ /*
+ * Figure out which screens will grow, which will shrink, and
+ * make sure it's possible.
+ */
+ if (count == 0)
+ return (0);
+ if (adj == A_SET) {
+ if (sp->t_maxrows == count)
+ return (0);
+ if (sp->t_maxrows > count) {
+ adj = A_DECREASE;
+ count = sp->t_maxrows - count;
+ } else {
+ adj = A_INCREASE;
+ count = count - sp->t_maxrows;
+ }
+ }
+
+ g_off = s_off = 0;
+ if (adj == A_DECREASE) {
+ if (count < 0)
+ count = -count;
+ s = sp;
+ if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)
+ goto toosmall;
+ if ((g = sp->q.cqe_prev) == (void *)&gp->dq) {
+ if ((g = sp->q.cqe_next) == (void *)&gp->dq)
+ goto toobig;
+ g_off = -count;
+ } else
+ s_off = count;
+ } else {
+ g = sp;
+ if ((s = sp->q.cqe_next) != (void *)&gp->dq)
+ if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)
+ s = NULL;
+ else
+ s_off = count;
+ else
+ s = NULL;
+ if (s == NULL) {
+ if ((s = sp->q.cqe_prev) == (void *)&gp->dq) {
+toobig: msgq(sp, M_BERR, adj == A_DECREASE ?
+ "227|The screen cannot shrink" :
+ "228|The screen cannot grow");
+ return (1);
+ }
+ if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) {
+toosmall: msgq(sp, M_BERR,
+ "226|The screen can only shrink to %d rows",
+ MINIMUM_SCREEN_ROWS);
+ return (1);
+ }
+ g_off = -count;
+ }
+ }
+
+ /*
+ * Fix up the screens; we could optimize the reformatting of the
+ * screen, but this isn't likely to be a common enough operation
+ * to make it worthwhile.
+ */
+ s->rows += -count;
+ s->woff += s_off;
+ g->rows += count;
+ g->woff += g_off;
+
+ g->t_rows += count;
+ if (g->t_minrows == g->t_maxrows)
+ g->t_minrows += count;
+ g->t_maxrows += count;
+ _TMAP(g) += count;
+ F_SET(g, SC_SCR_REFORMAT | SC_STATUS);
+
+ s->t_rows -= count;
+ s->t_maxrows -= count;
+ if (s->t_minrows > s->t_maxrows)
+ s->t_minrows = s->t_maxrows;
+ _TMAP(s) -= count;
+ F_SET(s, SC_SCR_REFORMAT | SC_STATUS);
+
+ return (0);
+}
+
+/*
+ * vs_getbg --
+ * Get the specified background screen, or, if name is NULL, the first
+ * background screen.
+ */
+static SCR *
+vs_getbg(sp, name)
+ SCR *sp;
+ char *name;
+{
+ GS *gp;
+ SCR *nsp;
+ char *p;
+
+ gp = sp->gp;
+
+ /* If name is NULL, return the first background screen on the list. */
+ if (name == NULL) {
+ nsp = gp->hq.cqh_first;
+ return (nsp == (void *)&gp->hq ? NULL : nsp);
+ }
+
+ /* Search for a full match. */
+ for (nsp = gp->hq.cqh_first;
+ nsp != (void *)&gp->hq; nsp = nsp->q.cqe_next)
+ if (!strcmp(nsp->frp->name, name))
+ break;
+ if (nsp != (void *)&gp->hq)
+ return (nsp);
+
+ /* Search for a last-component match. */
+ for (nsp = gp->hq.cqh_first;
+ nsp != (void *)&gp->hq; nsp = nsp->q.cqe_next) {
+ if ((p = strrchr(nsp->frp->name, '/')) == NULL)
+ p = nsp->frp->name;
+ else
+ ++p;
+ if (!strcmp(p, name))
+ break;
+ }
+ if (nsp != (void *)&gp->hq)
+ return (nsp);
+
+ return (NULL);
+}
OpenPOWER on IntegriCloud