root/usr.bin/vi/vi/vi.c
/*      $OpenBSD: vi.c,v 1.23 2022/02/20 19:45:51 tb Exp $      */

/*-
 * 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"

#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(SCR *, VICMD *, VIKEYS const *);
static gcret_t  v_cmd(SCR *, VICMD *, VICMD *, VICMD *, int *, int *);
static int      v_count(SCR *, CHAR_T, u_long *);
static void     v_dtoh(SCR *);
static int      v_init(SCR *);
static gcret_t  v_key(SCR *, int, EVENT *, u_int32_t);
static int      v_keyword(SCR *);
static int      v_motion(SCR *, VICMD *, VICMD *, int *);

#if defined(DEBUG) && defined(COMLOG)
static void     v_comlog(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(SCR **);
 */
int
vi(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,
                            "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,
                            "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,
                                    "Interrupted: mapped keys discarded");
                        else
                                msgq(sp, M_ERR, "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);
                        F_CLR(sp, SC_SCR_TOP);
                        F_SET(sp, SC_SCR_CENTER);
                        (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
                }

                /* Sync recovery if changes were made. */
                if (F_ISSET(sp->ep, F_RCV_SYNC))
                        rcv_sync(sp, 0);

                /* 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(SCR *sp, VICMD *dp, VICMD *vp, VICMD *ismotion, int *comcountp,
    int *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, "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, "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, "%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, "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(SCR *sp, VICMD *dm, VICMD *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(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,
                            "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, 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_CLR(sp, SC_SCR_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(SCR *sp)
{
        GS *gp;
        SCR *tsp;
        int hidden;

        /* Move all screens to the hidden queue, tossing screen maps. */
        hidden = 0;
        gp = sp->gp;
        while ((tsp = TAILQ_FIRST(&gp->dq))) {
                free(_HMAP(tsp));
                _HMAP(tsp) = NULL;
                TAILQ_REMOVE(&gp->dq, tsp, q);
                TAILQ_INSERT_TAIL(&gp->hq, tsp, q);
                ++hidden;
        }

        /* Move current screen back to the display queue. */
        TAILQ_REMOVE(&gp->hq, sp, q);
        TAILQ_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(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, "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(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(SCR *sp, 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,
                            "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(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(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