aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/vi/vi/v_ntext.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/vi/vi/v_ntext.c')
-rw-r--r--usr.bin/vi/vi/v_ntext.c1827
1 files changed, 1827 insertions, 0 deletions
diff --git a/usr.bin/vi/vi/v_ntext.c b/usr.bin/vi/vi/v_ntext.c
new file mode 100644
index 000000000000..e4060af97bc8
--- /dev/null
+++ b/usr.bin/vi/vi/v_ntext.c
@@ -0,0 +1,1827 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)v_ntext.c 8.95 (Berkeley) 3/24/94";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <db.h>
+#include <regex.h>
+
+#include "vi.h"
+#include "seq.h"
+#include "vcmd.h"
+#include "excmd.h"
+
+static int txt_abbrev __P((SCR *, TEXT *, CHAR_T *, int, int *, int *));
+static void txt_ai_resolve __P((SCR *, TEXT *));
+static TEXT *txt_backup __P((SCR *, EXF *, TEXTH *, TEXT *, u_int));
+static void txt_err __P((SCR *, EXF *, TEXTH *));
+static int txt_hex __P((SCR *, TEXT *, int *, CHAR_T *));
+static int txt_indent __P((SCR *, TEXT *));
+static int txt_margin __P((SCR *, TEXT *, int *, CHAR_T *));
+static int txt_outdent __P((SCR *, TEXT *));
+static void txt_showmatch __P((SCR *, EXF *));
+static void txt_Rcleanup __P((SCR *,
+ TEXTH *, TEXT *, const char *, const size_t));
+static int txt_resolve __P((SCR *, EXF *, TEXTH *));
+static void txt_unmap __P((SCR *, TEXT *, u_int *));
+
+/* Cursor character (space is hard to track on the screen). */
+#if defined(DEBUG) && 0
+#undef CURSOR_CH
+#define CURSOR_CH '+'
+#endif
+
+/* Local version of BINC. */
+#define TBINC(sp, lp, llen, nlen) { \
+ if ((nlen) > llen && binc(sp, &(lp), &(llen), nlen)) \
+ goto err; \
+}
+
+/*
+ * v_ntext --
+ * Read in text from the user.
+ *
+ * !!!
+ * Historic vi did a special screen optimization for tab characters. For
+ * the keystrokes "iabcd<esc>0C<tab>", the tab would overwrite the rest of
+ * the string when it was displayed. Because this implementation redisplays
+ * the entire line on each keystroke, the "bcd" gets pushed to the right as
+ * we ignore that the user has "promised" to change the rest of the characters.
+ * Users have noticed, but this isn't worth fixing, and, the way that the
+ * historic vi did it results in an even worse bug. Given the keystrokes
+ * "iabcd<esc>0R<tab><esc>", the "bcd" disappears, and magically reappears
+ * on the second <esc> key.
+ */
+int
+v_ntext(sp, ep, tiqh, tm, lp, len, rp, prompt, ai_line, flags)
+ SCR *sp;
+ EXF *ep;
+ TEXTH *tiqh;
+ MARK *tm; /* To MARK. */
+ const char *lp; /* Input line. */
+ const size_t len; /* Input line length. */
+ MARK *rp; /* Return MARK. */
+ int prompt; /* Prompt to display. */
+ recno_t ai_line; /* Line number to use for autoindent count. */
+ u_int flags; /* TXT_ flags. */
+{
+ /* State of abbreviation checks. */
+ enum { A_NOTSET, A_NOTWORD, A_INWORD } abb;
+ /* State of the "[^0]^D" sequences. */
+ enum { C_NOTSET, C_CARATSET, C_NOCHANGE, C_ZEROSET } carat_st;
+ /* State of the hex input character. */
+ enum { H_NOTSET, H_NEXTCHAR, H_INHEX } hex;
+ /* State of quotation. */
+ enum { Q_NOTSET, Q_NEXTCHAR, Q_THISCHAR } quoted;
+ CH ikey; /* Input character structure. */
+ CHAR_T ch; /* Input character. */
+ TEXT *tp, *ntp, ait; /* Input and autoindent text structures. */
+ size_t rcol; /* 0-N: insert offset in the replay buffer. */
+ size_t col; /* Current column. */
+ u_long margin; /* Wrapmargin value. */
+ u_int iflags; /* Input flags. */
+ int ab_cnt, ab_turnoff; /* Abbreviation count, if turned off. */
+ int eval; /* Routine return value. */
+ int replay; /* If replaying a set of input. */
+ int showmatch; /* Showmatch set on this character. */
+ int testnr; /* Test first character for nul replay. */
+ int max, tmp;
+ int unmap_tst; /* Input map needs testing. */
+ char *p;
+
+ /*
+ * Set the input flag, so tabs get displayed correctly
+ * and everyone knows that the text buffer is in use.
+ */
+ F_SET(sp, S_INPUT);
+
+ /* Local initialization. */
+ eval = 0;
+
+ /*
+ * 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.
+ */
+ 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 the starting line number. */
+ tp->lno = sp->lno;
+
+ /*
+ * 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
+ * END_CH.
+ */
+ if (len) {
+ if (LF_ISSET(TXT_OVERWRITE)) {
+ tp->owrite = (tm->cno - sp->cno) + 1;
+ tp->insert = (len - tm->cno) - 1;
+ } else
+ tp->insert = len - sp->cno;
+
+ if (LF_ISSET(TXT_EMARK))
+ tp->lb[tm->cno] = END_CH;
+ }
+
+ /*
+ * Many of the special cases in this routine 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 (txt_auto(sp, ep, ai_line, NULL, 0, tp))
+ return (1);
+ sp->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 = sp->cno;
+ } else
+ tp->offset = sp->cno;
+ }
+
+ /* If getting a command buffer from the user, there may be a prompt. */
+ if (LF_ISSET(TXT_PROMPT)) {
+ tp->lb[sp->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[sp->cno] = CURSOR_CH;
+ ++tp->len;
+ ++tp->insert;
+ }
+
+ /*
+ * Historic practice is that the wrapmargin value was a distance
+ * from the RIGHT-HAND column, not the left. It's more useful to
+ * us as a distance from the left-hand column.
+ *
+ * !!!
+ * Replay commands are not affected by wrapmargin values. What
+ * I found surprising was that people actually depend on it, as
+ * in this gem of a macro which centers lines:
+ *
+ * map #c $mq81a ^V^[81^V|D`qld0:s/ / /g^V^M$p
+ *
+ * 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.
+ */
+ if (LF_ISSET(TXT_REPLAY) || !LF_ISSET(TXT_WRAPMARGIN))
+ margin = 0;
+ else if ((margin = O_VAL(sp, O_WRAPMARGIN)) != 0)
+ margin = sp->cols - margin;
+
+ /* Initialize abbreviations checks. */
+ if (F_ISSET(sp->gp, G_ABBREV) && LF_ISSET(TXT_MAPINPUT)) {
+ abb = A_INWORD;
+ ab_cnt = ab_turnoff = 0;
+ } else
+ abb = A_NOTSET;
+
+ /*
+ * Set up the dot command. Dot commands are done by saving the
+ * actual characters and replaying the input. We have to push
+ * the characters onto the key stack and then handle them normally,
+ * otherwise things like wrapmargin will fail.
+ *
+ * XXX
+ * It would be nice if we could swallow backspaces and such, but
+ * it's not all that easy to do. Another possibility would be to
+ * recognize full line insertions, which could be performed quickly,
+ * without replay.
+ */
+nullreplay:
+ rcol = 0;
+ if (replay = LF_ISSET(TXT_REPLAY)) {
+ /*
+ * !!!
+ * Historically, it wasn't an error to replay non-existent
+ * input. This test is necessary, we get here by the user
+ * doing an input command followed by a nul.
+ *
+ * !!!
+ * Historically, vi did not remap or reabbreviate replayed
+ * input. It did, however, beep at you if you changed an
+ * abbreviation and then replayed the input. We're not that
+ * compatible.
+ */
+ if (VIP(sp)->rep == NULL)
+ return (0);
+ if (term_push(sp, VIP(sp)->rep, VIP(sp)->rep_cnt, 0, CH_NOMAP))
+ return (1);
+ testnr = 0;
+ abb = A_NOTSET;
+ LF_CLR(TXT_RECORD);
+ } else
+ testnr = 1;
+
+ unmap_tst = LF_ISSET(TXT_MAPINPUT) && LF_ISSET(TXT_INFOLINE);
+ iflags = LF_ISSET(TXT_MAPCOMMAND | TXT_MAPINPUT);
+ for (showmatch = 0,
+ carat_st = C_NOTSET, hex = H_NOTSET, quoted = Q_NOTSET;;) {
+ /*
+ * Reset the line and update the screen. (The txt_showmatch()
+ * code refreshes the screen for us.) Don't refresh unless
+ * we're about to wait on a character or we need to know where
+ * the cursor really is.
+ */
+ if (showmatch || margin || !KEYS_WAITING(sp)) {
+ if (sp->s_change(sp, ep, tp->lno, LINE_RESET))
+ goto err;
+ if (showmatch) {
+ showmatch = 0;
+ txt_showmatch(sp, ep);
+ } else if (sp->s_refresh(sp, ep))
+ goto err;
+ }
+
+ /* Get the next character. */
+next_ch: if (term_key(sp, &ikey, quoted == Q_THISCHAR ?
+ iflags & ~(TXT_MAPCOMMAND | TXT_MAPINPUT) :
+ iflags) != INP_OK)
+ goto err;
+ ch = ikey.ch;
+
+ /* Abbreviation check. See comment in txt_abbrev(). */
+#define MAX_ABBREVIATION_EXPANSION 256
+ if (ikey.flags & CH_ABBREVIATED) {
+ if (++ab_cnt > MAX_ABBREVIATION_EXPANSION) {
+ term_ab_flush(sp,
+ "Abbreviation exceeded maximum number of characters");
+ ab_cnt = 0;
+ continue;
+ }
+ } else
+ ab_cnt = 0;
+
+ /*
+ * !!!
+ * Historic feature. If the first character of the input is
+ * a nul, replay the previous input. This isn't documented
+ * anywhere, and is a great test of vi clones.
+ */
+ if (ch == '\0' && testnr) {
+ LF_SET(TXT_REPLAY);
+ goto nullreplay;
+ }
+ testnr = 0;
+
+ /*
+ * Check to see if the character fits into the input (and
+ * replay, if necessary) buffers. It isn't necessary to
+ * have tp->len bytes, since it doesn't consider overwrite
+ * characters, but not worth fixing.
+ */
+ if (LF_ISSET(TXT_RECORD)) {
+ TBINC(sp, VIP(sp)->rep, VIP(sp)->rep_len, rcol + 1);
+ VIP(sp)->rep[rcol++] = ch;
+ }
+ TBINC(sp, tp->lb, tp->lb_len, tp->len + 1);
+
+ /*
+ * If the character was quoted, replace the last character
+ * (the literal mark) with the new character. If quoted
+ * by someone else, simply insert the character.
+ *
+ * !!!
+ * Extension -- if the quoted character is HEX_CH, enter hex
+ * mode. If the user enters "<HEX_CH>[isxdigit()]*" we will
+ * try to use the value as a character. Anything else resets
+ * hex mode.
+ */
+ if (ikey.flags & CH_QUOTED)
+ goto insq_ch;
+ if (quoted == Q_THISCHAR) {
+ --sp->cno;
+ ++tp->owrite;
+ quoted = Q_NOTSET;
+
+ if (ch == HEX_CH)
+ hex = H_NEXTCHAR;
+ goto insq_ch;
+ }
+
+ switch (ikey.value) {
+ case K_CR:
+ case K_NL: /* New line. */
+#define LINE_RESOLVE { \
+ /* \
+ * Handle abbreviations. If there was one, \
+ * discard the replay characters. \
+ */ \
+ if (abb == A_INWORD && !replay) { \
+ if (txt_abbrev(sp, tp, &ch, \
+ LF_ISSET(TXT_INFOLINE), &tmp, \
+ &ab_turnoff)) \
+ goto err; \
+ if (tmp) { \
+ if (LF_ISSET(TXT_RECORD)) \
+ rcol -= tmp; \
+ goto next_ch; \
+ } \
+ } \
+ if (abb != A_NOTSET) \
+ abb = A_NOTWORD; \
+ if (unmap_tst) \
+ txt_unmap(sp, tp, &iflags); \
+ /* Handle hex numbers. */ \
+ if (hex == H_INHEX) { \
+ if (txt_hex(sp, tp, &tmp, &ch)) \
+ goto err; \
+ if (tmp) { \
+ hex = H_NOTSET; \
+ goto next_ch; \
+ } \
+ } \
+ /* Clean up for the 'R' command. */ \
+ if (LF_ISSET(TXT_REPLACE)) \
+ txt_Rcleanup(sp, tiqh, tp, lp, len); \
+ /* Delete any appended cursor. */ \
+ if (LF_ISSET(TXT_APPENDEOL)) { \
+ --tp->len; \
+ --tp->insert; \
+ } \
+}
+ LINE_RESOLVE;
+
+ /* CR returns from the vi command line. */
+ if (LF_ISSET(TXT_CR)) {
+ /*
+ * If a script window and not the colon
+ * line, push a <cr> so it gets executed.
+ */
+ if (F_ISSET(sp, S_SCRIPT) &&
+ !LF_ISSET(TXT_INFOLINE))
+ (void)term_push(sp,
+ "\r", 1, 0, CH_NOMAP);
+ goto k_escape;
+ }
+
+ /*
+ * Historic practice was to delete any <blank>
+ * characters following the inserted newline.
+ * This affects the 'R', 'c', and 's' commands.
+ */
+ for (p = tp->lb + sp->cno + tp->owrite;
+ tp->insert && isblank(*p);
+ ++p, ++tp->owrite, --tp->insert);
+
+ /*
+ * Move any remaining insert characters into
+ * a new TEXT structure.
+ */
+ if ((ntp = text_init(sp,
+ tp->lb + sp->cno + tp->owrite,
+ tp->insert, tp->insert + 32)) == NULL)
+ goto err;
+
+ /* Set bookkeeping for the new line. */
+ ntp->lno = tp->lno + 1;
+ ntp->insert = tp->insert;
+
+ /*
+ * Note if the user inserted any characters on this
+ * line. Done before calling txt_ai_resolve() because
+ * it changes the value of sp->cno without making the
+ * corresponding changes to tp->ai.
+ */
+ tmp = sp->cno <= tp->ai;
+
+ /*
+ * Resolve autoindented characters for the old line.
+ * Reset the autoindent line value. 0^D keeps the ai
+ * line from changing, ^D changes the level, even if
+ * there are no characters in the old line. Note,
+ * if using the current tp structure, use the cursor
+ * as the length, the user may have erased autoindent
+ * characters.
+ */
+ if (LF_ISSET(TXT_AUTOINDENT)) {
+ txt_ai_resolve(sp, tp);
+
+ if (carat_st == C_NOCHANGE) {
+ if (txt_auto(sp, ep,
+ OOBLNO, &ait, ait.ai, ntp))
+ goto err;
+ FREE_SPACE(sp, ait.lb, ait.lb_len);
+ } else
+ if (txt_auto(sp, ep,
+ OOBLNO, tp, sp->cno, ntp))
+ goto err;
+ carat_st = C_NOTSET;
+ }
+
+ /*
+ * If the user hasn't entered any characters, delete
+ * any autoindent characters.
+ *
+ * !!!
+ * Historic vi didn't get the insert test right, if
+ * there were characters after the cursor, entering
+ * a <cr> left the autoindent characters on the line.
+ */
+ if (tmp)
+ sp->cno = 0;
+
+ /* Reset bookkeeping for the old line. */
+ tp->len = sp->cno;
+ tp->ai = tp->insert = tp->owrite = 0;
+
+ /* New cursor position. */
+ sp->cno = ntp->ai;
+
+ /* New lines are TXT_APPENDEOL if nothing to insert. */
+ if (ntp->insert == 0) {
+ TBINC(sp, ntp->lb, ntp->lb_len, ntp->len + 1);
+ LF_SET(TXT_APPENDEOL);
+ ntp->lb[sp->cno] = CURSOR_CH;
+ ++ntp->insert;
+ ++ntp->len;
+ }
+
+ /* Update the old line. */
+ if (sp->s_change(sp, ep, tp->lno, LINE_RESET))
+ goto err;
+
+ /*
+ * Swap old and new TEXT's, 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:file_gline() will be wrong.)
+ */
+ tp = ntp;
+ CIRCLEQ_INSERT_TAIL(tiqh, tp, q);
+
+ /* Reset the cursor. */
+ sp->lno = tp->lno;
+
+ /* Update the new line. */
+ if (sp->s_change(sp, ep, tp->lno, LINE_INSERT))
+ goto err;
+
+ /* Set the renumber bit. */
+ F_SET(sp, S_RENUMBER);
+
+ /* Refresh if nothing waiting. */
+ if ((margin || !KEYS_WAITING(sp)) &&
+ sp->s_refresh(sp, ep))
+ goto err;
+ goto next_ch;
+ case K_ESCAPE: /* Escape. */
+ if (!LF_ISSET(TXT_ESCAPE))
+ goto ins_ch;
+
+ LINE_RESOLVE;
+
+ /*
+ * If there aren't any trailing characters in the line
+ * and the user hasn't entered any characters, delete
+ * the autoindent characters.
+ */
+ if (!tp->insert && sp->cno <= tp->ai) {
+ tp->len = tp->owrite = 0;
+ sp->cno = 0;
+ } else if (LF_ISSET(TXT_AUTOINDENT))
+ txt_ai_resolve(sp, tp);
+
+ /* If there are insert characters, copy them down. */
+k_escape: if (tp->insert && tp->owrite)
+ memmove(tp->lb + sp->cno,
+ tp->lb + sp->cno + tp->owrite, tp->insert);
+ tp->len -= tp->owrite;
+
+ /*
+ * Delete any lines that were inserted into the text
+ * structure and then erased.
+ */
+ while (tp->q.cqe_next != (void *)tiqh) {
+ ntp = tp->q.cqe_next;
+ CIRCLEQ_REMOVE(tiqh, ntp, q);
+ text_free(ntp);
+ }
+
+ /*
+ * If not resolving the lines into the file, end
+ * it with a nul.
+ *
+ * XXX
+ * This is wrong, should pass back a length.
+ */
+ if (LF_ISSET(TXT_RESOLVE)) {
+ if (txt_resolve(sp, ep, tiqh))
+ goto err;
+ /*
+ * Clear input flag -- input buffer no longer
+ * valid.
+ */
+ F_CLR(sp, S_INPUT);
+ } else {
+ TBINC(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 (rp != NULL) {
+ rp->lno = tp->lno;
+ rp->cno = sp->cno ? sp->cno - 1 : 0;
+ if (sp->s_change(sp, ep, rp->lno, LINE_RESET))
+ goto err;
+ }
+ goto ret;
+ case K_CARAT: /* Delete autoindent chars. */
+ if (LF_ISSET(TXT_AUTOINDENT) && sp->cno <= tp->ai)
+ carat_st = C_CARATSET;
+ goto ins_ch;
+ case K_ZERO: /* Delete autoindent chars. */
+ if (LF_ISSET(TXT_AUTOINDENT) && sp->cno <= tp->ai)
+ carat_st = 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 (sp->cno == 0 || tp->ai == 0)
+ break;
+ switch (carat_st) {
+ case C_CARATSET: /* ^^D */
+ if (sp->cno > tp->ai + tp->offset + 1)
+ goto ins_ch;
+
+ /* Save the ai string for later. */
+ ait.lb = NULL;
+ ait.lb_len = 0;
+ TBINC(sp, ait.lb, ait.lb_len, tp->ai);
+ memmove(ait.lb, tp->lb, tp->ai);
+ ait.ai = ait.len = tp->ai;
+
+ carat_st = C_NOCHANGE;
+ goto leftmargin;
+ case C_ZEROSET: /* 0^D */
+ if (sp->cno > tp->ai + tp->offset + 1)
+ goto ins_ch;
+ carat_st = C_NOTSET;
+leftmargin: tp->lb[sp->cno - 1] = ' ';
+ tp->owrite += sp->cno - tp->offset;
+ tp->ai = 0;
+ sp->cno = tp->offset;
+ break;
+ case C_NOTSET: /* ^D */
+ if (sp->cno > tp->ai + tp->offset)
+ goto ins_ch;
+ (void)txt_outdent(sp, tp);
+ break;
+ default:
+ abort();
+ }
+ break;
+ case K_VERASE: /* Erase the last character. */
+ /*
+ * If can erase over the prompt, return. Len is 0
+ * if backspaced over the prompt, 1 if only CR entered.
+ */
+ if (LF_ISSET(TXT_BS) && sp->cno <= tp->offset) {
+ tp->len = 0;
+ goto ret;
+ }
+
+ /*
+ * If at the beginning of the line, try and drop back
+ * to a previously inserted line.
+ */
+ if (sp->cno == 0) {
+ if ((ntp = txt_backup(sp,
+ ep, tiqh, tp, flags)) == NULL)
+ goto err;
+ tp = ntp;
+ break;
+ }
+
+ /* If nothing to erase, bell the user. */
+ if (sp->cno <= tp->offset) {
+ msgq(sp, M_BERR,
+ "No more characters to erase.");
+ break;
+ }
+
+ /* Drop back one character. */
+ --sp->cno;
+
+ /*
+ * Increment overwrite, decrement ai if deleted.
+ *
+ * !!!
+ * Historic vi did not permit users to use erase
+ * characters to delete autoindent characters.
+ */
+ ++tp->owrite;
+ if (sp->cno < tp->ai)
+ --tp->ai;
+ break;
+ case K_VINTR:
+ /*
+ * !!!
+ * Historically, <interrupt> exited the user from
+ * editing the infoline, and returned to the main
+ * screen. It also beeped the terminal, but that
+ * seems excessive.
+ */
+ if (LF_ISSET(TXT_INFOLINE)) {
+ tp->lb[tp->len = 0] = '\0';
+ goto ret;
+ }
+ goto ins_ch;
+ case K_VWERASE: /* Skip back one word. */
+ /*
+ * If at the beginning of the line, try and drop back
+ * to a previously inserted line.
+ */
+ if (sp->cno == 0) {
+ if ((ntp = txt_backup(sp,
+ ep, tiqh, tp, flags)) == NULL)
+ goto err;
+ tp = ntp;
+ }
+
+ /*
+ * If at offset, nothing to erase so bell the user.
+ */
+ if (sp->cno <= tp->offset) {
+ msgq(sp, M_BERR,
+ "No more characters to erase.");
+ break;
+ }
+
+ /*
+ * First werase goes back to any autoindent
+ * and second werase goes back to the offset.
+ *
+ * !!!
+ * Historic vi did not permit users to use erase
+ * characters to delete autoindent characters.
+ */
+ if (tp->ai && sp->cno > tp->ai)
+ max = tp->ai;
+ else {
+ tp->ai = 0;
+ max = tp->offset;
+ }
+
+ /* Skip over trailing space characters. */
+ while (sp->cno > max && isblank(tp->lb[sp->cno - 1])) {
+ --sp->cno;
+ ++tp->owrite;
+ }
+ if (sp->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. Here, the 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.
+ */
+ if (LF_ISSET(TXT_TTYWERASE))
+ while (sp->cno > max) {
+ --sp->cno;
+ ++tp->owrite;
+ if (isblank(tp->lb[sp->cno - 1]))
+ break;
+ }
+ else {
+ if (LF_ISSET(TXT_ALTWERASE)) {
+ --sp->cno;
+ ++tp->owrite;
+ if (isblank(tp->lb[sp->cno - 1]))
+ break;
+ }
+ if (sp->cno > max)
+ tmp = inword(tp->lb[sp->cno - 1]);
+ while (sp->cno > max) {
+ --sp->cno;
+ ++tp->owrite;
+ if (tmp != inword(tp->lb[sp->cno - 1])
+ || isblank(tp->lb[sp->cno - 1]))
+ break;
+ }
+ }
+ break;
+ case K_VKILL: /* Restart this line. */
+ /*
+ * If at the beginning of the line, try and drop back
+ * to a previously inserted line.
+ */
+ if (sp->cno == 0) {
+ if ((ntp = txt_backup(sp,
+ ep, tiqh, tp, flags)) == NULL)
+ goto err;
+ tp = ntp;
+ }
+
+ /* If at offset, nothing to erase so bell the user. */
+ if (sp->cno <= tp->offset) {
+ msgq(sp, M_BERR,
+ "No more characters to erase.");
+ 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 && sp->cno > tp->ai)
+ max = tp->ai;
+ else {
+ tp->ai = 0;
+ max = tp->offset;
+ }
+ tp->owrite += sp->cno - max;
+ sp->cno = max;
+ break;
+ case K_CNTRLT: /* Add autoindent char. */
+ if (!LF_ISSET(TXT_CNTRLT))
+ goto ins_ch;
+ if (txt_indent(sp, tp))
+ goto err;
+ goto ebuf_chk;
+ case K_CNTRLZ:
+ (void)sp->s_suspend(sp);
+ break;
+#ifdef HISTORIC_PRACTICE_IS_TO_INSERT_NOT_REPAINT
+ case K_FORMFEED:
+ F_SET(sp, S_REFRESH);
+ break;
+#endif
+ case K_RIGHTBRACE:
+ case K_RIGHTPAREN:
+ showmatch = LF_ISSET(TXT_SHOWMATCH);
+ goto ins_ch;
+ case K_VLNEXT: /* Quote the next character. */
+ /* If in hex mode, see if we've entered a hex value. */
+ if (hex == H_INHEX) {
+ if (txt_hex(sp, tp, &tmp, &ch))
+ goto err;
+ if (tmp) {
+ hex = H_NOTSET;
+ goto next_ch;
+ }
+ }
+ ch = '^';
+ quoted = Q_NEXTCHAR;
+ 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(ch) &&
+ ikey.value != K_FORMFEED && ikey.value != K_TAB) {
+ msgq(sp, M_BERR,
+ "Illegal character; quote to enter.");
+ break;
+ }
+insq_ch: /*
+ * If entering a non-word character after a word, check
+ * for abbreviations. If there was one, discard the
+ * replay characters. If entering a blank character,
+ * check for unmap commands, as well.
+ */
+ if (!inword(ch)) {
+ if (abb == A_INWORD && !replay) {
+ if (txt_abbrev(sp, tp, &ch,
+ LF_ISSET(TXT_INFOLINE),
+ &tmp, &ab_turnoff))
+ goto err;
+ if (tmp) {
+ if (LF_ISSET(TXT_RECORD))
+ rcol -= tmp;
+ goto next_ch;
+ }
+ }
+ if (isblank(ch) && unmap_tst)
+ txt_unmap(sp, tp, &iflags);
+ }
+ /* If in hex mode, see if we've entered a hex value. */
+ if (hex == H_INHEX && !isxdigit(ch)) {
+ if (txt_hex(sp, tp, &tmp, &ch))
+ goto err;
+ if (tmp) {
+ hex = H_NOTSET;
+ goto next_ch;
+ }
+ }
+ /* Check to see if we've crossed the margin. */
+ if (margin) {
+ if (sp->s_column(sp, ep, &col))
+ goto err;
+ if (col >= margin) {
+ if (txt_margin(sp, tp, &tmp, &ch))
+ goto err;
+ if (tmp)
+ goto next_ch;
+ }
+ }
+ if (abb != A_NOTSET)
+ abb = inword(ch) ? A_INWORD : A_NOTWORD;
+
+ if (tp->owrite) /* Overwrite a character. */
+ --tp->owrite;
+ else if (tp->insert) { /* Insert a character. */
+ ++tp->len;
+ if (tp->insert == 1)
+ tp->lb[sp->cno + 1] = tp->lb[sp->cno];
+ else
+ memmove(tp->lb + sp->cno + 1,
+ tp->lb + sp->cno, tp->insert);
+ }
+
+ tp->lb[sp->cno++] = ch;
+
+ /*
+ * 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 (sp->cno >= tp->len) {
+ TBINC(sp, tp->lb, tp->lb_len, tp->len + 1);
+ LF_SET(TXT_APPENDEOL);
+ tp->lb[sp->cno] = CURSOR_CH;
+ ++tp->insert;
+ ++tp->len;
+ }
+
+ if (hex == H_NEXTCHAR)
+ hex = H_INHEX;
+ if (quoted == Q_NEXTCHAR)
+ quoted = Q_THISCHAR;
+ break;
+ }
+#if defined(DEBUG) && 1
+ if (sp->cno + tp->insert + tp->owrite != tp->len)
+ msgq(sp, M_ERR,
+ "len %u != cno: %u ai: %u insert %u overwrite %u",
+ tp->len, sp->cno, tp->ai, tp->insert, tp->owrite);
+ tp->len = sp->cno + tp->insert + tp->owrite;
+#endif
+ }
+
+ /* Clear input flag. */
+ret: F_CLR(sp, S_INPUT);
+
+ if (LF_ISSET(TXT_RECORD))
+ VIP(sp)->rep_cnt = rcol;
+ return (eval);
+
+ /* Error jump. */
+err: eval = 1;
+ txt_err(sp, ep, tiqh);
+ goto ret;
+}
+
+/*
+ * 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;
+{
+ CHAR_T ch;
+ SEQ *qp;
+ size_t len, off;
+ char *p;
+
+ /*
+ * Find the start of the "word". Historically, abbreviations
+ * could be preceded by any non-word character.
+ */
+ for (off = sp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) {
+ if (!inword(*p)) {
+ ++p;
+ break;
+ }
+ ++len;
+ if (off == tp->ai || off == tp->offset)
+ 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
+ * unabbreviates "foo1", and the commands:
+ * :ab foo bar
+ * :ab bar baz
+ * unabbreviates "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 go ahead an turn
+ * abbreviations back on.
+ *
+ * This makes the layering look like a Nachos Supreme.
+ */
+ *didsubp = 0;
+ 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, 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 (term_push(sp, &ch, 1, 0, CH_ABBREVIATED))
+ return (1);
+ if (term_push(sp, qp->output, qp->olen, 0, CH_ABBREVIATED))
+ return (1);
+
+ /*
+ * Move the cursor to the start of the abbreviation,
+ * adjust the length.
+ */
+ sp->cno -= len;
+ tp->len -= len;
+
+ /* Copy any insert characters back. */
+ if (tp->insert)
+ memmove(tp->lb + sp->cno + tp->owrite,
+ tp->lb + sp->cno + tp->owrite + len, tp->insert);
+
+ /*
+ * 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, iflagsp)
+ SCR *sp;
+ TEXT *tp;
+ u_int *iflagsp;
+{
+ size_t len, off;
+ char *p;
+
+ /* Find the beginning of this "word". */
+ for (off = sp->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))
+ *iflagsp &= ~TXT_MAPINPUT;
+ else
+ *iflagsp |= TXT_MAPINPUT;
+}
+
+
+/* Offset to next column of stop size. */
+#define STOP_OFF(c, stop) (stop - (c) % stop)
+
+/*
+ * txt_ai_resolve --
+ * When a line is resolved by <esc> or <cr>, review autoindent
+ * characters.
+ */
+static void
+txt_ai_resolve(sp, tp)
+ SCR *sp;
+ TEXT *tp;
+{
+ u_long ts;
+ int del;
+ size_t cno, len, new, old, scno, spaces, tab_after_sp, tabs;
+ char *p;
+
+ /*
+ * If the line is empty, has an offset, or no autoindent
+ * characters, we're done.
+ */
+ if (!tp->len || tp->offset || !tp->ai)
+ 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 += STOP_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 + STOP_OFF(cno, ts) <= scno; ++tabs)
+ cno += STOP_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);
+ sp->cno -= del;
+ tp->len -= del;
+
+ /* Fill in space/tab characters. */
+ for (p = tp->lb; tabs--;)
+ *p++ = '\t';
+ while (spaces--)
+ *p++ = ' ';
+}
+
+/*
+ * txt_auto --
+ * Handle autoindent. If aitp isn't NULL, use it, otherwise,
+ * retrieve the line.
+ */
+int
+txt_auto(sp, ep, lno, aitp, len, tp)
+ SCR *sp;
+ EXF *ep;
+ recno_t lno;
+ size_t len;
+ TEXT *aitp, *tp;
+{
+ size_t nlen;
+ char *p, *t;
+
+ if (aitp == NULL) {
+ if ((t = file_gline(sp, ep, lno, &len)) == NULL)
+ return (0);
+ } 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, ep, tiqh, tp, flags)
+ SCR *sp;
+ EXF *ep;
+ TEXTH *tiqh;
+ TEXT *tp;
+ u_int flags;
+{
+ TEXT *ntp;
+ recno_t lno;
+ size_t total;
+
+ /* Get a handle on the previous TEXT structure. */
+ if ((ntp = tp->q.cqe_prev) == (void *)tiqh) {
+ msgq(sp, M_BERR, "Already at the beginning of the insert");
+ return (tp);
+ }
+
+ /* Make sure that we have enough space. */
+ total = ntp->len + tp->insert;
+ if (LF_ISSET(TXT_APPENDEOL))
+ ++total;
+ if (total > ntp->lb_len &&
+ binc(sp, &ntp->lb, &ntp->lb_len, total))
+ return (NULL);
+
+ /*
+ * Append a cursor or copy inserted bytes to the end of the old line.
+ * Test for appending a cursor first, because the TEXT insert field
+ * will be 1 if we're appending a cursor. I don't think there's a
+ * third case, so abort() if there is.
+ */
+ if (LF_ISSET(TXT_APPENDEOL)) {
+ ntp->lb[ntp->len] = CURSOR_CH;
+ ntp->insert = 1;
+ } else if (tp->insert) {
+ memmove(ntp->lb + ntp->len, tp->lb + tp->owrite, tp->insert);
+ ntp->insert = tp->insert;
+ } else
+ abort();
+
+ /* Set bookkeeping information. */
+ sp->lno = ntp->lno;
+ sp->cno = ntp->len;
+ ntp->len += ntp->insert;
+
+ /* Release the current TEXT. */
+ lno = tp->lno;
+ CIRCLEQ_REMOVE(tiqh, tp, q);
+ text_free(tp);
+
+ /* Update the old line on the screen. */
+ if (sp->s_change(sp, ep, lno, LINE_DELETE))
+ return (NULL);
+
+ /* Return the old line. */
+ return (ntp);
+}
+
+/*
+ * txt_err --
+ * Handle an error during input processing.
+ */
+static void
+txt_err(sp, ep, tiqh)
+ SCR *sp;
+ EXF *ep;
+ TEXTH *tiqh;
+{
+ recno_t lno;
+ size_t len;
+
+ /*
+ * 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;
+ file_gline(sp, ep, lno, &len) == NULL && lno > 0; --lno);
+
+ sp->lno = lno == 0 ? 1 : lno;
+ sp->cno = 0;
+
+ /* Redraw the screen, just in case. */
+ F_SET(sp, S_REDRAW);
+}
+
+/*
+ * txt_hex --
+ * Let the user insert any character value they want.
+ *
+ * !!!
+ * This is an extension. The pattern "^Vx[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, was_hex, pushcp)
+ SCR *sp;
+ TEXT *tp;
+ int *was_hex;
+ CHAR_T *pushcp;
+{
+ CHAR_T ch, 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[sp->cno];
+ tp->lb[sp->cno] = 0;
+
+ /* Find the previous HEX_CH. */
+ for (off = sp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) {
+ if (*p == HEX_CH) {
+ wp = p + 1;
+ break;
+ }
+ ++len;
+ /* If not on this line, there's nothing to do. */
+ if (off == tp->ai || off == tp->offset)
+ goto nothex;
+ }
+
+ /* If no length, then it wasn't a hex value. */
+ if (len == 0)
+ goto nothex;
+
+ /* Get the value. */
+ value = strtol(wp, NULL, 16);
+ if (value == LONG_MIN || value == LONG_MAX || value > MAX_CHAR_T) {
+nothex: tp->lb[sp->cno] = savec;
+ *was_hex = 0;
+ return (0);
+ }
+
+ ch = *pushcp;
+ if (term_push(sp, &ch, 1, 0, CH_NOMAP | CH_QUOTED))
+ return (1);
+ ch = value;
+ if (term_push(sp, &ch, 1, 0, CH_NOMAP | CH_QUOTED))
+ return (1);
+
+ tp->lb[sp->cno] = savec;
+
+ /* Move the cursor to the start of the hex value, adjust the length. */
+ sp->cno -= len + 1;
+ tp->len -= len + 1;
+
+ /* Copy any insert characters back. */
+ if (tp->insert)
+ memmove(tp->lb + sp->cno + tp->owrite,
+ tp->lb + sp->cno + tp->owrite + len + 1, tp->insert);
+
+ *was_hex = 1;
+ return (0);
+}
+
+/*
+ * Txt_indent and txt_outdent are 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 only had special meaning when
+ * they were the first characters typed after entering text input mode.
+ * Since normal erase characters couldn't erase autoindent (in this case
+ * ^T) characters, this meant that inserting text into previously existing
+ * text was quite strange, ^T only worked if it was the first keystroke,
+ * and then it could only be erased by using ^D. This implementation treats
+ * ^T specially anywhere it occurs in the input, and permits the standard
+ * erase characters to erase characters inserted using it.
+ *
+ * XXX
+ * Technically, txt_indent, txt_outdent should part of the screen interface,
+ * as they require knowledge of the size of a space character on the screen.
+ * (Not the size of tabs, because tabs are logically composed of spaces.)
+ * They're left in the text code because they're complicated, not to mention
+ * the gruesome awareness that if spaces aren't a single column on the screen
+ * for any language, we're into some serious, ah, for lack of a better word,
+ * "issues".
+ */
+
+/*
+ * txt_indent --
+ * Handle ^T indents.
+ */
+static int
+txt_indent(sp, tp)
+ SCR *sp;
+ TEXT *tp;
+{
+ u_long sw, ts;
+ size_t cno, off, scno, spaces, tabs;
+
+ ts = O_VAL(sp, O_TABSTOP);
+ sw = O_VAL(sp, O_SHIFTWIDTH);
+
+ /* Get the current screen column. */
+ for (off = scno = 0; off < sp->cno; ++off)
+ if (tp->lb[off] == '\t')
+ scno += STOP_OFF(scno, ts);
+ else
+ ++scno;
+
+ /* Count up spaces/tabs needed to get to the target. */
+ for (cno = scno, scno += STOP_OFF(scno, sw), tabs = 0;
+ cno + STOP_OFF(cno, ts) <= scno; ++tabs)
+ cno += STOP_OFF(cno, ts);
+ spaces = scno - cno;
+
+ /* Put space/tab characters in place of any overwrite characters. */
+ for (; tp->owrite && tabs; --tp->owrite, --tabs, ++tp->ai)
+ tp->lb[sp->cno++] = '\t';
+ for (; tp->owrite && spaces; --tp->owrite, --spaces, ++tp->ai)
+ tp->lb[sp->cno++] = ' ';
+
+ if (!tabs && !spaces)
+ return (0);
+
+ /* Make sure there's enough room. */
+ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + spaces + tabs);
+
+ /* Move the insert characters out of the way. */
+ if (tp->insert)
+ memmove(tp->lb + sp->cno + spaces + tabs,
+ tp->lb + sp->cno, tp->insert);
+
+ /* Add new space/tab characters. */
+ for (; tabs--; ++tp->len, ++tp->ai)
+ tp->lb[sp->cno++] = '\t';
+ for (; spaces--; ++tp->len, ++tp->ai)
+ tp->lb[sp->cno++] = ' ';
+ return (0);
+}
+
+/*
+ * txt_outdent --
+ * Handle ^D outdents.
+ *
+ */
+static int
+txt_outdent(sp, tp)
+ SCR *sp;
+ TEXT *tp;
+{
+ u_long sw, ts;
+ size_t cno, off, scno, spaces;
+
+ ts = O_VAL(sp, O_TABSTOP);
+ sw = O_VAL(sp, O_SHIFTWIDTH);
+
+ /* Get the current screen column. */
+ for (off = scno = 0; off < sp->cno; ++off)
+ if (tp->lb[off] == '\t')
+ scno += STOP_OFF(scno, ts);
+ else
+ ++scno;
+
+ /* Get the previous shiftwidth column. */
+ for (cno = scno; --scno % sw != 0;);
+
+ /* Decrement characters until less than or equal to that slot. */
+ for (; cno > scno; --sp->cno, --tp->ai, ++tp->owrite)
+ if (tp->lb[--off] == '\t')
+ cno -= STOP_OFF(cno, ts);
+ else
+ --cno;
+
+ /* Spaces needed to get to the target. */
+ spaces = scno - cno;
+
+ /* Maybe just a delete. */
+ if (spaces == 0)
+ return (0);
+
+ /* Make sure there's enough room. */
+ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + spaces);
+
+ /* Use up any overwrite characters. */
+ for (; tp->owrite && spaces; --spaces, ++tp->ai, --tp->owrite)
+ tp->lb[sp->cno++] = ' ';
+
+ /* Maybe that was enough. */
+ if (spaces == 0)
+ return (0);
+
+ /* Move the insert characters out of the way. */
+ if (tp->insert)
+ memmove(tp->lb + sp->cno + spaces,
+ tp->lb + sp->cno, tp->insert);
+
+ /* Add new space characters. */
+ for (; spaces--; ++tp->len, ++tp->ai)
+ tp->lb[sp->cno++] = ' ';
+ return (0);
+}
+
+/*
+ * txt_resolve --
+ * Resolve the input text chain into the file.
+ */
+static int
+txt_resolve(sp, ep, tiqh)
+ SCR *sp;
+ EXF *ep;
+ TEXTH *tiqh;
+{
+ TEXT *tp;
+ recno_t lno;
+
+ /* The first line replaces a current line. */
+ tp = tiqh->cqh_first;
+ if (file_sline(sp, ep, tp->lno, tp->lb, tp->len))
+ return (1);
+
+ /* All subsequent lines are appended into the file. */
+ for (lno = tp->lno; (tp = tp->q.cqe_next) != (void *)&sp->tiq; ++lno)
+ if (file_aline(sp, ep, 0, lno, tp->lb, tp->len))
+ return (1);
+ return (0);
+}
+
+/*
+ * txt_showmatch --
+ * Show a character match.
+ *
+ * !!!
+ * Historic vi tried to display matches even in the :colon command line.
+ * I think not.
+ */
+static void
+txt_showmatch(sp, ep)
+ SCR *sp;
+ EXF *ep;
+{
+ struct timeval second;
+ VCS cs;
+ MARK m;
+ fd_set zero;
+ int cnt, endc, startc;
+
+ /*
+ * Do a refresh first, in case the v_ntext() code hasn't done
+ * one in awhile, so the user can see what we're complaining
+ * about.
+ */
+ if (sp->s_refresh(sp, ep))
+ return;
+ /*
+ * We don't display the match if it's not on the screen. Find
+ * out what the first character on the screen is.
+ */
+ if (sp->s_position(sp, ep, &m, 0, P_TOP))
+ return;
+
+ /* Initialize the getc() interface. */
+ cs.cs_lno = sp->lno;
+ cs.cs_cno = sp->cno - 1;
+ if (cs_init(sp, ep, &cs))
+ return;
+ startc = (endc = cs.cs_ch) == ')' ? '(' : '{';
+
+ /* Search for the match. */
+ for (cnt = 1;;) {
+ if (cs_prev(sp, ep, &cs))
+ return;
+ if (cs.cs_lno < m.lno ||
+ cs.cs_lno == m.lno && cs.cs_cno < m.cno)
+ return;
+ if (cs.cs_flags != 0) {
+ if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) {
+ (void)sp->s_bell(sp);
+ return;
+ }
+ continue;
+ }
+ if (cs.cs_ch == endc)
+ ++cnt;
+ else if (cs.cs_ch == startc && --cnt == 0)
+ break;
+ }
+
+ /* Move to the match. */
+ m.lno = sp->lno;
+ m.cno = sp->cno;
+ sp->lno = cs.cs_lno;
+ sp->cno = cs.cs_cno;
+ (void)sp->s_refresh(sp, ep);
+
+ /*
+ * Sleep(3) is eight system calls. Do it fast -- besides,
+ * I don't want to wait an entire second.
+ */
+ FD_ZERO(&zero);
+ second.tv_sec = O_VAL(sp, O_MATCHTIME) / 10;
+ second.tv_usec = (O_VAL(sp, O_MATCHTIME) % 10) * 100000L;
+ (void)select(0, &zero, &zero, &zero, &second);
+
+ /* Return to the current location. */
+ sp->lno = m.lno;
+ sp->cno = m.cno;
+ (void)sp->s_refresh(sp, ep);
+}
+
+/*
+ * txt_margin --
+ * Handle margin wrap.
+ *
+ * !!!
+ * Historic vi belled the user 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, it tends to wake the cats.
+ */
+static int
+txt_margin(sp, tp, didbreak, pushcp)
+ SCR *sp;
+ TEXT *tp;
+ int *didbreak;
+ CHAR_T *pushcp;
+{
+ CHAR_T ch;
+ size_t len, off, tlen;
+ char *p, *wp;
+
+ /* Find the closest previous blank. */
+ for (off = sp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) {
+ if (isblank(*p)) {
+ wp = p + 1;
+ break;
+ }
+ ++len;
+ /* If it's the beginning of the line, there's nothing to do. */
+ if (off == tp->ai || off == tp->offset) {
+ *didbreak = 0;
+ return (0);
+ }
+ }
+
+ /*
+ * Historic practice is to delete any trailing whitespace
+ * from the previous line.
+ */
+ for (tlen = len;; --p, --off) {
+ if (!isblank(*p))
+ break;
+ ++tlen;
+ if (off == tp->ai || off == tp->offset)
+ break;
+ }
+
+ ch = *pushcp;
+ if (term_push(sp, &ch, 1, 0, CH_NOMAP))
+ return (1);
+ if (len && term_push(sp, wp, len, 0, CH_NOMAP | CH_QUOTED))
+ return (1);
+ ch = '\n';
+ if (term_push(sp, &ch, 1, 0, CH_NOMAP))
+ return (1);
+
+ sp->cno -= tlen;
+ tp->owrite += tlen;
+ *didbreak = 1;
+ return (0);
+}
+
+/*
+ * txt_Rcleanup --
+ * Resolve the input line for the 'R' command.
+ */
+static void
+txt_Rcleanup(sp, tiqh, tp, lp, len)
+ SCR *sp;
+ TEXTH *tiqh;
+ TEXT *tp;
+ const char *lp;
+ const size_t len;
+{
+ size_t tmp;
+
+ /*
+ * The 'R' command restores any overwritable characters in the
+ * first line to the original characters. Check to make sure
+ * that the cursor hasn't moved beyond the end of the original
+ * line.
+ */
+ if (tp != tiqh->cqh_first || tp->owrite == 0 || sp->cno >= len)
+ return;
+
+ /* Restore whatever we can restore from the original line. */
+ tmp = MIN(tp->owrite, len - sp->cno);
+ memmove(tp->lb + sp->cno, lp + sp->cno, tmp);
+
+ /*
+ * There can be more overwrite characters if the user extended the
+ * line and then erased it. What we have to do is delete whatever
+ * the user inserted and then erased. Regardless, we increase the
+ * insert character count to make the TEXT structure look right.
+ * (There shouldn't be any insert characters as 'R' replaces the
+ * entire line; if there are, this code isn't going to work).
+ */
+ if (tp->owrite > tmp)
+ tp->len -= tp->owrite - tmp;
+ tp->owrite = 0;
+ tp->insert = tmp;
+}