Exemple #1
0
/*
 * ex_sdisplay --
 *	Display the list of screens.
 *
 * PUBLIC: int ex_sdisplay __P((SCR *));
 */
int
ex_sdisplay(SCR *sp)
{
	GS *gp;
	SCR *tsp;
	int cnt, sep;
	size_t col, len;

	gp = sp->gp;
	if ((tsp = TAILQ_FIRST(&gp->hq)) == NULL) {
		msgq(sp, M_INFO, "149|No background screens to display");
		return (0);
	}

	col = len = sep = 0;
	for (cnt = 1; tsp != NULL && !INTERRUPTED(sp);
	    tsp = TAILQ_NEXT(tsp, q)) {
		col += len = strlen(tsp->frp->name) + sep;
		if (col >= sp->cols - 1) {
			col = len;
			sep = 0;
			(void)ex_puts(sp, "\n");
		} else if (cnt != 1) {
			sep = 1;
			(void)ex_puts(sp, " ");
		}
		(void)ex_puts(sp, tsp->frp->name);
		++cnt;
	}
	if (!INTERRUPTED(sp))
		(void)ex_puts(sp, "\n");
	return (0);
}
Exemple #2
0
/*
 * bdisplay --
 *
 *	Display buffers.
 */
static int
bdisplay(SCR *sp)
{
	CB *cbp;

	if (sp->wp->cutq.lh_first == NULL && sp->wp->dcbp == NULL) {
		msgq(sp, M_INFO, "123|No cut buffers to display");
		return (0);
	}

	/* Display regular cut buffers. */
	for (cbp = sp->wp->cutq.lh_first; cbp != NULL; cbp = cbp->q.le_next) {
		if (ISDIGIT(cbp->name))
			continue;
		if (cbp->textq.cqh_first != (void *)&cbp->textq)
			db(sp, cbp, NULL);
		if (INTERRUPTED(sp))
			return (0);
	}
	/* Display numbered buffers. */
	for (cbp = sp->wp->cutq.lh_first; cbp != NULL; cbp = cbp->q.le_next) {
		if (!ISDIGIT(cbp->name))
			continue;
		if (cbp->textq.cqh_first != (void *)&cbp->textq)
			db(sp, cbp, NULL);
		if (INTERRUPTED(sp))
			return (0);
	}
	/* Display default buffer. */
	if ((cbp = sp->wp->dcbp) != NULL)
		db(sp, cbp, "default buffer");
	return (0);
}
Exemple #3
0
/*
 * ex_prchars --
 *	Local routine to dump characters to the screen.
 */
static int
ex_prchars(SCR *sp, const CHAR_T *p, size_t *colp, size_t len, 
	    u_int flags, int repeatc)
{
	CHAR_T ch;
	char *kp;
	GS *gp;
	size_t col, tlen, ts;

	if (O_ISSET(sp, O_LIST))
		LF_SET(E_C_LIST);
	gp = sp->gp;
	ts = O_VAL(sp, O_TABSTOP);
	for (col = *colp; len--;)
		if ((ch = *p++) == L('\t') && !LF_ISSET(E_C_LIST))
			for (tlen = ts - col % ts;
			    col < sp->cols && tlen--; ++col) {
				(void)ex_printf(sp,
				    "%c", repeatc ? repeatc : ' ');
				if (INTERRUPTED(sp))
					goto intr;
			}
		else {
			kp = KEY_NAME(sp, ch);
			tlen = KEY_COL(sp, ch);

			/*
			 * Start a new line if the last character does not fit
			 * into the current line.  The implicit new lines are
			 * not interruptible.
			 */
			if (col + tlen > sp->cols) {
				col = 0;
				(void)ex_puts(sp, "\n");
			}

			col += tlen;
			if (!repeatc) {
				(void)ex_puts(sp, kp);
				if (INTERRUPTED(sp))
					goto intr;
			} else while (tlen--) {
				(void)ex_printf(sp, "%c", repeatc);
				if (INTERRUPTED(sp))
					goto intr;
			}
			if (col == sp->cols) {
				col = 0;
				(void)ex_puts(sp, "\n");
			}
		}
intr:	*colp = col;
	return (0);
}
Exemple #4
0
/*
 * ex_ldisplay --
 *	Display a line without any preceding number.
 *
 * PUBLIC: int ex_ldisplay(SCR *, const CHAR_T *, size_t, size_t, u_int);
 */
int
ex_ldisplay(SCR *sp, const CHAR_T *p, size_t len, size_t col, u_int flags)
{
	if (len > 0 && ex_prchars(sp, p, &col, len, LF_ISSET(E_C_LIST), 0))
		return (1);
	if (!INTERRUPTED(sp) && LF_ISSET(E_C_LIST)) {
		p = L("$");
		if (ex_prchars(sp, p, &col, 1, LF_ISSET(E_C_LIST), 0))
			return (1);
	}
	if (!INTERRUPTED(sp))
		(void)ex_puts(sp, "\n");
	return (0);
}
/*
 * ex_prchars --
 *	Local routine to dump characters to the screen.
 */
static int
ex_prchars(SCR *sp, const CHAR_T *p, size_t *colp, size_t len, 
	    u_int flags, int repeatc)
{
	CHAR_T ch;
	const char *kp;
	size_t col, tlen, ts;

	if (O_ISSET(sp, O_LIST))
		LF_SET(E_C_LIST);
	ts = O_VAL(sp, O_TABSTOP);
	for (col = *colp; len--;)
		if ((ch = *p++) == L('\t') && !LF_ISSET(E_C_LIST))
			for (tlen = ts - col % ts;
			    col < sp->cols && tlen--; ++col) {
				(void)ex_printf(sp,
				    "%c", repeatc ? repeatc : ' ');
				if (INTERRUPTED(sp))
					goto intr;
			}
		else {
			/* XXXX */
			if (INTISWIDE(ch)) {
			    CHAR_T str[2] = {0, 0};
			    str[0] = ch;
			    INT2CHAR(sp, str, 2, kp, tlen);
			} else {
			    kp = (char *)KEY_NAME(sp, ch);
			    tlen = KEY_LEN(sp, ch);
			}
			if (!repeatc  && col + tlen < sp->cols) {
				(void)ex_puts(sp, kp);
				col += tlen;
			} else
				for (; tlen--; ++kp, ++col) {
					if (col == sp->cols) {
						col = 0;
						(void)ex_puts(sp, "\n");
					}
					(void)ex_printf(sp,
					    "%c", repeatc ? repeatc : *kp);
					if (INTERRUPTED(sp))
						goto intr;
				}
		}
intr:	*colp = col;
	return (0);
}
Exemple #6
0
/*
 * ex_scprint --
 *	Display a line for the substitute with confirmation routine.
 *
 * PUBLIC: int ex_scprint(SCR *, MARK *, MARK *);
 */
int
ex_scprint(SCR *sp, MARK *fp, MARK *tp)
{
	CHAR_T *p;
	size_t col, len;

	col = 0;
	if (O_ISSET(sp, O_NUMBER)) {
		p = L("        ");
		if (ex_prchars(sp, p, &col, 8, 0, 0))
			return (1);
	}

	if (db_get(sp, fp->lno, DBG_FATAL, &p, &len))
		return (1);

	if (ex_prchars(sp, p, &col, fp->cno, 0, ' '))
		return (1);
	p += fp->cno;
	if (ex_prchars(sp,
	    p, &col, tp->cno == fp->cno ? 1 : tp->cno - fp->cno, 0, '^'))
		return (1);
	if (INTERRUPTED(sp))
		return (1);
	p = L("[ynq]");		/* XXX: should be msg_cat. */
	if (ex_prchars(sp, p, &col, 5, 0, 0))
		return (1);
	(void)ex_fflush(sp);
	return (0);
}
Exemple #7
0
/*
 * ex_viusage -- :viusage [key]
 *	Display vi usage strings.
 *
 * PUBLIC: int ex_viusage __P((SCR *, EXCMD *));
 */
