static int confirmed(line *a) { register int c, ch; if (xflag == 0) return (1); pofix(); pline(lineno(a)); if (inopen) ex_putchar('\n' | RE_QUOTE); c = column(loc1 - 1); ugo(c - 1 + (inopen ? 1 : 0), ' '); ugo(column(loc2 - 1) - c, '^'); flush(); ch = c = getkey(); again: if (c == '\r') c = '\n'; if (inopen) ex_putchar(c), flush(); if (c != '\n' && c != EOF) { c = getkey(); goto again; } noteinp(); return (ch == 'y'); }
set() { register char *cp; register struct option *op; register int c; bool no; extern short ospeed; setnoaddr(); if (skipend()) { if (peekchar() != EOF) ignchar(); propts(); return; } do { cp = optname; do { if (cp < &optname[ONMSZ - 2]) *cp++ = getchar(); } while (isalnum(peekchar())); *cp = 0; cp = optname; if (eq("all", cp)) { if (inopen) pofix(); prall(); goto next; } no = 0; if (cp[0] == 'n' && cp[1] == 'o') { cp += 2; no++; } /* Implement w300, w1200, and w9600 specially */ if (eq(cp, "w300")) { if (ospeed >= B1200) { dontset: ignore(getchar()); /* = */ ignore(getnum()); /* value */ continue; } cp = "window"; } else if (eq(cp, "w1200")) { if (ospeed < B1200 || ospeed >= B2400) goto dontset; cp = "window"; } else if (eq(cp, "w9600")) { if (ospeed < B2400) goto dontset; cp = "window"; } for (op = options; op < &options[NOPTS]; op++) if (eq(op->oname, cp) || op->oabbrev && eq(op->oabbrev, cp)) break; if (op->oname == 0) serror("%s: No such option@- 'set all' gives all option values", cp); c = skipwh(); if (peekchar() == '?') { ignchar(); printone: propt(op); noonl(); goto next; } if (op->otype == ONOFF) { op->ovalue = 1 - no; if (op == &options[PROMPT]) oprompt = 1 - no; goto next; } if (no) serror("Option %s is not a toggle", op->oname); if (c != 0 || setend()) goto printone; if (getchar() != '=') serror("Missing =@in assignment to option %s", op->oname); switch (op->otype) { case NUMERIC: if (!isdigit(peekchar())) error("Digits required@after ="); op->ovalue = getnum(); if (value(TABSTOP) <= 0) value(TABSTOP) = TABS; if (op == &options[WINDOW]) { if (value(WINDOW) >= LINES) value(WINDOW) = LINES-1; vsetsiz(value(WINDOW)); } break; case STRING: case OTERM: cp = optname; while (!setend()) { if (cp >= &optname[ONMSZ]) error("String too long@in option assignment"); /* adb change: allow whitepace in strings */ if( (*cp = getchar()) == '\\') if( peekchar() != EOF) *cp = getchar(); cp++; } *cp = 0; if (op->otype == OTERM) { /* * At first glance it seems like we shouldn't care if the terminal type * is changed inside visual mode, as long as we assume the screen is * a mess and redraw it. However, it's a much harder problem than that. * If you happen to change from 1 crt to another that both have the same * size screen, it's OK. But if the screen size if different, the stuff * that gets initialized in vop() will be wrong. This could be overcome * by redoing the initialization, e.g. making the first 90% of vop into * a subroutine. However, the most useful case is where you forgot to do * a setenv before you went into the editor and it thinks you're on a dumb * terminal. Ex treats this like hardcopy and goes into HARDOPEN mode. * This loses because the first part of vop calls oop in this case. * The problem is so hard I gave up. I'm not saying it can't be done, * but I am saying it probably isn't worth the effort. */ if (inopen) error("Can't change type of terminal from within open/visual"); setterm(optname); } else { CP(op->osvalue, optname); op->odefault = 1; } break; } next: flush(); } while (!skipend()); eol(); }
vmain() { register int c, cnt, i; char esave[TUBECOLS]; char *oglobp; char d; line *addr; int ind, nlput; int shouldpo = 0; int onumber, olist, (*OPline)(), (*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 (isdigit(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' || !isalpha(c) && !isdigit(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 ((isdigit(c) && c!='0') || c == '"') { ungetkey(c); goto looptop; } if (!value(REMAP)) { c = op; break; } if (++maphopcnt > 256) error("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. */ 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 (isupper(c)) c = tolower(c); forbid(!islower(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 (isdigit(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 = 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 = 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--; 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 '~': { char mbuf[4]; setLAST(); mbuf[0] = 'r'; mbuf[1] = *cursor; mbuf[2] = cursor[1]==0 ? 0 : ' '; mbuf[3] = 0; if (isalpha(mbuf[1])) mbuf[1] ^= ' '; /* toggle the case */ macpush(mbuf, 1); } 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) putchar(*cursor); 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(); /* 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("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++; } 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; strcpy(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; #ifndef CBREAK vcook(); #endif commands(1, 1); if (dot == zero && dol > zero) dot = one; #ifndef CBREAK vraw(); #endif ONERR #ifndef CBREAK vraw(); #endif copy(esave, vtube[WECHO], TUBECOLS); ENDCATCH fixol(); Pline = OPline; Putchar = OPutchar; ungetchar(d); 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(); sync(); } 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); } /* * 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, LINES); 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); } }
/* * Main loop for command mode command decoding. * A few commands are executed here, but main function * is to strip command addresses, do a little address oriented * processing and call command routines to do the real work. */ void commands(bool noprompt, bool exitoneof) { register line *addr; register int c; register int lchng; int given; int seensemi; int cnt; bool hadpr; resetflav(); nochng(); for (;;) { /* * If dot at last command * ended up at zero, advance to one if there is a such. */ if (dot <= zero) { dot = zero; if (dol > zero) dot = one; } shudclob = 0; /* * If autoprint or trailing print flags, * print the line at the specified offset * before the next command. */ if (pflag || (lchng != chng && value(AUTOPRINT) && !inglobal && !inopen && endline)) { pflag = 0; nochng(); if (dol != zero) { addr1 = addr2 = dot + poffset; if (addr1 < one || addr1 > dol) error("Offset out-of-bounds|Offset after command too large"); setdot1(); goto print; } } nochng(); /* * Print prompt if appropriate. * If not in global flush output first to prevent * going into pfast mode unreasonably. */ if (inglobal == 0) { flush(); if (!hush && value(PROMPT) && !globp && !noprompt && endline) { ex_putchar(':'); hadpr = 1; } TSYNC(); } /* * Gobble up the address. * Degenerate addresses yield ".". */ addr2 = 0; given = seensemi = 0; do { addr1 = addr2; addr = address(0); c = getcd(); if (addr == 0) { if (c == ',') addr = dot; else if (addr1 != 0) { addr2 = dot; break; } else break; } addr2 = addr; given++; if (c == ';') { c = ','; dot = addr; seensemi = 1; } } while (c == ','); if (c == '%') { /* %: same as 1,$ */ addr1 = one; addr2 = dol; given = 2; c = ex_getchar(); } if (addr1 == 0) addr1 = addr2; if (c == ':') c = ex_getchar(); /* * Set command name for special character commands. */ tailspec(c); /* * If called via : escape from open or visual, limit * the set of available commands here to save work below. */ if (inopen) { if (c=='\n' || c=='\r' || c==CTRL('d') || c==EOF) { if (addr2) dot = addr2; if (c == EOF) return; continue; } if (any(c, "o")) notinvis: tailprim(Command, 1, 1); } switch (c) { case 'a': switch(peekchar()) { case 'b': /* abbreviate */ tail("abbreviate"); setnoaddr(); mapcmd(0, 1); anyabbrs = 1; continue; case 'r': /* args */ tail("args"); setnoaddr(); eol(); pargs(); continue; } /* append */ if (inopen) goto notinvis; tail("append"); setdot(); aiflag = exclam(); ex_newline(); vmacchng(0); deletenone(); setin(addr2); inappend = 1; ignore(append(gettty, addr2)); inappend = 0; nochng(); continue; case 'c': switch (peekchar()) { /* copy */ case 'o': tail("copy"); vmacchng(0); move(); continue; #ifdef CHDIR /* cd */ case 'd': tail("cd"); goto changdir; /* chdir */ case 'h': ignchar(); if (peekchar() == 'd') { register char *p; tail2of("chdir"); changdir: if (savedfile[0] == '/' || !value(WARN)) ignore(exclam()); else ignore(quickly()); if (skipend()) { p = getenv("HOME"); if (p == NULL) error("Home directory unknown"); } else getone(), p = file; eol(); if (chdir(p) < 0) filioerr(p); if (savedfile[0] != '/') edited = 0; continue; } if (inopen) tailprim("change", 2, 1); tail2of("change"); break; #endif default: if (inopen) goto notinvis; tail("change"); break; } /* change */ aiflag = exclam(); setCNL(); vmacchng(0); setin(addr1); delete(0); inappend = 1; ignore(append(gettty, addr1 - 1)); inappend = 0; nochng(); continue; /* delete */ case 'd': /* * Caution: dp and dl have special meaning already. */ tail("delete"); c = cmdreg(); setCNL(); vmacchng(0); if (c) YANKreg(c); delete(0); appendnone(); continue; /* edit */ /* ex */ case 'e': tail(peekchar() == 'x' ? "ex" : "edit"); editcmd: if (!exclam() && chng) c = 'E'; filename(c); if (c == 'E') { ungetchar(lastchar()); ignore(quickly()); } setnoaddr(); doecmd: init(); addr2 = zero; laste++; ex_sync(); rop(c); nochng(); continue; /* file */ case 'f': tail("file"); setnoaddr(); filename(c); noonl(); /* synctmp(); */ continue; /* global */ case 'g': tail("global"); global(!exclam()); nochng(); continue; /* insert */ case 'i': if (inopen) goto notinvis; tail("insert"); setdot(); nonzero(); aiflag = exclam(); ex_newline(); vmacchng(0); deletenone(); setin(addr2); inappend = 1; ignore(append(gettty, addr2 - 1)); inappend = 0; if (dot == zero && dol > zero) dot = one; nochng(); continue; /* join */ case 'j': tail("join"); c = exclam(); setcount(); nonzero(); ex_newline(); vmacchng(0); if (given < 2 && addr2 != dol) addr2++; join(c); continue; /* k */ case 'k': casek: pastwh(); c = ex_getchar(); if (endcmd(c)) serror("Mark what?|%s requires following letter", Command); ex_newline(); if (!islower(c)) error("Bad mark|Mark must specify a letter"); setdot(); nonzero(); names[c - 'a'] = *addr2 &~ 01; anymarks = 1; continue; /* list */ case 'l': tail("list"); setCNL(); ignorf(setlist(1)); pflag = 0; goto print; case 'm': if (peekchar() == 'a') { ignchar(); if (peekchar() == 'p') { /* map */ tail2of("map"); setnoaddr(); mapcmd(0, 0); continue; } /* mark */ tail2of("mark"); goto casek; } /* move */ tail("move"); vmacchng(0); move(); continue; case 'n': if (peekchar() == 'u') { tail("number"); goto numberit; } /* next */ tail("next"); setnoaddr(); ckaw(); ignore(quickly()); if (getargs()) makargs(); next(); c = 'e'; filename(c); goto doecmd; /* open */ case 'o': tail("open"); oop(); pflag = 0; nochng(); continue; case 'p': case 'P': switch (peekchar()) { /* put */ case 'u': tail("put"); setdot(); c = cmdreg(); eol(); vmacchng(0); if (c) putreg(c); else put(); continue; case 'r': ignchar(); if (peekchar() == 'e') { /* preserve */ tail2of("preserve"); eol(); if (preserve() == 0) error("Preserve failed!"); else error("File preserved."); } tail2of("print"); break; default: tail("print"); break; } /* print */ setCNL(); pflag = 0; print: nonzero(); if (CL && span() > EX_LINES) { flush1(); vclear(); } plines(addr1, addr2, 1); continue; /* quit */ case 'q': tail("quit"); setnoaddr(); c = quickly(); eol(); if (!c) quit: nomore(); if (inopen) { vgoto(WECHO, 0); if (!ateopr()) vnfl(); else { tostop(); } flush(); setty(normf); } cleanup(1); ex_exit(0); case 'r': if (peekchar() == 'e') { ignchar(); switch (peekchar()) { /* rewind */ case 'w': tail2of("rewind"); setnoaddr(); if (!exclam()) { ckaw(); if (chng && dol > zero) error("No write@since last chage (:rewind! overrides)"); } eol(); erewind(); next(); c = 'e'; ungetchar(lastchar()); filename(c); goto doecmd; /* recover */ case 'c': tail2of("recover"); setnoaddr(); c = 'e'; if (!exclam() && chng) c = 'E'; filename(c); if (c == 'E') { ungetchar(lastchar()); ignore(quickly()); } init(); addr2 = zero; laste++; ex_sync(); recover(); rop2(); revocer(); if (status == 0) rop3(c); if (dol != zero) change(); nochng(); continue; } tail2of("read"); } else tail("read"); /* read */ if (savedfile[0] == 0 && dol == zero) c = 'e'; pastwh(); vmacchng(0); if (peekchar() == '!') { setdot(); ignchar(); unix0(0); filter(0); continue; } filename(c); rop(c); nochng(); if (inopen && endline && addr1 > zero && addr1 < dol) dot = addr1 + 1; continue; case 's': switch (peekchar()) { /* * Caution: 2nd char cannot be c, g, or r * because these have meaning to substitute. */ /* set */ case 'e': tail("set"); setnoaddr(); set(); continue; /* shell */ case 'h': tail("shell"); setNAEOL(); vnfl(); putpad(TE); flush(); unixwt(1, unixex("-i", (char *) 0, 0, 0)); vcontin(0); continue; /* source */ case 'o': #ifdef notdef if (inopen) goto notinvis; #endif tail("source"); setnoaddr(); getone(); eol(); source(file, 0); continue; #ifdef SIGTSTP /* stop, suspend */ case 't': tail("stop"); goto suspend; case 'u': tail("suspend"); suspend: if (!dosusp) error("Old tty driver|Not using new tty driver/shell"); c = exclam(); eol(); if (!c) ckaw(); onsusp(0); continue; #endif } /* fall into ... */ /* & */ /* ~ */ /* substitute */ case '&': case '~': Command = "substitute"; if (c == 's') tail(Command); vmacchng(0); if (!substitute(c)) pflag = 0; continue; /* t */ case 't': if (peekchar() == 'a') { tail("tag"); tagfind(exclam()); if (!inopen) lchng = chng - 1; else nochng(); continue; } tail("t"); vmacchng(0); move(); continue; case 'u': if (peekchar() == 'n') { ignchar(); switch(peekchar()) { /* unmap */ case 'm': tail2of("unmap"); setnoaddr(); mapcmd(1, 0); continue; /* unabbreviate */ case 'a': tail2of("unabbreviate"); setnoaddr(); mapcmd(1, 1); anyabbrs = 1; continue; } /* undo */ tail2of("undo"); } else tail("undo"); setnoaddr(); markDOT(); c = exclam(); ex_newline(); undo(c); continue; case 'v': switch (peekchar()) { case 'e': /* version */ tail("version"); setNAEOL(); ex_printf("@(#) Version 3.6, 11/3/80" " (4.0BSD). git " "160803 14:24" +5); noonl(); continue; /* visual */ case 'i': tail("visual"); if (inopen) { c = 'e'; goto editcmd; } vop(); pflag = 0; nochng(); continue; } /* v */ tail("v"); global(0); nochng(); continue; /* write */ case 'w': c = peekchar(); tail(c == 'q' ? "wq" : "write"); wq: if (skipwh() && peekchar() == '!') { pofix(); ignchar(); setall(); unix0(0); filter(1); } else { setall(); wop(1); nochng(); } if (c == 'q') goto quit; continue; /* xit */ case 'x': tail("xit"); if (!chng) goto quit; c = 'q'; goto wq; /* yank */ case 'y': tail("yank"); c = cmdreg(); setcount(); eol(); vmacchng(0); if (c) YANKreg(c); else yank(); continue; /* z */ case 'z': zop(0); pflag = 0; continue; /* * */ /* @ */ case '*': case '@': c = ex_getchar(); if (c=='\n' || c=='\r') ungetchar(c); if (any(c, "@*\n\r")) c = lastmac; if (isupper(c)) c = tolower(c); if (!islower(c)) error("Bad register"); ex_newline(); setdot(); cmdmac(c); continue; /* | */ case '|': endline = 0; goto caseline; /* \n */ case '\n': endline = 1; caseline: notempty(); if (addr2 == 0) { if (UP != NOSTR && c == '\n' && !inglobal) c = CTRL('k'); if (inglobal) addr1 = addr2 = dot; else { if (dot == dol) error("At EOF|At end-of-file"); addr1 = addr2 = dot + 1; } } setdot(); nonzero(); if (seensemi) addr1 = addr2; ex_getline(*addr1); if (c == CTRL('k')) { flush1(); destline--; if (hadpr) shudclob = 1; } plines(addr1, addr2, 1); continue; /* " */ case '"': comment(); continue; /* # */ case '#': numberit: setCNL(); ignorf(setnumb(1)); pflag = 0; goto print; /* = */ case '=': ex_newline(); setall(); if (inglobal == 2) pofix(); ex_printf("%d", lineno(addr2)); noonl(); continue; /* ! */ case '!': if (addr2 != 0) { vmacchng(0); unix0(0); setdot(); filter(2); } else { unix0(1); pofix(); putpad(TE); flush(); unixwt(1, unixex("-c", uxb, 0, 0)); vclrech(1); /* vcontin(0); */ nochng(); } continue; /* < */ /* > */ case '<': case '>': for (cnt = 1; peekchar() == c; cnt++) ignchar(); setCNL(); vmacchng(0); shift(c, cnt); continue; /* ^D */ /* EOF */ case CTRL('d'): case EOF: if (exitoneof) { if (addr2 != 0) dot = addr2; return; } if (!isatty(0)) { if (intty) /* * Chtty sys call at UCB may cause a * input which was a tty to suddenly be * turned into /dev/null. */ onhup(0); return; } if (addr2 != 0) { setlastchar('\n'); putnl(); } if (dol == zero) { if (addr2 == 0) putnl(); notempty(); } ungetchar(EOF); zop(hadpr); continue; default: if (!isalpha(c)) break; ungetchar(c); tailprim("", 0, 0); } ierror("What?|Unknown command character '%c'", c); } }
/* * Put the character c on the screen at the current cursor position. * This routine handles wraparound and scrolling and understands not * to roll when splitw is set, i.e. we are working in the echo area. * There is a bunch of hacking here dealing with the difference between * QUOTE, QUOTE|' ', and ' ' for CONCEPT-100 like terminals, and also * code to deal with terminals which overstrike, including CRT's where * you can erase overstrikes with some work. CRT's which do underlining * implicitly which has to be erased (like CONCEPTS) are also handled. */ int vputchar(register int c) { register cell *tp; register int d, m, n; #ifndef BIT8 c &= (QUOTE|TRIM); #endif #ifdef TRACE if (trace) tracec(c); #endif /* Fix problem of >79 chars on echo line. */ if (destcol >= WCOLS-1 && splitw && destline == WECHO) pofix(); #ifdef MB if (mb_cur_max > 1) { if (c == MULTICOL) return c; /* * If a multicolumn character extends beyond the screen * width, it must be put on the next line. A tilde is * printed as an indicator but must disappear when the * text is moved at a later time. */ if (c == ('~'|INVBIT|QUOTE)) c = '~'|INVBIT; else if (c == ('~'|INVBIT)) return c; else if (destcol < WCOLS && destcol + colsc(c==QUOTE ? ' ' : c&TRIM&~MULTICOL) - 1 >= WCOLS) vputchar('~'|INVBIT|QUOTE); } #endif /* MB */ if (destcol >= WCOLS) { destline += destcol / WCOLS; destcol %= WCOLS; } if (destline > WBOT && (!splitw || destline > WECHO)) vrollup(destline); tp = vtube[destline] + destcol; if (c == QUOTE) { if (insmode) { /* * When in insert mode, tabs have to expand * to real, printed blanks. */ c = ' ' | QUOTE; goto def; } if (*tp == 0) { /* * A ``space''. */ if ((hold & HOLDPUPD) == 0) *tp = QUOTE; destcol++; return c; } /* * A ``space'' ontop of a part of a tab. */ if (*tp & QUOTE) { destcol++; return c; } c = ' ' | QUOTE; goto def; } #ifdef notdef #ifdef BIT8 if (c == ' ' | QUOTE) { c = ' '; goto def; } #endif #endif switch (c) { case '\t': vgotab(); return c; case ' ': /* * We can get away without printing a space in a number * of cases, but not always. We get away with doing nothing * if we are not in insert mode, and not on a CONCEPT-100 * like terminal, and either not in hardcopy open or in hardcopy * open on a terminal with no overstriking, provided, * in all cases, that nothing has ever been displayed * at this position. Ugh. */ if (!insmode && !IN && (state != HARDOPEN || OS) && (*tp"E)) { *tp = ' '; destcol++; return c; } goto def; def: default: d = *tp & TRIM; /* * Now get away with doing nothing if the characters * are the same, provided we are not in insert mode * and if we are in hardopen, that the terminal has overstrike. */ if ((d & ~MULTICOL) == (c & TRIM & ~MULTICOL) && !insmode && (state != HARDOPEN || OS) && c != MULTICOL) { n = colsc(d); for (m = 1; m < n; m++) if ((tp[m] & (MULTICOL|TRIM)) != MULTICOL) break; if (m == n) { if ((hold & HOLDPUPD) == 0) *tp = c | (n > 1 ? MULTICOL : 0); destcol += n; return c; } } /* * Backwards looking optimization. * The low level cursor motion routines will use * a cursor motion right sequence to step 1 character * right. On, e.g., a DM3025A this is 2 characters * and printing is noticeably slower at 300 baud. * Since the low level routines are not allowed to use * spaces for positioning, we discover the common * case of a single space here and force a space * to be printed. */ if (destcol == outcol + 1 && tp[-1] == ' ' && outline == destline) { vputc(' '); outcol++; } /* * This is an inline expansion a call to vcsync() dictated * by high frequency in a profile. */ if (outcol != destcol || outline != destline) vgoto(destline, destcol); /* * Deal with terminals which have overstrike. * We handle erasing general overstrikes, erasing * underlines on terminals (such as CONCEPTS) which * do underlining correctly automatically (e.g. on nroff * output), and remembering, in hardcopy mode, * that we have overstruct something. */ if (!insmode && d && d != ' ' && d != (c & TRIM)) { if (EO && (OS || UL && (c == '_' || d == '_'))) { vputc(' '); outcol++, destcol++; back1(); } else rubble = 1; } /* * Unless we are just bashing characters around for * inner working of insert mode, update the display. */ if ((hold & HOLDPUPD) == 0) *tp = c; /* * In insert mode, put out the IC sequence, padded * based on the depth of the current line. * A terminal which had no real insert mode, rather * opening a character position at a time could do this. * Actually should use depth to end of current line * but this rarely matters. */ #ifdef notdef if (insmode) #else /* * It seems today's termcap writers consider this * an either-or situation; if both im and ic * are used vi puts out additional spaces. * * SVR4 ex does not include this change. If it hits * your terminal, change back to the old way and * mail me a description. * * GR July 2000 */ if (insmode && (!IM || !*IM)) #endif /* !notdef */ { n = colsc(c&TRIM); for (m = 0; m < n; m++) vputp(IC, DEPTH(vcline)); } vputc(c & TRIM); /* * In insert mode, IP is a post insert pad. */ if (insmode) vputp(IP, DEPTH(vcline)); destcol++, outcol++; /* * CONCEPT braindamage in early models: after a wraparound * the next newline is eaten. It's hungry so we just * feed it now rather than worrying about it. * Fixed to use return linefeed to work right * on vt100/tab132 as well as concept. */ if (XN && outcol % WCOLS == 0) { vputc('\r'); vputc('\n'); } } #ifdef MB if (mb_cur_max > 1 && (d = colsc(c&TRIM&~MULTICOL)) > 1) { if ((hold & HOLDPUPD) == 0) *tp |= MULTICOL; while (--d) { if ((hold & HOLDPUPD) == 0) *++tp = MULTICOL; destcol++; outcol++; } } #endif /* MB */ return c; }