Esempio n. 1
0
/*
 * ulcase --
 *	Change part of a line's case.
 */
static int
ulcase(SCR *sp, db_recno_t lno, CHAR_T *lp, size_t len, size_t scno, size_t ecno)
{
	size_t blen;
	int change, rval;
	ARG_CHAR_T ch;
	CHAR_T *p, *t, *bp;

	GET_SPACE_RETW(sp, bp, blen, len);
	MEMMOVEW(bp, lp, len);

	change = rval = 0;
	for (p = bp + scno, t = bp + ecno + 1; p < t; ++p) {
		ch = (UCHAR_T)*p;
		if (ISLOWER(ch)) {
			*p = TOUPPER(ch);
			change = 1;
		} else if (ISUPPER(ch)) {
			*p = TOLOWER(ch);
			change = 1;
		}
	}

	if (change && db_set(sp, lno, bp, len))
		rval = 1;

	FREE_SPACEW(sp, bp, blen);
	return (rval);
}
Esempio n. 2
0
/*
 * v_searchw -- [count]^A
 *	Search for the word under the cursor.
 *
 * PUBLIC: int v_searchw __P((SCR *, VICMD *));
 */
int
v_searchw(SCR *sp, VICMD *vp)
{
	size_t blen, len;
	int rval;
	CHAR_T *bp, *p;

	/* An upper bound for the SIZE of the RE under construction. */
	len = VIP(sp)->klen + MAX(RE_WSTART_LEN, 1)
	    + MAX(RE_WSTOP_LEN, RE_NWSTOP_LEN);
	GET_SPACE_RETW(sp, bp, blen, len);
	p = bp;

	/* Only the first character can be non-word, see v_curword. */
	if (inword(VIP(sp)->keyw[0])) {
		MEMCPY(p, RE_WSTART, RE_WSTART_LEN);
		p += RE_WSTART_LEN;
	} else if (is_special(VIP(sp)->keyw[0])) {
		MEMCPY(p, L("\\"), 1);
		p += 1;
	}

	MEMCPY(p, VIP(sp)->keyw, VIP(sp)->klen);
	p += VIP(sp)->klen;

	if (inword(p[-1])) {
		MEMCPY(p, RE_WSTOP, RE_WSTOP_LEN);
		p += RE_WSTOP_LEN;
	} else {
		/*
		 * The keyword is a single non-word character.
		 * We want it to stay the same when typing ^A several times
		 * in a row, just the way the other cases behave.
		 */
		MEMCPY(p, RE_NWSTOP, RE_NWSTOP_LEN);
		p += RE_NWSTOP_LEN;
	}

	len = p - bp;
	rval = v_search(sp, vp, bp, len, SEARCH_SET, FORWARD);

	FREE_SPACEW(sp, bp, blen);
	return (rval);
}
Esempio n. 3
0
/*
 * v_searchw -- [count]^A
 *	Search for the word under the cursor.
 *
 * PUBLIC: int v_searchw __P((SCR *, VICMD *));
 */
int
v_searchw(SCR *sp, VICMD *vp)
{
	size_t blen, len;
	int rval;
	CHAR_T *bp, *p;

	len = VIP(sp)->klen + MAX(RE_WSTART_LEN, 1)
	    + MAX(RE_WSTOP_LEN, RE_NWSTOP_LEN);

	GET_SPACE_RETW(sp, bp, blen, len);
	p = bp;

	/* Only the first character can be non-word, see v_curword. */
	if (inword(VIP(sp)->keyw[0]))
		p = MEMPCPY(p, RE_WSTART, RE_WSTART_LEN);
	else if (is_especial(VIP(sp)->keyw[0]))
		p = MEMPCPY(p, L("\\"), 1);

	p = MEMPCPY(p, VIP(sp)->keyw, VIP(sp)->klen);

	if (inword(p[-1]))
		p = MEMPCPY(p, RE_WSTOP, RE_WSTOP_LEN);
	else
		/*
		 * The keyword is a single non-word character.
		 * We want it to stay the same when typing ^A several times
		 * in a row, just the way the other cases behave.
		 */
		p = MEMPCPY(p, RE_NWSTOP, RE_NWSTOP_LEN);

	len = p - bp;
	rval = v_search(sp, vp, bp, len, SEARCH_SET | SEARCH_EXTEND, FORWARD);

	FREE_SPACEW(sp, bp, blen);
	return (rval);
}
Esempio n. 4
0
/*
 * put --
 *	Put text buffer contents into the file.
 *
 * PUBLIC: int put __P((SCR *, CB *, CHAR_T *, MARK *, MARK *, int));
 */
