/* * * 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; } }