int
ex_viusage(SCR *sp, EXCMD *cmdp)
{
	VIKEYS const *kp;
	int key;

	switch (cmdp->argc) {
	case 1:
		if (cmdp->argv[0]->len != 1) {
			ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
			return (1);
		}
		key = cmdp->argv[0]->bp[0];
		if (key > MAXVIKEY)
			goto nokey;

		/* Special case: '[' and ']' commands. */
		if ((key == '[' || key == ']') && cmdp->argv[0]->bp[1] != key)
			goto nokey;

		/* Special case: ~ command. */
		if (key == '~' && O_ISSET(sp, O_TILDEOP))
			kp = &tmotion;
		else
			kp = &vikeys[key];

		if (kp->usage == NULL)
nokey:			(void)ex_printf(sp,
			    "The %s key has no current meaning\n",
			    KEY_NAME(sp, key));
		else
			(void)ex_printf(sp,
			    "  Key:%s%s\nUsage: %s\n",
			    isblank((unsigned char)*kp->help) ? "" : " ", kp->help, kp->usage);
		break;
	case 0:
		for (key = 0; key <= MAXVIKEY && !INTERRUPTED(sp); ++key) {
			/* Special case: ~ command. */
			if (key == '~' && O_ISSET(sp, O_TILDEOP))
				kp = &tmotion;
			else
				kp = &vikeys[key];
			if (kp->help != NULL)
				(void)ex_printf(sp, "%s\n", kp->help);
		}
		break;
	default:
		abort();
	}
	return (0);
}
Exemple #8
0
/*
 * ex_print --
 *	Print the selected lines.
 *
 * PUBLIC: int ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t);
 */
int
ex_print(SCR *sp, EXCMD *cmdp, MARK *fp, MARK *tp, u_int32_t flags)
{
	GS *gp;
	recno_t from, to;
	size_t col, len;
	CHAR_T *p;
	CHAR_T buf[10];

	NEEDFILE(sp, cmdp);

	gp = sp->gp;
	for (from = fp->lno, to = tp->lno; from <= to; ++from) {
		col = 0;

		/*
		 * Display the line number.  The %6 format is specified
		 * by POSIX 1003.2, and is almost certainly large enough.
		 * Check, though, just in case.
		 */
		if (LF_ISSET(E_C_HASH)) {
			if (from <= 999999) {
				SPRINTF(buf, SIZE(buf), L("%6u  "), from);
				p = buf;
			} else
				p = L("TOOBIG  ");
			if (ex_prchars(sp, p, &col, 8, 0, 0))
				return (1);
		}

		/*
		 * Display the line.  The format for E_C_PRINT isn't very good,
		 * especially in handling end-of-line tabs, but they're almost
		 * backward compatible.
		 */
		if (db_get(sp, from, DBG_FATAL, &p, &len))
			return (1);

		if (len == 0 && !LF_ISSET(E_C_LIST))
			(void)ex_puts(sp, "\n");
		else if (ex_ldisplay(sp, p, len, col, flags))
			return (1);

		if (INTERRUPTED(sp))
			break;
	}
	return (0);
}
Exemple #9
0
/*
 * filter_ldisplay --
 *	Display output from a utility.
 *
 * !!!
 * Historically, the characters were passed unmodified to the terminal.
 * We use the ex print routines to make sure they're printable.
 */
static int
filter_ldisplay(SCR *sp, FILE *fp)
{
	size_t len;
	size_t wlen;
	CHAR_T *wp;

	EX_PRIVATE *exp;

	for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) {
		FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen);
		if (ex_ldisplay(sp, wp, wlen, 0, 0))
			break;
	}
	if (ferror(fp))
		msgq(sp, M_SYSERR, "filter read");
	(void)fclose(fp);
	return (0);
}
Exemple #10
0
/*
 * db --
 *	Display a buffer.
 */
static void
db(SCR *sp, CB *cbp, const char *np)
{
	CHAR_T *p;
	GS *gp;
	TEXT *tp;
	size_t len;
	const unsigned char *name = (const void *)np;

	gp = sp->gp;
	(void)ex_printf(sp, "********** %s%s\n",
	    name == NULL ? KEY_NAME(sp, cbp->name) : name,
	    F_ISSET(cbp, CB_LMODE) ? " (line mode)" : " (character mode)");
	for (tp = cbp->textq.cqh_first;
	    tp != (void *)&cbp->textq; tp = tp->q.cqe_next) {
		for (len = tp->len, p = tp->lb; len--; ++p) {
			(void)ex_puts(sp, (char *)KEY_NAME(sp, *p));
			if (INTERRUPTED(sp))
				return;
		}
		(void)ex_puts(sp, "\n");
	}
}
Exemple #11
0
/*
 * v_put -- [buffer]p
 *	Insert the contents of the buffer after the cursor.
 *
 * PUBLIC: int v_put __P((SCR *, VICMD *));
 */
int
v_put(SCR *sp, VICMD *vp)
{
	u_long cnt;

	if (F_ISSET(vp, VC_ISDOT))
		inc_buf(sp, vp);

	/*
	 * !!!
	 * Historic vi did not support a count with the 'p' and 'P'
	 * commands.  It's useful, so we do.
	 */
	for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
		if (put(sp, NULL,
		    F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
		    &vp->m_start, &vp->m_final, 1))
			return (1);
		vp->m_start = vp->m_final;
		if (INTERRUPTED(sp))
			return (1);
	}
	return (0);
}
Exemple #12
0
/*
 * b_search --
 *	Do a backward search.
 *
 * PUBLIC: int b_search __P((SCR *,
 * PUBLIC:    MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int));
 */
int
b_search(
	SCR *sp,
	MARK *fm,
	MARK *rm,
	CHAR_T *ptrn,
	size_t plen,
	CHAR_T **eptrn,
	u_int flags)
{
	busy_t btype;
	recno_t lno;
	regmatch_t match[1];
	size_t coff, last, len;
	int cnt, eval, rval, wrapped;
	CHAR_T *l;

	if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags))
		return (1);

	/*
	 * If doing incremental search, set the "starting" position past the
	 * current column, so that we search a minimal distance and still
	 * match special patterns, e.g., \> for the end of a word.  This is
	 * safe when the cursor is at the end of a line because we only use
	 * it for comparison with the location of the match.
	 *
	 * Otherwise, start searching immediately before the cursor.  If in
	 * the first column, start search on the previous line.
	 */
	if (LF_ISSET(SEARCH_INCR)) {
		lno = fm->lno;
		coff = fm->cno + 1;
	} else {
		if (fm->cno == 0) {
			if (fm->lno == 1 && !O_ISSET(sp, O_WRAPSCAN)) {
				if (LF_ISSET(SEARCH_MSG))
					search_msg(sp, S_SOF);
				return (1);
			}
			lno = fm->lno - 1;
		} else
			lno = fm->lno;
		coff = fm->cno;
	}

	btype = BUSY_ON;
	for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) {
		if (cnt-- == 0) {
			if (INTERRUPTED(sp))
				break;
			if (LF_ISSET(SEARCH_MSG)) {
				search_busy(sp, btype);
				btype = BUSY_UPDATE;
			}
			cnt = INTERRUPT_CHECK;
		}
		if ((wrapped && lno < fm->lno) || lno == 0) {
			if (wrapped) {
				if (LF_ISSET(SEARCH_MSG))
					search_msg(sp, S_NOTFOUND);
				break;
			}
			if (!O_ISSET(sp, O_WRAPSCAN)) {
				if (LF_ISSET(SEARCH_MSG))
					search_msg(sp, S_SOF);
				break;
			}
			if (db_last(sp, &lno))
				break;
			if (lno == 0) {
				if (LF_ISSET(SEARCH_MSG))
					search_msg(sp, S_EMPTY);
				break;
			}
			++lno;
			wrapped = 1;
			continue;
		}

		if (db_get(sp, lno, 0, &l, &len))
			break;

		/* Set the termination. */
		match[0].rm_so = 0;
		match[0].rm_eo = len;

#if defined(DEBUG) && 0
		TRACE(sp, "B search: %lu from 0 to %qu\n", lno, match[0].rm_eo);
#endif
		/* Search the line. */
		eval = regexec(&sp->re_c, l, 1, match,
		    (match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND);
		if (eval == REG_NOMATCH)
			continue;
		if (eval != 0) {
			if (LF_ISSET(SEARCH_MSG))
				re_error(sp, eval, &sp->re_c);
			else
				(void)sp->gp->scr_bell(sp);
			break;
		}

		/* Check for a match starting past the cursor. */
		if (coff != 0 && match[0].rm_so >= coff)
			continue;

		/* Warn if the search wrapped. */
		if (wrapped && LF_ISSET(SEARCH_WMSG))
			search_msg(sp, S_WRAP);

#if defined(DEBUG) && 0
		TRACE(sp, "B found: %qu to %qu\n",
		    match[0].rm_so, match[0].rm_eo);
#endif
		/*
		 * We now have the first match on the line.  Step through the
		 * line character by character until find the last acceptable
		 * match.  This is painful, we need a better interface to regex
		 * to make this work.
		 */
		for (;;) {
			last = match[0].rm_so++;
			if (match[0].rm_so >= len)
				break;
			match[0].rm_eo = len;
			eval = regexec(&sp->re_c, l, 1, match,
			    (match[0].rm_so == 0 ? 0 : REG_NOTBOL) |
			    REG_STARTEND);
			if (eval == REG_NOMATCH)
				break;
			if (eval != 0) {
				if (LF_ISSET(SEARCH_MSG))
					re_error(sp, eval, &sp->re_c);
				else
					(void)sp->gp->scr_bell(sp);
				goto err;
			}
			if (coff && match[0].rm_so >= coff)
				break;
		}
		rm->lno = lno;

		/* See comment in f_search(). */
		if (!LF_ISSET(SEARCH_EOL) && last >= len)
			rm->cno = len != 0 ? len - 1 : 0;
		else
			rm->cno = last;
		rval = 0;
		break;
	}

err:	if (LF_ISSET(SEARCH_MSG))
		search_busy(sp, BUSY_OFF);
	return (rval);
}
Exemple #13
0
/*
 * f_search --
 *	Do a forward search.
 *
 * PUBLIC: int f_search __P((SCR *,
 * PUBLIC:    MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int));
 */
