summaryrefslogtreecommitdiffstats
path: root/contrib/nvi/vi/vi.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/nvi/vi/vi.c')
-rw-r--r--contrib/nvi/vi/vi.c1251
1 files changed, 1251 insertions, 0 deletions
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
OpenPOWER on IntegriCloud