int
put(SCR *sp, CB *cbp, ARG_CHAR_T *namep, MARK *cp, MARK *rp, int append)
{
	ARG_CHAR_T name;
	TEXT *ltp, *tp;
	db_recno_t lno;
	size_t blen, clen, len;
	int rval;
	CHAR_T *bp, *t;
	CHAR_T *p;

	if (cbp == NULL) {
		if (namep == NULL) {
			cbp = sp->wp->dcbp;
			if (cbp == NULL) {
				msgq(sp, M_ERR,
				    "053|The default buffer is empty");
				return (1);
			}
		} else {
			name = *namep;
			CBNAME(sp, cbp, name);
			if (cbp == NULL) {
				msgq(sp, M_ERR, "054|Buffer %s is empty",
				    KEY_NAME(sp, name));
				return (1);
			}
		}
	}
	tp = cbp->textq.cqh_first;

	/*
	 * It's possible to do a put into an empty file, meaning that the cut
	 * buffer simply becomes the file.  It's a special case so that we can
	 * ignore it in general.
	 *
	 * !!!
	 * Historically, pasting into a file with no lines in vi would preserve
	 * the single blank line.  This is surely a result of the fact that the
	 * historic vi couldn't deal with a file that had no lines in it.  This
	 * implementation treats that as a bug, and does not retain the blank
	 * line.
	 *
	 * Historical practice is that the cursor ends at the first character
	 * in the file.
	 */
	if (cp->lno == 1) {
		if (db_last(sp, &lno))
			return (1);
		if (lno == 0) {
			for (; tp != (void *)&cbp->textq;
			    ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next)
				if (db_append(sp, 1, lno, tp->lb, tp->len))
					return (1);
			rp->lno = 1;
			rp->cno = 0;
			return (0);
		}
	}

	/* If a line mode buffer, append each new line into the file. */
	if (F_ISSET(cbp, CB_LMODE)) {
		lno = append ? cp->lno : cp->lno - 1;
		rp->lno = lno + 1;
		for (; tp != (void *)&cbp->textq;
		    ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next)
			if (db_append(sp, 1, lno, tp->lb, tp->len))
				return (1);
		rp->cno = 0;
		(void)nonblank(sp, rp->lno, &rp->cno);
		return (0);
	}

	/*
	 * If buffer was cut in character mode, replace the current line with
	 * one built from the portion of the first line to the left of the
	 * split plus the first line in the CB.  Append each intermediate line
	 * in the CB.  Append a line built from the portion of the first line
	 * to the right of the split plus the last line in the CB.
	 *
	 * Get the first line.
	 */
	lno = cp->lno;
	if (db_get(sp, lno, DBG_FATAL, &p, &len))
		return (1);

	GET_SPACE_RETW(sp, bp, blen, tp->len + len + 1);
	t = bp;

	/* Original line, left of the split. */
	if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) {
		MEMCPYW(bp, p, clen);
		p += clen;
		t += clen;
	}

	/* First line from the CB. */
	if (tp->len != 0) {
		MEMCPYW(t, tp->lb, tp->len);
		t += tp->len;
	}

	/* Calculate length left in the original line. */
	clen = len == 0 ? 0 : len - (cp->cno + (append ? 1 : 0));

	/*
	 * !!!
	 * In the historical 4BSD version of vi, character mode puts within
	 * a single line have two cursor behaviors: if the put is from the
	 * unnamed buffer, the cursor moves to the character inserted which
	 * appears last in the file.  If the put is from a named buffer,
	 * the cursor moves to the character inserted which appears first
	 * in the file.  In System III/V, it was changed at some point and
	 * the cursor always moves to the first character.  In both versions
	 * of vi, character mode puts that cross line boundaries leave the
	 * cursor on the first character.  Nvi implements the System III/V
	 * behavior, and expect POSIX.2 to do so as well.
	 */
	rp->lno = lno;
	rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0);

	/*
	 * If no more lines in the CB, append the rest of the original
	 * line and quit.  Otherwise, build the last line before doing
	 * the intermediate lines, because the line changes will lose
	 * the cached line.
	 */
	if (tp->q.cqe_next == (void *)&cbp->textq) {
		if (clen > 0) {
			MEMCPYW(t, p, clen);
			t += clen;
		}
		if (db_set(sp, lno, bp, t - bp))
			goto err;
		if (sp->rptlchange != lno) {
			sp->rptlchange = lno;
			++sp->rptlines[L_CHANGED];
		}
	} else {
		/*
		 * Have to build both the first and last lines of the
		 * put before doing any sets or we'll lose the cached
		 * line.  Build both the first and last lines in the
		 * same buffer, so we don't have to have another buffer
		 * floating around.
		 *
		 * Last part of original line; check for space, reset
		 * the pointer into the buffer.
		 */
		ltp = cbp->textq.cqh_last;
		len = t - bp;
		ADD_SPACE_RETW(sp, bp, blen, ltp->len + clen);
		t = bp + len;

		/* Add in last part of the CB. */
		MEMCPYW(t, ltp->lb, ltp->len);
		if (clen)
			MEMCPYW(t + ltp->len, p, clen);
		clen += ltp->len;

		/*
		 * Now: bp points to the first character of the first
		 * line, t points to the last character of the last
		 * line, t - bp is the length of the first line, and
		 * clen is the length of the last.  Just figured you'd
		 * want to know.
		 *
		 * Output the line replacing the original line.
		 */
		if (db_set(sp, lno, bp, t - bp))
			goto err;
		if (sp->rptlchange != lno) {
			sp->rptlchange = lno;
			++sp->rptlines[L_CHANGED];
		}

		/* Output any intermediate lines in the CB. */
		for (tp = tp->q.cqe_next;
		    tp->q.cqe_next != (void *)&cbp->textq;
		    ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next)
			if (db_append(sp, 1, lno, tp->lb, tp->len))
				goto err;

		if (db_append(sp, 1, lno, t, clen))
			goto err;
		++sp->rptlines[L_ADDED];
	}
	rval = 0;

	if (0)