int
f_search(
	SCR *sp,
	MARK *fm,
	MARK *rm,
	CHAR_T *ptrn,
	size_t plen,
	CHAR_T **eptrn,
	u_int flags)
{
	busy_t btype;
	recno_t lno;
	regmatch_t match[1];
	size_t coff, len;
	int cnt, eval, rval, wrapped;
	CHAR_T *l;

	if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags))
		return (1);

	if (LF_ISSET(SEARCH_FILE)) {
		lno = 1;
		coff = 0;
	} else {
		if (db_get(sp, fm->lno, DBG_FATAL, &l, &len))
			return (1);
		lno = fm->lno;

		/*
		 * If doing incremental search, start searching at the previous
		 * column, so that we search a minimal distance and still match
		 * special patterns, e.g., \< for beginning of a word.
		 *
		 * Otherwise, start searching immediately after the cursor.  If
		 * at the end of the line, start searching on the next line.
		 * This is incompatible (read bug fix) with the historic vi --
		 * searches for the '$' pattern never moved forward, and the
		 * "-t foo" didn't work if the 'f' was the first character in
		 * the file.
		 */
		if (LF_ISSET(SEARCH_INCR)) {
			if ((coff = fm->cno) != 0)
				--coff;
		} else if (fm->cno + 1 >= len) {
			coff = 0;
			lno = fm->lno + 1;
			if (db_get(sp, lno, 0, &l, &len)) {
				if (!O_ISSET(sp, O_WRAPSCAN)) {
					if (LF_ISSET(SEARCH_MSG))
						search_msg(sp, S_EOF);
					return (1);
				}
				lno = 1;
			}
		} else
			coff = fm->cno + 1;
	}

	btype = BUSY_ON;
	for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; ++lno, coff = 0) {
		if (cnt-- == 0) {
			if (INTERRUPTED(sp))
				break;
			if (LF_ISSET(SEARCH_MSG)) {
				search_busy(sp, btype);
				btype = BUSY_UPDATE;
			}
			cnt = INTERRUPT_CHECK;
		}
		if ((wrapped && lno > fm->lno) || db_get(sp, lno, 0, &l, &len)) {
			if (wrapped) {
				if (LF_ISSET(SEARCH_MSG))
					search_msg(sp, S_NOTFOUND);
				break;
			}
			if (!O_ISSET(sp, O_WRAPSCAN)) {
				if (LF_ISSET(SEARCH_MSG))
					search_msg(sp, S_EOF);
				break;
			}
			lno = 0;
			wrapped = 1;
			continue;
		}

		/* If already at EOL, just keep going. */
		if (len != 0 && coff == len)
			continue;

		/* Set the termination. */
		match[0].rm_so = coff;
		match[0].rm_eo = len;

#if defined(DEBUG) && 0
		TRACE(sp, "F search: %lu from %u to %u\n",
		    lno, coff, len != 0 ? len - 1 : len);
#endif
		/* Search the line. */
		eval = regexec(&sp->re_c, l, 1, match,
		    (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND);
		if (eval == REG_NOMATCH)
			continue;
		if (eval != 0) {
			if (LF_ISSET(SEARCH_MSG))
				re_error(sp, eval, &sp->re_c);
			else
				(void)sp->gp->scr_bell(sp);
			break;
		}

		/* Warn if the search wrapped. */
		if (wrapped && LF_ISSET(SEARCH_WMSG))
			search_msg(sp, S_WRAP);

#if defined(DEBUG) && 0
		TRACE(sp, "F search: %qu to %qu\n",
		    match[0].rm_so, match[0].rm_eo);
#endif
		rm->lno = lno;
		rm->cno = match[0].rm_so;

		/*
		 * If a change command, it's possible to move beyond the end
		 * of a line.  Historic vi generally got this wrong (e.g. try
		 * "c?$<cr>").  Not all that sure this gets it right, there
		 * are lots of strange cases.
		 */
		if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len)
			rm->cno = len != 0 ? len - 1 : 0;

		rval = 0;
		break;
	}

	if (LF_ISSET(SEARCH_MSG))
		search_busy(sp, BUSY_OFF);
	return (rval);
}
Exemple #14
0
static int
s(SCR *sp, EXCMD *cmdp, char *s, regex_t *re, u_int flags)
{
	EVENT ev;
	MARK from, to;
	TEXTH tiq;
	recno_t elno, lno, slno;
	regmatch_t match[10];
	size_t blen, cnt, last, lbclen, lblen, len, llen;
	size_t offset, saved_offset, scno;
	int lflag, nflag, pflag, rflag;
	int didsub, do_eol_match, eflags, empty_ok, eval;
	int linechanged, matched, quit, rval;
	unsigned long ul;
	char *bp, *lb;

	NEEDFILE(sp, cmdp);

	slno = sp->lno;
	scno = sp->cno;

	/*
	 * !!!
	 * Historically, the 'g' and 'c' suffices were always toggled as flags,
	 * so ":s/A/B/" was the same as ":s/A/B/ccgg".  If O_EDCOMPATIBLE was
	 * not set, they were initialized to 0 for all substitute commands.  If
	 * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user
	 * specified substitute/replacement patterns (see ex_s()).
	 */
	if (!O_ISSET(sp, O_EDCOMPATIBLE))
		sp->c_suffix = sp->g_suffix = 0;

	/*
	 * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but
	 * it only displayed the last change.  I'd disallow them, but they are
	 * useful in combination with the [v]global commands.  In the current
	 * model the problem is combining them with the 'c' flag -- the screen
	 * would have to flip back and forth between the confirm screen and the
	 * ex print screen, which would be pretty awful.  We do display all
	 * changes, though, for what that's worth.
	 *
	 * !!!
	 * Historic vi was fairly strict about the order of "options", the
	 * count, and "flags".  I'm somewhat fuzzy on the difference between
	 * options and flags, anyway, so this is a simpler approach, and we
	 * just take it them in whatever order the user gives them.  (The ex
	 * usage statement doesn't reflect this.)
	 */
	lflag = nflag = pflag = rflag = 0;
	if (s == NULL)
		goto noargs;
	for (lno = OOBLNO; *s != '\0'; ++s)
		switch (*s) {
		case ' ':
		case '\t':
			continue;
		case '+':
			++cmdp->flagoff;
			break;
		case '-':
			--cmdp->flagoff;
			break;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			if (lno != OOBLNO)
				goto usage;
			errno = 0;
			if ((ul = strtoul(s, &s, 10)) >= UINT_MAX)
				errno = ERANGE;
			if (*s == '\0')		/* Loop increment correction. */
				--s;
			if (errno == ERANGE) {
				if (ul >= UINT_MAX)
					msgq(sp, M_ERR, "Count overflow");
				else
					msgq(sp, M_SYSERR, NULL);
				return (1);
			}
			lno = (recno_t)ul;
			/*
			 * In historic vi, the count was inclusive from the
			 * second address.
			 */
			cmdp->addr1.lno = cmdp->addr2.lno;
			cmdp->addr2.lno += lno - 1;
			if (!db_exist(sp, cmdp->addr2.lno) &&
			    db_last(sp, &cmdp->addr2.lno))
				return (1);
			break;
		case '#':
			nflag = 1;
			break;
		case 'c':
			sp->c_suffix = !sp->c_suffix;

			/* Ex text structure initialization. */
			if (F_ISSET(sp, SC_EX)) {
				memset(&tiq, 0, sizeof(TEXTH));
				TAILQ_INIT(&tiq);
			}
			break;
		case 'g':
			sp->g_suffix = !sp->g_suffix;
			break;
		case 'l':
			lflag = 1;
			break;
		case 'p':
			pflag = 1;
			break;
		case 'r':
			if (LF_ISSET(SUB_FIRST)) {
				msgq(sp, M_ERR,
		    "Regular expression specified; r flag meaningless");
				return (1);
			}
			if (!F_ISSET(sp, SC_RE_SEARCH)) {
				ex_emsg(sp, NULL, EXM_NOPREVRE);
				return (1);
			}
			rflag = 1;
			re = &sp->re_c;
			break;
		default:
			goto usage;
		}

	if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) {
usage:		ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
		return (1);
	}

