BSD 4 release
[unix-history] / usr / src / cmd / ex / ex_vops.c
/* Copyright (c) 1980 Regents of the University of California */
static char *sccsid = "@(#)ex_vops.c 6.3 10/23/80";
#include "ex.h"
#include "ex_tty.h"
#include "ex_vis.h"
/*
* This file defines the operation sequences which interface the
* logical changes to the file buffer with the internal and external
* display representations.
*/
/*
* Undo.
*
* Undo is accomplished in two ways. We often for small changes in the
* current line know how (in terms of a change operator) how the change
* occurred. Thus on an intelligent terminal we can undo the operation
* by another such operation, using insert and delete character
* stuff. The pointers vU[AD][12] index the buffer vutmp when this
* is possible and provide the necessary information.
*
* The other case is that the change involved multiple lines or that
* we have moved away from the line or forgotten how the change was
* accomplished. In this case we do a redisplay and hope that the
* low level optimization routines (which don't look for winning
* via insert/delete character) will not lose too badly.
*/
char *vUA1, *vUA2;
char *vUD1, *vUD2;
vUndo()
{
/*
* Avoid UU which clobbers ability to do u.
*/
if (vundkind == VCAPU || vUNDdot != dot) {
beep();
return;
}
CP(vutmp, linebuf);
vUD1 = linebuf; vUD2 = strend(linebuf);
putmk1(dot, vUNDsav);
getDOT();
vUA1 = linebuf; vUA2 = strend(linebuf);
vundkind = VCAPU;
if (state == ONEOPEN || state == HARDOPEN) {
vjumpto(dot, vUNDcurs, 0);
return;
}
vdirty(vcline, 1);
vsyncCL();
cursor = linebuf;
vfixcurs();
}
vundo(show)
bool show; /* if true update the screen */
{
register int cnt;
register line *addr;
register char *cp;
char temp[LBSIZE];
bool savenote;
int (*OO)();
short oldhold = hold;
switch (vundkind) {
case VMANYINS:
wcursor = 0;
addr1 = undap1;
addr2 = undap2 - 1;
vsave();
YANKreg('1');
notecnt = 0;
/* fall into ... */
case VMANY:
case VMCHNG:
vsave();
addr = dot - vcline;
notecnt = 1;
if (undkind == UNDPUT && undap1 == undap2) {
beep();
break;
}
/*
* Undo() call below basically replaces undap1 to undap2-1
* with dol through unddol-1. Hack screen image to
* reflect this replacement.
*/
if (show)
if (undkind == UNDMOVE)
vdirty(0, LINES);
else
vreplace(undap1 - addr, undap2 - undap1,
undkind == UNDPUT ? 0 : unddol - dol);
savenote = notecnt;
undo(1);
if (show && (vundkind != VMCHNG || addr != dot))
killU();
vundkind = VMANY;
cnt = dot - addr;
if (cnt < 0 || cnt > vcnt || state != VISUAL) {
if (show)
vjumpto(dot, NOSTR, '.');
break;
}
if (!savenote)
notecnt = 0;
if (show) {
vcline = cnt;
vrepaint(vmcurs);
}
vmcurs = 0;
break;
case VCHNG:
case VCAPU:
vundkind = VCHNG;
strcpy(temp, vutmp);
strcpy(vutmp, linebuf);
doomed = column(vUA2 - 1) - column(vUA1 - 1);
strcLIN(temp);
cp = vUA1; vUA1 = vUD1; vUD1 = cp;
cp = vUA2; vUA2 = vUD2; vUD2 = cp;
if (!show)
break;
cursor = vUD1;
if (state == HARDOPEN) {
doomed = 0;
vsave();
vopen(dot, WBOT);
vnline(cursor);
break;
}
/*
* Pseudo insert command.
*/
vcursat(cursor);
OO = Outchar; Outchar = vinschar; hold |= HOLDQIK;
vprepins();
temp[vUA2 - linebuf] = 0;
for (cp = &temp[vUA1 - linebuf]; *cp;)
putchar(*cp++);
Outchar = OO; hold = oldhold;
endim();
physdc(cindent(), cindent() + doomed);
doomed = 0;
vdirty(vcline, 1);
vsyncCL();
if (cursor > linebuf && cursor >= strend(linebuf))
cursor--;
vfixcurs();
break;
case VNONE:
beep();
break;
}
}
/*
* Routine to handle a change inside a macro.
* Fromvis is true if we were called from a visual command (as
* opposed to an ex command). This has nothing to do with being
* in open/visual mode as :s/foo/bar is not fromvis.
*/
vmacchng(fromvis)
bool fromvis;
{
line *savedot, *savedol;
char *savecursor;
char savelb[LBSIZE];
int nlines, more;
register line *a1, *a2;
char ch; /* DEBUG */
int copyw(), copywR();
if (!inopen)
return;
if (!vmacp)
vch_mac = VC_NOTINMAC;
#ifdef TRACE
if (trace)
fprintf(trace, "vmacchng, vch_mac=%d, linebuf='%s', *dot=%o\n", vch_mac, linebuf, *dot);
#endif
if (vmacp && fromvis)
vsave();
#ifdef TRACE
if (trace)
fprintf(trace, "after vsave, linebuf='%s', *dot=%o\n", linebuf, *dot);
#endif
switch(vch_mac) {
case VC_NOCHANGE:
vch_mac = VC_ONECHANGE;
break;
case VC_ONECHANGE:
/* Save current state somewhere */
#ifdef TRACE
vudump("before vmacchng hairy case");
#endif
savedot = dot; savedol = dol; savecursor = cursor;
CP(savelb, linebuf);
nlines = dol - zero;
while ((line *) endcore - truedol < nlines)
morelines();
copyw(truedol+1, zero+1, nlines);
truedol += nlines;
#ifdef TRACE
visdump("before vundo");
#endif
/* Restore state as it was at beginning of macro */
vundo(0);
#ifdef TRACE
visdump("after vundo");
vudump("after vundo");
#endif
/* Do the saveall we should have done then */
saveall();
#ifdef TRACE
vudump("after saveall");
#endif
/* Restore current state from where saved */
more = savedol - dol; /* amount we shift everything by */
if (more)
(*(more>0 ? copywR : copyw))(savedol+1, dol+1, truedol-dol);
unddol += more; truedol += more; undap2 += more;
truedol -= nlines;
copyw(zero+1, truedol+1, nlines);
dot = savedot; dol = savedol ; cursor = savecursor;
CP(linebuf, savelb);
vch_mac = VC_MANYCHANGE;
/* Arrange that no further undo saving happens within macro */
otchng = tchng; /* Copied this line blindly - bug? */
inopen = -1; /* no need to save since it had to be 1 or -1 before */
vundkind = VMANY;
#ifdef TRACE
vudump("after vmacchng");
#endif
break;
case VC_NOTINMAC:
case VC_MANYCHANGE:
/* Nothing to do for various reasons. */
break;
}
}
/*
* Initialize undo information before an append.
*/
vnoapp()
{
vUD1 = vUD2 = cursor;
}
/*
* All the rest of the motion sequences have one or more
* cases to deal with. In the case wdot == 0, operation
* is totally within current line, from cursor to wcursor.
* If wdot is given, but wcursor is 0, then operation affects
* the inclusive line range. The hardest case is when both wdot
* and wcursor are given, then operation affects from line dot at
* cursor to line wdot at wcursor.
*/
/*
* Move is simple, except for moving onto new lines in hardcopy open mode.
*/
vmove()
{
register int cnt;
if (wdot) {
if (wdot < one || wdot > dol) {
beep();
return;
}
cnt = wdot - dot;
wdot = NOLINE;
if (cnt)
killU();
vupdown(cnt, wcursor);
return;
}
/*
* When we move onto a new line, save information for U undo.
*/
if (vUNDdot != dot) {
vUNDsav = *dot;
vUNDcurs = wcursor;
vUNDdot = dot;
}
/*
* In hardcopy open, type characters to left of cursor
* on new line, or back cursor up if its to left of where we are.
* In any case if the current line is ``rubbled'' i.e. has trashy
* looking overstrikes on it or \'s from deletes, we reprint
* so it is more comprehensible (and also because we can't work
* if we let it get more out of sync since column() won't work right.
*/
if (state == HARDOPEN) {
register char *cp;
if (rubble) {
register int c;
int oldhold = hold;
sethard();
cp = wcursor;
c = *cp;
*cp = 0;
hold |= HOLDDOL;
vreopen(WTOP, lineDOT(), vcline);
hold = oldhold;
*cp = c;
} else if (wcursor > cursor) {
vfixcurs();
for (cp = cursor; *cp && cp < wcursor;) {
register int c = *cp++ & TRIM;
putchar(c ? c : ' ');
}
}
}
vsetcurs(wcursor);
}
/*
* Delete operator.
*
* Hard case of deleting a range where both wcursor and wdot
* are specified is treated as a special case of change and handled
* by vchange (although vchange may pass it back if it degenerates
* to a full line range delete.)
*/
vdelete(c)
char c;
{
register char *cp;
register int i;
if (wdot) {
if (wcursor) {
vchange('d');
return;
}
if ((i = xdw()) < 0)
return;
if (state != VISUAL) {
vgoto(LINE(0), 0);
vputchar('@');
}
wdot = dot;
vremote(i, delete, 0);
notenam = "delete";
DEL[0] = 0;
killU();
vreplace(vcline, i, 0);
if (wdot > dol)
vcline--;
vrepaint(NOSTR);
return;
}
if (wcursor < linebuf)
wcursor = linebuf;
if (cursor == wcursor) {
beep();
return;
}
i = vdcMID();
cp = cursor;
setDEL();
CP(cp, wcursor);
if (cp > linebuf && (cp[0] == 0 || c == '#'))
cp--;
if (state == HARDOPEN) {
bleep(i, cp);
cursor = cp;
return;
}
physdc(column(cursor - 1), i);
DEPTH(vcline) = 0;
vreopen(LINE(vcline), lineDOT(), vcline);
vsyncCL();
vsetcurs(cp);
}
/*
* Change operator.
*
* In a single line we mark the end of the changed area with '$'.
* On multiple whole lines, we clear the lines first.
* Across lines with both wcursor and wdot given, we delete
* and sync then append (but one operation for undo).
*/
vchange(c)
char c;
{
register char *cp;
register int i, ind, cnt;
line *addr;
if (wdot) {
/*
* Change/delete of lines or across line boundaries.
*/
if ((cnt = xdw()) < 0)
return;
getDOT();
if (wcursor && cnt == 1) {
/*
* Not really.
*/
wdot = 0;
if (c == 'd') {
vdelete(c);
return;
}
goto smallchange;
}
if (cursor && wcursor) {
/*
* Across line boundaries, but not
* necessarily whole lines.
* Construct what will be left.
*/
*cursor = 0;
strcpy(genbuf, linebuf);
getline(*wdot);
if (strlen(genbuf) + strlen(wcursor) > LBSIZE - 2) {
getDOT();
beep();
return;
}
strcat(genbuf, wcursor);
if (c == 'd' && *vpastwh(genbuf) == 0) {
/*
* Although this is a delete
* spanning line boundaries, what
* would be left is all white space,
* so take it all away.
*/
wcursor = 0;
getDOT();
op = 0;
notpart(lastreg);
notpart('1');
vdelete(c);
return;
}
ind = -1;
} else if (c == 'd' && wcursor == 0) {
vdelete(c);
return;
} else
#ifdef LISPCODE
/*
* We are just substituting text for whole lines,
* so determine the first autoindent.
*/
if (value(LISP) && value(AUTOINDENT))
ind = lindent(dot);
else
#endif
ind = whitecnt(linebuf);
i = vcline >= 0 ? LINE(vcline) : WTOP;
/*
* Delete the lines from the buffer,
* and remember how the partial stuff came about in
* case we are told to put.
*/
addr = dot;
vremote(cnt, delete, 0);
setpk();
notenam = "delete";
if (c != 'd')
notenam = "change";
/*
* If DEL[0] were nonzero, put would put it back
* rather than the deleted lines.
*/
DEL[0] = 0;
if (cnt > 1)
killU();
/*
* Now hack the screen image coordination.
*/
vreplace(vcline, cnt, 0);
wdot = NOLINE;
noteit(0);
vcline--;
if (addr <= dol)
dot--;
/*
* If this is a across line delete/change,
* cursor stays where it is; just splice together the pieces
* of the new line. Otherwise generate a autoindent
* after a S command.
*/
if (ind >= 0) {
*genindent(ind) = 0;
vdoappend(genbuf);
} else {
vmcurs = cursor;
strcLIN(genbuf);
vdoappend(linebuf);
}
/*
* Indicate a change on hardcopies by
* erasing the current line.
*/
if (c != 'd' && state != VISUAL && state != HARDOPEN) {
int oldhold = hold;
hold |= HOLDAT, vclrlin(i, dot), hold = oldhold;
}
/*
* Open the line (logically) on the screen, and
* update the screen tail. Unless we are really a delete
* go off and gather up inserted characters.
*/
vcline++;
if (vcline < 0)
vcline = 0;
vopen(dot, i);
vsyncCL();
noteit(1);
if (c != 'd') {
if (ind >= 0) {
cursor = linebuf;
linebuf[0] = 0;
vfixcurs();
} else {
ind = 0;
vcursat(cursor);
}
vappend('x', 1, ind);
return;
}
if (*cursor == 0 && cursor > linebuf)
cursor--;
vrepaint(cursor);
return;
}
smallchange:
/*
* The rest of this is just low level hacking on changes
* of small numbers of characters.
*/
if (wcursor < linebuf)
wcursor = linebuf;
if (cursor == wcursor) {
beep();
return;
}
i = vdcMID();
cp = cursor;
if (state != HARDOPEN)
vfixcurs();
/*
* Put out the \\'s indicating changed text in hardcopy,
* or mark the end of the change with $ if not hardcopy.
*/
if (state == HARDOPEN)
bleep(i, cp);
else {
vcursbef(wcursor);
putchar('$');
i = cindent();
}
/*
* Remember the deleted text for possible put,
* and then prepare and execute the input portion of the change.
*/
cursor = cp;
setDEL();
CP(cursor, wcursor);
if (state != HARDOPEN) {
vcursaft(cursor - 1);
doomed = i - cindent();
} else {
/*
sethard();
wcursor = cursor;
cursor = linebuf;
vgoto(outline, value(NUMBER) << 3);
vmove();
*/
doomed = 0;
}
prepapp();
vappend('c', 1, 0);
}
/*
* Open new lines.
*
* Tricky thing here is slowopen. This causes display updating
* to be held off so that 300 baud dumb terminals don't lose badly.
* This also suppressed counts, which otherwise say how many blank
* space to open up. Counts are also suppressed on intelligent terminals.
* Actually counts are obsoleted, since if your terminal is slow
* you are better off with slowopen.
*/
voOpen(c, cnt)
char c;
register int cnt;
{
register int ind = 0, i;
short oldhold = hold;
if (value(SLOWOPEN) || value(REDRAW) && AL && DL)
cnt = 1;
vsave();
setLAST();
if (value(AUTOINDENT))
ind = whitecnt(linebuf);
if (c == 'O') {
vcline--;
dot--;
if (dot > zero)
getDOT();
}
if (value(AUTOINDENT)) {
#ifdef LISPCODE
if (value(LISP))
ind = lindent(dot + 1);
#endif
}
killU();
prepapp();
if (FIXUNDO)
vundkind = VMANY;
if (state != VISUAL)
c = WBOT + 1;
else {
c = vcline < 0 ? WTOP - cnt : LINE(vcline) + DEPTH(vcline);
if (c < ZERO)
c = ZERO;
i = LINE(vcline + 1) - c;
if (i < cnt && c <= WBOT && (!AL || !DL))
vinslin(c, cnt - i, vcline);
}
*genindent(ind) = 0;
vdoappend(genbuf);
vcline++;
oldhold = hold;
hold |= HOLDROL;
vopen(dot, c);
hold = oldhold;
if (value(SLOWOPEN))
/*
* Oh, so lazy!
*/
vscrap();
else
vsync1(LINE(vcline));
cursor = linebuf;
linebuf[0] = 0;
vappend('o', 1, ind);
}
/*
* > < and = shift operators.
*
* Note that =, which aligns lisp, is just a ragged sort of shift,
* since it never distributes text between lines.
*/
char vshnam[2] = { 'x', 0 };
vshftop()
{
register line *addr;
register int cnt;
if ((cnt = xdw()) < 0)
return;
addr = dot;
vremote(cnt, vshift, 0);
vshnam[0] = op;
notenam = vshnam;
dot = addr;
vreplace(vcline, cnt, cnt);
if (state == HARDOPEN)
vcnt = 0;
vrepaint(NOSTR);
}
/*
* !.
*
* Filter portions of the buffer through unix commands.
*/
vfilter()
{
register line *addr;
register int cnt;
char *oglobp, d;
if ((cnt = xdw()) < 0)
return;
if (vglobp)
vglobp = uxb;
if (readecho('!'))
return;
oglobp = globp; globp = genbuf + 1;
d = peekc; ungetchar(0);
CATCH
fixech();
unix0(0);
ONERR
splitw = 0;
ungetchar(d);
vrepaint(cursor);
globp = oglobp;
return;
ENDCATCH
ungetchar(d); globp = oglobp;
addr = dot;
CATCH
vgoto(WECHO, 0); flusho();
vremote(cnt, filter, 2);
ONERR
vdirty(0, LINES);
ENDCATCH
if (dot == zero && dol > zero)
dot = one;
splitw = 0;
notenam = "";
/*
* BUG: we shouldn't be depending on what undap2 and undap1 are,
* since we may be inside a macro. What's really wanted is the
* number of lines we read from the filter. However, the mistake
* will be an overestimate so it only results in extra work,
* it shouldn't cause any real screwups.
*/
vreplace(vcline, cnt, undap2 - undap1);
dot = addr;
if (dot > dol) {
dot--;
vcline--;
}
vrepaint(NOSTR);
}
/*
* Xdw exchanges dot and wdot if appropriate and also checks
* that wdot is reasonable. Its name comes from
* xchange dotand wdot
*/
xdw()
{
register char *cp;
register int cnt;
/*
register int notp = 0;
*/
if (wdot == NOLINE || wdot < one || wdot > dol) {
beep();
return (-1);
}
vsave();
setLAST();
if (dot > wdot) {
register line *addr;
vcline -= dot - wdot;
addr = dot; dot = wdot; wdot = addr;
cp = cursor; cursor = wcursor; wcursor = cp;
}
/*
* If a region is specified but wcursor is at the begining
* of the last line, then we move it to be the end of the
* previous line (actually off the end).
*/
if (cursor && wcursor == linebuf && wdot > dot) {
wdot--;
getDOT();
if (vpastwh(linebuf) >= cursor)
wcursor = 0;
else {
getline(*wdot);
wcursor = strend(linebuf);
getDOT();
}
/*
* Should prepare in caller for possible dot == wdot.
*/
}
cnt = wdot - dot + 1;
if (vreg) {
vremote(cnt, YANKreg, vreg);
/*
if (notp)
notpart(vreg);
*/
}
/*
* Kill buffer code. If delete operator is c or d, then save
* the region in numbered buffers.
*
* BUG: This may be somewhat inefficient due
* to the way named buffer are implemented,
* necessitating some optimization.
*/
vreg = 0;
if (any(op, "cd")) {
vremote(cnt, YANKreg, '1');
/*
if (notp)
notpart('1');
*/
}
return (cnt);
}
/*
* Routine for vremote to call to implement shifts.
*/
vshift()
{
shift(op, 1);
}
/*
* Replace a single character with the next input character.
* A funny kind of insert.
*/
vrep(cnt)
register int cnt;
{
register int i, c;
if (cnt > strlen(cursor)) {
beep();
return;
}
i = column(cursor + cnt - 1);
vcursat(cursor);
doomed = i - cindent();
if (!vglobp) {
c = getesc();
if (c == 0) {
vfixcurs();
return;
}
ungetkey(c);
}
CP(vutmp, linebuf);
if (FIXUNDO)
vundkind = VCHNG;
wcursor = cursor + cnt;
vUD1 = cursor; vUD2 = wcursor;
CP(cursor, wcursor);
prepapp();
vappend('r', cnt, 0);
*lastcp++ = INS[0];
setLAST();
}
/*
* Yank.
*
* Yanking to string registers occurs for free (essentially)
* in the routine xdw().
*/
vyankit()
{
register int cnt;
if (wdot) {
if ((cnt = xdw()) < 0)
return;
vremote(cnt, yank, 0);
setpk();
notenam = "yank";
if (FIXUNDO)
vundkind = VNONE;
DEL[0] = 0;
wdot = NOLINE;
if (notecnt <= vcnt - vcline && notecnt < value(REPORT))
notecnt = 0;
vrepaint(cursor);
return;
}
takeout(DEL);
}
/*
* Set pkill variables so a put can
* know how to put back partial text.
* This is necessary because undo needs the complete
* line images to be saved, while a put wants to trim
* the first and last lines. The compromise
* is for put to be more clever.
*/
setpk()
{
if (wcursor) {
pkill[0] = cursor;
pkill[1] = wcursor;
}
}