err:		rval = 1;

	FREE_SPACEW(sp, bp, blen);
	return (rval);
}
Esempio n. 5
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);
}
Esempio n. 6
0
/*
 * v_replace -- [count]r<char>
 *
 * !!!
 * The r command in historic vi was almost beautiful in its badness.  For
 * example, "r<erase>" and "r<word erase>" beeped the terminal and deleted
 * a single character.  "Nr<carriage return>", where N was greater than 1,
 * inserted a single carriage return.  "r<escape>" did cancel the command,
 * but "r<literal><escape>" erased a single character.  To enter a literal
 * <literal> character, it required three <literal> characters after the
 * command.  This may not be right, but at least it's not insane.
 *
 * PUBLIC: int v_replace __P((SCR *, VICMD *));
 */
int
v_replace(SCR *sp, VICMD *vp)
{
	EVENT ev;
	VI_PRIVATE *vip;
	TEXT *tp;
	size_t blen, len;
	u_long cnt;
	int quote, rval;
	CHAR_T *bp;
	CHAR_T *p;

	vip = VIP(sp);

	/*
	 * If the line doesn't exist, or it's empty, replacement isn't
	 * allowed.  It's not hard to implement, but:
	 *
	 *	1: It's historic practice (vi beeped before the replacement
	 *	   character was even entered).
	 *	2: For consistency, this change would require that the more
	 *	   general case, "Nr", when the user is < N characters from
	 *	   the end of the line, also work, which would be a bit odd.
	 *	3: Replacing with a <newline> has somewhat odd semantics.
	 */
	if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len))
		return (1);
	if (len == 0) {
		msgq(sp, M_BERR, "186|No characters to replace");
		return (1);
	}

	/*
	 * Figure out how many characters to be replace.  For no particular
	 * reason (other than that the semantics of replacing the newline
	 * are confusing) only permit the replacement of the characters in
	 * the current line.  I suppose we could append replacement characters
	 * to the line, but I see no compelling reason to do so.  Check this
	 * before we get the character to match historic practice, where Nr
	 * failed immediately if there were less than N characters from the
	 * cursor to the end of the line.
	 */
	cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
	vp->m_stop.lno = vp->m_start.lno;
	vp->m_stop.cno = vp->m_start.cno + cnt - 1;
	if (vp->m_stop.cno > len - 1) {
		v_eol(sp, &vp->m_start);
		return (1);
	}

	/*
	 * If it's not a repeat, reset the current mode and get a replacement
	 * character.
	 */
	quote = 0;
	if (!F_ISSET(vp, VC_ISDOT)) {
		sp->showmode = SM_REPLACE;
		if (vs_refresh(sp, 0))
			return (1);
next:		if (v_event_get(sp, &ev, 0, 0))
			return (1);

		switch (ev.e_event) {
		case E_CHARACTER:
			/*
			 * <literal_next> means escape the next character.
			 * <escape> means they changed their minds.
			 */
			if (!quote) {
				if (ev.e_value == K_VLNEXT) {
					quote = 1;
					goto next;
				}
				if (ev.e_value == K_ESCAPE)
					return (0);
			}
			vip->rlast = ev.e_c;
			vip->rvalue = ev.e_value;
			break;
		case E_ERR:
		case E_EOF:
			F_SET(sp, SC_EXIT_FORCE);
			return (1);
		case E_INTERRUPT:
			/* <interrupt> means they changed their minds. */
			return (0);
		case E_WRESIZE:
			/* <resize> interrupts the input mode. */
			v_emsg(sp, NULL, VIM_WRESIZE);
			return (0);
		case E_REPAINT:
			if (vs_repaint(sp, &ev))
				return (1);
			goto next;
		default:
			v_event_err(sp, &ev);
			return (0);
		}
	}

	/* Copy the line. */
	GET_SPACE_RETW(sp, bp, blen, len);
	MEMMOVE(bp, p, len);
	p = bp;

	/*
	 * Versions of nvi before 1.57 created N new lines when they replaced
	 * N characters with <carriage-return> or <newline> characters.  This
	 * is different from the historic vi, which replaced N characters with
	 * a single new line.  Users complained, so we match historic practice.
	 */
	if ((!quote && vip->rvalue == K_CR) || vip->rvalue == K_NL) {
		/* Set return line. */
		vp->m_stop.lno = vp->m_start.lno + 1;
		vp->m_stop.cno = 0;

		/* The first part of the current line. */
		if (db_set(sp, vp->m_start.lno, p, vp->m_start.cno))
			goto err_ret;

		/*
		 * The rest of the current line.  And, of course, now it gets
		 * tricky.  If there are characters left in the line and if
		 * the autoindent edit option is set, white space after the
		 * replaced character is discarded, autoindent is applied, and
		 * the cursor moves to the last indent character.
		 */
		p += vp->m_start.cno + cnt;
		len -= vp->m_start.cno + cnt;
		if (len != 0 && O_ISSET(sp, O_AUTOINDENT))
			for (; len && isblank(*p); --len, ++p);

		if ((tp = text_init(sp, p, len, len)) == NULL)
			goto err_ret;

		if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) {
			if (v_txt_auto(sp, vp->m_start.lno, NULL, 0, tp))
				goto err_ret;
			vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0;
		} else
			vp->m_stop.cno = 0;

		vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0;
		if (db_append(sp, 1, vp->m_start.lno, tp->lb, tp->len))