noargs:	if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) {
		msgq(sp, M_ERR,
"The #, l and p flags may not be combined with the c flag in vi mode");
		return (1);
	}

	/*
	 * bp:		if interactive, line cache
	 * blen:	if interactive, line cache length
	 * lb:		build buffer pointer.
	 * lbclen:	current length of built buffer.
	 * lblen;	length of build buffer.
	 */
	bp = lb = NULL;
	blen = lbclen = lblen = 0;

	/* For each line... */
	for (matched = quit = 0, lno = cmdp->addr1.lno,
	    elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) {

		/* Someone's unhappy, time to stop. */
		if (INTERRUPTED(sp))
			break;

		/* Get the line. */
		if (db_get(sp, lno, DBG_FATAL, &s, &llen))
			goto err;

		/*
		 * Make a local copy if doing confirmation -- when calling
		 * the confirm routine we're likely to lose the cached copy.
		 */
		if (sp->c_suffix) {
			if (bp == NULL) {
				GET_SPACE_RET(sp, bp, blen, llen);
			} else
				ADD_SPACE_RET(sp, bp, blen, llen);
			memcpy(bp, s, llen);
			s = bp;
		}

		/* Start searching from the beginning. */
		offset = 0;
		len = llen;

		/* Reset the build buffer offset. */
		lbclen = 0;

		/* Reset empty match flag. */
		empty_ok = 1;

		/*
		 * We don't want to have to do a setline if the line didn't
		 * change -- keep track of whether or not this line changed.
		 * If doing confirmations, don't want to keep setting the
		 * line if change is refused -- keep track of substitutions.
		 */
		didsub = linechanged = 0;

		/* New line, do an EOL match. */
		do_eol_match = 1;

		/* It's not nul terminated, but we pretend it is. */
		eflags = REG_STARTEND;

		/*
		 * The search area is from s + offset to the EOL.
		 *
		 * Generally, match[0].rm_so is the offset of the start
		 * of the match from the start of the search, and offset
		 * is the offset of the start of the last search.
		 */
nextmatch:	match[0].rm_so = 0;
		match[0].rm_eo = len;

		/* Get the next match. */
		eval = regexec(re, (char *)s + offset, 10, match, eflags);

		/*
		 * There wasn't a match or if there was an error, deal with
		 * it.  If there was a previous match in this line, resolve
		 * the changes into the database.  Otherwise, just move on.
		 */
		if (eval == REG_NOMATCH)
			goto endmatch;
		if (eval != 0) {
			re_error(sp, eval, re);
			goto err;
		}
		matched = 1;

		/* Only the first search can match an anchored expression. */
		eflags |= REG_NOTBOL;

		/*
		 * !!!
		 * It's possible to match 0-length strings -- for example, the
		 * command s;a*;X;, when matched against the string "aabb" will
		 * result in "XbXbX", i.e. the matches are "aa", the space
		 * between the b's and the space between the b's and the end of
		 * the string.  There is a similar space between the beginning
		 * of the string and the a's.  The rule that we use (because vi
		 * historically used it) is that any 0-length match, occurring
		 * immediately after a match, is ignored.  Otherwise, the above
		 * example would have resulted in "XXbXbX".  Another example is
		 * incorrectly using " *" to replace groups of spaces with one
		 * space.
		 *
		 * The way we do this is that if we just had a successful match,
		 * the starting offset does not skip characters, and the match
		 * is empty, ignore the match and move forward.  If there's no
		 * more characters in the string, we were attempting to match
		 * after the last character, so quit.
		 */
		if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) {
			empty_ok = 1;
			if (len == 0)
				goto endmatch;
			BUILD(sp, s + offset, 1)
			++offset;
			--len;
			goto nextmatch;
		}

		/* Confirm change. */
		if (sp->c_suffix) {
			/*
			 * Set the cursor position for confirmation.  Note,
			 * if we matched on a '$', the cursor may be past
			 * the end of line.
			 */
			from.lno = to.lno = lno;
			from.cno = match[0].rm_so + offset;
			to.cno = match[0].rm_eo + offset;
			/*
			 * Both ex and vi have to correct for a change before
			 * the first character in the line.
			 */
			if (llen == 0)
				from.cno = to.cno = 0;
			if (F_ISSET(sp, SC_VI)) {
				/*
				 * Only vi has to correct for a change after
				 * the last character in the line.
				 *
				 * XXX
				 * It would be nice to change the vi code so
				 * that we could display a cursor past EOL.
				 */
				if (to.cno >= llen)
					to.cno = llen - 1;
				if (from.cno >= llen)
					from.cno = llen - 1;

				sp->lno = from.lno;
				sp->cno = from.cno;
				if (vs_refresh(sp, 1))
					goto err;

				vs_update(sp, "Confirm change? [n]", NULL);

				if (v_event_get(sp, &ev, 0, 0))
					goto err;
				switch (ev.e_event) {
				case E_CHARACTER:
					break;
				case E_EOF:
				case E_ERR:
				case E_INTERRUPT:
					goto lquit;
				default:
					v_event_err(sp, &ev);
					goto lquit;
				}
			} else {
				if (ex_print(sp, cmdp, &from, &to, 0) ||
				    ex_scprint(sp, &from, &to))
					goto lquit;
				if (ex_txt(sp, &tiq, 0, TXT_CR))
					goto err;
				ev.e_c = TAILQ_FIRST(&tiq)->lb[0];
			}

			switch (ev.e_c) {
			case CH_YES:
				break;
			default:
			case CH_NO:
				didsub = 0;
				BUILD(sp, s +offset, match[0].rm_eo);
				goto skip;
			case CH_QUIT:
				/* Set the quit/interrupted flags. */
lquit:				quit = 1;
				F_SET(sp->gp, G_INTERRUPTED);

				/*
				 * Resolve any changes, then return to (and
				 * exit from) the main loop.
				 */
				goto endmatch;
			}
		}

		/*
		 * Set the cursor to the last position changed, converting
		 * from 1-based to 0-based.
		 */
		sp->lno = lno;
		sp->cno = match[0].rm_so;

		/* Copy the bytes before the match into the build buffer. */
		BUILD(sp, s + offset, match[0].rm_so);

		/* Substitute the matching bytes. */
		didsub = 1;
		if (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match))
			goto err;

		/* Set the change flag so we know this line was modified. */
		linechanged = 1;

		/* Move past the matched bytes. */
