mirror of https://github.com/tildeclub/ex-vi.git
1443 lines
32 KiB
C
1443 lines
32 KiB
C
/*
|
|
* This code contains changes by
|
|
* Gunnar Ritter, Freiburg i. Br., Germany, 2002. All rights reserved.
|
|
*
|
|
* Conditions 1, 2, and 4 and the no-warranty notice below apply
|
|
* to these changes.
|
|
*
|
|
*
|
|
* Copyright (c) 1980, 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.
|
|
*
|
|
*
|
|
* Copyright(C) Caldera International Inc. 2001-2002. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* Redistributions of source code and documentation must retain the
|
|
* above copyright notice, this list of conditions and the following
|
|
* disclaimer.
|
|
* 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.
|
|
* All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed or owned by Caldera
|
|
* International, Inc.
|
|
* Neither the name of Caldera International, Inc. nor the names of
|
|
* other contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
|
|
* INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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
|
|
#ifdef DOSCCS
|
|
static char sccsid[] = "@(#)ex_vmain.c 1.29 (gritter) 2/17/05";
|
|
#endif
|
|
#endif
|
|
|
|
/* from ex_vmain.c 7.7 (Berkeley) 6/7/85 */
|
|
|
|
#include "ex.h"
|
|
#include "ex_tty.h"
|
|
#include "ex_vis.h"
|
|
|
|
/*
|
|
* This is the main routine for visual.
|
|
* We here decode the count and possible named buffer specification
|
|
* preceding a command and interpret a few of the commands.
|
|
* Commands which involve a target (i.e. an operator) are decoded
|
|
* in the routine operate in ex_voperate.c.
|
|
*/
|
|
|
|
#define forbid(a) { if (a) goto fonfon; }
|
|
|
|
void
|
|
vmain(void)
|
|
{
|
|
int c, cnt, i;
|
|
cell esave[TUBECOLS];
|
|
char *oglobp;
|
|
short d;
|
|
line *addr;
|
|
int ind, nlput;
|
|
int shouldpo = 0;
|
|
int onumber = 0, olist = 0;
|
|
void (*OPline)(int) = NULL;
|
|
int (*OPutchar)(int) = NULL;
|
|
|
|
CLOBBGRD(c);
|
|
CLOBBGRD(cnt);
|
|
CLOBBGRD(i);
|
|
CLOBBGRD(oglobp);
|
|
CLOBBGRD(addr);
|
|
CLOBBGRD(shouldpo);
|
|
CLOBBGRD(onumber);
|
|
CLOBBGRD(olist);
|
|
CLOBBGRD(OPline);
|
|
CLOBBGRD(OPutchar);
|
|
|
|
vch_mac = VC_NOTINMAC;
|
|
|
|
/*
|
|
* If we started as a vi command (on the command line)
|
|
* then go process initial commands (recover, next or tag).
|
|
*/
|
|
if (initev) {
|
|
oglobp = globp;
|
|
globp = initev;
|
|
hadcnt = cnt = 0;
|
|
i = tchng;
|
|
addr = dot;
|
|
goto doinit;
|
|
}
|
|
|
|
/*
|
|
* NB:
|
|
*
|
|
* The current line is always in the line buffer linebuf,
|
|
* and the cursor at the position cursor. You should do
|
|
* a vsave() before moving off the line to make sure the disk
|
|
* copy is updated if it has changed, and a getDOT() to get
|
|
* the line back if you mung linebuf. The motion
|
|
* routines in ex_vwind.c handle most of this.
|
|
*/
|
|
for (;;) {
|
|
/*
|
|
* Decode a visual command.
|
|
* First sync the temp file if there has been a reasonable
|
|
* amount of change. Clear state for decoding of next
|
|
* command.
|
|
*/
|
|
TSYNC();
|
|
vglobp = 0;
|
|
vreg = 0;
|
|
hold = 0;
|
|
seenprompt = 1;
|
|
wcursor = 0;
|
|
Xhadcnt = hadcnt = 0;
|
|
Xcnt = cnt = 1;
|
|
splitw = 0;
|
|
if (i = holdupd) {
|
|
if (state == VISUAL)
|
|
ignore(peekkey());
|
|
holdupd = 0;
|
|
/*
|
|
if (LINE(0) < ZERO) {
|
|
vclear();
|
|
vcnt = 0;
|
|
i = 3;
|
|
}
|
|
*/
|
|
if (state != VISUAL) {
|
|
vcnt = 0;
|
|
vsave();
|
|
vrepaint(cursor);
|
|
} else if (i == 3)
|
|
vredraw(WTOP);
|
|
else
|
|
vsync(WTOP);
|
|
vfixcurs();
|
|
}
|
|
|
|
/*
|
|
* Gobble up counts and named buffer specifications.
|
|
*/
|
|
for (;;) {
|
|
looptop:
|
|
#ifdef MDEBUG
|
|
if (trace)
|
|
fprintf(trace, "pc=%c",peekkey());
|
|
#endif
|
|
if (xisdigit(peekkey()) && peekkey() != '0') {
|
|
hadcnt = 1;
|
|
cnt = vgetcnt();
|
|
forbid (cnt <= 0);
|
|
}
|
|
if (peekkey() != '"')
|
|
break;
|
|
ignore(getkey()), c = getkey();
|
|
/*
|
|
* Buffer names be letters or digits.
|
|
* But not '0' as that is the source of
|
|
* an 'empty' named buffer spec in the routine
|
|
* kshift (see ex_temp.c).
|
|
*/
|
|
forbid (c == '0' || !xisalpha(c) && !xisdigit(c));
|
|
vreg = c;
|
|
}
|
|
reread:
|
|
/*
|
|
* Come to reread from below after some macro expansions.
|
|
* The call to map allows use of function key pads
|
|
* by performing a terminal dependent mapping of inputs.
|
|
*/
|
|
#ifdef MDEBUG
|
|
if (trace)
|
|
fprintf(trace,"pcb=%c,",peekkey());
|
|
#endif
|
|
op = getkey();
|
|
maphopcnt = 0;
|
|
do {
|
|
/*
|
|
* Keep mapping the char as long as it changes.
|
|
* This allows for double mappings, e.g., q to #,
|
|
* #1 to something else.
|
|
*/
|
|
c = op;
|
|
op = map(c,arrows);
|
|
#ifdef MDEBUG
|
|
if (trace)
|
|
fprintf(trace,"pca=%c,",c);
|
|
#endif
|
|
/*
|
|
* Maybe the mapped to char is a count. If so, we have
|
|
* to go back to the "for" to interpret it. Likewise
|
|
* for a buffer name.
|
|
*/
|
|
if ((xisdigit(c) && c!='0') || c == '"') {
|
|
ungetkey(c);
|
|
goto looptop;
|
|
}
|
|
if (!value(REMAP)) {
|
|
c = op;
|
|
break;
|
|
}
|
|
if (++maphopcnt > 256)
|
|
error(catgets(catd, 1, 225,
|
|
"Infinite macro loop"));
|
|
} while (c != op);
|
|
|
|
/*
|
|
* Begin to build an image of this command for possible
|
|
* later repeat in the buffer workcmd. It will be copied
|
|
* to lastcmd by the routine setLAST
|
|
* if/when completely specified.
|
|
*/
|
|
lastcp = workcmd;
|
|
if (!vglobp)
|
|
*lastcp++ = c;
|
|
|
|
/*
|
|
* First level command decode.
|
|
*/
|
|
if (c == ATTN)
|
|
goto case_ATTN;
|
|
switch (c) {
|
|
|
|
/*
|
|
* ^L Clear screen e.g. after transmission error.
|
|
*/
|
|
|
|
/*
|
|
* ^R Retype screen, getting rid of @ lines.
|
|
* If in open, equivalent to ^L.
|
|
* On terminals where the right arrow key sends
|
|
* ^L we make ^R act like ^L, since there is no
|
|
* way to get ^L. These terminals (adm31, tvi)
|
|
* are intelligent so ^R is useless. Soroc
|
|
* will probably foul this up, but nobody has
|
|
* one of them.
|
|
*/
|
|
case CTRL('l'):
|
|
case CTRL('r'):
|
|
if (c == CTRL('l') || (KR && *KR==CTRL('l'))) {
|
|
vclear();
|
|
vdirty(0, vcnt);
|
|
}
|
|
if (state != VISUAL) {
|
|
/*
|
|
* Get a clean line, throw away the
|
|
* memory of what is displayed now,
|
|
* and move back onto the current line.
|
|
*/
|
|
vclean();
|
|
vcnt = 0;
|
|
vmoveto(dot, cursor, 0);
|
|
continue;
|
|
}
|
|
vredraw(WTOP);
|
|
/*
|
|
* Weird glitch -- when we enter visual
|
|
* in a very small window we may end up with
|
|
* no lines on the screen because the line
|
|
* at the top is too long. This forces the screen
|
|
* to be expanded to make room for it (after
|
|
* we have printed @'s ick showing we goofed).
|
|
*/
|
|
if (vcnt == 0)
|
|
vrepaint(cursor);
|
|
vfixcurs();
|
|
continue;
|
|
|
|
/*
|
|
* $ Escape just cancels the current command
|
|
* with a little feedback.
|
|
*/
|
|
case ESCAPE:
|
|
beep();
|
|
continue;
|
|
|
|
/*
|
|
* @ Macros. Bring in the macro and put it
|
|
* in vmacbuf, point vglobp there and punt.
|
|
*/
|
|
case '@':
|
|
c = getesc();
|
|
if (c == 0)
|
|
continue;
|
|
if (c == '@')
|
|
c = lastmac;
|
|
if (xisupper(c))
|
|
c = xtolower(c);
|
|
forbid(!xislower(c));
|
|
lastmac = c;
|
|
vsave();
|
|
CATCH
|
|
char tmpbuf[BUFSIZ];
|
|
|
|
regbuf(c,tmpbuf,sizeof(vmacbuf));
|
|
macpush(tmpbuf, 1);
|
|
ONERR
|
|
lastmac = 0;
|
|
splitw = 0;
|
|
getDOT();
|
|
vrepaint(cursor);
|
|
continue;
|
|
ENDCATCH
|
|
vmacp = vmacbuf;
|
|
goto reread;
|
|
|
|
/*
|
|
* . Repeat the last (modifying) open/visual command.
|
|
*/
|
|
case '.':
|
|
/*
|
|
* Check that there was a last command, and
|
|
* take its count and named buffer unless they
|
|
* were given anew. Special case if last command
|
|
* referenced a numeric named buffer -- increment
|
|
* the number and go to a named buffer again.
|
|
* This allows a sequence like "1pu.u.u...
|
|
* to successively look for stuff in the kill chain
|
|
* much as one does in EMACS with C-Y and M-Y.
|
|
*/
|
|
forbid (lastcmd[0] == 0);
|
|
if (hadcnt)
|
|
lastcnt = cnt;
|
|
if (vreg)
|
|
lastreg = vreg;
|
|
else if (xisdigit(lastreg) && lastreg < '9')
|
|
lastreg++;
|
|
vreg = lastreg;
|
|
cnt = lastcnt;
|
|
hadcnt = lasthad;
|
|
vglobp = lastcmd;
|
|
goto reread;
|
|
|
|
/*
|
|
* ^U Scroll up. A count sticks around for
|
|
* future scrolls as the scroll amount.
|
|
* Attempt to hold the indentation from the
|
|
* top of the screen (in logical lines).
|
|
*
|
|
* BUG: A ^U near the bottom of the screen
|
|
* on a dumb terminal (which can't roll back)
|
|
* causes the screen to be cleared and then
|
|
* redrawn almost as it was. In this case
|
|
* one should simply move the cursor.
|
|
*/
|
|
case CTRL('u'):
|
|
if (hadcnt)
|
|
vSCROLL = cnt;
|
|
cnt = vSCROLL;
|
|
if (state == VISUAL)
|
|
ind = vcline, cnt += ind;
|
|
else
|
|
ind = 0;
|
|
vmoving = 0;
|
|
vup(cnt, ind, 1);
|
|
vnline(NOSTR);
|
|
continue;
|
|
|
|
/*
|
|
* ^D Scroll down. Like scroll up.
|
|
*/
|
|
case CTRL('d'):
|
|
#ifdef TRACE
|
|
if (trace)
|
|
fprintf(trace, "before vdown in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
|
|
#endif
|
|
if (hadcnt)
|
|
vSCROLL = cnt;
|
|
cnt = vSCROLL;
|
|
if (state == VISUAL)
|
|
ind = vcnt - vcline - 1, cnt += ind;
|
|
else
|
|
ind = 0;
|
|
vmoving = 0;
|
|
vdown(cnt, ind, 1);
|
|
#ifdef TRACE
|
|
if (trace)
|
|
fprintf(trace, "before vnline in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
|
|
#endif
|
|
vnline(NOSTR);
|
|
#ifdef TRACE
|
|
if (trace)
|
|
fprintf(trace, "after vnline in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
|
|
#endif
|
|
continue;
|
|
|
|
/*
|
|
* ^E Glitch the screen down (one) line.
|
|
* Cursor left on same line in file.
|
|
*/
|
|
case CTRL('e'):
|
|
if (state != VISUAL)
|
|
continue;
|
|
if (!hadcnt)
|
|
cnt = 1;
|
|
/* Bottom line of file already on screen */
|
|
forbid(lineDOL()-lineDOT() <= vcnt-1-vcline);
|
|
ind = vcnt - vcline - 1 + cnt;
|
|
vdown(ind, ind, 1);
|
|
vnline(cursor);
|
|
continue;
|
|
|
|
/*
|
|
* ^Y Like ^E but up
|
|
*/
|
|
case CTRL('y'):
|
|
if (state != VISUAL)
|
|
continue;
|
|
if (!hadcnt)
|
|
cnt = 1;
|
|
forbid(lineDOT()-1<=vcline); /* line 1 already there */
|
|
ind = vcline + cnt;
|
|
vup(ind, ind, 1);
|
|
vnline(cursor);
|
|
continue;
|
|
|
|
|
|
/*
|
|
* m Mark position in mark register given
|
|
* by following letter. Return is
|
|
* accomplished via ' or `; former
|
|
* to beginning of line where mark
|
|
* was set, latter to column where marked.
|
|
*/
|
|
case 'm':
|
|
/*
|
|
* Getesc is generally used when a character
|
|
* is read as a latter part of a command
|
|
* to allow one to hit rubout/escape to cancel
|
|
* what you have typed so far. These characters
|
|
* are mapped to 0 by the subroutine.
|
|
*/
|
|
c = getesc();
|
|
if (c == 0)
|
|
continue;
|
|
|
|
/*
|
|
* Markreg checks that argument is a letter
|
|
* and also maps ' and ` to the end of the range
|
|
* to allow '' or `` to reference the previous
|
|
* context mark.
|
|
*/
|
|
c = markreg(c);
|
|
forbid (c == 0);
|
|
vsave();
|
|
names[c - 'a'] = (*dot &~ 01);
|
|
ncols[c - 'a'] = cursor;
|
|
anymarks = 1;
|
|
continue;
|
|
|
|
/*
|
|
* ^F Window forwards, with 2 lines of continuity.
|
|
* Count repeats.
|
|
*/
|
|
case CTRL('f'):
|
|
vsave();
|
|
if (vcnt > 2) {
|
|
addr = dot + (vcnt - vcline) - 2 + (cnt-1)*basWLINES;
|
|
forbid(addr > dol);
|
|
dot = (line*)addr;
|
|
vcnt = vcline = 0;
|
|
}
|
|
vzop(0, 0, '+');
|
|
continue;
|
|
|
|
/*
|
|
* ^B Window backwards, with 2 lines of continuity.
|
|
* Inverse of ^F.
|
|
*/
|
|
case CTRL('b'):
|
|
vsave();
|
|
if (one + vcline != dot && vcnt > 2) {
|
|
addr = dot - vcline + 2 - (cnt-1)*basWLINES;
|
|
forbid (addr <= zero);
|
|
dot = (line*)addr;
|
|
vcnt = vcline = 0;
|
|
}
|
|
vzop(0, 0, '^');
|
|
continue;
|
|
|
|
/*
|
|
* z Screen adjustment, taking a following character:
|
|
* z<CR> current line to top
|
|
* z<NL> like z<CR>
|
|
* z- current line to bottom
|
|
* also z+, z^ like ^F and ^B.
|
|
* A preceding count is line to use rather
|
|
* than current line. A count between z and
|
|
* specifier character changes the screen size
|
|
* for the redraw.
|
|
*
|
|
*/
|
|
case 'z':
|
|
if (state == VISUAL) {
|
|
i = vgetcnt();
|
|
if (i > 0)
|
|
vsetsiz(i);
|
|
c = getesc();
|
|
if (c == 0)
|
|
continue;
|
|
}
|
|
vsave();
|
|
vzop(hadcnt, cnt, c);
|
|
continue;
|
|
|
|
/*
|
|
* Y Yank lines, abbreviation for y_ or yy.
|
|
* Yanked lines can be put later if no
|
|
* changes intervene, or can be put in named
|
|
* buffers and put anytime in this session.
|
|
*/
|
|
case 'Y':
|
|
ungetkey('_');
|
|
c = 'y';
|
|
break;
|
|
|
|
/*
|
|
* J Join lines, 2 by default. Count is number
|
|
* of lines to join (no join operator sorry.)
|
|
*/
|
|
case 'J':
|
|
forbid (dot == dol);
|
|
if (cnt == 1)
|
|
cnt = 2;
|
|
if (cnt > (i = dol - dot + 1))
|
|
cnt = i;
|
|
vsave();
|
|
vmacchng(1);
|
|
setLAST();
|
|
cursor = strend(linebuf);
|
|
vremote(cnt, join, 0);
|
|
notenam = "join";
|
|
vmoving = 0;
|
|
killU();
|
|
vreplace(vcline, cnt, 1);
|
|
if (!*cursor && cursor > linebuf)
|
|
cursor += skipleft(linebuf, cursor);
|
|
if (notecnt == 2)
|
|
notecnt = 0;
|
|
vrepaint(cursor);
|
|
continue;
|
|
|
|
/*
|
|
* S Substitute text for whole lines, abbrev for c_.
|
|
* Count is number of lines to change.
|
|
*/
|
|
case 'S':
|
|
ungetkey('_');
|
|
c = 'c';
|
|
break;
|
|
|
|
/*
|
|
* O Create a new line above current and accept new
|
|
* input text, to an escape, there.
|
|
* A count specifies, for dumb terminals when
|
|
* slowopen is not set, the number of physical
|
|
* line space to open on the screen.
|
|
*
|
|
* o Like O, but opens lines below.
|
|
*/
|
|
case 'O':
|
|
case 'o':
|
|
vmacchng(1);
|
|
voOpen(c, cnt);
|
|
continue;
|
|
|
|
/*
|
|
* C Change text to end of line, short for c$.
|
|
*/
|
|
case 'C':
|
|
if (*cursor) {
|
|
ungetkey('$'), c = 'c';
|
|
break;
|
|
}
|
|
goto appnd;
|
|
|
|
/*
|
|
* ~ Switch case of letter under cursor
|
|
*/
|
|
case '~':
|
|
vswitch(cnt);
|
|
continue;
|
|
|
|
|
|
/*
|
|
* A Append at end of line, short for $a.
|
|
*/
|
|
case 'A':
|
|
operate('$', 1);
|
|
appnd:
|
|
c = 'a';
|
|
/* fall into ... */
|
|
|
|
/*
|
|
* a Appends text after cursor. Text can continue
|
|
* through arbitrary number of lines.
|
|
*/
|
|
case 'a':
|
|
if (*cursor) {
|
|
if (state == HARDOPEN) {
|
|
int c, n;
|
|
nextc(c, cursor, n);
|
|
putchar(c);
|
|
cursor += n;
|
|
} else
|
|
cursor += skipright(linebuf, cursor);
|
|
}
|
|
goto insrt;
|
|
|
|
/*
|
|
* I Insert at beginning of whitespace of line,
|
|
* short for ^i.
|
|
*/
|
|
case 'I':
|
|
operate('^', 1);
|
|
c = 'i';
|
|
/* fall into ... */
|
|
|
|
/*
|
|
* R Replace characters, one for one, by input
|
|
* (logically), like repeated r commands.
|
|
*
|
|
* BUG: This is like the typeover mode of many other
|
|
* editors, and is only rarely useful. Its
|
|
* implementation is a hack in a low level
|
|
* routine and it doesn't work very well, e.g.
|
|
* you can't move around within a R, etc.
|
|
*/
|
|
case 'R':
|
|
/* fall into... */
|
|
|
|
/*
|
|
* i Insert text to an escape in the buffer.
|
|
* Text is arbitrary. This command reminds of
|
|
* the i command in bare teco.
|
|
*/
|
|
case 'i':
|
|
insrt:
|
|
/*
|
|
* Common code for all the insertion commands.
|
|
* Save for redo, position cursor, prepare for append
|
|
* at command and in visual undo. Note that nothing
|
|
* is doomed, unless R when all is, and save the
|
|
* current line in a the undo temporary buffer.
|
|
*/
|
|
vmacchng(1);
|
|
setLAST();
|
|
vcursat(cursor);
|
|
prepapp();
|
|
vnoapp();
|
|
doomed = c == 'R' ? 10000 : 0;
|
|
if(FIXUNDO)
|
|
vundkind = VCHNG;
|
|
vmoving = 0;
|
|
CP(vutmp, linebuf);
|
|
|
|
/*
|
|
* If this is a repeated command, then suppress
|
|
* fake insert mode on dumb terminals which looks
|
|
* ridiculous and wastes lots of time even at 9600B.
|
|
*/
|
|
if (vglobp)
|
|
hold = HOLDQIK;
|
|
vappend(c, cnt, 0);
|
|
continue;
|
|
|
|
/*
|
|
* ^? An attention, normally a ^?, just beeps.
|
|
* If you are a vi command within ex, then
|
|
* two ATTN's will drop you back to command mode.
|
|
*/
|
|
case_ATTN:
|
|
beep();
|
|
if (initev || peekkey() != ATTN)
|
|
continue;
|
|
/* fall into... */
|
|
|
|
/*
|
|
* ^\ A quit always gets command mode.
|
|
*/
|
|
case QUIT:
|
|
/*
|
|
* Have to be careful if we were called
|
|
* g/xxx/vi
|
|
* since a return will just start up again.
|
|
* So we simulate an interrupt.
|
|
*/
|
|
if (inglobal)
|
|
onintr(SIGINT);
|
|
/* fall into... */
|
|
|
|
#ifdef notdef
|
|
/*
|
|
* q Quit back to command mode, unless called as
|
|
* vi on command line in which case dont do it
|
|
*/
|
|
case 'q': /* quit */
|
|
if (initev) {
|
|
vsave();
|
|
CATCH
|
|
error(catgets(catd, 1, 226,
|
|
"Q gets ex command mode, :q leaves vi"));
|
|
ENDCATCH
|
|
splitw = 0;
|
|
getDOT();
|
|
vrepaint(cursor);
|
|
continue;
|
|
}
|
|
#endif
|
|
/* fall into... */
|
|
|
|
/*
|
|
* Q Is like q, but always gets to command mode
|
|
* even if command line invocation was as vi.
|
|
*/
|
|
case 'Q':
|
|
vsave();
|
|
/*
|
|
* If we are in the middle of a macro, throw away
|
|
* the rest and fix up undo.
|
|
* This code copied from getbr().
|
|
*/
|
|
if (vmacp) {
|
|
vmacp = 0;
|
|
if (inopen == -1) /* don't screw up undo for esc esc */
|
|
vundkind = VMANY;
|
|
inopen = 1; /* restore old setting now that macro done */
|
|
}
|
|
return;
|
|
|
|
|
|
/*
|
|
* ZZ Like :x
|
|
*/
|
|
case 'Z':
|
|
forbid(getkey() != 'Z');
|
|
oglobp = globp;
|
|
globp = "x";
|
|
vclrech(0);
|
|
goto gogo;
|
|
|
|
/*
|
|
* P Put back text before cursor or before current
|
|
* line. If text was whole lines goes back
|
|
* as whole lines. If part of a single line
|
|
* or parts of whole lines splits up current
|
|
* line to form many new lines.
|
|
* May specify a named buffer, or the delete
|
|
* saving buffers 1-9.
|
|
*
|
|
* p Like P but after rather than before.
|
|
*/
|
|
case 'P':
|
|
case 'p':
|
|
vmoving = 0;
|
|
#ifdef notdef
|
|
forbid (!vreg && value(UNDOMACRO) && inopen < 0);
|
|
#endif
|
|
/*
|
|
* If previous delete was partial line, use an
|
|
* append or insert to put it back so as to
|
|
* use insert mode on intelligent terminals.
|
|
*/
|
|
if (!vreg && DEL[0]) {
|
|
forbid ((DEL[0] & (QUOTE|TRIM)) == OVERBUF);
|
|
vglobp = DEL;
|
|
ungetkey(c == 'p' ? 'a' : 'i');
|
|
goto reread;
|
|
}
|
|
|
|
/*
|
|
* If a register wasn't specified, then make
|
|
* sure there is something to put back.
|
|
*/
|
|
forbid (!vreg && unddol == dol);
|
|
/*
|
|
* If we just did a macro the whole buffer is in
|
|
* the undo save area. We don't want to put THAT.
|
|
*/
|
|
forbid (vundkind == VMANY && undkind==UNDALL);
|
|
vsave();
|
|
vmacchng(1);
|
|
setLAST();
|
|
i = 0;
|
|
if (vreg && partreg(vreg) || !vreg && pkill[0]) {
|
|
/*
|
|
* Restoring multiple lines which were partial
|
|
* lines; will leave cursor in middle
|
|
* of line after shoving restored text in to
|
|
* split the current line.
|
|
*/
|
|
i++;
|
|
if (c == 'p' && *cursor)
|
|
cursor += skipright(linebuf, cursor);
|
|
} else {
|
|
/*
|
|
* In whole line case, have to back up dot
|
|
* for P; also want to clear cursor so
|
|
* cursor will eventually be positioned
|
|
* at the beginning of the first put line.
|
|
*/
|
|
cursor = 0;
|
|
if (c == 'P') {
|
|
dot--, vcline--;
|
|
c = 'p';
|
|
}
|
|
}
|
|
killU();
|
|
|
|
/*
|
|
* The call to putreg can potentially
|
|
* bomb since there may be nothing in a named buffer.
|
|
* We thus put a catch in here. If we didn't and
|
|
* there was an error we would end up in command mode.
|
|
*/
|
|
addr = dol; /* old dol */
|
|
CATCH
|
|
vremote(1, vreg ? putreg : put, vreg);
|
|
ONERR
|
|
if (vreg == -1) {
|
|
splitw = 0;
|
|
if (op == 'P')
|
|
dot++, vcline++;
|
|
goto pfixup;
|
|
}
|
|
ENDCATCH
|
|
splitw = 0;
|
|
nlput = dol - addr + 1;
|
|
if (!i) {
|
|
/*
|
|
* Increment undap1, undap2 to make up
|
|
* for their incorrect initialization in the
|
|
* routine vremote before calling put/putreg.
|
|
*/
|
|
if (FIXUNDO)
|
|
undap1++, undap2++;
|
|
vcline++;
|
|
nlput--;
|
|
|
|
/*
|
|
* After a put want current line first line,
|
|
* and dot was made the last line put in code
|
|
* run so far. This is why we increment vcline
|
|
* above and decrease dot here.
|
|
*/
|
|
dot -= nlput - 1;
|
|
}
|
|
#ifdef TRACE
|
|
if (trace)
|
|
fprintf(trace, "vreplace(%d, %d, %d), undap1=%d, undap2=%d, dot=%d\n", vcline, i, nlput, lineno(undap1), lineno(undap2), lineno(dot));
|
|
#endif
|
|
vreplace(vcline, i, nlput);
|
|
if (state != VISUAL) {
|
|
/*
|
|
* Special case in open mode.
|
|
* Force action on the screen when a single
|
|
* line is put even if it is identical to
|
|
* the current line, e.g. on YP; otherwise
|
|
* you can't tell anything happened.
|
|
*/
|
|
vjumpto(dot, cursor, '.');
|
|
continue;
|
|
}
|
|
pfixup:
|
|
vrepaint(cursor);
|
|
vfixcurs();
|
|
continue;
|
|
|
|
/*
|
|
* ^^ Return to previous file.
|
|
* Like a :e #, and thus can be used after a
|
|
* "No Write" diagnostic.
|
|
*/
|
|
case CTRL('^'):
|
|
forbid (hadcnt);
|
|
vsave();
|
|
ckaw();
|
|
oglobp = globp;
|
|
if (value(AUTOWRITE))
|
|
globp = "e! #";
|
|
else
|
|
globp = "e #";
|
|
goto gogo;
|
|
|
|
/*
|
|
* ^] Takes word after cursor as tag, and then does
|
|
* tag command. Read ``go right to''.
|
|
*/
|
|
case CTRL(']'):
|
|
grabtag();
|
|
oglobp = globp;
|
|
globp = "tag";
|
|
goto gogo;
|
|
|
|
/*
|
|
* & Like :&
|
|
*/
|
|
case '&':
|
|
oglobp = globp;
|
|
globp = "&";
|
|
goto gogo;
|
|
|
|
/*
|
|
* ^G Bring up a status line at the bottom of
|
|
* the screen, like a :file command.
|
|
*
|
|
* BUG: Was ^S but doesn't work in cbreak mode
|
|
*/
|
|
case CTRL('g'):
|
|
oglobp = globp;
|
|
globp = "file";
|
|
gogo:
|
|
addr = dot;
|
|
vsave();
|
|
goto doinit;
|
|
|
|
#ifdef SIGTSTP
|
|
/*
|
|
* ^Z: suspend editor session and temporarily return
|
|
* to shell. Only works with Berkeley/IIASA process
|
|
* control in kernel.
|
|
*/
|
|
case CTRL('z'):
|
|
forbid(dosusp == 0 || !ldisc);
|
|
vsave();
|
|
oglobp = globp;
|
|
globp = "stop";
|
|
goto gogo;
|
|
#endif
|
|
|
|
/*
|
|
* : Read a command from the echo area and
|
|
* execute it in command mode.
|
|
*/
|
|
case ':':
|
|
forbid (hadcnt);
|
|
vsave();
|
|
i = tchng;
|
|
addr = dot;
|
|
if (readecho(c)) {
|
|
esave[0] = 0;
|
|
goto fixup;
|
|
}
|
|
getDOT();
|
|
/*
|
|
* Use the visual undo buffer to store the global
|
|
* string for command mode, since it is idle right now.
|
|
*/
|
|
oglobp = globp;
|
|
CP(vutmp, genbuf+1);
|
|
globp = vutmp;
|
|
doinit:
|
|
esave[0] = 0;
|
|
fixech();
|
|
|
|
/*
|
|
* Have to finagle around not to lose last
|
|
* character after this command (when run from ex
|
|
* command mode). This is clumsy.
|
|
*/
|
|
d = peekc; ungetchar(0);
|
|
if (shouldpo) {
|
|
/*
|
|
* So after a "Hit return..." ":", we do
|
|
* another "Hit return..." the next time
|
|
*/
|
|
pofix();
|
|
shouldpo = 0;
|
|
}
|
|
CATCH
|
|
/*
|
|
* Save old values of options so we can
|
|
* notice when they change; switch into
|
|
* cooked mode so we are interruptible.
|
|
*/
|
|
onumber = value(NUMBER);
|
|
olist = value(LIST);
|
|
OPline = Pline;
|
|
OPutchar = Putchar;
|
|
commands(1, 1);
|
|
if (dot == zero && dol > zero)
|
|
dot = one;
|
|
ONERR
|
|
copy(esave, vtube[WECHO],
|
|
TUBECOLS * sizeof *esave);
|
|
ENDCATCH
|
|
fixol();
|
|
Pline = OPline;
|
|
Putchar = OPutchar;
|
|
ungetchar(d);
|
|
if (globp && tflag < 0) {
|
|
tflag = 0;
|
|
goto doinit;
|
|
}
|
|
globp = oglobp;
|
|
|
|
/*
|
|
* If we ended up with no lines in the buffer, make
|
|
* a line, and don't consider the buffer changed.
|
|
*/
|
|
if (dot == zero) {
|
|
fixzero();
|
|
/*synced();*/
|
|
}
|
|
splitw = 0;
|
|
|
|
/*
|
|
* Special case: did list/number options change?
|
|
*/
|
|
if (onumber != value(NUMBER))
|
|
setnumb(value(NUMBER));
|
|
if (olist != value(LIST))
|
|
setlist(value(LIST));
|
|
|
|
fixup:
|
|
/*
|
|
* If a change occurred, other than
|
|
* a write which clears changes, then
|
|
* we should allow an undo even if .
|
|
* didn't move.
|
|
*
|
|
* BUG: You can make this wrong by
|
|
* tricking around with multiple commands
|
|
* on one line of : escape, and including
|
|
* a write command there, but its not
|
|
* worth worrying about.
|
|
*/
|
|
if (FIXUNDO && tchng && tchng != i)
|
|
vundkind = VMANY, cursor = 0;
|
|
|
|
/*
|
|
* If we are about to do another :, hold off
|
|
* updating of screen.
|
|
*/
|
|
if (vcnt < 0 && Peekkey == ':') {
|
|
getDOT();
|
|
shouldpo = 1;
|
|
continue;
|
|
}
|
|
shouldpo = 0;
|
|
|
|
/*
|
|
* In the case where the file being edited is
|
|
* new; e.g. if the initial state hasn't been
|
|
* saved yet, then do so now.
|
|
*/
|
|
if (unddol == truedol) {
|
|
vundkind = VNONE;
|
|
Vlines = lineDOL();
|
|
if (!inglobal)
|
|
savevis();
|
|
addr = zero;
|
|
vcnt = 0;
|
|
if (esave[0] == 0)
|
|
copy(esave, vtube[WECHO],
|
|
TUBECOLS * sizeof *esave);
|
|
}
|
|
|
|
/*
|
|
* If the current line moved reset the cursor position.
|
|
*/
|
|
if (dot != addr) {
|
|
vmoving = 0;
|
|
cursor = 0;
|
|
}
|
|
|
|
/*
|
|
* If current line is not on screen or if we are
|
|
* in open mode and . moved, then redraw.
|
|
*/
|
|
i = vcline + (dot - addr);
|
|
if (i < 0 || i >= vcnt && i >= -vcnt || state != VISUAL && dot != addr) {
|
|
if (state == CRTOPEN)
|
|
vup1();
|
|
if (vcnt > 0)
|
|
vcnt = 0;
|
|
vjumpto(dot, (char *) 0, '.');
|
|
} else {
|
|
/*
|
|
* Current line IS on screen.
|
|
* If we did a [Hit return...] then
|
|
* restore vcnt and clear screen if in visual
|
|
*/
|
|
vcline = i;
|
|
if (vcnt < 0) {
|
|
vcnt = -vcnt;
|
|
if (state == VISUAL)
|
|
vclear();
|
|
else if (state == CRTOPEN) {
|
|
vcnt = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Limit max value of vcnt based on $
|
|
*/
|
|
i = vcline + lineDOL() - lineDOT() + 1;
|
|
if (i < vcnt)
|
|
vcnt = i;
|
|
|
|
/*
|
|
* Dirty and repaint.
|
|
*/
|
|
vdirty(0, TLINES);
|
|
vrepaint(cursor);
|
|
}
|
|
|
|
/*
|
|
* If in visual, put back the echo area
|
|
* if it was clobberred.
|
|
*/
|
|
if (state == VISUAL) {
|
|
int sdc = destcol, sdl = destline;
|
|
|
|
splitw++;
|
|
vigoto(WECHO, 0);
|
|
for (i = 0; i < TUBECOLS - 1; i++) {
|
|
if (esave[i] == 0)
|
|
break;
|
|
vputchar(esave[i]);
|
|
}
|
|
splitw = 0;
|
|
vgoto(sdl, sdc);
|
|
}
|
|
continue;
|
|
|
|
/*
|
|
* u undo the last changing command.
|
|
*/
|
|
case 'u':
|
|
vundo(1);
|
|
continue;
|
|
|
|
/*
|
|
* U restore current line to initial state.
|
|
*/
|
|
case 'U':
|
|
vUndo();
|
|
continue;
|
|
|
|
fonfon:
|
|
beep();
|
|
vmacp = 0;
|
|
inopen = 1; /* might have been -1 */
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Rest of commands are decoded by the operate
|
|
* routine.
|
|
*/
|
|
operate(c, cnt);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Grab the word after the cursor so we can look for it as a tag.
|
|
*/
|
|
void
|
|
grabtag(void)
|
|
{
|
|
register char *cp, *dp;
|
|
|
|
cp = vpastwh(cursor);
|
|
if (*cp) {
|
|
dp = lasttag;
|
|
do {
|
|
if (dp < &lasttag[sizeof lasttag - 2])
|
|
*dp++ = *cp;
|
|
cp++;
|
|
} while (isalpha(*cp&0377) || isdigit(*cp&0377)
|
|
|| *cp == '_'
|
|
#ifdef LISPCODE
|
|
|| (value(LISP) && *cp == '-')
|
|
#endif /* LISPCODE */
|
|
);
|
|
*dp++ = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Before appending lines, set up addr1 and
|
|
* the command mode undo information.
|
|
*/
|
|
void
|
|
prepapp(void)
|
|
{
|
|
|
|
addr1 = dot;
|
|
deletenone();
|
|
addr1++;
|
|
appendnone();
|
|
}
|
|
|
|
/*
|
|
* Execute function f with the address bounds addr1
|
|
* and addr2 surrounding cnt lines starting at dot.
|
|
*/
|
|
void
|
|
vremote(int cnt, void (*f)(int), int arg)
|
|
{
|
|
register int oing = inglobal;
|
|
|
|
addr1 = dot;
|
|
addr2 = dot + cnt - 1;
|
|
inglobal = 0;
|
|
if (FIXUNDO)
|
|
undap1 = undap2 = dot;
|
|
(*f)(arg);
|
|
inglobal = oing;
|
|
if (FIXUNDO)
|
|
vundkind = VMANY;
|
|
vmcurs = 0;
|
|
}
|
|
|
|
/*
|
|
* Save the current contents of linebuf, if it has changed.
|
|
*/
|
|
void
|
|
vsave(void)
|
|
{
|
|
char temp[LBSIZE];
|
|
|
|
CP(temp, linebuf);
|
|
if (FIXUNDO && vundkind == VCHNG || vundkind == VCAPU) {
|
|
/*
|
|
* If the undo state is saved in the temporary buffer
|
|
* vutmp, then we sync this into the temp file so that
|
|
* we will be able to undo even after we have moved off
|
|
* the line. It would be possible to associate a line
|
|
* with vutmp but we assume that vutmp is only associated
|
|
* with line dot (e.g. in case ':') above, so beware.
|
|
*/
|
|
prepapp();
|
|
CP(linebuf, vutmp);
|
|
putmark(dot);
|
|
vremote(1, yank, 0);
|
|
vundkind = VMCHNG;
|
|
notecnt = 0;
|
|
undkind = UNDCHANGE;
|
|
}
|
|
/*
|
|
* Get the line out of the temp file and do nothing if it hasn't
|
|
* changed. This may seem like a loss, but the line will
|
|
* almost always be in a read buffer so this may well avoid disk i/o.
|
|
*/
|
|
getDOT();
|
|
if (strcmp(linebuf, temp) == 0)
|
|
return;
|
|
strcLIN(temp);
|
|
putmark(dot);
|
|
}
|
|
|
|
#undef forbid
|
|
#define forbid(a) if (a) { beep(); return; }
|
|
|
|
/*
|
|
* Do a z operation.
|
|
* Code here is rather long, and very uninteresting.
|
|
*/
|
|
void
|
|
vzop(int hadcnt, int cnt, register int c)
|
|
{
|
|
register line *addr;
|
|
|
|
if (state != VISUAL) {
|
|
/*
|
|
* Z from open; always like a z=.
|
|
* This code is a mess and should be cleaned up.
|
|
*/
|
|
vmoveitup(1, 1);
|
|
vgoto(outline, 0);
|
|
ostop(normf);
|
|
setoutt();
|
|
addr2 = dot;
|
|
vclear();
|
|
destline = WECHO;
|
|
zop2(Xhadcnt ? Xcnt : value(WINDOW) - 1, '=');
|
|
if (state == CRTOPEN)
|
|
putnl();
|
|
putNFL();
|
|
termreset();
|
|
Outchar = vputchar;
|
|
ignore(ostart());
|
|
vcnt = 0;
|
|
outline = destline = 0;
|
|
vjumpto(dot, cursor, 0);
|
|
return;
|
|
}
|
|
if (hadcnt) {
|
|
addr = zero + cnt;
|
|
if (addr < one)
|
|
addr = one;
|
|
if (addr > dol)
|
|
addr = dol;
|
|
markit(addr);
|
|
} else
|
|
switch (c) {
|
|
|
|
case '+':
|
|
addr = dot + vcnt - vcline;
|
|
break;
|
|
|
|
case '^':
|
|
addr = dot - vcline - 1;
|
|
forbid (addr < one);
|
|
c = '-';
|
|
break;
|
|
|
|
default:
|
|
addr = dot;
|
|
break;
|
|
}
|
|
switch (c) {
|
|
|
|
case '.':
|
|
case '-':
|
|
break;
|
|
|
|
case '^':
|
|
forbid (addr <= one);
|
|
break;
|
|
|
|
case '+':
|
|
forbid (addr >= dol);
|
|
/* fall into ... */
|
|
|
|
case CR:
|
|
case NL:
|
|
c = CR;
|
|
break;
|
|
|
|
default:
|
|
beep();
|
|
return;
|
|
}
|
|
vmoving = 0;
|
|
vjumpto(addr, NOSTR, c);
|
|
}
|
|
|
|
cell *
|
|
str2cell(cell *dst, register char *src)
|
|
{
|
|
register cell *cp = dst;
|
|
|
|
#ifdef MB
|
|
if (mb_cur_max > 1) {
|
|
int c, n;
|
|
do {
|
|
nextc(c, src, n);
|
|
src += n;
|
|
*cp++ = c;
|
|
} while (src[-n]);
|
|
} else
|
|
#endif /* MB */
|
|
while (*cp++ = *src++ & 0377);
|
|
return dst;
|
|
}
|
|
|
|
char *
|
|
cell2str(char *dst, register cell *src)
|
|
{
|
|
register char *cp = dst;
|
|
|
|
while (*cp++ = *src++);
|
|
return dst;
|
|
}
|
|
|
|
cell *
|
|
cellcpy(cell *dst, register cell *src)
|
|
{
|
|
register cell *cp = dst;
|
|
|
|
while (*cp++ = *src++);
|
|
return dst;
|
|
}
|
|
|
|
size_t
|
|
cellen(register cell *cp)
|
|
{
|
|
register size_t sz = 0;
|
|
|
|
while (*cp++)
|
|
sz++;
|
|
return sz;
|
|
}
|
|
|
|
cell *
|
|
cellcat(cell *dst, register cell *src)
|
|
{
|
|
register cell *cp = dst;
|
|
|
|
while (*cp)
|
|
cp++;
|
|
cellcpy(cp, src);
|
|
return dst;
|
|
}
|