err_ret:		rval = 1;
		else {
			text_free(tp);
			rval = 0;
		}
	} else {
		STRSET(bp + vp->m_start.cno, vip->rlast, cnt);
		rval = db_set(sp, vp->m_start.lno, bp, len);
	}
	FREE_SPACEW(sp, bp, blen);

	vp->m_final = vp->m_stop;
	return (rval);
}
Esempio n. 7
0
/*
 * v_change -- [buffer][count]c[count]motion
 *	       [buffer][count]C
 *	       [buffer][count]S
 *	Change command.
 *
 * PUBLIC: int v_change __P((SCR *, VICMD *));
 */
int
v_change(SCR *sp, VICMD *vp)
{
	size_t blen, len;
	u_int32_t flags;
	int isempty, lmode, rval;
	CHAR_T *bp;
	CHAR_T *p;

	/*
	 * 'c' can be combined with motion commands that set the resulting
	 * cursor position, i.e. "cG".  Clear the VM_RCM flags and make the
	 * resulting cursor position stick, inserting text has its own rules
	 * for cursor positioning.
	 */
	F_CLR(vp, VM_RCM_MASK);
	F_SET(vp, VM_RCM_SET);

	/*
	 * Find out if the file is empty, it's easier to handle it as a
	 * special case.
	 */
	if (vp->m_start.lno == vp->m_stop.lno &&
	    db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
		if (!isempty)
			return (1);
		return (v_ia(sp, vp));
	}

	flags = set_txt_std(sp, vp, 0);
	sp->showmode = SM_CHANGE;

	/*
	 * Move the cursor to the start of the change.  Note, if autoindent
	 * is turned on, the cc command in line mode changes from the first
	 * *non-blank* character of the line, not the first character.  And,
	 * to make it just a bit more exciting, the initial space is handled
	 * as auto-indent characters.
	 */
	lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0;
	if (lmode) {
		vp->m_start.cno = 0;
		if (O_ISSET(sp, O_AUTOINDENT)) {
			if (nonblank(sp, vp->m_start.lno, &vp->m_start.cno))
				return (1);
			LF_SET(TXT_AICHARS);
		}
	}
	sp->lno = vp->m_start.lno;
	sp->cno = vp->m_start.cno;

	LOG_CORRECT;

	/*
	 * If not in line mode and changing within a single line, copy the
	 * text and overwrite it.
	 */
	if (!lmode && vp->m_start.lno == vp->m_stop.lno) {
		/*
		 * !!!
		 * Historic practice, c did not cut into the numeric buffers,
		 * only the unnamed one.
		 */
		if (cut(sp,
		    F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
		    &vp->m_start, &vp->m_stop, lmode))
			return (1);
		if (len == 0)
			LF_SET(TXT_APPENDEOL);
		LF_SET(TXT_EMARK | TXT_OVERWRITE);
		return (v_txt(sp, vp, &vp->m_stop, p, len,
		    0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags));
	}

	/*
	 * It's trickier if in line mode or changing over multiple lines.  If
	 * we're in line mode delete all of the lines and insert a replacement
	 * line which the user edits.  If there was leading whitespace in the
	 * first line being changed, we copy it and use it as the replacement.
	 * If we're not in line mode, we delete the text and start inserting.
	 *
	 * !!!
	 * Copy the text.  Historic practice, c did not cut into the numeric
	 * buffers, only the unnamed one.
	 */
	if (cut(sp,
	    F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL,
	    &vp->m_start, &vp->m_stop, lmode))
		return (1);

	/* If replacing entire lines and there's leading text. */
	if (lmode && vp->m_start.cno) {
		/*
		 * Get a copy of the first line changed, and copy out the
		 * leading text.
		 */
		if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len))
			return (1);
		GET_SPACE_RETW(sp, bp, blen, vp->m_start.cno);
		MEMMOVEW(bp, p, vp->m_start.cno);
	} else
		bp = NULL;

	/* Delete the text. */
	if (del(sp, &vp->m_start, &vp->m_stop, lmode))
		return (1);

	/* If replacing entire lines, insert a replacement line. */
	if (lmode) {
		if (db_insert(sp, vp->m_start.lno, bp, vp->m_start.cno))
			return (1);
		sp->lno = vp->m_start.lno;
		len = sp->cno = vp->m_start.cno;
	}

	/* Get the line we're editing. */
	if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
		if (!isempty)
			return (1);
		len = 0;
	}

	/* Check to see if we're appending to the line. */
	if (vp->m_start.cno >= len)
		LF_SET(TXT_APPENDEOL);

	rval = v_txt(sp, vp, NULL, p, len,
	    0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags);

	if (bp != NULL)
		FREE_SPACEW(sp, bp, blen);
	return (rval);
}
/*
 * shift --
 *	Ex shift support.
 */
