4.4BSD/usr/src/contrib/nvi/nvi/vi/v_ntext.c
/*-
* Copyright (c) 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static char sccsid[] = "@(#)v_ntext.c 8.1 (Berkeley) 6/9/93";
#endif /* not lint */
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include "vi.h"
#include "vcmd.h"
static int txt_abbrev __P((SCR *, TEXT *));
static TEXT *txt_backup __P((SCR *, EXF *, HDR *, TEXT *, u_int));
static void txt_err __P((SCR *, EXF *, HDR *));
static int txt_indent __P((SCR *, TEXT *));
static int txt_outdent __P((SCR *, TEXT *));
static int txt_resolve __P((SCR *, EXF *, HDR *));
/* Cursor character (space is hard to track on the screen). */
#ifdef DEBUG
#define CURSOR_CHAR '+'
#else
#define CURSOR_CHAR ' '
#endif
/* Error jump. */
#define ERR { \
eval = 1; \
txt_err(sp, ep, hp); \
goto ret; \
}
/* Local version of BINC. */
#define TBINC(sp, lp, llen, nlen) { \
if ((nlen) > llen && binc(sp, &(lp), &(llen), nlen)) \
ERR; \
}
/*
* newtext --
* Read in text from the user.
*/
int
v_ntext(sp, ep, hp, tm, p, len, rp, prompt, ai_line, flags)
SCR *sp;
EXF *ep;
HDR *hp;
MARK *tm; /* To MARK. */
char *p; /* Input line. */
size_t len; /* Input line length. */
MARK *rp; /* Return MARK. */
int prompt; /* Prompt to display. */
recno_t ai_line; /* Line number to use for autoindent count. */
u_int flags; /* TXT_ flags. */
{
/* State of the "[^0]^D" sequences. */
enum { C_CARATSET, C_NOCHANGE, C_NOTSET, C_ZEROSET } carat_st;
/* State of quotation. */
enum { Q_NOTSET, Q_NEXTCHAR, Q_THISCHAR } quoted;
/* State of abbreviation checks. */
enum { L_NOCHECK, L_SPACE, L_NOTSPACE } lch;
TEXT *tp, *ntp; /* Input text structures. */
TEXT *aitp; /* Autoindent text structure. */
size_t rcol; /* 0-N: insert offset in the replay buffer. */
int ch; /* Input character. */
int eval; /* Routine return value. */
int replay; /* If replaying a set of input. */
int tty_cwait; /* Characters waiting. */
int max, tmp;
/* Set the input flag, so tabs get displayed correctly. */
F_SET(sp, S_INPUT);
/* Set text buffer in-use flag. */
F_SET(hp, HDR_INUSE);
/* Set return value. */
eval = 0;
/*
* Get one TEXT structure with some initial buffer space, reusing
* the last one if it's big enough. (All TEXT bookkeeping fields
* default to 0 -- text_init() handles this.) If changing a line,
* copy it into the TEXT buffer.
*/
if (hp->next != hp) {
tp = hp->next;
if (tp->next != (TEXT *)hp || tp->lb_len < len + 32) {
hdr_text_free(hp);
goto newtp;
}
tp->ai = tp->insert = tp->offset = tp->overwrite = 0;
if (p != NULL) {
tp->len = len;
memmove(tp->lb, p, len);
} else
tp->len = 0;
} else {
newtp: if ((tp = text_init(sp, p, len, len + 32)) == NULL)
return (1);
HDR_INSERT(tp, hp, next, prev, TEXT);
}
/* Set the starting line number. */
tp->lno = sp->lno;
/*
* Set the insert and overwrite counts. If overwriting characters,
* do insertion afterward. If not overwriting characters, assume
* doing insertion. If change is to a mark, emphasize it with an
* END_CH.
*/
if (len) {
if (LF_ISSET(TXT_OVERWRITE)) {
tp->overwrite = tm->cno - sp->cno;
tp->insert = len - tm->cno;
} else
tp->insert = len - sp->cno;
if (LF_ISSET(TXT_EMARK))
tp->lb[tm->cno - 1] = END_CH;
}
/*
* Many of the special cases in this routine are to handle autoindent
* support. Some utter fool decided that it would be a good idea if
* "^^D" and "0^D" deleted all of the autoindented characters. In an
* editor that takes single character input from the user, this is so
* stupid as to be unbelievable. Note also that "^^D" resets the next
* lines' autoindent, but "0^D" doesn't.
*
* We assume that autoindent only happens on empty lines, so insert
* and overwrite will be zero. If doing autoindent, figure out how
* much indentation we need and fill it in. Update input column and
* screen cursor as necessary.
*/
if (LF_ISSET(TXT_AUTOINDENT) && ai_line != OOBLNO) {
if (txt_auto(sp, ep, ai_line, NULL, tp))
return (1);
sp->cno = tp->ai;
} else {
/*
* The change command has a special "feature" -- leading space
* characters are handled as autoindent characters. Beauty!
*/
if (LF_ISSET(TXT_AICHARS)) {
tp->offset = 0;
tp->ai = sp->cno;
} else
tp->offset = sp->cno;
}
aitp = tp;
/* If getting a command buffer from the user, there may be a prompt. */
if (LF_ISSET(TXT_PROMPT)) {
tp->lb[sp->cno++] = prompt;
++tp->len;
++tp->offset;
}
/*
* If appending after the end-of-line, add a space into the buffer
* and move the cursor right. This space is inserted, i.e. pushed
* along, and then deleted when the line is resolved. Assumes that
* the cursor is already positioned at the end of the line. This
* avoids the nastiness of having the cursor reside on a magical
* column, i.e. a column that doesn't really exist. The only down
* side is that we may wrap lines or scroll the screen before it's
* strictly necessary. Not a big deal.
*/
if (LF_ISSET(TXT_APPENDEOL)) {
tp->lb[sp->cno] = CURSOR_CHAR;
++tp->len;
++tp->insert;
}
/*
* Set up the dot command. Dot commands are done by saving the
* actual characters and replaying the input.
*
* XXX
* It would be nice if we could swallow backspaces and such, but
* it's not all that easy to do. Another possibility would be to
* recognize full line insertions, which could be performed quickly,
* without replay.
*/
rcol = 0;
replay = LF_ISSET(TXT_REPLAY);
/* Initialize abbreviations check. */
lch = F_ISSET(sp, S_ABBREV) &&
LF_ISSET(TXT_MAPINPUT) ? L_NOTSPACE : L_NOCHECK;
for (carat_st = C_NOTSET, quoted = Q_NOTSET, tty_cwait = 0;;) {
/* Reset the line and update the screen. */
if (sp->s_change(sp, ep, tp->lno, LINE_RESET))
ERR;
/* Three chosen by random selection. */
if (tty_cwait > 3 || !term_waiting(sp)) {
tty_cwait = 0;
if (sp->s_refresh(sp, ep))
ERR;
} else
++tty_cwait;
next_ch: if (replay)
ch = sp->rep[rcol++];
else {
/* Get the character. */
ch = term_key(sp, flags & TXT_GETKEY_MASK);
/*
* Check if the character fits into the input and
* replay buffers; allocate space as necesssary.
* It's not necessary to check tp->len, since it
* doesn't include the overwrite characters, but
* it's not worth fixing it.
*/
TBINC(sp, tp->lb, tp->lb_len, tp->len + 1);
if (LF_ISSET(TXT_RECORD)) {
/* Store the character into replay buffer. */
TBINC(sp, sp->rep, sp->rep_len, rcol + 1);
sp->rep[rcol++] = ch;
}
}
/*
* If the character was quoted, replace the last
* character (the literal mark) with the new character.
*/
if (quoted == Q_THISCHAR) {
--sp->cno;
++tp->overwrite;
quoted = Q_NOTSET;
goto ins_ch;
}
switch (sp->special[ch]) {
case K_CR:
case K_NL: /* New line. */
#define LINE_RESOLVE { \
/* Handle abbreviations. */ \
if (lch == L_NOTSPACE && txt_abbrev(sp, tp)) \
ERR; \
if (lch != L_NOCHECK) \
lch = L_SPACE; \
/* \
* The "R" command doesn't delete characters \
* that it could have overwritten. Other input \
* modes do. \
*/ \
if (LF_ISSET(TXT_REPLACE)) { \
tp->insert += tp->overwrite; \
tp->overwrite = 0; \
} \
/* Delete any appended cursor. */ \
if (LF_ISSET(TXT_APPENDEOL)) { \
--tp->len; \
--tp->insert; \
} \
/* \
* If the user has not inserted any characters \
* and there aren't any other characters in the \
* line, delete the autoindent characters. \
*/ \
if (LF_ISSET(TXT_AUTOINDENT) && \
!tp->insert && sp->cno <= tp->ai) { \
tp->len = tp->overwrite = 0; \
sp->cno = 0; \
} \
}
LINE_RESOLVE;
if (LF_ISSET(TXT_CR))
goto k_escape;
/*
* Move any remaining insert characters into
* a new TEXT structure.
*/
if ((ntp = text_init(sp,
tp->lb + sp->cno + tp->overwrite,
tp->insert, tp->insert + 32)) == NULL)
ERR;
HDR_INSERT(ntp, hp, next, prev, TEXT);
/* Set bookkeeping for the new line. */
ntp->lno = tp->lno + 1;
ntp->insert = tp->insert;
/*
* Reset the autoindent line. 0^D keeps the ai
* line from changing, ^D changes the level, even
* if no characters in the line.
*/
if (LF_ISSET(TXT_AUTOINDENT)) {
if (carat_st != C_NOCHANGE)
aitp = tp;
if (txt_auto(sp, ep, OOBLNO, aitp, ntp))
ERR;
carat_st = C_NOTSET;
}
/* Can now reset bookkeeping for the old line. */
tp->len = sp->cno;
tp->ai = tp->insert = tp->overwrite = 0;
/* New cursor position. */
sp->cno = ntp->ai;
/* New lines are TXT_APPENDEOL if nothing to insert. */
if (ntp->insert == 0) {
TBINC(sp, tp->lb, tp->lb_len, tp->len + 1);
LF_SET(TXT_APPENDEOL);
ntp->lb[sp->cno] = CURSOR_CHAR;
++ntp->insert;
++ntp->len;
}
/* Update the old line. */
if (sp->s_change(sp, ep, tp->lno, LINE_RESET))
ERR;
/* Swap old and new TEXT's. */
tp = ntp;
/* Reset the cursor. */
sp->lno = tp->lno;
/* Update the new line. */
if (sp->s_change(sp, ep, tp->lno, LINE_INSERT) ||
sp->s_refresh(sp, ep))
ERR;
goto next_ch;
case K_ESCAPE: /* Escape. */
if (!LF_ISSET(TXT_ESCAPE))
goto ins_ch;
LINE_RESOLVE;
/* If there are insert characters, copy them down. */
k_escape: if (tp->insert && tp->overwrite)
memmove(tp->lb + sp->cno,
tp->lb + sp->cno + tp->overwrite,
tp->insert);
tp->len -= tp->overwrite;
/*
* Delete any lines that were inserted into the text
* structure and then erased.
*/
while (tp->next != (TEXT *)hp) {
HDR_DELETE(tp->next, next, prev, TEXT);
text_free(tp->next);
}
/*
* If not resolving the lines into the file, end
* it with a nul.
*/
if (LF_ISSET(TXT_RESOLVE)) {
if (txt_resolve(sp, ep, hp))
ERR;
} else {
TBINC(sp, tp->lb, tp->lb_len, tp->len + 1);
tp->lb[tp->len] = '\0';
}
/*
* Set the return cursor position to rest on the last
* inserted character.
*/
if (rp != NULL) {
rp->lno = tp->lno;
rp->cno = sp->cno ? sp->cno - 1 : 0;
if (sp->s_change(sp, ep, rp->lno, LINE_RESET))
ERR;
}
goto ret;
case K_CARAT: /* Delete autoindent chars. */
if (LF_ISSET(TXT_AUTOINDENT) && sp->cno <= tp->ai)
carat_st = C_CARATSET;
goto ins_ch;
case K_ZERO: /* Delete autoindent chars. */
if (LF_ISSET(TXT_AUTOINDENT) && sp->cno <= tp->ai)
carat_st = C_ZEROSET;
goto ins_ch;
case K_CNTRLD: /* Delete autoindent char. */
if (!LF_ISSET(TXT_AUTOINDENT))
goto ins_ch;
switch (carat_st) {
case C_CARATSET: /* ^^D */
if (sp->cno == 0 || sp->cno > tp->ai + 1)
goto ins_ch;
carat_st = C_NOTSET;
tp->overwrite += sp->cno;
tp->ai = sp->cno = 0;
break;
case C_ZEROSET: /* 0^D */
if (sp->cno == 0 || sp->cno > tp->ai + 1)
goto ins_ch;
carat_st = C_NOCHANGE;
tp->overwrite += sp->cno;
tp->ai = sp->cno = 0;
break;
case C_NOTSET: /* ^D */
if (sp->cno == 0 || sp->cno > tp->ai)
goto ins_ch;
(void)txt_outdent(sp, tp);
break;
default:
abort();
}
break;
case K_CNTRLT: /* Add autoindent char. */
if (!LF_ISSET(TXT_CNTRLT) || sp->cno > tp->ai)
goto ins_ch;
if (txt_indent(sp, tp))
break;
break;
case K_VERASE: /* Erase the last character. */
/* If can erase over the prompt, return. */
if (LF_ISSET(TXT_BS) && sp->cno <= tp->offset) {
tp->len = tp->offset;
goto ret;
}
/*
* If at the beginning of the line, try and drop back
* to a previously inserted line.
*/
if (sp->cno == 0) {
if ((ntp =
txt_backup(sp, ep, hp, tp, flags)) == NULL)
ERR;
tp = ntp;
break;
}
/* If nothing to erase, bell the user. */
if (sp->cno <= tp->offset) {
msgq(sp, M_BERR,
"No more characters to erase.");
break;
}
/* Drop back one character. */
--sp->cno;
/* Increment overwrite, decrement ai if deleted. */
++tp->overwrite;
if (sp->cno < tp->ai)
--tp->ai;
break;
case K_VWERASE: /* Skip back one word. */
/*
* If at the beginning of the line, try and drop back
* to a previously inserted line.
*/
if (sp->cno == 0) {
if ((ntp =
txt_backup(sp, ep, hp, tp, flags)) == NULL)
ERR;
tp = ntp;
}
/*
* If at offset, nothing to erase so bell the user.
*/
if (sp->cno <= tp->offset) {
msgq(sp, M_BERR,
"No more characters to erase.");
break;
}
/*
* First werase goes back to any autoindent
* and second werase goes back to the offset.
*/
if (tp->ai && sp->cno > tp->ai)
max = tp->ai;
else
max = tp->offset;
/* Skip over trailing space characters. */
while (sp->cno > max && isspace(tp->lb[sp->cno - 1])) {
--sp->cno;
++tp->overwrite;
}
if (sp->cno == max)
break;
for (tmp =
inword(tp->lb[sp->cno - 1]); sp->cno > max;) {
--sp->cno;
++tp->overwrite;
if (tmp != inword(tp->lb[sp->cno - 1]) ||
isspace(tp->lb[sp->cno - 1]))
break;
}
break;
case K_VKILL: /* Restart this line. */
/*
* If at the beginning of the line, try and drop back
* to a previously inserted line.
*/
if (sp->cno == 0) {
if ((ntp =
txt_backup(sp, ep, hp, tp, flags)) == NULL)
ERR;
tp = ntp;
}
/* If at offset, nothing to erase so bell the user. */
if (sp->cno <= tp->offset) {
msgq(sp, M_BERR,
"No more characters to erase.");
break;
}
/*
* First kill goes back to any autoindent
* and second kill goes back to the offset.
*/
if (tp->ai && sp->cno > tp->ai)
max = tp->ai;
else
max = tp->offset;
tp->overwrite += sp->cno - max;
sp->cno = max;
break;
case K_CNTRLZ:
(void)sp->s_suspend(sp);
break;
case K_FORMFEED:
F_SET(sp, S_REFRESH);
break;
case K_VLNEXT: /* Quote the next character. */
ch = '^';
quoted = Q_NEXTCHAR;
/* FALLTHROUGH */
default: /* Insert the character. */
/*
* If entering a space character after a word, check
* for abbreviations.
*/
ins_ch: if (isspace(ch) &&
lch == L_NOTSPACE && txt_abbrev(sp, tp))
ERR;
if (lch != L_NOCHECK)
lch = isspace(ch) ? L_SPACE : L_NOTSPACE;
if (tp->overwrite) /* Overwrite a character. */
--tp->overwrite;
else if (tp->insert) { /* Insert a character. */
++tp->len;
if (tp->insert == 1)
tp->lb[sp->cno + 1] = tp->lb[sp->cno];
else
memmove(tp->lb + sp->cno + 1,
tp->lb + sp->cno, tp->insert);
}
tp->lb[sp->cno++] = ch;
/*
* If we've reached the end of the buffer, then we
* need to switch into insert mode. This happens
* when there's a change to a mark and the user puts
* in more characters than the length of the motion.
*/
if (sp->cno >= tp->len) {
TBINC(sp, tp->lb, tp->lb_len, tp->len + 1);
LF_SET(TXT_APPENDEOL);
tp->lb[sp->cno] = CURSOR_CHAR;
++tp->insert;
++tp->len;
}
if (quoted == Q_NEXTCHAR)
quoted = Q_THISCHAR;
break;
}
#if DEBUG && 1
if (sp->cno + tp->insert + tp->overwrite != tp->len)
msgq(sp, M_ERR,
"len %u != cno: %u ai: %u insert %u overwrite %u",
tp->len, sp->cno, tp->ai, tp->insert,
tp->overwrite);
tp->len = sp->cno + tp->insert + tp->overwrite;
#endif
}
/* Clear input, text buffer in-use flags. */
ret: F_CLR(sp, S_INPUT);
F_CLR(hp, HDR_INUSE);
return (eval);
}
/*
* txt_abbrev --
* Handle abbreviations.
*/
static int
txt_abbrev(sp, tp)
SCR *sp;
TEXT *tp;
{
SEQ *qp;
size_t diff, len, off;
char *p;
/* Find the beginning of this "word". */
for (off = sp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) {
if (isspace(*p)) {
++p;
break;
}
++len;
if (off == tp->ai || off == tp->offset)
break;
}
/* Check for any abbreviations. */
if ((qp = seq_find(sp, p, len, SEQ_ABBREV, NULL)) == NULL)
return (0);
/* Copy up or down, depending on the lengths. */
if (len < qp->olen) {
diff = qp->olen - len;
BINC(sp, tp->lb, tp->lb_len, tp->len + diff);
memmove(tp->lb + sp->cno + diff, tp->lb + sp->cno, tp->insert);
sp->cno += diff;
tp->len += diff;
} else if (len > qp->olen) {
diff = len - qp->olen;
memmove(tp->lb + sp->cno - diff, tp->lb + sp->cno, tp->insert);
sp->cno -= diff;
tp->len -= diff;
}
memmove(p, qp->output, qp->olen);
return (0);
}
/*
* txt_auto --
* Handle autoindent. If aitp isn't NULL, use it, otherwise,
* retrieve the line.
*/
int
txt_auto(sp, ep, lno, aitp, tp)
SCR *sp;
EXF *ep;
recno_t lno;
TEXT *aitp, *tp;
{
size_t len, nlen;
char *p, *t;
if (aitp == NULL) {
if ((p = t = file_gline(sp, ep, lno, &len)) == NULL)
return (0);
} else {
len = aitp->len ? aitp->len : aitp->ai;
p = t = aitp->lb;
}
for (nlen = 0; len; ++p) {
if (!isspace(*p))
break;
/* If last character is a space, it counts. */
if (--len == 0) {
++p;
break;
}
}
/* No indentation. */
if (p == t)
return (0);
/* Set count. */
nlen = p - t;
/* Make sure the buffer's big enough. */
BINC(sp, tp->lb, tp->lb_len, tp->len + nlen);
/* Copy the indentation into the new buffer. */
memmove(tp->lb + nlen, tp->lb, tp->len);
memmove(tp->lb, t, nlen);
tp->len += nlen;
/* Return the additional length. */
tp->ai = nlen;
return (0);
}
/*
* txt_backup --
* Back up to the previously edited line.
*/
static TEXT *
txt_backup(sp, ep, hp, tp, flags)
SCR *sp;
EXF *ep;
HDR *hp;
TEXT *tp;
u_int flags;
{
TEXT *ntp;
size_t col;
if (tp->prev == (TEXT *)hp) {
msgq(sp, M_BERR, "Already at the beginning of the insert");
return (tp);
}
/* Update the old line on the screen. */
if (sp->s_change(sp, ep, tp->lno, LINE_DELETE))
return (NULL);
/* Get a handle on the previous TEXT structure. */
ntp = tp->prev;
/* Make sure that we can get enough space. */
if (LF_ISSET(TXT_APPENDEOL) && ntp->len + 1 > ntp->lb_len &&
binc(sp, &ntp->lb, &ntp->lb_len, ntp->len + 1))
return (NULL);
/*
* Release current TEXT; now committed to the swap, nothing
* better fail.
*/
HDR_DELETE(tp, next, prev, TEXT);
text_free(tp);
/* Swap TEXT's. */
tp = ntp;
/* Set bookkeeping information. */
col = tp->len;
if (LF_ISSET(TXT_APPENDEOL)) {
tp->lb[col] = CURSOR_CHAR;
++tp->insert;
++tp->len;
}
sp->lno = tp->lno;
sp->cno = col;
return (tp);
}
/*
* txt_err --
* Handle an error during input processing.
*/
static void
txt_err(sp, ep, hp)
SCR *sp;
EXF *ep;
HDR *hp;
{
recno_t lno;
size_t len;
/*
* The problem with input processing is that the cursor is at an
* indeterminate position since some input may have been lost due
* to a malloc error. So, try to go back to the place from which
* the cursor started, knowing that it may no longer be available.
*
* We depend on at least one line number being set in the text
* chain.
*/
for (lno = ((TEXT *)(hp->next))->lno;
file_gline(sp, ep, lno, &len) == NULL && lno > 0; --lno);
sp->lno = lno == 0 ? 1 : lno;
sp->cno = 0;
/* Redraw the screen, just in case. */
F_SET(sp, S_REDRAW);
}
/*
* Txt_indent and txt_outdent are truly strange. ^T and ^D do movements to
* the next or previous shiftwidth value, i.e. for a 1-based numbering, with
* shiftwidth=3, ^T moves a cursor on the 7th, 8th or 9th column to the 10th
* column, and ^D moves it back.
*
* XXX
* Technically, txt_indent, txt_outdent should part of the screen interface,
* as they require knowledge of the size of a space character on the screen.
* (Not the size of tabs, because tabs are logically composed of spaces.)
* They're left in the text code because they're complicated, not to mention
* the gruesome awareness that if spaces aren't a single column on the screen
* for any language we're into some serious, for lack of a better word,
* "issues".
*/
/* Offset to next column of stop size. */
#define STOP_OFF(c, stop) (stop - (c) % stop)
/*
* txt_indent --
* Handle ^T indents.
*/
static int
txt_indent(sp, tp)
SCR *sp;
TEXT *tp;
{
u_long sw, ts;
size_t cno, off, scno, spaces, tabs;
sw = O_VAL(sp, O_SHIFTWIDTH);
ts = O_VAL(sp, O_TABSTOP);
/* Get the current screen column. */
for (off = scno = 0; off < sp->cno; ++off)
if (tp->lb[off] == '\t')
scno += STOP_OFF(scno, ts);
else
++scno;
/* Count up spaces/tabs needed to get to the target. */
for (cno = scno, scno += STOP_OFF(scno, sw), tabs = 0;
cno + STOP_OFF(cno, ts) <= scno; ++tabs)
cno += STOP_OFF(cno, ts);
spaces = scno - cno;
/* Make sure there's enough room. */
BINC(sp, tp->lb, tp->lb_len, tp->len + spaces + tabs);
/* Move the insert characters out of the way. */
if (tp->insert)
memmove(tp->lb + sp->cno + spaces + tabs,
tp->lb + sp->cno, tp->insert);
/* Add new space/tab characters. */
for (; tabs--; ++tp->len, ++tp->ai)
tp->lb[sp->cno++] = '\t';
for (; spaces--; ++tp->len, ++tp->ai)
tp->lb[sp->cno++] = ' ';
return (0);
}
/*
* txt_outdent --
* Handle ^D outdents.
*
*/
static int
txt_outdent(sp, tp)
SCR *sp;
TEXT *tp;
{
u_long sw, ts;
size_t cno, off, scno, spaces;
sw = O_VAL(sp, O_SHIFTWIDTH);
ts = O_VAL(sp, O_TABSTOP);
/* Get the current screen column. */
for (off = scno = 0; off < sp->cno; ++off)
if (tp->lb[off] == '\t')
scno += STOP_OFF(scno, ts);
else
++scno;
/* Get the previous shiftwidth column. */
for (cno = scno; --scno % sw != 0;);
/* Decrement characters until less than or equal to that slot. */
for (; cno > scno; --sp->cno, --tp->ai, ++tp->overwrite)
if (tp->lb[--off] == '\t')
cno -= STOP_OFF(cno, ts);
else
--cno;
/* Spaces needed to get to the target. */
spaces = scno - cno;
/* Maybe just a delete. */
if (spaces == 0)
return (0);
/* Make sure there's enough room. */
BINC(sp, tp->lb, tp->lb_len, tp->len + spaces);
/* Use up any overwrite characters. */
for (; tp->overwrite && spaces; --spaces, ++tp->ai, --tp->overwrite)
tp->lb[sp->cno++] = ' ';
/* Maybe that was enough. */
if (spaces == 0)
return (0);
/* Move the insert characters out of the way. */
if (tp->insert)
memmove(tp->lb + sp->cno + spaces,
tp->lb + sp->cno, tp->insert);
/* Add new space characters. */
for (; spaces--; ++tp->len, ++tp->ai)
tp->lb[sp->cno++] = ' ';
return (0);
}
/*
* txt_resolve --
* Resolve the input text chain into the file.
*/
static int
txt_resolve(sp, ep, hp)
SCR *sp;
EXF *ep;
HDR *hp;
{
TEXT *tp;
recno_t lno;
tp = hp->next;
/* The first line replaces a current line. */
if (file_sline(sp, ep, tp->lno, tp->lb, tp->len))
return (1);
/* All subsequent lines are appended into the file. */
for (lno = tp->lno; (tp = tp->next) != (TEXT *)&sp->txthdr; ++lno)
if (file_aline(sp, ep, 0, lno, tp->lb, tp->len))
return (1);
return (0);
}