skip:		offset += match[0].rm_eo;
		len -= match[0].rm_eo;

		/* A match cannot be followed by an empty pattern. */
		empty_ok = 0;

		/*
		 * If doing a global change with confirmation, we have to
		 * update the screen.  The basic idea is to store the line
		 * so the screen update routines can find it, and restart.
		 */
		if (didsub && sp->c_suffix && sp->g_suffix) {
			/*
			 * The new search offset will be the end of the
			 * modified line.
			 */
			saved_offset = lbclen;

			/* Copy the rest of the line. */
			if (len)
				BUILD(sp, s + offset, len)

			/* Set the new offset. */
			offset = saved_offset;

			/* Store inserted lines, adjusting the build buffer. */
			last = 0;
			if (sp->newl_cnt) {
				for (cnt = 0;
				    cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) {
					if (db_insert(sp, lno,
					    lb + last, sp->newl[cnt] - last))
						goto err;
					last = sp->newl[cnt] + 1;
					++sp->rptlines[L_ADDED];
				}
				lbclen -= last;
				offset -= last;
				sp->newl_cnt = 0;
			}

			/* Store and retrieve the line. */
			if (db_set(sp, lno, lb + last, lbclen))
				goto err;
			if (db_get(sp, lno, DBG_FATAL, &s, &llen))
				goto err;
			ADD_SPACE_RET(sp, bp, blen, llen)
			memcpy(bp, s, llen);
			s = bp;
			len = llen - offset;

			/* Restart the build. */
			lbclen = 0;
			BUILD(sp, s, offset);

			/*
			 * If we haven't already done the after-the-string
			 * match, do one.  Set REG_NOTEOL so the '$' pattern
			 * only matches once.
			 */
			if (!do_eol_match)
				goto endmatch;
			if (offset == len) {
				do_eol_match = 0;
				eflags |= REG_NOTEOL;
			}
			goto nextmatch;
		}

		/*
		 * If it's a global:
		 *
		 * If at the end of the string, do a test for the after
		 * the string match.  Set REG_NOTEOL so the '$' pattern
		 * only matches once.
		 */
		if (sp->g_suffix && do_eol_match) {
			if (len == 0) {
				do_eol_match = 0;
				eflags |= REG_NOTEOL;
			}
			goto nextmatch;
		}

endmatch:	if (!linechanged)
			continue;

		/* Copy any remaining bytes into the build buffer. */
		if (len)
			BUILD(sp, s + offset, len)

		/* Store inserted lines, adjusting the build buffer. */
		last = 0;
		if (sp->newl_cnt) {
			for (cnt = 0;
			    cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) {
				if (db_insert(sp,
				    lno, lb + last, sp->newl[cnt] - last))
					goto err;
				last = sp->newl[cnt] + 1;
				++sp->rptlines[L_ADDED];
			}
			lbclen -= last;
			sp->newl_cnt = 0;
		}

		/* Store the changed line. */
		if (db_set(sp, lno, lb + last, lbclen))
			goto err;

		/* Update changed line counter. */
		if (sp->rptlchange != lno) {
			sp->rptlchange = lno;
			++sp->rptlines[L_CHANGED];
		}

		/*
		 * !!!
		 * Display as necessary.  Historic practice is to only
		 * display the last line of a line split into multiple
		 * lines.
		 */
		if (lflag || nflag || pflag) {
			from.lno = to.lno = lno;
			from.cno = to.cno = 0;
			if (lflag)
				(void)ex_print(sp, cmdp, &from, &to, E_C_LIST);
			if (nflag)
				(void)ex_print(sp, cmdp, &from, &to, E_C_HASH);
			if (pflag)
				(void)ex_print(sp, cmdp, &from, &to, E_C_PRINT);
		}
	}

	/*
	 * !!!
	 * Historically, vi attempted to leave the cursor at the same place if
	 * the substitution was done at the current cursor position.  Otherwise
	 * it moved it to the first non-blank of the last line changed.  There
	 * were some problems: for example, :s/$/foo/ with the cursor on the
	 * last character of the line left the cursor on the last character, or
	 * the & command with multiple occurrences of the matching string in the
	 * line usually left the cursor in a fairly random position.
	 *
	 * We try to do the same thing, with the exception that if the user is
	 * doing substitution with confirmation, we move to the last line about
	 * which the user was consulted, as opposed to the last line that they
	 * actually changed.  This prevents a screen flash if the user doesn't
	 * change many of the possible lines.
	 */
	if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) {
		sp->cno = 0;
		(void)nonblank(sp, sp->lno, &sp->cno);
	}

	/*
	 * If not in a global command, and nothing matched, say so.
	 * Else, if none of the lines displayed, put something up.
	 */
	rval = 0;
	if (!matched) {
		if (!F_ISSET(sp, SC_EX_GLOBAL)) {
			msgq(sp, M_ERR, "No match found");
			goto err;
		}
	} else if (!lflag && !nflag && !pflag)
		F_SET(cmdp, E_AUTOPRINT);

	if (0) {
err:		rval = 1;
	}

	if (bp != NULL)
		FREE_SPACE(sp, bp, blen);
	if (lb != NULL)
		free(lb);
	return (rval);
}
Exemple #15
0
/*
 * ex_writefp --
 *	Write a range of lines to a FILE *.
 *
 * PUBLIC: int ex_writefp __P((SCR *,
 * PUBLIC:    char *, FILE *, MARK *, MARK *, u_long *, u_long *, int));
 */
int
ex_writefp(SCR *sp, char *name, FILE *fp, MARK *fm, MARK *tm, u_long *nlno, u_long *nch, int silent)
{
	struct stat sb;
	GS *gp;
	u_long ccnt;			/* XXX: can't print off_t portably. */
	recno_t fline, tline, lcnt;
	size_t len;
	int rval;
	char *msg;
	CHAR_T *p;
	char *f;
	size_t flen;
	int isutf16;

	gp = sp->gp;
	fline = fm->lno;
	tline = tm->lno;

	if (nlno != NULL) {
		*nch = 0;
		*nlno = 0;
	}

	/*
	 * The vi filter code has multiple processes running simultaneously,
	 * and one of them calls ex_writefp().  The "unsafe" function calls
	 * in this code are to db_get() and msgq().  Db_get() is safe, see
	 * the comment in ex_filter.c:ex_filter() for details.  We don't call
	 * msgq if the multiple process bit in the EXF is set.
	 *
	 * !!!
	 * Historic vi permitted files of 0 length to be written.  However,
	 * since the way vi got around dealing with "empty" files was to
	 * always have a line in the file no matter what, it wrote them as
	 * files of a single, empty line.  We write empty files.
	 *
	 * "Alex, I'll take vi trivia for $1000."
	 */
	ccnt = 0;
	lcnt = 0;
	msg = "253|Writing...";

	if (O_ISSET(sp, O_FILEENCODING)) {
		isutf16 = !strncasecmp(O_STR(sp, O_FILEENCODING), "utf-16", 6);
		isutf16 += !strncasecmp(O_STR(sp, O_FILEENCODING), "utf-16le", 8);
	} else isutf16 = 0;

	if (tline != 0) {
		if (isutf16 == 1 && fwrite("\xfe\xff", 1, 2, fp) != 2)
			goto err;
		if (isutf16 == 2 && fwrite("\xff\xfe", 1, 2, fp) != 2)
			goto err;
		for (; fline <= tline; ++fline, ++lcnt) {
			/* Caller has to provide any interrupt message. */
			if ((lcnt + 1) % INTERRUPT_CHECK == 0) {
				if (INTERRUPTED(sp))
					break;
				if (!silent) {
					gp->scr_busy(sp, msg, msg == NULL ?
					    BUSY_UPDATE : BUSY_ON);
					msg = NULL;
				}
			}
			if (db_get(sp, fline, DBG_FATAL, &p, &len))
				goto err;
			INT2FILE(sp, p, len, f, flen);
			if (fwrite(f, 1, flen, fp) != flen)
				goto err;
			ccnt += len;
			/* UTF-16 w/o BOM is big-endian */
			switch (isutf16) {
			case 1:		/* UTF-16BE */
				if (fwrite("\0\x0a", 1, 2, fp) != 2)
					goto done;
				break;
			case 2:		/* UTF-16LE */
				if (fwrite("\x0a\0", 1, 2, fp) != 2)
					goto done;
				break;
			default:
				if (putc('\n', fp) != '\n')
					goto done;
			}
			++ccnt;
		}
	}

done:	if (fflush(fp))
		goto err;
	/*
	 * XXX
	 * I don't trust NFS -- check to make sure that we're talking to
	 * a regular file and sync so that NFS is forced to flush.
	 */
	if (!fstat(fileno(fp), &sb) &&
	    S_ISREG(sb.st_mode) && fsync(fileno(fp)))
		goto err;

	if (fclose(fp)) {
		fp = NULL;
		goto err;
	}

	rval = 0;
	if (0) {
err:		if (!F_ISSET(sp->ep, F_MULTILOCK))
			msgq_str(sp, M_SYSERR, name, "%s");
		if (fp != NULL)
			fclose(fp);
		rval = 1;
	}

	if (!silent)
		gp->scr_busy(sp, NULL, BUSY_OFF);

	/* Report the possibly partial transfer. */
	if (nlno != NULL) {
		*nch = ccnt;
		*nlno = lcnt;
	}
	return (rval);
}
Exemple #16
0
/*
 * vs_ex_resolve --
 *	Deal with ex message output.
 *
 * This routine is called when exiting a colon command to resolve any ex
 * output that may have occurred.
 *
 * PUBLIC: int vs_ex_resolve __P((SCR *, int *));
 */
