windows-nt/Source/XPSP1/NT/sdktools/vi/normal.c
2020-09-26 16:20:57 +08:00

1070 lines
31 KiB
C

/*
*
* Contains the main routine for processing characters in command mode.
* Communicates closely with the code in ops.c to handle the operators.
*/
#include "stevie.h"
#include "ops.h"
/*
* Generally speaking, every command in normal() should either clear any
* pending operator (with CLEAROP), or set the motion type variable.
*/
#define CLEAROP (operator=NOP, namedbuff = -1) /* clear any pending operator */
int operator = NOP; /* current pending operator */
int mtype; /* type of the current cursor motion */
bool_t mincl; /* true if char motion is inclusive */
LNPTR startop; /* cursor pos. at start of operator */
/*
* Operators can have counts either before the operator, or between the
* operator and the following cursor motion as in:
*
* d3w or 3dw
*
* If a count is given before the operator, it is saved in opnum. If
* normal() is called with a pending operator, the count in opnum (if
* present) overrides any count that came later.
*/
static int opnum = 0;
#define DEFAULT1(x) (((x) == 0) ? 1 : (x))
void HighlightCheck();
/*
* normal(c)
*
* Execute a command in command mode.
*
* This is basically a big switch with the cases arranged in rough categories
* in the following order:
*
* 1. File positioning commands
* 2. Control commands (e.g. ^G, Z, screen redraw, etc)
* 3. Character motions
* 4. Search commands (of various kinds)
* 5. Edit commands (e.g. J, x, X)
* 6. Insert commands (e.g. i, o, O, A)
* 7. Operators
* 8. Abbreviations (e.g. D, C)
* 9. Marks
*/
void
normal(c)
register int c;
{
register int n;
register char *s; /* temporary variable for misc. strings */
bool_t flag = FALSE;
int type = 0; /* used in some operations to modify type */
int dir = FORWARD; /* search direction */
int nchar = NUL;
bool_t finish_op;
/*
* If there is an operator pending, then the command we take
* this time will terminate it. Finish_op tells us to finish
* the operation before returning this time (unless the operation
* was cancelled.
*/
finish_op = (operator != NOP);
/*
* If we're in the middle of an operator AND we had a count before
* the operator, then that count overrides the current value of
* Prenum. What this means effectively, is that commands like
* "3dw" get turned into "d3w" which makes things fall into place
* pretty neatly.
*/
if (finish_op) {
if (opnum != 0)
Prenum = opnum;
} else {
opnum = 0;
}
u_lcheck(); /* clear the "line undo" buffer if we've moved */
HighlightCheck();
switch (c & 0xff) {
/*
* named buffer support
*/
case('"'):
// not allowed anywhere but at the beginning of a command
if(finish_op || !isalpha(namedbuff = vgetc())) {
CLEAROP;
beep();
} else {
}
break;
/*
* Screen positioning commands
*/
case CTRL('D'):
CLEAROP;
if (Prenum)
P(P_SS) = (Prenum > Rows-1) ? Rows-1 : Prenum;
scrollup(P(P_SS));
onedown(P(P_SS));
updatescreen();
break;
case CTRL('U'):
CLEAROP;
if (Prenum)
P(P_SS) = (Prenum > Rows-1) ? Rows-1 : Prenum;
scrolldown(P(P_SS));
oneup(P(P_SS));
updatescreen();
break;
/*
* This is kind of a hack. If we're moving by one page, the calls
* to stuffin() do exactly the right thing in terms of leaving
* some context, and so on. If a count was given, we don't have
* to worry about these issues.
*/
case K_PAGEDOWN:
case CTRL('F'):
CLEAROP;
n = DEFAULT1(Prenum);
if (n > 1) {
if ( ! onedown(Rows * n) )
beep();
cursupdate();
} else {
screenclear();
stuffin("Lz\nM");
}
break;
case K_PAGEUP:
case CTRL('B'):
CLEAROP;
n = DEFAULT1(Prenum);
if (n > 1) {
if ( ! oneup(Rows * n) )
beep();
cursupdate();
} else {
screenclear();
stuffin("Hz-M");
}
break;
case CTRL('E'):
CLEAROP;
scrollup(DEFAULT1(Prenum));
updatescreen();
break;
case CTRL('Y'):
CLEAROP;
scrolldown(DEFAULT1(Prenum));
updatescreen();
break;
case 'z':
CLEAROP;
switch (vgetc()) {
case NL: /* put Curschar at top of screen */
case CR:
*Topchar = *Curschar;
Topchar->index = 0;
updatescreen();
break;
case '.': /* put Curschar in middle of screen */
n = Rows/2;
goto dozcmd;
case '-': /* put Curschar at bottom of screen */
n = Rows-1;
/* fall through */
dozcmd:
{
register LNPTR *lp = Curschar;
register int l = 0;
while ((l < n) && (lp != NULL)) {
l += plines(lp);
*Topchar = *lp;
lp = prevline(lp);
}
}
Topchar->index = 0;
updatescreen();
break;
default:
beep();
}
break;
/*
* Control commands
*/
case ':':
CLEAROP;
if ((s = getcmdln(c)) != NULL)
docmdln(s);
break;
case CTRL('L'):
CLEAROP;
screenclear();
updatescreen();
break;
case CTRL('O'): /* ignored */
/*
* A command that's ignored can be useful. We use it at
* times when we want to postpone redraws. By stuffing
* in a control-o, redraws get suspended until the editor
* gets back around to processing input.
*/
break;
case CTRL('G'):
CLEAROP;
fileinfo();
break;
case K_CGRAVE: /* shorthand command */
CLEAROP;
stuffin(":e #\n");
break;
case 'Z': /* write, if changed, and exit */
if (vgetc() != 'Z') {
beep();
break;
}
doxit();
break;
/*
* Macro evaluates true if char 'c' is a valid identifier character
*/
# define IDCHAR(c) (isalpha(c) || isdigit(c) || (c) == '_')
case CTRL(']'): /* :ta to current identifier */
CLEAROP;
{
char ch;
LNPTR save;
save = *Curschar;
/*
* First back up to start of identifier. This
* doesn't match the real vi but I like it a
* little better and it shouldn't bother anyone.
*/
ch = (char)gchar(Curschar);
while (IDCHAR(ch)) {
if (!oneleft())
break;
ch = (char)gchar(Curschar);
}
if (!IDCHAR(ch))
oneright();
stuffin(":ta ");
/*
* Now grab the chars in the identifier
*/
ch = (char)gchar(Curschar);
while (IDCHAR(ch)) {
stuffin(mkstr(ch));
if (!oneright())
break;
ch = (char)gchar(Curschar);
}
stuffin("\n");
*Curschar = save; /* restore, in case of error */
}
break;
/*
* Character motion commands
*/
case 'G':
mtype = MLINE;
*Curschar = *gotoline(Prenum);
beginline(TRUE);
break;
case 'H':
mtype = MLINE;
*Curschar = *Topchar;
for (n = Prenum; n && onedown(1) ;n--)
;
beginline(TRUE);
break;
case 'M':
mtype = MLINE;
*Curschar = *Topchar;
for (n = 0; n < Rows/2 && onedown(1) ;n++)
;
beginline(TRUE);
break;
case 'L':
mtype = MLINE;
*Curschar = *prevline(Botchar);
for (n = Prenum; n && oneup(1) ;n--)
;
beginline(TRUE);
break;
case 'l':
case K_RARROW:
case ' ':
mtype = MCHAR;
mincl = FALSE;
n = DEFAULT1(Prenum);
while (n--) {
if ( ! oneright() )
beep();
}
set_want_col = TRUE;
break;
case 'h':
case K_LARROW:
case CTRL('H'):
mtype = MCHAR;
mincl = FALSE;
n = DEFAULT1(Prenum);
while (n--) {
if ( ! oneleft() )
beep();
}
set_want_col = TRUE;
break;
case '-':
flag = TRUE;
/* fall through */
case 'k':
case K_UARROW:
case CTRL('P'):
mtype = MLINE;
if ( ! oneup(DEFAULT1(Prenum)) )
beep();
if (flag)
beginline(TRUE);
break;
case '+':
case CR:
case NL:
flag = TRUE;
/* fall through */
case 'j':
case K_DARROW:
case CTRL('N'):
mtype = MLINE;
if ( ! onedown(DEFAULT1(Prenum)) )
beep();
if (flag)
beginline(TRUE);
break;
/*
* This is a strange motion command that helps make operators
* more logical. It is actually implemented, but not documented
* in the real 'vi'. This motion command actually refers to "the
* current line". Commands like "dd" and "yy" are really an alternate
* form of "d_" and "y_". It does accept a count, so "d3_" works to
* delete 3 lines.
*/
case '_':
lineop:
mtype = MLINE;
onedown(DEFAULT1(Prenum)-1);
break;
case '|':
mtype = MCHAR;
mincl = TRUE;
beginline(FALSE);
if (Prenum > 0)
*Curschar = *coladvance(Curschar, Prenum-1);
Curswant = Prenum - 1;
break;
/*
* Word Motions
*/
case 'B':
type = 1;
/* fall through */
case 'b':
mtype = MCHAR;
mincl = FALSE;
set_want_col = TRUE;
for (n = DEFAULT1(Prenum); n > 0 ;n--) {
LNPTR *pos;
if ((pos = bck_word(Curschar, type)) == NULL) {
beep();
CLEAROP;
break;
} else
*Curschar = *pos;
}
break;
case 'W':
type = 1;
/* fall through */
case 'w':
/*
* This is a little strange. To match what the real vi
* does, we effectively map 'cw' to 'ce', and 'cW' to 'cE'.
* This seems impolite at first, but it's really more
* what we mean when we say 'cw'.
*/
if (operator == CHANGE)
goto doecmd;
mtype = MCHAR;
mincl = FALSE;
set_want_col = TRUE;
for (n = DEFAULT1(Prenum); n > 0 ;n--) {
LNPTR *pos;
if ((pos = fwd_word(Curschar, type)) == NULL) {
beep();
CLEAROP;
break;
} else
*Curschar = *pos;
}
break;
case 'E':
type = 1;
/* fall through */
case 'e':
doecmd:
mtype = MCHAR;
mincl = TRUE;
set_want_col = TRUE;
for (n = DEFAULT1(Prenum); n > 0 ;n--) {
LNPTR *pos;
/*
* The first motion gets special treatment if we're
* do a 'CHANGE'.
*/
if (n == DEFAULT1(Prenum))
pos = end_word(Curschar,type,operator==CHANGE);
else
pos = end_word(Curschar, type, FALSE);
if (pos == NULL) {
beep();
CLEAROP;
break;
} else
*Curschar = *pos;
}
break;
case '$':
mtype = MCHAR;
mincl = TRUE;
while ( oneright() )
;
Curswant = 999; /* so we stay at the end */
break;
case '^':
mtype = MCHAR;
mincl = FALSE;
beginline(TRUE);
break;
case '0':
mtype = MCHAR;
mincl = TRUE;
beginline(FALSE);
break;
/*
* Searches of various kinds
*/
case '?':
case '/':
s = getcmdln(c); /* get the search string */
/*
* If they backspaced out of the search command,
* just bag everything.
*/
if (s == NULL) {
CLEAROP;
break;
}
mtype = MCHAR;
mincl = FALSE;
set_want_col = TRUE;
/*
* If no string given, pass NULL to repeat the prior search.
* If the search fails, abort any pending operator.
*/
if (!dosearch(
(c == '/') ? FORWARD : BACKWARD,
(*s == NUL) ? NULL : s
))
CLEAROP;
break;
case 'n':
mtype = MCHAR;
mincl = FALSE;
set_want_col = TRUE;
if (!repsearch(0))
CLEAROP;
break;
case 'N':
mtype = MCHAR;
mincl = FALSE;
set_want_col = TRUE;
if (!repsearch(1))
CLEAROP;
break;
/*
* Character searches
*/
case 'T':
dir = BACKWARD;
/* fall through */
case 't':
type = 1;
goto docsearch;
case 'F':
dir = BACKWARD;
/* fall through */
case 'f':
docsearch:
mtype = MCHAR;
mincl = TRUE;
set_want_col = TRUE;
if ((nchar = vgetc()) == ESC) /* search char */
break;
for (n = DEFAULT1(Prenum); n > 0 ;n--) {
if (!searchc(nchar, dir, type)) {
CLEAROP;
beep();
}
}
break;
case ',':
flag = 1;
/* fall through */
case ';':
mtype = MCHAR;
mincl = TRUE;
set_want_col = TRUE;
for (n = DEFAULT1(Prenum); n > 0 ;n--) {
if (!crepsearch(flag)) {
CLEAROP;
beep();
}
}
break;
case '[': /* function searches */
dir = BACKWARD;
/* fall through */
case ']':
mtype = MLINE;
set_want_col = TRUE;
if (vgetc() != c) {
beep();
CLEAROP;
break;
}
if (!findfunc(dir)) {
beep();
CLEAROP;
}
break;
case '%':
{
char initc;
LNPTR *pos;
LNPTR save;
int done = 0;
mtype = MCHAR;
mincl = TRUE;
save = *Curschar; /* save position in case we fail */
while (!done) {
initc = (char)gchar(Curschar);
switch (initc) {
case '(':
case ')':
case '{':
case '}':
case '[':
case ']':
//
// Currently on a showmatch character.
//
done = 1;
break;
default:
//
// Didn't find anything try next character.
//
if (oneright() == FALSE) {
//
// no more on the line. Restore
// location and let the showmatch()
// call fail and beep the user.
//
*Curschar = save;
done = 1;
}
break;
}
}
if ((pos = showmatch()) == NULL) {
beep();
CLEAROP;
} else {
setpcmark();
*Curschar = *pos;
set_want_col = TRUE;
}
break;
}
/*
* Edits
*/
case '.': /* repeat last change (usually) */
/*
* If a delete is in effect, we let '.' help out the same
* way that '_' helps for some line operations. It's like
* an 'l', but subtracts one from the count and is inclusive.
*/
if (operator == DELETE || operator == CHANGE) {
if (Prenum != 0) {
n = DEFAULT1(Prenum) - 1;
while (n--)
if (! oneright())
break;
}
mtype = MCHAR;
mincl = TRUE;
} else { /* a normal 'redo' */
CLEAROP;
stuffin(Redobuff);
}
break;
case 'u':
CLEAROP;
u_undo();
break;
case 'U':
CLEAROP;
u_lundo();
break;
case 'x':
CLEAROP;
if (lineempty()) /* can't do it on a blank line */
beep();
if (Prenum)
stuffnum(Prenum);
stuffin("d.");
break;
case 'X':
CLEAROP;
if (!oneleft())
beep();
else {
u_saveline();
if (Prenum) {
int i=Prenum;
sprintf(Redobuff, "%dX", i);
while (--i)
oneleft();
stuffnum(Prenum);
stuffin("d.");
}
else {
strcpy(Redobuff, "X");
delchar(TRUE);
updateline();
}
}
break;
case 'r':
CLEAROP;
if (lineempty()) { /* Nothing to replace */
beep();
break;
}
if ((nchar = vgetc()) == ESC)
break;
if ((nchar & 0x80) || nchar == CR || nchar == NL) {
beep();
break;
}
u_saveline();
/* Change current character. */
pchar(Curschar, nchar);
/* Save stuff necessary to redo it */
sprintf(Redobuff, "r%c", nchar);
CHANGED;
updateline();
break;
case '~': /* swap case */
if (!P(P_TO)) {
CLEAROP;
if (lineempty()) {
beep();
break;
}
c = gchar(Curschar);
if (isalpha(c)) {
if (islower(c))
c = toupper(c);
else
c = tolower(c);
}
u_saveline();
pchar(Curschar, c); /* Change current character. */
oneright();
strcpy(Redobuff, "~");
CHANGED;
updateline();
}
#ifdef TILDEOP
else {
if (operator == TILDE) /* handle '~~' */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar;
operator = TILDE;
}
#endif
break;
case 'J':
CLEAROP;
u_save(Curschar->linep->prev, Curschar->linep->next->next);
if (!dojoin(TRUE))
beep();
strcpy(Redobuff, "J");
updatescreen();
break;
/*
* Inserts
*/
case 'A':
set_want_col = TRUE;
while (oneright())
;
/* fall through */
case 'a':
CLEAROP;
/* Works just like an 'i'nsert on the next character. */
if (!lineempty())
inc(Curschar);
u_saveline();
startinsert(mkstr(c), FALSE);
break;
case 'I':
beginline(TRUE);
/* fall through */
case 'i':
case K_INSERT:
CLEAROP;
u_saveline();
startinsert(mkstr(c), FALSE);
break;
case 'o':
CLEAROP;
u_save(Curschar->linep, Curschar->linep->next);
opencmd(FORWARD, TRUE);
startinsert("o", TRUE);
break;
case 'O':
CLEAROP;
u_save(Curschar->linep->prev, Curschar->linep);
opencmd(BACKWARD, TRUE);
startinsert("O", TRUE);
break;
case 'R':
CLEAROP;
u_saveline();
startinsert("R", FALSE);
break;
/*
* Operators
*/
case 'd':
if (operator == DELETE) /* handle 'dd' */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar;
operator = DELETE;
break;
case 'c':
if (operator == CHANGE) /* handle 'cc' */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar;
operator = CHANGE;
break;
case 'y':
if (operator == YANK) /* handle 'yy' */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar;
operator = YANK;
break;
case '>':
if (operator == RSHIFT) /* handle >> */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar;
operator = RSHIFT;
break;
case '<':
if (operator == LSHIFT) /* handle << */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar; /* save current position */
operator = LSHIFT;
break;
case '!':
if (operator == FILTER) /* handle '!!' */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar;
operator = FILTER;
break;
case 'p':
doput(FORWARD);
break;
case 'P':
doput(BACKWARD);
break;
case 'v':
if (operator == LOWERCASE) /* handle 'vv' */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar;
operator = LOWERCASE;
break;
case 'V':
if (operator == UPPERCASE) /* handle 'VV' */
goto lineop;
if (Prenum != 0)
opnum = Prenum;
startop = *Curschar;
operator = UPPERCASE;
break;
/*
* Abbreviations
*/
case 'D':
stuffin("d$");
break;
case 'Y':
if (Prenum)
stuffnum(Prenum);
stuffin("yy");
break;
case 'C':
stuffin("c$");
break;
case 's': /* substitute characters */
if (Prenum)
stuffnum(Prenum);
stuffin("c.");
break;
/*
* Marks
*/
case 'm':
CLEAROP;
if (!setmark(vgetc()))
beep();
break;
case '\'':
flag = TRUE;
/* fall through */
case '`':
{
LNPTR mtmp, *mark = getmark(vgetc());
if (mark == NULL) {
beep();
CLEAROP;
} else {
mtmp = *mark;
setpcmark();
*Curschar = mtmp;
if (flag)
beginline(TRUE);
}
mtype = flag ? MLINE : MCHAR;
mincl = TRUE; /* ignored if not MCHAR */
set_want_col = TRUE;
}
break;
default:
CLEAROP;
beep();
break;
}
/*
* If an operation is pending, handle it...
*/
if (finish_op) { /* we just finished an operator */
if (operator == NOP) /* ... but it was cancelled */
return;
switch (operator) {
case LSHIFT:
case RSHIFT:
doshift(operator, c, nchar, Prenum);
break;
case DELETE:
dodelete(c, nchar, Prenum);
break;
case YANK:
(void) doyank(); /* no redo on yank... */
break;
case CHANGE:
dochange(c, nchar, Prenum);
break;
case FILTER:
dofilter(c, nchar, Prenum);
break;
#ifdef TILDEOP
case TILDE:
dotilde(c, nchar, Prenum);
break;
#endif
case LOWERCASE:
case UPPERCASE:
docasechange((char)c,
(char)nchar,
Prenum,
operator == UPPERCASE);
break;
default:
beep();
}
operator = NOP;
}
}