/* * Put the cursor ``at'' cp. */ void vcursat(register char *cp) { if (cp <= linebuf && linebuf[0] == 0) vgotoCL(value(NUMBER) << 3); else vgotoCL(column(cp + skipleft(linebuf, cp))); }
/* * Find single character c, in direction dir from cursor. */ int find(int c) { for(;;) { if (edge()) return (0); wcursor += dir>0 ? skipright(linebuf, wcursor) : skipleft(linebuf, wcursor-1); if (samechar(wcursor, c)) return (1); } }
/* * 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.) */ void vdelete(int c) { register char *cp; register int i; if (wdot) { if (wcursor) { vchange(EOF); 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 + skipleft(linebuf, cursor)), i); DEPTH(vcline) = 0; vreopen(LINE(vcline), lineDOT(), vcline); vsyncCL(); vsetcurs(cp); }
/* * Common code for middle part of delete * and change operating on parts of lines. */ int vdcMID(void) { register char *cp; squish(); setLAST(); if (FIXUNDO) vundkind = VCHNG, CP(vutmp, linebuf); if (wcursor < cursor) cp = wcursor, wcursor = cursor, cursor = cp; vUD1 = vUA1 = vUA2 = cursor; vUD2 = wcursor; return (column(wcursor + skipleft(linebuf, wcursor))); }
int lnext(void) { if (dir > 0) { if (*wcursor) wcursor += skipright(linebuf, wcursor); if (*wcursor) return (1); if (wdot >= llimit) { if (lf == vmove && wcursor > linebuf) wcursor += skipleft(linebuf, wcursor); return (0); } wdot++; getline(*wdot); wcursor = linebuf; return (1); } else { wcursor += skipleft(linebuf, wcursor); if (wcursor >= linebuf) return (1); #ifdef LISPCODE if (lf == (void (*)(int))lindent && linebuf[0] == '(') llimit = wdot; #endif if (wdot <= llimit) { wcursor = linebuf; return (0); } wdot--; getline(*wdot); wcursor = linebuf[0] == 0 ? linebuf : strend(linebuf) - 1; return (1); } }
/* * Get a line into genbuf after gcursor. * Cnt limits the number of input characters * accepted and is used for handling the replace * single character command. Aescaped is the location * where we stick a termination indicator (whether we * ended with an ESCAPE or a newline/return. * * We do erase-kill type processing here and also * are careful about the way we do this so that it is * repeatable. (I.e. so that your kill doesn't happen, * when you repeat an insert if it was escaped with \ the * first time you did it. commch is the command character * involved, including the prompt for readline. */ char * vgetline(int cnt, char *gcursor, bool *aescaped, int commch) { register int c, ch; register char *cp; int x, y, iwhite, backsl=0; cell *iglobp; char cstr[2]; int (*OO)(int) = Outchar; /* * Clear the output state and counters * for autoindent backwards motion (counts of ^D, etc.) * Remember how much white space at beginning of line so * as not to allow backspace over autoindent. */ *aescaped = 0; ogcursor = gcursor; flusho(); CDCNT = 0; HADUP = 0; HADZERO = 0; gobbled = 0; iwhite = whitecnt(genbuf); iglobp = vglobp; /* * Carefully avoid using vinschar in the echo area. */ if (splitw) Outchar = vputchar; else { Outchar = vinschar; vprepins(); } for (;;) { backsl = 0; if (gobblebl) gobblebl--; if (cnt != 0) { cnt--; if (cnt == 0) goto vadone; } c = getkey(); if (c != ATTN) c &= (QUOTE|TRIM); ch = c; maphopcnt = 0; if (vglobp == 0 && Peekkey == 0 && commch != 'r') while ((ch = map(c, immacs)) != c) { c = ch; if (!value(REMAP)) break; if (++maphopcnt > 256) error(catgets(catd, 1, 234, "Infinite macro loop")); } if (!iglobp) { /* * Erase-kill type processing. * Only happens if we were not reading * from untyped input when we started. * Map users erase to ^H, kill to -1 for switch. */ if (c == tty.c_cc[VERASE]) c = CTRL('h'); else if (c == tty.c_cc[VKILL]) c = -1; if (c == ATTN) goto case_ATTN; switch (c) { /* * ^? Interrupt drops you back to visual * command mode with an unread interrupt * still in the input buffer. * * ^\ Quit does the same as interrupt. * If you are a ex command rather than * a vi command this will drop you * back to command mode for sure. */ case QUIT: case_ATTN: ungetkey(c); goto vadone; /* * ^H Backs up a character in the input. * * BUG: Can't back around line boundaries. * This is hard because stuff has * already been saved for repeat. */ case CTRL('h'): bakchar: cp = gcursor + skipleft(ogcursor, gcursor); if (cp < ogcursor) { if (splitw) { /* * Backspacing over readecho * prompt. Pretend delete but * don't beep. */ ungetkey(c); goto vadone; } beep(); continue; } goto vbackup; /* * ^W Back up a white/non-white word. */ case CTRL('w'): wdkind = 1; for (cp = gcursor; cp > ogcursor && isspace(cp[-1]&0377); cp--) continue; for (c = wordch(cp - 1); cp > ogcursor && wordof(c, cp - 1); cp--) continue; goto vbackup; /* * users kill Kill input on this line, back to * the autoindent. */ case -1: cp = ogcursor; vbackup: if (cp == gcursor) { beep(); continue; } endim(); *cp = 0; c = cindent(); vgotoCL(qcolumn(cursor + skipleft(linebuf, cursor), genbuf)); if (doomed >= 0) doomed += c - cindent(); gcursor = cp; continue; /* * \ Followed by erase or kill * maps to just the erase or kill. */ case '\\': x = destcol, y = destline; putchar('\\'); vcsync(); c = getkey(); if (c == tty.c_cc[VERASE] || c == tty.c_cc[VKILL]) { vgoto(y, x); if (doomed >= 0) doomed++; goto def; } ungetkey(c), c = '\\'; backsl = 1; break; /* * ^Q Super quote following character * Only ^@ is verboten (trapped at * a lower level) and \n forces a line * split so doesn't really go in. * * ^V Synonym for ^Q */ case CTRL('q'): case CTRL('v'): x = destcol, y = destline; putchar('^'); vgoto(y, x); c = getkey(); if (c != NL) { if (doomed >= 0) doomed++; goto def; } break; } } /* * If we get a blank not in the echo area * consider splitting the window in the wrapmargin. */ if (c != NL && !splitw) { if (c == ' ' && gobblebl) { gobbled = 1; continue; } if (value(WRAPMARGIN) && (outcol >= OCOLUMNS - value(WRAPMARGIN) || (backsl && outcol == 0)) && commch != 'r') { /* * At end of word and hit wrapmargin. * Move the word to next line and keep going. */ wdkind = 1; gappend(c); if (backsl) gappend(getkey()); *gcursor = 0; /* * Find end of previous word if we are past it. */ for (cp=gcursor; cp>ogcursor && isspace(cp[-1]&0377); cp--) ; if (outcol+(backsl?OCOLUMNS:0) - (gcursor-cp) >= OCOLUMNS - value(WRAPMARGIN)) { /* * Find beginning of previous word. */ for (; cp>ogcursor && !isspace(cp[-1]&0377); cp--) ; if (cp <= ogcursor) { /* * There is a single word that * is too long to fit. Just * let it pass, but beep for * each new letter to warn * the luser. */ c = *--gcursor; *gcursor = 0; beep(); goto dontbreak; } /* * Save it for next line. */ macpush(cp, 0); cp--; } macpush("\n", 0); /* * Erase white space before the word. */ while (cp > ogcursor && isspace(cp[-1]&0377)) cp--; /* skip blank */ gobblebl = 3; goto vbackup; } dontbreak:; } /* * Word abbreviation mode. */ cstr[0] = c; if (anyabbrs && gcursor > ogcursor && !wordch(cstr) && wordch(gcursor-1)) { int wdtype, abno; cstr[1] = 0; wdkind = 1; cp = gcursor + skipleft(ogcursor, gcursor); for (wdtype = wordch(cp - 1); cp > ogcursor && wordof(wdtype, cp - 1); cp--) ; *gcursor = 0; for (abno=0; abbrevs[abno].mapto; abno++) { if (!abbrevs[abno].hadthis && eq(cp, abbrevs[abno].cap)) { abbrevs[abno].hadthis++; macpush(cstr, 0); macpush(abbrevs[abno].mapto, 0); goto vbackup; } } } #ifdef BIT8 if (c == OVERBUF) goto btrp; #endif switch (c) { /* * ^M Except in repeat maps to \n. */ case CR: if (vglobp) goto def; c = '\n'; /* presto chango ... */ /* * \n Start new line. */ case NL: *aescaped = c; goto vadone; /* * escape End insert unless repeat and more to repeat. */ case ESCAPE: if (lastvgk) goto def; goto vadone; /* * ^D Backtab. * ^T Software forward tab. * * Unless in repeat where this means these * were superquoted in. */ case CTRL('d'): case CTRL('t'): if (vglobp) goto def; /* fall into ... */ /* * ^D|QUOTE Is a backtab (in a repeated command). */ #ifndef BIT8 case CTRL('d') | QUOTE: #else btrp: #endif *gcursor = 0; cp = vpastwh(genbuf); c = whitecnt(genbuf); if (ch == CTRL('t')) { /* * ^t just generates new indent replacing * current white space rounded up to soft * tab stop increment. */ if (cp != gcursor) /* * BUG: Don't hack ^T except * right after initial * white space. */ continue; cp = genindent(iwhite = backtab(c + value(SHIFTWIDTH) + 1)); ogcursor = cp; goto vbackup; } /* * ^D works only if we are at the (end of) the * generated autoindent. We count the ^D for repeat * purposes. */ if (c == iwhite && c != 0) { if (cp == gcursor) { iwhite = backtab(c); CDCNT++; ogcursor = cp = genindent(iwhite); goto vbackup; } else if (&cp[1] == gcursor && (*cp == '^' || *cp == '0')) { /* * ^^D moves to margin, then back * to current indent on next line. * * 0^D moves to margin and then * stays there. */ HADZERO = *cp == '0'; ogcursor = cp = genbuf; HADUP = 1 - HADZERO; CDCNT = 1; endim(); back1(); vputchar(' '); goto vbackup; } } if (vglobp && vglobp - iglobp >= 2 && (vglobp[-2] == '^' || vglobp[-2] == '0') && gcursor == ogcursor + 1) goto bakchar; continue; default: /* * Possibly discard control inputs. */ if (!vglobp && junk(c)) { beep(); continue; } def: if (!backsl) { /* int cnt; */ putchar(c); flush(); } if (gcursor > &genbuf[LBSIZE - 2]) error(catgets(catd, 1, 235, "Line too long")); gappend(c & TRIM); vcsync(); if (value(SHOWMATCH) && !iglobp) if (c == ')' || c == '}') lsmatch(gcursor); continue; } } vadone: *gcursor = 0; if (Outchar != termchar) Outchar = OO; endim(); return (gcursor); }
void vappend(int ch, int cnt, int indent) /* int ch; /\* mjm: char --> int */ { register int i = 0; register char *gcursor; bool escape; int repcnt, savedoomed; short oldhold = hold; #ifdef SIGWINCH sigset_t set, oset; #endif /* * Before a move in hardopen when the line is dirty * or we are in the middle of the printed representation, * we retype the line to the left of the cursor so the * insert looks clean. */ if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) { rubble = 1; gcursor = cursor; i = *gcursor; *gcursor = ' '; wcursor = gcursor; vmove(0); *gcursor = i; } vaifirst = indent == 0; showmode(ch); /* * Handle replace character by (eventually) * limiting the number of input characters allowed * in the vgetline routine. */ if (ch == 'r') repcnt = 2; else repcnt = 0; /* * If an autoindent is specified, then * generate a mixture of blanks to tabs to implement * it and place the cursor after the indent. * Text read by the vgetline routine will be placed in genbuf, * so the indent is generated there. */ if (value(AUTOINDENT) && indent != 0) { gcursor = genindent(indent); *gcursor = 0; vgotoCL(qcolumn(cursor + skipright(cursor, linebuf), genbuf)); } else { gcursor = genbuf; *gcursor = 0; if (ch == 'o') vfixcurs(); } /* * Prepare for undo. Pointers delimit inserted portion of line. */ vUA1 = vUA2 = cursor; /* * If we are not in a repeated command and a ^@ comes in * then this means the previous inserted text. * If there is none or it was too long to be saved, * then beep() and also arrange to undo any damage done * so far (e.g. if we are a change.) */ if ((vglobp && *vglobp == 0) || peekbr()) { if ((INS[0] & (QUOTE|TRIM)) == OVERBUF) { beep(); if (!splitw) ungetkey('u'); doomed = 0; hold = oldhold; showmode(0); return; } /* * Unread input from INS. * An escape will be generated at end of string. * Hold off n^^2 type update on dumb terminals. */ vglobp = INS; hold |= HOLDQIK; } else if (vglobp == 0) /* * Not a repeated command, get * a new inserted text for repeat. */ INS[0] = 0; /* * For wrapmargin to hack away second space after a '.' * when the first space caused a line break we keep * track that this happened in gobblebl, which says * to gobble up a blank silently. */ gobblebl = 0; #ifdef SIGWINCH sigemptyset(&set); sigaddset(&set, SIGWINCH); sigprocmask(SIG_BLOCK, &set, &oset); #endif /* * Text gathering loop. * New text goes into genbuf starting at gcursor. * cursor preserves place in linebuf where text will eventually go. */ if (*cursor == 0 || state == CRTOPEN) hold |= HOLDROL; for (;;) { if (ch == 'r' && repcnt == 0) escape = 0; else { gcursor = vgetline(repcnt, gcursor, &escape, ch); /* * After an append, stick information * about the ^D's and ^^D's and 0^D's in * the repeated text buffer so repeated * inserts of stuff indented with ^D as backtab's * can work. */ if (HADUP) addc('^'); else if (HADZERO) addc('0'); while (CDCNT > 0) #ifndef BIT8 addc('\204'), CDCNT--; #else addc(OVERBUF), CDCNT--; #endif if (gobbled) addc(' '); addtext(ogcursor); } repcnt = 0; /* * Smash the generated and preexisting indents together * and generate one cleanly made out of tabs and spaces * if we are using autoindent. */ if (!vaifirst && value(AUTOINDENT)) { i = fixindent(indent); if (!HADUP) indent = i; gcursor = strend(genbuf); } /* * Limit the repetition count based on maximum * possible line length; do output implied * by further count (> 1) and cons up the new line * in linebuf. */ cnt = vmaxrep(ch, cnt); CP(gcursor + skipright(ogcursor, gcursor), cursor); do { CP(cursor, genbuf); if (cnt > 1) { int oldhold = hold; Outchar = vinschar; hold |= HOLDQIK; ex_printf("%s", genbuf); hold = oldhold; Outchar = vputchar; } cursor += gcursor - genbuf; } while (--cnt > 0); endim(); vUA2 = cursor; if (escape != '\n') CP(cursor, gcursor + skipright(ogcursor, gcursor)); /* * If doomed characters remain, clobber them, * and reopen the line to get the display exact. */ if (state != HARDOPEN) { DEPTH(vcline) = 0; savedoomed = doomed; if (doomed > 0) { register int cind = cindent(); physdc(cind, cind + doomed); doomed = 0; } i = vreopen(LINE(vcline), lineDOT(), vcline); #ifdef TRACE if (trace) fprintf(trace, "restoring doomed from %d to %d\n", doomed, savedoomed); #endif if (ch == 'R') doomed = savedoomed; } /* * All done unless we are continuing on to another line. */ if (escape != '\n') break; /* * Set up for the new line. * First save the current line, then construct a new * first image for the continuation line consisting * of any new autoindent plus the pushed ahead text. */ showmode(0); killU(); addc(gobblebl ? ' ' : '\n'); vsave(); cnt = 1; if (value(AUTOINDENT)) { #ifdef LISPCODE if (value(LISP)) indent = lindent(dot + 1); else #endif if (!HADUP && vaifirst) indent = whitecnt(linebuf); vaifirst = 0; strcLIN(vpastwh(gcursor + 1)); gcursor = genindent(indent); *gcursor = 0; if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2]) gcursor = genbuf; CP(gcursor, linebuf); } else { CP(genbuf, gcursor + skipright(ogcursor, gcursor)); gcursor = genbuf; } /* * If we started out as a single line operation and are now * turning into a multi-line change, then we had better yank * out dot before it changes so that undo will work * correctly later. */ if (FIXUNDO && vundkind == VCHNG) { vremote(1, yank, 0); undap1--; } /* * Now do the append of the new line in the buffer, * and update the display. If slowopen * we don't do very much. */ vdoappend(genbuf); vundkind = VMANYINS; vcline++; if (state != VISUAL) vshow(dot, NOLINE); else { i += LINE(vcline - 1); vopen(dot, i); if (value(SLOWOPEN)) vscrap(); else vsync1(LINE(vcline)); } strcLIN(gcursor); *gcursor = 0; cursor = linebuf; vgotoCL(qcolumn(cursor + skipleft(ogcursor, cursor), genbuf)); showmode(ch); } /* * All done with insertion, position the cursor * and sync the screen. */ showmode(0); hold = oldhold; if (cursor > linebuf) cursor += skipleft(linebuf, cursor); if (state != HARDOPEN) vsyncCL(); else if (cursor > linebuf) back1(); doomed = 0; wcursor = cursor; vmove(0); #ifdef SIGWINCH sigprocmask(SIG_SETMASK, &oset, NULL); #endif }
/* * 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). */ void vchange(int 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 == EOF) { 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 == EOF && *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 == EOF && 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 != EOF) 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 != EOF && 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 != EOF) { 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 += skipleft(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 { int c, d, n; vcursbef(wcursor); d = skipleft(linebuf, wcursor); nextc(c, &wcursor[d], n); if (colsc(c) > 1) putchar(' '); 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); }
void vundo ( int show /* if true update the screen */ ) { register int cnt; register line *addr; register char *cp; char *temp = smalloc(LBSIZE); bool savenote; int (*OO)(int); 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, TLINES); 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; CP(temp, vutmp); CP(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;) { int c, n; nextc(c, cp, n); cp += n; putchar(c); } Outchar = OO; hold = oldhold; endim(); physdc(cindent(), cindent() + doomed); doomed = 0; vdirty(vcline, 1); vsyncCL(); if (cursor > linebuf && cursor >= strend(linebuf)) cursor += skipleft(linebuf, cursor); vfixcurs(); break; case VNONE: beep(); break; } free(temp); }
/* * Decode an operator/operand type command. * Eventually we switch to an operator subroutine in ex_vops.c. * The work here is setting up a function variable to point * to the routine we want, and manipulation of the variables * wcursor and wdot, which mark the other end of the affected * area. If wdot is zero, then the current line is the other end, * and if wcursor is zero, then the first non-blank location of the * other line is implied. */ void operate(register int c, register int cnt) { register int i = 0; void (*moveop)(int), (*deleteop)(int); void (*opf)(int); bool subop = 0; char *oglobp, *ocurs; register line *addr; line *odot; static int lastFKND, lastFCHR; short d; cell nullcell[1], qmarkcell[2], slashcell[2]; CLOBBGRD(opf); CLOBBGRD(d); qmarkcell[0] = '?'; slashcell[0] = '/'; nullcell[0] = qmarkcell[1] = slashcell[1] = 0; moveop = vmove, deleteop = vdelete; wcursor = cursor; wdot = NOLINE; notecnt = 0; dir = 1; switch (c) { /* * d delete operator. */ case 'd': moveop = vdelete; deleteop = (void (*)(int))beep; break; /* * s substitute characters, like c\040, i.e. change space. */ case 's': ungetkey(' '); subop++; /* fall into ... */ /* * c Change operator. */ case 'c': if (c == 'c' && workcmd[0] == 'C' || workcmd[0] == 'S') subop++; moveop = vchange; deleteop = (void (*)(int))beep; break; /* * ! Filter through a UNIX command. */ case '!': moveop = vfilter; deleteop = (void (*)(int))beep; break; /* * y Yank operator. Place specified text so that it * can be put back with p/P. Also yanks to named buffers. */ case 'y': moveop = vyankit; deleteop = (void (*)(int))beep; break; /* * = Reformat operator (for LISP). */ #ifdef LISPCODE case '=': forbid(!value(LISP)); /* fall into ... */ #endif /* * > Right shift operator. * < Left shift operator. */ case '<': case '>': moveop = vshftop; deleteop = (void (*)(int))beep; break; /* * r Replace character under cursor with single following * character. */ case 'r': vmacchng(1); vrep(cnt); return; default: goto nocount; } vmacchng(1); /* * Had an operator, so accept another count. * Multiply counts together. */ if (xisdigit(peekkey()) && peekkey() != '0') { cnt *= vgetcnt(); Xcnt = cnt; forbid (cnt <= 0); } /* * Get next character, mapping it and saving as * part of command for repeat. */ c = map(getesc(),arrows); if (c == 0) return; if (!subop) *lastcp++ = c; nocount: opf = moveop; switch (c) { /* * b Back up a word. * B Back up a word, liberal definition. */ case 'b': case 'B': dir = -1; /* fall into ... */ /* * w Forward a word. * W Forward a word, liberal definition. */ case 'W': case 'w': wdkind = c & ' '; forbid(llfind(2, cnt, opf, 0) < 0); vmoving = 0; break; /* * E to end of following blank/nonblank word */ case 'E': wdkind = 0; goto ein; /* * e To end of following word. */ case 'e': wdkind = 1; ein: forbid(llfind(3, cnt - 1, opf, 0) < 0); vmoving = 0; break; /* * ( Back an s-expression. */ case '(': dir = -1; /* fall into... */ /* * ) Forward an s-expression. */ case ')': forbid(llfind(0, cnt, opf, (line *) 0) < 0); markDOT(); break; /* * { Back an s-expression, but don't stop on atoms. * In text mode, a paragraph. For C, a balanced set * of {}'s. */ case '{': dir = -1; /* fall into... */ /* * } Forward an s-expression, but don't stop on atoms. * In text mode, back paragraph. For C, back a balanced * set of {}'s. */ case '}': forbid(llfind(1, cnt, opf, (line *) 0) < 0); markDOT(); break; /* * % To matching () or {}. If not at ( or { scan for * first such after cursor on this line. */ case '%': vsave(); i = lmatchp((line *) 0); #ifdef TRACE if (trace) fprintf(trace, "after lmatchp in %, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol)); #endif getDOT(); forbid(!i); if (opf != vmove) if (dir > 0) wcursor += skipright(linebuf, wcursor); else cursor += skipright(linebuf, cursor); else markDOT(); vmoving = 0; break; /* * [ Back to beginning of defun, i.e. an ( in column 1. * For text, back to a section macro. * For C, back to a { in column 1 (~~ beg of function.) */ case '[': dir = -1; /* fall into ... */ /* * ] Forward to next defun, i.e. a ( in column 1. * For text, forward section. * For C, forward to a } in column 1 (if delete or such) * or if a move to a { in column 1. */ case ']': if (!vglobp) forbid(getkey() != c); forbid (Xhadcnt); vsave(); i = lbrack(c, opf); getDOT(); forbid(!i); markDOT(); if (ospeed > B300) hold |= HOLDWIG; break; /* * , Invert last find with f F t or T, like inverse * of ;. */ case ',': forbid (lastFKND == 0); c = xisupper(lastFKND&TRIM) ? xtolower(lastFKND&TRIM) : xtoupper(lastFKND&TRIM); i = lastFCHR; if (vglobp == 0) vglobp = nullcell; subop++; goto nocount; /* * 0 To beginning of real line. */ case '0': wcursor = linebuf; vmoving = 0; break; /* * ; Repeat last find with f F t or T. */ case ';': forbid (lastFKND == 0); c = lastFKND; i = lastFCHR; subop++; goto nocount; /* * F Find single character before cursor in current line. * T Like F, but stops before character. */ case 'F': /* inverted find */ case 'T': dir = -1; /* fall into ... */ /* * f Find single character following cursor in current line. * t Like f, but stope before character. */ case 'f': /* find */ case 't': if (!subop) { i = getesc(); if (i == 0) return; *lastcp++ = i; } if (vglobp == 0) lastFKND = c, lastFCHR = i; for (; cnt > 0; cnt--) forbid (find(i) == 0); vmoving = 0; switch (c) { case 'T': wcursor += skipright(linebuf, wcursor); break; case 't': wcursor += skipleft(linebuf, wcursor); case 'f': fixup: if (moveop != vmove) wcursor += skipright(linebuf, wcursor); break; } break; /* * | Find specified print column in current line. */ case '|': if (Pline == numbline) cnt += 8; vmovcol = cnt; vmoving = 1; wcursor = vfindcol(cnt); break; /* * ^ To beginning of non-white space on line. */ case '^': wcursor = vskipwh(linebuf); vmoving = 0; break; /* * $ To end of line. */ case '$': if (opf == vmove) { vmoving = 1; vmovcol = 20000; } else vmoving = 0; if (cnt > 1) { if (opf == vmove) { wcursor = 0; cnt--; } else wcursor = linebuf; /* This is wrong at EOF */ wdot = dot + cnt; break; } if (linebuf[0]) { wcursor = strend(linebuf) - 1; goto fixup; } wcursor = linebuf; break; /* * h Back a character. * ^H Back a character. */ case 'h': case CTRL('h'): dir = -1; /* fall into ... */ /* * space Forward a character. */ case 'l': case ' ': forbid (margin() || opf == vmove && edge()); while (cnt > 0 && !margin()) { wcursor += dir>0 ? skipright(linebuf, wcursor) : skipleft(linebuf, wcursor); cnt--; } if (margin() && opf == vmove || wcursor < linebuf) wcursor -= dir; vmoving = 0; break; /* * D Delete to end of line, short for d$. */ case 'D': cnt = INF; goto deleteit; /* * X Delete character before cursor. */ case 'X': dir = -1; /* fall into ... */ deleteit: /* * x Delete character at cursor, leaving cursor where it is. */ case 'x': if (margin()) goto errlab; vmacchng(1); while (cnt > 0 && !margin()) { wcursor += dir > 0 ? skipright(linebuf, wcursor) : skipleft(linebuf, wcursor); cnt--; } opf = deleteop; vmoving = 0; break; default: /* * Stuttered operators are equivalent to the operator on * a line, thus turn dd into d_. */ if (opf == vmove || c != workcmd[0]) { errlab: beep(); vmacp = 0; return; } /* fall into ... */ /* * _ Target for a line or group of lines. * Stuttering is more convenient; this is mostly * for aesthetics. */ case '_': wdot = dot + cnt - 1; vmoving = 0; wcursor = 0; break; /* * H To first, home line on screen. * Count is for count'th line rather than first. */ case 'H': wdot = (dot - vcline) + cnt - 1; if (opf == vmove) markit(wdot); vmoving = 0; wcursor = 0; break; /* * - Backwards lines, to first non-white character. */ case '-': wdot = dot - cnt; vmoving = 0; wcursor = 0; break; /* * ^P To previous line same column. Ridiculous on the * console of the VAX since it puts console in LSI mode. */ case 'k': case CTRL('p'): wdot = dot - cnt; if (vmoving == 0) vmoving = 1, vmovcol = lcolumn(cursor); wcursor = 0; break; /* * L To last line on screen, or count'th line from the * bottom. */ case 'L': wdot = dot + vcnt - vcline - cnt; if (opf == vmove) markit(wdot); vmoving = 0; wcursor = 0; break; /* * M To the middle of the screen. */ case 'M': wdot = dot + ((vcnt + 1) / 2) - vcline - 1; if (opf == vmove) markit(wdot); vmoving = 0; wcursor = 0; break; /* * + Forward line, to first non-white. * * CR Convenient synonym for +. */ case '+': case CR: wdot = dot + cnt; vmoving = 0; wcursor = 0; break; /* * ^N To next line, same column if possible. * * LF Linefeed is a convenient synonym for ^N. */ case CTRL('n'): case 'j': case NL: wdot = dot + cnt; if (vmoving == 0) vmoving = 1, vmovcol = lcolumn(cursor); wcursor = 0; break; /* * n Search to next match of current pattern. */ case 'n': vglobp = vscandir; c = *vglobp++; goto nocount; /* * N Like n but in reverse direction. */ case 'N': vglobp = vscandir[0] == '/' ? qmarkcell : slashcell; c = *vglobp++; goto nocount; /* * ' Return to line specified by following mark, * first white position on line. * * ` Return to marked line at remembered column. */ case '\'': case '`': d = c; c = getesc(); if (c == 0) return; c = markreg(c); forbid (c == 0); wdot = getmark(c); forbid (wdot == NOLINE); forbid (Xhadcnt); vmoving = 0; wcursor = d == '`' ? ncols[c - 'a'] : 0; if (opf == vmove && (wdot != dot || (d == '`' && wcursor != cursor))) markDOT(); if (wcursor) { vsave(); getline(*wdot); if (wcursor > strend(linebuf)) wcursor = 0; getDOT(); } if (ospeed > B300) hold |= HOLDWIG; break; /* * G Goto count'th line, or last line if no count * given. */ case 'G': if (!Xhadcnt) cnt = lineDOL(); wdot = zero + cnt; forbid (wdot < one || wdot > dol); if (opf == vmove) markit(wdot); vmoving = 0; wcursor = 0; break; /* * / Scan forward for following re. * ? Scan backward for following re. */ case '/': case '?': forbid (Xhadcnt); vsave(); ocurs = cursor; odot = dot; wcursor = 0; if (readecho(c)) return; if (!vglobp) vscandir[0] = genbuf[0]; oglobp = globp; CP(vutmp, genbuf); globp = vutmp; d = peekc; fromsemi: ungetchar(0); fixech(); CATCH addr = address(cursor); ONERR slerr: globp = oglobp; dot = odot; cursor = ocurs; ungetchar(d); splitw = 0; vclean(); vjumpto(dot, ocurs, 0); return; ENDCATCH if (globp == 0) globp = ""; else if (peekc) --globp; if (*globp == ';') { /* /foo/;/bar/ */ globp++; dot = addr; cursor = loc1; goto fromsemi; } dot = odot; ungetchar(d); c = 0; if (*globp == 'z') globp++, c = '\n'; if (any(*globp, "^+-.")) c = *globp++; i = 0; while (xisdigit(*globp&TRIM)) i = i * 10 + *globp++ - '0'; if (any(*globp, "^+-.")) c = *globp++; if (*globp) { /* random junk after the pattern */ beep(); goto slerr; } globp = oglobp; splitw = 0; vmoving = 0; wcursor = loc1; if (i != 0) vsetsiz(i); if (opf == vmove) { if (state == ONEOPEN || state == HARDOPEN) outline = destline = WBOT; if (addr != dot || loc1 != cursor) markDOT(); if (loc1 > linebuf && *loc1 == 0) loc1--; if (c) vjumpto(addr, loc1, c); else { vmoving = 0; if (loc1) { vmoving++; vmovcol = column(loc1); } getDOT(); if (state == CRTOPEN && addr != dot) vup1(); vupdown(addr - dot, NOSTR); } return; } lastcp[-1] = 'n'; getDOT(); wdot = addr; break; } /* * Apply. */ if (vreg && wdot == 0) wdot = dot; (*opf)(c); wdot = NOLINE; }
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); } }