int
vs_ex_resolve(SCR *sp, int *continuep)
{
	EVENT ev;
	GS *gp;
	VI_PRIVATE *vip;
	sw_t wtype;

	gp = sp->gp;
	vip = VIP(sp);
	*continuep = 0;

	/* If we ran any ex command, we can't trust the cursor position. */
	F_SET(vip, VIP_CUR_INVALID);

	/* Terminate any partially written message. */
	if (vip->lcontinue != 0) {
		vs_output(sp, vip->mtype, ".", 1);
		vip->lcontinue = 0;

		vip->mtype = M_NONE;
	}

	/*
	 * If we switched out of the vi screen into ex, switch back while we
	 * figure out what to do with the screen and potentially get another
	 * command to execute.
	 *
	 * If we didn't switch into ex, we're not required to wait, and less
	 * than 2 lines of output, we can continue without waiting for the
	 * wait.
	 *
	 * Note, all other code paths require waiting, so we leave the report
	 * of modified lines until later, so that we won't wait for no other
	 * reason than a threshold number of lines were modified.  This means
	 * we display cumulative line modification reports for groups of ex
	 * commands.  That seems right to me (well, at least not wrong).
	 */
	if (F_ISSET(sp, SC_SCR_EXWROTE)) {
		if (sp->gp->scr_screen(sp, SC_VI))
			return (1);
	} else
		if (!F_ISSET(sp, SC_EX_WAIT_YES) && vip->totalcount < 2) {
			F_CLR(sp, SC_EX_WAIT_NO);
			return (0);
		}

	/* Clear the required wait flag, it's no longer needed. */
	F_CLR(sp, SC_EX_WAIT_YES);

	/*
	 * Wait, unless explicitly told not to wait or the user interrupted
	 * the command.  If the user is leaving the screen, for any reason,
	 * they can't continue with further ex commands.
	 */
	if (!F_ISSET(sp, SC_EX_WAIT_NO) && !INTERRUPTED(sp)) {
		wtype = F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE |
		    SC_FSWITCH | SC_SSWITCH) ? SCROLL_W : SCROLL_W_EX;
		if (F_ISSET(sp, SC_SCR_EXWROTE))
			vs_wait(sp, continuep, wtype);
		else
			vs_scroll(sp, continuep, wtype);
		if (*continuep)
			return (0);
	}

	/* If ex wrote on the screen, refresh the screen image. */
	if (F_ISSET(sp, SC_SCR_EXWROTE))
		F_SET(vip, VIP_N_EX_PAINT);

	/*
	 * If we're not the bottom of the split screen stack, the screen
	 * image itself is wrong, so redraw everything.
	 */
	if (sp->q.cqe_next != (void *)&sp->wp->scrq)
		F_SET(sp, SC_SCR_REDRAW);

	/* If ex changed the underlying file, the map itself is wrong. */
	if (F_ISSET(vip, VIP_N_EX_REDRAW))
		F_SET(sp, SC_SCR_REFORMAT);

	/* Ex may have switched out of the alternate screen, return. */
	(void)gp->scr_attr(sp, SA_ALTERNATE, 1);

	/*
	 * Whew.  We're finally back home, after what feels like years.
	 * Kiss the ground.
	 */
	F_CLR(sp, SC_SCR_EXWROTE | SC_EX_WAIT_NO);

	/*
	 * We may need to repaint some of the screen, e.g.:
	 *
	 *	:set
	 *	:!ls
	 *
	 * gives us a combination of some lines that are "wrong", and a need
	 * for a full refresh.
	 */
	if (vip->totalcount > 1) {
		/* Set up the redraw of the overwritten lines. */
		ev.e_event = E_REPAINT;
		ev.e_flno = vip->totalcount >=
		    sp->rows ? 1 : sp->rows - vip->totalcount;
		ev.e_tlno = sp->rows;

		/* Reset the count of overwriting lines. */
		vip->linecount = vip->lcontinue = vip->totalcount = 0;

		/* Redraw. */
		(void)v_erepaint(sp, &ev);
	} else
		/* Reset the count of overwriting lines. */
		vip->linecount = vip->lcontinue = vip->totalcount = 0;

	return (0);
}
Exemple #17
0
/*
 * vs_output --
 *	Output the text to the screen.
 */
static void
vs_output(SCR *sp, mtype_t mtype, const char *line, int llen)
{
	unsigned char *kp;
	GS *gp;
	VI_PRIVATE *vip;
	size_t chlen, notused;
	int ch, len, rlen, tlen;
	const char *p, *t;
	char *cbp, *ecbp, cbuf[128];

	gp = sp->gp;
	vip = VIP(sp);
	for (p = line, rlen = llen; llen > 0;) {
		/* Get the next physical line. */
		if ((p = memchr(line, '\n', llen)) == NULL)
			len = llen;
		else
			len = p - line;

		/*
		 * The max is sp->cols characters, and we may have already
		 * written part of the line.
		 */
		if (len + vip->lcontinue > sp->cols)
			len = sp->cols - vip->lcontinue;

		/*
		 * If the first line output, do nothing.  If the second line
		 * output, draw the divider line.  If drew a full screen, we
		 * remove the divider line.  If it's a continuation line, move
		 * to the continuation point, else, move the screen up.
		 */
		if (vip->lcontinue == 0) {
			if (!IS_ONELINE(sp)) {
				if (vip->totalcount == 1) {
					(void)gp->scr_move(sp,
					    LASTLINE(sp) - 1, 0);
					(void)gp->scr_clrtoeol(sp);
					(void)vs_divider(sp);
					F_SET(vip, VIP_DIVIDER);
					++vip->totalcount;
					++vip->linecount;
				}
				if (vip->totalcount == sp->t_maxrows &&
				    F_ISSET(vip, VIP_DIVIDER)) {
					--vip->totalcount;
					--vip->linecount;
					F_CLR(vip, VIP_DIVIDER);
				}
			}
			if (vip->totalcount != 0)
				vs_scroll(sp, NULL, SCROLL_W_QUIT);

			(void)gp->scr_move(sp, LASTLINE(sp), 0);
			++vip->totalcount;
			++vip->linecount;

			if (INTERRUPTED(sp))
				break;
		} else
			(void)gp->scr_move(sp, LASTLINE(sp), vip->lcontinue);

		/* Error messages are in inverse video. */
		if (mtype == M_ERR)
			(void)gp->scr_attr(sp, SA_INVERSE, 1);

		/* Display the line, doing character translation. */
#define	FLUSH {								\
	*cbp = '\0';							\
	(void)gp->scr_addstr(sp, cbuf, cbp - cbuf);			\
	cbp = cbuf;							\
}
		ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;
		for (t = line, tlen = len; tlen--; ++t) {
			ch = *t;
			/*
			 * Replace tabs with spaces, there are places in
			 * ex that do column calculations without looking
			 * at <tabs> -- and all routines that care about
			 * <tabs> do their own expansions.  This catches
			 * <tabs> in things like tag search strings.
			 */
			if (ch == '\t')
				ch = ' ';
			chlen = KEY_LEN(sp, ch);
			if (cbp + chlen >= ecbp)
				FLUSH;
			for (kp = KEY_NAME(sp, ch); chlen--;)
				*cbp++ = *kp++;
		}
		if (cbp > cbuf)
			FLUSH;
		if (mtype == M_ERR)
			(void)gp->scr_attr(sp, SA_INVERSE, 0);

		/* Clear the rest of the line. */
		(void)gp->scr_clrtoeol(sp);

		/* If we loop, it's a new line. */
		vip->lcontinue = 0;

		/* Reset for the next line. */
		line += len;
		llen -= len;
		if (p != NULL) {
			++line;
			--llen;
		}
	}

	/* Set up next continuation line. */
	if (p == NULL)
		gp->scr_cursor(sp, &notused, &vip->lcontinue);
}
Exemple #18
0
/*
 * vs_msg --
 *	Display ex output or error messages for the screen.
 *
 * This routine is the default editor interface for all ex output, and all ex
 * and vi error/informational messages.  It implements the standard strategy
 * of stealing lines from the bottom of the vi text screen.  Screens using an
 * alternate method of displaying messages, e.g. dialog boxes, should set their
 * scr_msg function to the correct function before calling the editor.
 *
 * PUBLIC: void vs_msg __P((SCR *, mtype_t, char *, size_t));
 */