static int
shift(SCR *sp, EXCMD *cmdp, enum which rl)
{
	db_recno_t from, to;
	size_t blen, len, newcol, newidx, oldcol, oldidx, sw;
	int curset;
	CHAR_T *p;
	CHAR_T *bp, *tbp;

	NEEDFILE(sp, cmdp);

	if (O_VAL(sp, O_SHIFTWIDTH) == 0) {
		msgq(sp, M_INFO, "152|shiftwidth option set to 0");
		return (0);
	}

	/* Copy the lines being shifted into the unnamed buffer. */
	if (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE))
		return (1);

	/*
	 * The historic version of vi permitted the user to string any number
	 * of '>' or '<' characters together, resulting in an indent of the
	 * appropriate levels.  There's a special hack in ex_cmd() so that
	 * cmdp->argv[0] points to the string of '>' or '<' characters.
	 *
	 * Q: What's the difference between the people adding features
	 *    to vi and the Girl Scouts?
	 * A: The Girl Scouts have mint cookies and adult supervision.
	 */
	for (p = cmdp->argv[0]->bp, sw = 0; *p == '>' || *p == '<'; ++p)
		sw += O_VAL(sp, O_SHIFTWIDTH);

	GET_SPACE_RETW(sp, bp, blen, 256);

	curset = 0;
	for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) {
		if (db_get(sp, from, DBG_FATAL, &p, &len))
			goto err;
		if (!len) {
			if (sp->lno == from)
				curset = 1;
			continue;
		}

		/*
		 * Calculate the old indent amount and the number of
		 * characters it used.
		 */
		for (oldidx = 0, oldcol = 0; oldidx < len; ++oldidx)
			if (p[oldidx] == ' ')
				++oldcol;
			else if (p[oldidx] == '\t')
				oldcol += O_VAL(sp, O_TABSTOP) -
				    oldcol % O_VAL(sp, O_TABSTOP);
			else
				break;

		/* Calculate the new indent amount. */
		if (rl == RIGHT)
			newcol = oldcol + sw;
		else {
			newcol = oldcol < sw ? 0 : oldcol - sw;
			if (newcol == oldcol) {
				if (sp->lno == from)
					curset = 1;
				continue;
			}
		}

		/* Get a buffer that will hold the new line. */
		ADD_SPACE_RETW(sp, bp, blen, newcol + len);

		/*
		 * Build a new indent string and count the number of
		 * characters it uses.
		 */
		tbp = bp;
		newidx = 0;
		if (!O_ISSET(sp, O_EXPANDTAB)) {
			for (; newcol >= O_VAL(sp, O_TABSTOP); ++newidx) {
				*tbp++ = '\t';
				newcol -= O_VAL(sp, O_TABSTOP);
			}
		}
		for (; newcol > 0; --newcol, ++newidx)
			*tbp++ = ' ';

		/* Add the original line. */
		MEMCPYW(tbp, p + oldidx, len - oldidx);

		/* Set the replacement line. */
		if (db_set(sp, from, bp, (tbp + (len - oldidx)) - bp)) {
err:			FREE_SPACEW(sp, bp, blen);
			return (1);
		}

		/*
		 * !!!
		 * The shift command in historic vi had the usual bizarre
		 * collection of cursor semantics.  If called from vi, the
		 * cursor was repositioned to the first non-blank character
		 * of the lowest numbered line shifted.  If called from ex,
		 * the cursor was repositioned to the first non-blank of the
		 * highest numbered line shifted.  Here, if the cursor isn't
		 * part of the set of lines that are moved, move it to the
		 * first non-blank of the last line shifted.  (This makes
		 * ":3>>" in vi work reasonably.)  If the cursor is part of
		 * the shifted lines, it doesn't get moved at all.  This
		 * permits shifting of marked areas, i.e. ">'a." shifts the
		 * marked area twice, something that couldn't be done with
		 * historic vi.
		 */
		if (sp->lno == from) {
			curset = 1;
			if (newidx > oldidx)
				sp->cno += newidx - oldidx;
			else if (sp->cno >= oldidx - newidx)
				sp->cno -= oldidx - newidx;
		}
	}
	if (!curset) {
		sp->lno = to;
		sp->cno = 0;
		(void)nonblank(sp, to, &sp->cno);
	}

	FREE_SPACEW(sp, bp, blen);

	sp->rptlines[L_SHIFT] += cmdp->addr2.lno - cmdp->addr1.lno + 1;
	return (0);
}
Esempio n. 9
0
/*
 * v_increment -- [count]#[#+-]
 *	Increment/decrement a keyword number.
 *
 * PUBLIC: int v_increment __P((SCR *, VICMD *));
 */
int
v_increment(SCR *sp, VICMD *vp)
{
	enum nresult nret;
	u_long ulval;
	long change, ltmp, lval;
	size_t beg, blen, end, len, nlen, wlen;
	int base, isempty, rval;
	CHAR_T *ntype, nbuf[100];
	CHAR_T *bp, *p, *t;

	/* Validate the operator. */
	if (vp->character == '#')
		vp->character = '+';
	if (vp->character != '+' && vp->character != '-') {
		v_emsg(sp, vp->kp->usage, VIM_USAGE);
		return (1);
	}

	/* If new value set, save it off, but it has to fit in a long. */
	if (F_ISSET(vp, VC_C1SET)) {
		if (vp->count > LONG_MAX) {
			inc_err(sp, NUM_OVER);
			return (1);
		}
		change = vp->count;
	} else
		change = 1;

	/* Get the line. */
	if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
		if (isempty)
			goto nonum;
		return (1);
	}

	/*
	 * Skip any leading space before the number.  Getting a cursor word
	 * implies moving the cursor to its beginning, if we moved, refresh
	 * now.
	 */
	for (beg = vp->m_start.cno; beg < len && ISSPACE(p[beg]); ++beg);
	if (beg >= len)
		goto nonum;
	if (beg != vp->m_start.cno) {
		sp->cno = beg;
		(void)vs_refresh(sp, 0);
	}