void
vs_msg(SCR *sp, mtype_t mtype, char *line, size_t len)
{
	GS *gp;
	VI_PRIVATE *vip;
	size_t maxcols, oldx, oldy, padding;
	const char *e, *s, *t;

	gp = sp->gp;
	vip = VIP(sp);

	/*
	 * Ring the bell if it's scheduled.
	 *
	 * XXX
	 * Shouldn't we save this, too?
	 */
	if (F_ISSET(sp, SC_TINPUT_INFO) || F_ISSET(gp, G_BELLSCHED)) {
		if (F_ISSET(sp, SC_SCR_VI)) {
			F_CLR(gp, G_BELLSCHED);
			(void)gp->scr_bell(sp);
		} else
			F_SET(gp, G_BELLSCHED);
	}

	/*
	 * If vi is using the error line for text input, there's no screen
	 * real-estate for the error message.  Nothing to do without some
	 * information as to how important the error message is.
	 */
	if (F_ISSET(sp, SC_TINPUT_INFO))
		return;

	/*
	 * Ex or ex controlled screen output.
	 *
	 * If output happens during startup, e.g., a .exrc file, we may be
	 * in ex mode but haven't initialized the screen.  Initialize here,
	 * and in this case, stay in ex mode.
	 *
	 * If the SC_SCR_EXWROTE bit is set, then we're switching back and
	 * forth between ex and vi, but the screen is trashed and we have
	 * to respect that.  Switch to ex mode long enough to put out the
	 * message.
	 *
	 * If the SC_EX_WAIT_NO bit is set, turn it off -- we're writing to
	 * the screen, so previous opinions are ignored.
	 */
	if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) {
		if (!F_ISSET(sp, SC_SCR_EX)) {
			if (F_ISSET(sp, SC_SCR_EXWROTE)) {
				if (sp->gp->scr_screen(sp, SC_EX))
					return;
			} else
				if (ex_init(sp))
					return;
		}

		if (mtype == M_ERR)
			(void)gp->scr_attr(sp, SA_INVERSE, 1);
		(void)printf("%.*s", (int)len, line);
		if (mtype == M_ERR)
			(void)gp->scr_attr(sp, SA_INVERSE, 0);
		(void)fflush(stdout);

		F_CLR(sp, SC_EX_WAIT_NO);

		if (!F_ISSET(sp, SC_SCR_EX))
			(void)sp->gp->scr_screen(sp, SC_VI);
		return;
	}

	/* If the vi screen isn't ready, save the message. */
	if (!F_ISSET(sp, SC_SCR_VI)) {
		(void)vs_msgsave(sp, mtype, line, len);
		return;
	}

	/* Save the cursor position. */
	(void)gp->scr_cursor(sp, &oldy, &oldx);

	/* If it's an ex output message, just write it out. */
	if (mtype == M_NONE) {
		vs_output(sp, mtype, line, len);
		goto ret;
	}

	/*
	 * If it's a vi message, strip the trailing <newline> so we can
	 * try and paste messages together.
	 */
	if (line[len - 1] == '\n')
		--len;

	/*
	 * If a message won't fit on a single line, try to split on a <blank>.
	 * If a subsequent message fits on the same line, write a separator
	 * and output it.  Otherwise, put out a newline.
	 *
	 * Need up to two padding characters normally; a semi-colon and a
	 * separating space.  If only a single line on the screen, add some
	 * more for the trailing continuation message.
	 *
	 * XXX
	 * Assume that periods and semi-colons take up a single column on the
	 * screen.
	 *
	 * XXX
	 * There are almost certainly pathological cases that will break this
	 * code.
	 */
	if (IS_ONELINE(sp))
		(void)msg_cmsg(sp, CMSG_CONT_S, &padding);
	else
		padding = 0;
	padding += 2;

	maxcols = sp->cols - 1;
	if (vip->lcontinue != 0) {
		if (len + vip->lcontinue + padding > maxcols)
			vs_output(sp, vip->mtype, ".\n", 2);
		else  {
			vs_output(sp, vip->mtype, ";", 1);
			vs_output(sp, M_NONE, " ", 1);
		}
	}
	vip->mtype = mtype;
	for (s = line;; s = t) {
		for (; len > 0 && isblank((unsigned char)*s); --len, ++s);
		if (len == 0)
			break;
		if (len + vip->lcontinue > maxcols) {
			for (e = s + (maxcols - vip->lcontinue);
			    e > s && !isblank((unsigned char)*e); --e);
			if (e == s)
				 e = t = s + (maxcols - vip->lcontinue);
			else
				for (t = e; isblank((unsigned char)e[-1]); --e);
		} else
			e = t = s + len;

		/*
		 * If the message ends in a period, discard it, we want to
		 * gang messages where possible.
		 */
		len -= t - s;
		if (len == 0 && (e - s) > 1 && s[(e - s) - 1] == '.')
			--e;
		vs_output(sp, mtype, s, e - s);

		if (len != 0)
			vs_output(sp, M_NONE, "\n", 1);

		if (INTERRUPTED(sp))
			break;
	}

ret:	(void)gp->scr_move(sp, oldy, oldx);
	(void)gp->scr_refresh(sp, 0);
}
Exemple #19
0
/*
 * ex_readfp --
 *	Read lines into the file.
 *
 * PUBLIC: int ex_readfp __P((SCR *, char *, FILE *, MARK *, db_recno_t *, int));
 */
int
ex_readfp(SCR *sp, char *name, FILE *fp, MARK *fm, db_recno_t *nlinesp, int silent)
{
	EX_PRIVATE *exp;
	GS *gp;
	db_recno_t lcnt, lno;
	size_t len;
	u_long ccnt;			/* XXX: can't print off_t portably. */
	int nf, rval;
	char *p;
	size_t wlen;
	CHAR_T *wp;

	gp = sp->gp;
	exp = EXP(sp);

	/*
	 * Add in the lines from the output.  Insertion starts at the line
	 * following the address.
	 */
	ccnt = 0;
	lcnt = 0;
	p = "147|Reading...";
	for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) {
		if ((lcnt + 1) % INTERRUPT_CHECK == 0) {
			if (INTERRUPTED(sp))
				break;
			if (!silent) {
				gp->scr_busy(sp, p,
				    p == NULL ? BUSY_UPDATE : BUSY_ON);
				p = NULL;
			}
		}
		FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen);
		if (db_append(sp, 1, lno, wp, wlen))
			goto err;
		ccnt += len;
	}

	if (ferror(fp) || fclose(fp))
		goto err;

	/* Return the number of lines read in. */
	if (nlinesp != NULL)
		*nlinesp = lcnt;

	if (!silent) {
		p = msg_print(sp, name, &nf);
		msgq(sp, M_INFO,
		    "148|%s: %lu lines, %lu characters", p, lcnt, ccnt);
		if (nf)
			FREE_SPACE(sp, p, 0);
	}

	rval = 0;
	if (0) {
err:		msgq_str(sp, M_SYSERR, name, "%s");
		(void)fclose(fp);
		rval = 1;
	}

	if (!silent)
		gp->scr_busy(sp, NULL, BUSY_OFF);
	return (rval);
}
Exemple #20
0
/*
 * ex_usage -- :exusage [cmd]
 *	Display ex usage strings.
 *
 * PUBLIC: int ex_usage __P((SCR *, EXCMD *));
 */
int
ex_usage(SCR *sp, EXCMD *cmdp)
{
	ARGS *ap;
	EXCMDLIST const *cp;
	int newscreen;
	CHAR_T *p, nb[MAXCMDNAMELEN + 5];
	const CHAR_T *name;

	switch (cmdp->argc) {
	case 1:
		ap = cmdp->argv[0];
		if (ISUPPER((UCHAR_T)ap->bp[0])) {
			newscreen = 1;
			ap->bp[0] = TOLOWER((UCHAR_T)ap->bp[0]);
		} else
			newscreen = 0;
		for (cp = cmds; cp->name != NULL &&
		    memcmp(ap->bp, cp->name, ap->len); ++cp);
		if (cp->name == NULL ||
		    (newscreen && !F_ISSET(cp, E_NEWSCREEN))) {
			const char *nstr;
			size_t nlen;

			if (newscreen)
				ap->bp[0] = TOUPPER((UCHAR_T)ap->bp[0]);

			INT2CHAR(sp, ap->bp, ap->len + 1, nstr, nlen);
			(void)ex_printf(sp, "The %.*s command is unknown\n",
			    (int)ap->len, nstr);
		} else {
			(void)ex_printf(sp,
			    "Command: %s\n  Usage: %s\n", cp->help, cp->usage);
			/*
			 * !!!
			 * The "visual" command has two modes, one from ex,
			 * one from the vi colon line.  Don't ask.
			 */
			if (cp != &cmds[C_VISUAL_EX] &&
			    cp != &cmds[C_VISUAL_VI])
				break;
			if (cp == &cmds[C_VISUAL_EX])
				cp = &cmds[C_VISUAL_VI];
			else
				cp = &cmds[C_VISUAL_EX];
			(void)ex_printf(sp,
			    "Command: %s\n  Usage: %s\n", cp->help, cp->usage);
		}
		break;
	case 0:
		for (cp = cmds; cp->name != NULL && !INTERRUPTED(sp); ++cp) {
			/*
			 * The ^D command has an unprintable name.
			 *
			 * XXX
			 * We display both capital and lower-case versions of
			 * the appropriate commands -- no need to add in extra
			 * room, they're all short names.
			 */
			if (cp == &cmds[C_SCROLL])
				name = L("^D");
			else if (F_ISSET(cp, E_NEWSCREEN)) {
				nb[0] = L('[');
				nb[1] = TOUPPER((UCHAR_T)cp->name[0]);
				nb[2] = cp->name[0];
				nb[3] = L(']');
				for (name = cp->name + 1,
				    p = nb + 4; (*p++ = *name++) != '\0';);
				name = nb;
			} else
				name = cp->name;
			(void)ex_printf(sp,
			    WVS": %s\n", MAXCMDNAMELEN, name, cp->help);
		}
		break;
	default:
		abort();
	}
	return (0);
}
Exemple #21
0
/*
 * del --
 *	Delete a range of text.
 *
 * PUBLIC: int del __P((SCR *, MARK *, MARK *, int));
 */
int
del(
	SCR *sp,
	MARK *fm,
	MARK *tm,
	int lmode)
{
	recno_t lno;
	size_t blen, len, nlen, tlen;
	CHAR_T *bp, *p;
	int eof, rval;

	bp = NULL;

	/* Case 1 -- delete in line mode. */
	if (lmode) {
		for (lno = tm->lno; lno >= fm->lno; --lno) {
			if (db_delete(sp, lno))
				return (1);
			++sp->rptlines[L_DELETED];
			if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp))
				break;
		}
		goto done;
	}

	/*
	 * Case 2 -- delete to EOF.  This is a special case because it's
	 * easier to pick it off than try and find it in the other cases.
 	 */
	if (db_last(sp, &lno))
		return (1);
	if (tm->lno >= lno) {
		if (tm->lno == lno) {
			if (db_get(sp, lno, DBG_FATAL, &p, &len))
				return (1);
			eof = tm->cno != ENTIRE_LINE && tm->cno >= len ? 1 : 0;
		} else
			eof = 1;
		if (eof) {
			for (lno = tm->lno; lno > fm->lno; --lno) {
				if (db_delete(sp, lno))
					return (1);
				++sp->rptlines[L_DELETED];
				if (lno %
				    INTERRUPT_CHECK == 0 && INTERRUPTED(sp))
					break;
			}
			if (db_get(sp, fm->lno, DBG_FATAL, &p, &len))
				return (1);
			GET_SPACE_RETW(sp, bp, blen, fm->cno);
			MEMCPY(bp, p, fm->cno);
			if (db_set(sp, fm->lno, bp, fm->cno))
				return (1);
			goto done;
		}
	}

	/* Case 3 -- delete within a single line. */
	if (tm->lno == fm->lno) {
		if (db_get(sp, fm->lno, DBG_FATAL, &p, &len))
			return (1);
		GET_SPACE_RETW(sp, bp, blen, len);
		if (fm->cno != 0)
			MEMCPY(bp, p, fm->cno);
		MEMCPY(bp + fm->cno, p + (tm->cno + 1), 
			len - (tm->cno + 1));
		if (db_set(sp, fm->lno,
		    bp, len - ((tm->cno - fm->cno) + 1)))
			goto err;
		goto done;
	}

	/*
	 * Case 4 -- delete over multiple lines.
	 *
	 * Copy the start partial line into place.
	 */
	if ((tlen = fm->cno) != 0) {
		if (db_get(sp, fm->lno, DBG_FATAL, &p, NULL))
			return (1);
		GET_SPACE_RETW(sp, bp, blen, tlen + 256);
		MEMCPY(bp, p, tlen);
	}

	/* Copy the end partial line into place. */
	if (db_get(sp, tm->lno, DBG_FATAL, &p, &len))
		goto err;
	if (len != 0 && tm->cno != len - 1) {
		/*
		 * XXX
		 * We can overflow memory here, if the total length is greater
		 * than SIZE_T_MAX.  The only portable way I've found to test
		 * is depending on the overflow being less than the value.
		 */
		nlen = (len - (tm->cno + 1)) + tlen;
		if (tlen > nlen) {
			msgq(sp, M_ERR, "002|Line length overflow");
			goto err;
		}
		if (tlen == 0) {
			GET_SPACE_RETW(sp, bp, blen, nlen);
		} else
			ADD_SPACE_RETW(sp, bp, blen, nlen);

		MEMCPY(bp + tlen, p + (tm->cno + 1), len - (tm->cno + 1));
		tlen += len - (tm->cno + 1);
	}

	/* Set the current line. */
	if (db_set(sp, fm->lno, bp, tlen))
		goto err;

	/* Delete the last and intermediate lines. */
	for (lno = tm->lno; lno > fm->lno; --lno) {
		if (db_delete(sp, lno))
			goto err;
		++sp->rptlines[L_DELETED];
		if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp))
			break;
	}

done:	rval = 0;
	if (0)
err:		rval = 1;
	if (bp != NULL)
		FREE_SPACEW(sp, bp, blen);
	return (rval);
}
Exemple #22
0
/*
 * vi --
 * 	Main vi command loop.
 *
 * PUBLIC: int vi __P((SCR **));
 */
int
vi(SCR **spp)
{
	GS *gp;
	MARK abs;
	SCR *next, *sp;
	VICMD cmd = { 0 }, *vp;
	VI_PRIVATE *vip;
	int comcount, mapped, rval;

	/* Get the first screen. */
	sp = *spp;
	gp = sp->gp;

	/* Point to the command structure. */
	vp = &cmd;

	/* 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:
			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);
			gp->scr_discard(sp, NULL);
			break;
		}
	}
	if (0)
ret:		rval = 1;
	return (rval);
}