#undef	ishex
#define	ishex(c)	(ISXDIGIT(c))
#undef	isoctal
#define	isoctal(c)	((c) >= '0' && (c) <= '7')

	/*
	 * Look for 0[Xx], or leading + or - signs, guess at the base.
	 * The character after that must be a number.  Wlen is set to
	 * the remaining characters in the line that could be part of
	 * the number.
	 */
	wlen = len - beg;
	if (p[beg] == '0' && wlen > 2 &&
	    (p[beg + 1] == 'X' || p[beg + 1] == 'x')) {
		base = 16;
		end = beg + 2;
		if (!ishex(p[end]))
			goto decimal;
		ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL];
	} else if (p[beg] == '0' && wlen > 1) {
		base = 8;
		end = beg + 1;
		if (!isoctal(p[end]))
			goto decimal;
		ntype = fmt[OCTAL];
	} else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) {
		base = 10;
		end = beg + 1;
		ntype = fmt[SDEC];
		if (!isdigit(p[end]))
			goto nonum;
	} else {
decimal:	base = 10;
		end = beg;
		ntype = fmt[DEC];
		if (!isdigit(p[end])) {
nonum:			msgq(sp, M_ERR, "181|Cursor not in a number");
			return (1);
		}
	}

	/* Find the end of the word, possibly correcting the base. */
	while (++end < len) {
		switch (base) {
		case 8:
			if (isoctal(p[end]))
				continue;
			if (p[end] == '8' || p[end] == '9') {
				base = 10;
				ntype = fmt[DEC];
				continue;
			}
			break;
		case 10:
			if (isdigit(p[end]))
				continue;
			break;
		case 16:
			if (ishex(p[end]))
				continue;
			break;
		default:
			abort();
			/* NOTREACHED */
		}
		break;
	}
	wlen = (end - beg);

	/*
	 * XXX
	 * If the line was at the end of the buffer, we have to copy it
	 * so we can guarantee that it's NULL-terminated.  We make the
	 * buffer big enough to fit the line changes as well, and only
	 * allocate once.
	 */
	GET_SPACE_RETW(sp, bp, blen, len + 50);
	if (end == len) {
		MEMMOVE(bp, &p[beg], wlen);
		bp[wlen] = '\0';
		t = bp;
	} else
		t = &p[beg];

	/*
	 * Octal or hex deal in unsigned longs, everything else is done
	 * in signed longs.
	 */
	if (base == 10) {
		if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK)
			goto err;
		ltmp = vp->character == '-' ? -change : change;
		if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) {
			nret = NUM_OVER;
			goto err;
		}
		if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) {
			nret = NUM_UNDER;
			goto err;
		}
		lval += ltmp;
		/* If we cross 0, signed numbers lose their sign. */
		if (lval == 0 && ntype == fmt[SDEC])
			ntype = fmt[DEC];
		nlen = SPRINTF(nbuf, sizeof(nbuf), ntype, lval);
	} else {
		if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK)
			goto err;
		if (vp->character == '+') {
			if (!NPFITS(ULONG_MAX, ulval, change)) {
				nret = NUM_OVER;
				goto err;
			}
			ulval += change;
		} else {
			if (ulval < change) {
				nret = NUM_UNDER;
				goto err;
			}
			ulval -= change;
		}

		/* Correct for literal "0[Xx]" in format. */
		if (base == 16)
			wlen -= 2;

		nlen = SPRINTF(nbuf, sizeof(nbuf), ntype, wlen, ulval);
	}

	/* Build the new line. */
	MEMMOVE(bp, p, beg);
	MEMMOVE(bp + beg, nbuf, nlen);
	MEMMOVE(bp + beg + nlen, p + end, len - beg - (end - beg));
	len = beg + nlen + (len - beg - (end - beg));

	nret = NUM_OK;
	rval = db_set(sp, vp->m_start.lno, bp, len);

	if (0) {
err:		rval = 1;
		inc_err(sp, nret);
	}
	if (bp != NULL)
		FREE_SPACEW(sp, bp, blen);
	return (rval);
}
Esempio n. 10
0
/*
 * ex_move -- :[line [,line]] mo[ve] line
 *	Move selected lines.
 *
 * PUBLIC: int ex_move __P((SCR *, EXCMD *));
 */
int
ex_move(SCR *sp, EXCMD *cmdp)
{
	LMARK *lmp;
	MARK fm1, fm2;
	db_recno_t cnt, diff, fl, tl, mfl, mtl;
	size_t blen, len;
	int mark_reset;
	CHAR_T *bp;
	CHAR_T *p;

	NEEDFILE(sp, cmdp);

	/*
	 * It's not possible to move things into the area that's being
	 * moved.
	 */
	fm1 = cmdp->addr1;
	fm2 = cmdp->addr2;
	if (cmdp->lineno >= fm1.lno && cmdp->lineno <= fm2.lno) {
		msgq(sp, M_ERR, "139|Destination line is inside move range");
		return (1);
	}

	/*
	 * Log the positions of any marks in the to-be-deleted lines.  This
	 * has to work with the logging code.  What happens is that we log
	 * the old mark positions, make the changes, then log the new mark
	 * positions.  Then the marks end up in the right positions no matter
	 * which way the log is traversed.
	 *
	 * XXX
	 * Reset the MARK_USERSET flag so that the log can undo the mark.
	 * This isn't very clean, and should probably be fixed.
	 */
	fl = fm1.lno;
	tl = cmdp->lineno;

	/* Log the old positions of the marks. */
	mark_reset = 0;
	LIST_FOREACH(lmp, &sp->ep->marks, q)
		if (lmp->name != ABSMARK1 &&
		    lmp->lno >= fl && lmp->lno <= tl) {
			mark_reset = 1;
			F_CLR(lmp, MARK_USERSET);
			(void)log_mark(sp, lmp);
		}

	/* Get memory for the copy. */
	GET_SPACE_RETW(sp, bp, blen, 256);

	/* Move the lines. */
	diff = (fm2.lno - fm1.lno) + 1;
	if (tl > fl) {				/* Destination > source. */
		mfl = tl - diff;
		mtl = tl;
		for (cnt = diff; cnt--;) {
			if (db_get(sp, fl, DBG_FATAL, &p, &len))
				return (1);
			BINC_RETW(sp, bp, blen, len);
			MEMCPYW(bp, p, len);
			if (db_append(sp, 1, tl, bp, len))
				return (1);
			if (mark_reset)
				LIST_FOREACH(lmp, &sp->ep->marks, q)
					if (lmp->name != ABSMARK1 &&
					    lmp->lno == fl)
						lmp->lno = tl + 1;
			if (db_delete(sp, fl))
				return (1);
		}
	} else {				/* Destination < source. */
Esempio n. 11
0
/*
 * sscr_insert --
 *	Take a line from the shell and insert it into the file.
 */
static int
sscr_insert(SCR *sp)
{
	struct timeval tv;
	CHAR_T *endp, *p, *t;
	SCRIPT *sc;
	fd_set rdfd;
	db_recno_t lno;
	size_t blen, len = 0, tlen;
	e_key_t value;
	int nr, rval;
	CHAR_T *bp;

	/* Find out where the end of the file is. */
	if (db_last(sp, &lno))
		return (1);

#define	MINREAD	1024
	GET_SPACE_RETW(sp, bp, blen, MINREAD);
	endp = bp;

	/* Read the characters. */
	rval = 1;
	sc = sp->script;
more:	switch (nr = read(sc->sh_master, endp, MINREAD)) {
	case  0:			/* EOF; shell just exited. */
		sscr_end(sp);
		rval = 0;
		goto ret;
	case -1:			/* Error or interrupt. */
		msgq(sp, M_SYSERR, "shell");
		goto ret;
	default:
		endp += nr;
		break;
	}

	/* Append the lines into the file. */
	for (p = t = bp; p < endp; ++p) {
		value = KEY_VAL(sp, *p);
		if (value == K_CR || value == K_NL) {
			len = p - t;
			if (db_append(sp, 1, lno++, t, len))
				goto ret;
			t = p + 1;
		}
	}
	if (p > t) {
		len = p - t;
		/*
		 * If the last thing from the shell isn't another prompt, wait
		 * up to 1/10 of a second for more stuff to show up, so that
		 * we don't break the output into two separate lines.  Don't
		 * want to hang indefinitely because some program is hanging,
		 * confused the shell, or whatever.
		 */
		if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
			tv.tv_sec = 0;
			tv.tv_usec = 100000;
			FD_ZERO(&rdfd);
			FD_SET(sc->sh_master, &rdfd);
			if (select(sc->sh_master + 1,
			    &rdfd, NULL, NULL, &tv) == 1) {
				MEMMOVE(bp, t, len);
				endp = bp + len;
				goto more;
			}
		}
		if (sscr_setprompt(sp, t, len))
			return (1);
		if (db_append(sp, 1, lno++, t, len))
			goto ret;
	}

	/* The cursor moves to EOF. */
	sp->lno = lno;
	sp->cno = len ? len - 1 : 0;
	rval = vs_refresh(sp, 1);

ret:	FREE_SPACEW(sp, bp, blen);
	return (rval);
}
Esempio n. 12
0
/*
 * sscr_exec --
 *	Take a line and hand it off to the shell.
 *
 * PUBLIC: int sscr_exec __P((SCR *, db_recno_t));
 */
int
sscr_exec(SCR *sp, db_recno_t lno)
{
	SCRIPT *sc;
	db_recno_t last_lno;
	size_t blen, len, last_len, tlen;
	int isempty, matchprompt, rval;
	ssize_t nw;
	CHAR_T *bp = NULL;
	CHAR_T *p;

	/* If there's a prompt on the last line, append the command. */
	if (db_last(sp, &last_lno))
		return (1);
	if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len))
		return (1);
	if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
		matchprompt = 1;
		GET_SPACE_RETW(sp, bp, blen, last_len + 128);
		MEMMOVEW(bp, p, last_len);
	} else
		matchprompt = 0;

	/* Get something to execute. */
	if (db_eget(sp, lno, &p, &len, &isempty)) {
		if (isempty)
			goto empty;
		goto err1;
	}

	/* Empty lines aren't interesting. */
	if (len == 0)
		goto empty;

	/* Delete any prompt. */
	if (sscr_matchprompt(sp, p, len, &tlen)) {
		if (tlen == len) {
empty:			msgq(sp, M_BERR, "151|No command to execute");
			goto err1;
		}
		p += (len - tlen);
		len = tlen;
	}

	/* Push the line to the shell. */
	sc = sp->script;
	if ((size_t)(nw = write(sc->sh_master, p, len)) != len)
		goto err2;
	rval = 0;
	if (write(sc->sh_master, "\n", 1) != 1) {
err2:		if (nw == 0)
			errno = EIO;
		msgq(sp, M_SYSERR, "shell");
		goto err1;
	}

	if (matchprompt) {
		ADD_SPACE_RETW(sp, bp, blen, last_len + len);
		MEMMOVEW(bp + last_len, p, len);
		if (db_set(sp, last_lno, bp, last_len + len))
err1:			rval = 1;
	}
	if (matchprompt)
		FREE_SPACEW(sp, bp, blen);
	return (rval);
}