Commit | Line | Data |
---|---|---|
299f2784 MH |
1 | /* Copyright (c) 1981 Regents of the University of California */ |
2 | static char *sccsid = "@(#)ex_vmain.c 7.1 %G%"; | |
2281ca6a MH |
3 | #include "ex.h" |
4 | #include "ex_tty.h" | |
5 | #include "ex_vis.h" | |
6 | ||
7 | /* | |
8 | * This is the main routine for visual. | |
9 | * We here decode the count and possible named buffer specification | |
10 | * preceding a command and interpret a few of the commands. | |
11 | * Commands which involve a target (i.e. an operator) are decoded | |
12 | * in the routine operate in ex_voperate.c. | |
13 | */ | |
14 | ||
15 | #define forbid(a) { if (a) goto fonfon; } | |
16 | ||
17 | vmain() | |
18 | { | |
19 | register int c, cnt, i; | |
20 | char esave[TUBECOLS]; | |
21 | char *oglobp; | |
22 | char d; | |
23 | line *addr; | |
887e3e0d MH |
24 | int ind, nlput; |
25 | int shouldpo = 0; | |
2281ca6a MH |
26 | int onumber, olist, (*OPline)(), (*OPutchar)(); |
27 | ||
d266c416 MH |
28 | vch_mac = VC_NOTINMAC; |
29 | ||
2281ca6a MH |
30 | /* |
31 | * If we started as a vi command (on the command line) | |
32 | * then go process initial commands (recover, next or tag). | |
33 | */ | |
34 | if (initev) { | |
35 | oglobp = globp; | |
36 | globp = initev; | |
37 | hadcnt = cnt = 0; | |
38 | i = tchng; | |
39 | addr = dot; | |
40 | goto doinit; | |
41 | } | |
42 | ||
43 | /* | |
44 | * NB: | |
45 | * | |
46 | * The current line is always in the line buffer linebuf, | |
47 | * and the cursor at the position cursor. You should do | |
48 | * a vsave() before moving off the line to make sure the disk | |
49 | * copy is updated if it has changed, and a getDOT() to get | |
50 | * the line back if you mung linebuf. The motion | |
51 | * routines in ex_vwind.c handle most of this. | |
52 | */ | |
53 | for (;;) { | |
54 | /* | |
55 | * Decode a visual command. | |
56 | * First sync the temp file if there has been a reasonable | |
57 | * amount of change. Clear state for decoding of next | |
58 | * command. | |
59 | */ | |
60 | TSYNC(); | |
61 | vglobp = 0; | |
62 | vreg = 0; | |
63 | hold = 0; | |
04379bab | 64 | seenprompt = 1; |
2281ca6a MH |
65 | wcursor = 0; |
66 | Xhadcnt = hadcnt = 0; | |
67 | Xcnt = cnt = 1; | |
68 | splitw = 0; | |
69 | if (i = holdupd) { | |
70 | if (state == VISUAL) | |
71 | ignore(peekkey()); | |
72 | holdupd = 0; | |
73 | /* | |
74 | if (LINE(0) < ZERO) { | |
75 | vclear(); | |
76 | vcnt = 0; | |
77 | i = 3; | |
78 | } | |
79 | */ | |
80 | if (state != VISUAL) { | |
81 | vcnt = 0; | |
82 | vsave(); | |
83 | vrepaint(cursor); | |
84 | } else if (i == 3) | |
85 | vredraw(WTOP); | |
86 | else | |
87 | vsync(WTOP); | |
88 | vfixcurs(); | |
89 | } | |
90 | ||
91 | /* | |
92 | * Gobble up counts and named buffer specifications. | |
93 | */ | |
94 | for (;;) { | |
95 | looptop: | |
96 | #ifdef MDEBUG | |
97 | if (trace) | |
98 | fprintf(trace, "pc=%c",peekkey()); | |
99 | #endif | |
100 | if (isdigit(peekkey()) && peekkey() != '0') { | |
101 | hadcnt = 1; | |
102 | cnt = vgetcnt(); | |
103 | forbid (cnt <= 0); | |
104 | } | |
105 | if (peekkey() != '"') | |
106 | break; | |
107 | ignore(getkey()), c = getkey(); | |
108 | /* | |
109 | * Buffer names be letters or digits. | |
110 | * But not '0' as that is the source of | |
111 | * an 'empty' named buffer spec in the routine | |
112 | * kshift (see ex_temp.c). | |
113 | */ | |
114 | forbid (c == '0' || !isalpha(c) && !isdigit(c)); | |
115 | vreg = c; | |
116 | } | |
117 | reread: | |
118 | /* | |
119 | * Come to reread from below after some macro expansions. | |
120 | * The call to map allows use of function key pads | |
121 | * by performing a terminal dependent mapping of inputs. | |
122 | */ | |
123 | #ifdef MDEBUG | |
124 | if (trace) | |
125 | fprintf(trace,"pcb=%c,",peekkey()); | |
126 | #endif | |
127 | op = getkey(); | |
d266c416 | 128 | maphopcnt = 0; |
2281ca6a MH |
129 | do { |
130 | /* | |
131 | * Keep mapping the char as long as it changes. | |
132 | * This allows for double mappings, e.g., q to #, | |
133 | * #1 to something else. | |
134 | */ | |
135 | c = op; | |
136 | op = map(c,arrows); | |
137 | #ifdef MDEBUG | |
138 | if (trace) | |
139 | fprintf(trace,"pca=%c,",c); | |
140 | #endif | |
141 | /* | |
142 | * Maybe the mapped to char is a count. If so, we have | |
143 | * to go back to the "for" to interpret it. Likewise | |
144 | * for a buffer name. | |
145 | */ | |
146 | if ((isdigit(c) && c!='0') || c == '"') { | |
147 | ungetkey(c); | |
148 | goto looptop; | |
149 | } | |
04379bab MH |
150 | if (!value(REMAP)) { |
151 | c = op; | |
887e3e0d | 152 | break; |
04379bab | 153 | } |
d266c416 MH |
154 | if (++maphopcnt > 256) |
155 | error("Infinite macro loop"); | |
2281ca6a MH |
156 | } while (c != op); |
157 | ||
158 | /* | |
159 | * Begin to build an image of this command for possible | |
160 | * later repeat in the buffer workcmd. It will be copied | |
161 | * to lastcmd by the routine setLAST | |
162 | * if/when completely specified. | |
163 | */ | |
164 | lastcp = workcmd; | |
165 | if (!vglobp) | |
166 | *lastcp++ = c; | |
167 | ||
168 | /* | |
169 | * First level command decode. | |
170 | */ | |
171 | switch (c) { | |
172 | ||
173 | /* | |
174 | * ^L Clear screen e.g. after transmission error. | |
175 | */ | |
2281ca6a MH |
176 | |
177 | /* | |
178 | * ^R Retype screen, getting rid of @ lines. | |
179 | * If in open, equivalent to ^L. | |
887e3e0d MH |
180 | * On terminals where the right arrow key sends |
181 | * ^L we make ^R act like ^L, since there is no | |
182 | * way to get ^L. These terminals (adm31, tvi) | |
183 | * are intelligent so ^R is useless. Soroc | |
184 | * will probably foul this up, but nobody has | |
185 | * one of them. | |
2281ca6a | 186 | */ |
887e3e0d | 187 | case CTRL(l): |
2281ca6a | 188 | case CTRL(r): |
887e3e0d MH |
189 | if (c == CTRL(l) || (KR && *KR==CTRL(l))) { |
190 | vclear(); | |
191 | vdirty(0, vcnt); | |
192 | } | |
2281ca6a MH |
193 | if (state != VISUAL) { |
194 | /* | |
195 | * Get a clean line, throw away the | |
196 | * memory of what is displayed now, | |
197 | * and move back onto the current line. | |
198 | */ | |
199 | vclean(); | |
200 | vcnt = 0; | |
201 | vmoveto(dot, cursor, 0); | |
202 | continue; | |
203 | } | |
204 | vredraw(WTOP); | |
205 | /* | |
206 | * Weird glitch -- when we enter visual | |
207 | * in a very small window we may end up with | |
208 | * no lines on the screen because the line | |
209 | * at the top is too long. This forces the screen | |
210 | * to be expanded to make room for it (after | |
211 | * we have printed @'s ick showing we goofed). | |
212 | */ | |
213 | if (vcnt == 0) | |
214 | vrepaint(cursor); | |
215 | vfixcurs(); | |
216 | continue; | |
217 | ||
218 | /* | |
219 | * $ Escape just cancels the current command | |
220 | * with a little feedback. | |
221 | */ | |
222 | case ESCAPE: | |
223 | beep(); | |
224 | continue; | |
225 | ||
226 | /* | |
227 | * @ Macros. Bring in the macro and put it | |
228 | * in vmacbuf, point vglobp there and punt. | |
229 | */ | |
230 | case '@': | |
887e3e0d MH |
231 | c = getesc(); |
232 | if (c == 0) | |
233 | continue; | |
2281ca6a MH |
234 | if (c == '@') |
235 | c = lastmac; | |
236 | if (isupper(c)) | |
237 | c = tolower(c); | |
238 | forbid(!islower(c)); | |
239 | lastmac = c; | |
240 | vsave(); | |
241 | CATCH | |
242 | char tmpbuf[BUFSIZ]; | |
243 | ||
244 | regbuf(c,tmpbuf,sizeof(vmacbuf)); | |
887e3e0d | 245 | macpush(tmpbuf, 1); |
2281ca6a MH |
246 | ONERR |
247 | lastmac = 0; | |
248 | splitw = 0; | |
249 | getDOT(); | |
250 | vrepaint(cursor); | |
251 | continue; | |
252 | ENDCATCH | |
253 | vmacp = vmacbuf; | |
254 | goto reread; | |
255 | ||
256 | /* | |
257 | * . Repeat the last (modifying) open/visual command. | |
258 | */ | |
259 | case '.': | |
260 | /* | |
261 | * Check that there was a last command, and | |
262 | * take its count and named buffer unless they | |
263 | * were given anew. Special case if last command | |
264 | * referenced a numeric named buffer -- increment | |
265 | * the number and go to a named buffer again. | |
266 | * This allows a sequence like "1pu.u.u... | |
267 | * to successively look for stuff in the kill chain | |
268 | * much as one does in EMACS with C-Y and M-Y. | |
269 | */ | |
270 | forbid (lastcmd[0] == 0); | |
271 | if (hadcnt) | |
272 | lastcnt = cnt; | |
273 | if (vreg) | |
274 | lastreg = vreg; | |
275 | else if (isdigit(lastreg) && lastreg < '9') | |
276 | lastreg++; | |
277 | vreg = lastreg; | |
278 | cnt = lastcnt; | |
279 | hadcnt = lasthad; | |
280 | vglobp = lastcmd; | |
281 | goto reread; | |
282 | ||
283 | /* | |
284 | * ^U Scroll up. A count sticks around for | |
285 | * future scrolls as the scroll amount. | |
286 | * Attempt to hold the indentation from the | |
287 | * top of the screen (in logical lines). | |
288 | * | |
289 | * BUG: A ^U near the bottom of the screen | |
290 | * on a dumb terminal (which can't roll back) | |
291 | * causes the screen to be cleared and then | |
292 | * redrawn almost as it was. In this case | |
293 | * one should simply move the cursor. | |
294 | */ | |
295 | case CTRL(u): | |
296 | if (hadcnt) | |
297 | vSCROLL = cnt; | |
298 | cnt = vSCROLL; | |
299 | if (state == VISUAL) | |
300 | ind = vcline, cnt += ind; | |
301 | else | |
302 | ind = 0; | |
303 | vmoving = 0; | |
304 | vup(cnt, ind, 1); | |
305 | vnline(NOSTR); | |
306 | continue; | |
307 | ||
308 | /* | |
309 | * ^D Scroll down. Like scroll up. | |
310 | */ | |
311 | case CTRL(d): | |
f0f2d980 MH |
312 | #ifdef TRACE |
313 | if (trace) | |
314 | fprintf(trace, "before vdown in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol)); | |
315 | #endif | |
2281ca6a MH |
316 | if (hadcnt) |
317 | vSCROLL = cnt; | |
318 | cnt = vSCROLL; | |
319 | if (state == VISUAL) | |
320 | ind = vcnt - vcline - 1, cnt += ind; | |
321 | else | |
322 | ind = 0; | |
323 | vmoving = 0; | |
324 | vdown(cnt, ind, 1); | |
f0f2d980 MH |
325 | #ifdef TRACE |
326 | if (trace) | |
327 | fprintf(trace, "before vnline in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol)); | |
328 | #endif | |
2281ca6a | 329 | vnline(NOSTR); |
f0f2d980 MH |
330 | #ifdef TRACE |
331 | if (trace) | |
332 | fprintf(trace, "after vnline in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol)); | |
333 | #endif | |
2281ca6a MH |
334 | continue; |
335 | ||
336 | /* | |
337 | * ^E Glitch the screen down (one) line. | |
338 | * Cursor left on same line in file. | |
339 | */ | |
340 | case CTRL(e): | |
341 | if (state != VISUAL) | |
342 | continue; | |
343 | if (!hadcnt) | |
344 | cnt = 1; | |
345 | /* Bottom line of file already on screen */ | |
346 | forbid(lineDOL()-lineDOT() <= vcnt-1-vcline); | |
347 | ind = vcnt - vcline - 1 + cnt; | |
348 | vdown(ind, ind, 1); | |
349 | vnline(cursor); | |
350 | continue; | |
351 | ||
352 | /* | |
353 | * ^Y Like ^E but up | |
354 | */ | |
355 | case CTRL(y): | |
356 | if (state != VISUAL) | |
357 | continue; | |
358 | if (!hadcnt) | |
359 | cnt = 1; | |
360 | forbid(lineDOT()-1<=vcline); /* line 1 already there */ | |
361 | ind = vcline + cnt; | |
362 | vup(ind, ind, 1); | |
363 | vnline(cursor); | |
364 | continue; | |
365 | ||
366 | ||
367 | /* | |
368 | * m Mark position in mark register given | |
369 | * by following letter. Return is | |
370 | * accomplished via ' or `; former | |
371 | * to beginning of line where mark | |
372 | * was set, latter to column where marked. | |
373 | */ | |
374 | case 'm': | |
375 | /* | |
376 | * Getesc is generally used when a character | |
377 | * is read as a latter part of a command | |
378 | * to allow one to hit rubout/escape to cancel | |
379 | * what you have typed so far. These characters | |
380 | * are mapped to 0 by the subroutine. | |
381 | */ | |
382 | c = getesc(); | |
383 | if (c == 0) | |
384 | continue; | |
385 | ||
386 | /* | |
387 | * Markreg checks that argument is a letter | |
388 | * and also maps ' and ` to the end of the range | |
389 | * to allow '' or `` to reference the previous | |
390 | * context mark. | |
391 | */ | |
392 | c = markreg(c); | |
393 | forbid (c == 0); | |
394 | vsave(); | |
395 | names[c - 'a'] = (*dot &~ 01); | |
396 | ncols[c - 'a'] = cursor; | |
397 | anymarks = 1; | |
398 | continue; | |
399 | ||
400 | /* | |
401 | * ^F Window forwards, with 2 lines of continuity. | |
d266c416 | 402 | * Count repeats. |
2281ca6a MH |
403 | */ |
404 | case CTRL(f): | |
405 | vsave(); | |
2281ca6a | 406 | if (vcnt > 2) { |
04379bab MH |
407 | addr = dot + (vcnt - vcline) - 2 + (cnt-1)*basWLINES; |
408 | forbid(addr > dol); | |
409 | dot = addr; | |
2281ca6a MH |
410 | vcnt = vcline = 0; |
411 | } | |
412 | vzop(0, 0, '+'); | |
413 | continue; | |
414 | ||
415 | /* | |
416 | * ^B Window backwards, with 2 lines of continuity. | |
417 | * Inverse of ^F. | |
418 | */ | |
419 | case CTRL(b): | |
420 | vsave(); | |
2281ca6a | 421 | if (one + vcline != dot && vcnt > 2) { |
04379bab MH |
422 | addr = dot - vcline - 2 + (cnt-1)*basWLINES; |
423 | forbid (addr <= zero); | |
424 | dot = addr; | |
2281ca6a MH |
425 | vcnt = vcline = 0; |
426 | } | |
427 | vzop(0, 0, '^'); | |
428 | continue; | |
429 | ||
430 | /* | |
431 | * z Screen adjustment, taking a following character: | |
432 | * z<CR> current line to top | |
433 | * z<NL> like z<CR> | |
434 | * z- current line to bottom | |
435 | * also z+, z^ like ^F and ^B. | |
436 | * A preceding count is line to use rather | |
437 | * than current line. A count between z and | |
438 | * specifier character changes the screen size | |
439 | * for the redraw. | |
440 | * | |
441 | */ | |
442 | case 'z': | |
443 | if (state == VISUAL) { | |
444 | i = vgetcnt(); | |
445 | if (i > 0) | |
446 | vsetsiz(i); | |
447 | c = getesc(); | |
448 | if (c == 0) | |
449 | continue; | |
450 | } | |
451 | vsave(); | |
452 | vzop(hadcnt, cnt, c); | |
453 | continue; | |
454 | ||
455 | /* | |
456 | * Y Yank lines, abbreviation for y_ or yy. | |
457 | * Yanked lines can be put later if no | |
458 | * changes intervene, or can be put in named | |
459 | * buffers and put anytime in this session. | |
460 | */ | |
461 | case 'Y': | |
462 | ungetkey('_'); | |
463 | c = 'y'; | |
464 | break; | |
465 | ||
466 | /* | |
467 | * J Join lines, 2 by default. Count is number | |
468 | * of lines to join (no join operator sorry.) | |
469 | */ | |
470 | case 'J': | |
471 | forbid (dot == dol); | |
472 | if (cnt == 1) | |
473 | cnt = 2; | |
474 | if (cnt > (i = dol - dot + 1)) | |
475 | cnt = i; | |
476 | vsave(); | |
d266c416 | 477 | vmacchng(1); |
2281ca6a MH |
478 | setLAST(); |
479 | cursor = strend(linebuf); | |
480 | vremote(cnt, join, 0); | |
481 | notenam = "join"; | |
482 | vmoving = 0; | |
483 | killU(); | |
484 | vreplace(vcline, cnt, 1); | |
485 | if (!*cursor && cursor > linebuf) | |
486 | cursor--; | |
487 | if (notecnt == 2) | |
488 | notecnt = 0; | |
489 | vrepaint(cursor); | |
490 | continue; | |
491 | ||
492 | /* | |
493 | * S Substitute text for whole lines, abbrev for c_. | |
494 | * Count is number of lines to change. | |
495 | */ | |
496 | case 'S': | |
497 | ungetkey('_'); | |
498 | c = 'c'; | |
499 | break; | |
500 | ||
501 | /* | |
502 | * O Create a new line above current and accept new | |
503 | * input text, to an escape, there. | |
504 | * A count specifies, for dumb terminals when | |
505 | * slowopen is not set, the number of physical | |
506 | * line space to open on the screen. | |
507 | * | |
508 | * o Like O, but opens lines below. | |
509 | */ | |
510 | case 'O': | |
511 | case 'o': | |
d266c416 | 512 | vmacchng(1); |
04379bab | 513 | voOpen(c, cnt); |
2281ca6a MH |
514 | continue; |
515 | ||
516 | /* | |
517 | * C Change text to end of line, short for c$. | |
518 | */ | |
519 | case 'C': | |
520 | if (*cursor) { | |
521 | ungetkey('$'), c = 'c'; | |
522 | break; | |
523 | } | |
524 | goto appnd; | |
525 | ||
526 | /* | |
527 | * ~ Switch case of letter under cursor | |
528 | */ | |
529 | case '~': | |
530 | { | |
531 | char mbuf[4]; | |
d266c416 | 532 | setLAST(); |
2281ca6a MH |
533 | mbuf[0] = 'r'; |
534 | mbuf[1] = *cursor; | |
535 | mbuf[2] = cursor[1]==0 ? 0 : ' '; | |
536 | mbuf[3] = 0; | |
537 | if (isalpha(mbuf[1])) | |
538 | mbuf[1] ^= ' '; /* toggle the case */ | |
887e3e0d | 539 | macpush(mbuf, 1); |
2281ca6a MH |
540 | } |
541 | continue; | |
542 | ||
543 | ||
544 | /* | |
545 | * A Append at end of line, short for $a. | |
546 | */ | |
547 | case 'A': | |
548 | operate('$', 1); | |
549 | appnd: | |
550 | c = 'a'; | |
551 | /* fall into ... */ | |
552 | ||
553 | /* | |
554 | * a Appends text after cursor. Text can continue | |
555 | * through arbitrary number of lines. | |
556 | */ | |
557 | case 'a': | |
558 | if (*cursor) { | |
559 | if (state == HARDOPEN) | |
560 | putchar(*cursor); | |
561 | cursor++; | |
562 | } | |
563 | goto insrt; | |
564 | ||
565 | /* | |
566 | * I Insert at beginning of whitespace of line, | |
567 | * short for ^i. | |
568 | */ | |
569 | case 'I': | |
570 | operate('^', 1); | |
571 | c = 'i'; | |
572 | /* fall into ... */ | |
573 | ||
574 | /* | |
575 | * R Replace characters, one for one, by input | |
576 | * (logically), like repeated r commands. | |
577 | * | |
578 | * BUG: This is like the typeover mode of many other | |
579 | * editors, and is only rarely useful. Its | |
580 | * implementation is a hack in a low level | |
581 | * routine and it doesn't work very well, e.g. | |
582 | * you can't move around within a R, etc. | |
583 | */ | |
584 | case 'R': | |
585 | /* fall into... */ | |
586 | ||
587 | /* | |
588 | * i Insert text to an escape in the buffer. | |
589 | * Text is arbitrary. This command reminds of | |
590 | * the i command in bare teco. | |
591 | */ | |
592 | case 'i': | |
593 | insrt: | |
594 | /* | |
595 | * Common code for all the insertion commands. | |
596 | * Save for redo, position cursor, prepare for append | |
597 | * at command and in visual undo. Note that nothing | |
598 | * is doomed, unless R when all is, and save the | |
599 | * current line in a the undo temporary buffer. | |
600 | */ | |
d266c416 | 601 | vmacchng(1); |
2281ca6a MH |
602 | setLAST(); |
603 | vcursat(cursor); | |
604 | prepapp(); | |
605 | vnoapp(); | |
606 | doomed = c == 'R' ? 10000 : 0; | |
887e3e0d MH |
607 | if(FIXUNDO) |
608 | vundkind = VCHNG; | |
609 | vmoving = 0; | |
2281ca6a MH |
610 | CP(vutmp, linebuf); |
611 | ||
612 | /* | |
613 | * If this is a repeated command, then suppress | |
614 | * fake insert mode on dumb terminals which looks | |
615 | * ridiculous and wastes lots of time even at 9600B. | |
616 | */ | |
617 | if (vglobp) | |
618 | hold = HOLDQIK; | |
619 | vappend(c, cnt, 0); | |
620 | continue; | |
621 | ||
622 | /* | |
623 | * ^? An attention, normally a ^?, just beeps. | |
624 | * If you are a vi command within ex, then | |
625 | * two ATTN's will drop you back to command mode. | |
626 | */ | |
627 | case ATTN: | |
628 | beep(); | |
629 | if (initev || peekkey() != ATTN) | |
630 | continue; | |
631 | /* fall into... */ | |
632 | ||
633 | /* | |
634 | * ^\ A quit always gets command mode. | |
635 | */ | |
636 | case QUIT: | |
637 | /* | |
638 | * Have to be careful if we were called | |
639 | * g/xxx/vi | |
640 | * since a return will just start up again. | |
641 | * So we simulate an interrupt. | |
642 | */ | |
643 | if (inglobal) | |
644 | onintr(); | |
645 | /* fall into... */ | |
646 | ||
887e3e0d | 647 | #ifdef notdef |
2281ca6a MH |
648 | /* |
649 | * q Quit back to command mode, unless called as | |
650 | * vi on command line in which case dont do it | |
651 | */ | |
652 | case 'q': /* quit */ | |
653 | if (initev) { | |
654 | vsave(); | |
655 | CATCH | |
656 | error("Q gets ex command mode, :q leaves vi"); | |
657 | ENDCATCH | |
658 | splitw = 0; | |
659 | getDOT(); | |
660 | vrepaint(cursor); | |
661 | continue; | |
662 | } | |
887e3e0d | 663 | #endif |
2281ca6a MH |
664 | /* fall into... */ |
665 | ||
666 | /* | |
667 | * Q Is like q, but always gets to command mode | |
668 | * even if command line invocation was as vi. | |
669 | */ | |
670 | case 'Q': | |
671 | vsave(); | |
d266c416 MH |
672 | /* |
673 | * If we are in the middle of a macro, throw away | |
674 | * the rest and fix up undo. | |
675 | * This code copied from getbr(). | |
676 | */ | |
677 | if (vmacp) { | |
678 | vmacp = 0; | |
679 | if (inopen == -1) /* don't screw up undo for esc esc */ | |
680 | vundkind = VMANY; | |
681 | inopen = 1; /* restore old setting now that macro done */ | |
682 | } | |
2281ca6a MH |
683 | return; |
684 | ||
887e3e0d MH |
685 | |
686 | /* | |
687 | * ZZ Like :x | |
688 | */ | |
689 | case 'Z': | |
690 | forbid(getkey() != 'Z'); | |
691 | oglobp = globp; | |
692 | globp = "x"; | |
693 | vclrech(0); | |
694 | goto gogo; | |
695 | ||
2281ca6a MH |
696 | /* |
697 | * P Put back text before cursor or before current | |
698 | * line. If text was whole lines goes back | |
699 | * as whole lines. If part of a single line | |
700 | * or parts of whole lines splits up current | |
701 | * line to form many new lines. | |
702 | * May specify a named buffer, or the delete | |
703 | * saving buffers 1-9. | |
704 | * | |
705 | * p Like P but after rather than before. | |
706 | */ | |
707 | case 'P': | |
708 | case 'p': | |
709 | vmoving = 0; | |
d266c416 | 710 | #ifdef notdef |
887e3e0d | 711 | forbid (!vreg && value(UNDOMACRO) && inopen < 0); |
d266c416 | 712 | #endif |
2281ca6a MH |
713 | /* |
714 | * If previous delete was partial line, use an | |
715 | * append or insert to put it back so as to | |
716 | * use insert mode on intelligent terminals. | |
717 | */ | |
718 | if (!vreg && DEL[0]) { | |
719 | forbid ((DEL[0] & (QUOTE|TRIM)) == OVERBUF); | |
720 | vglobp = DEL; | |
721 | ungetkey(c == 'p' ? 'a' : 'i'); | |
722 | goto reread; | |
723 | } | |
724 | ||
725 | /* | |
726 | * If a register wasn't specified, then make | |
727 | * sure there is something to put back. | |
728 | */ | |
729 | forbid (!vreg && unddol == dol); | |
d266c416 MH |
730 | /* |
731 | * If we just did a macro the whole buffer is in | |
732 | * the undo save area. We don't want to put THAT. | |
733 | */ | |
734 | forbid (vundkind == VMANY && undkind==UNDALL); | |
2281ca6a | 735 | vsave(); |
d266c416 | 736 | vmacchng(1); |
2281ca6a MH |
737 | setLAST(); |
738 | i = 0; | |
739 | if (vreg && partreg(vreg) || !vreg && pkill[0]) { | |
740 | /* | |
741 | * Restoring multiple lines which were partial | |
742 | * lines; will leave cursor in middle | |
743 | * of line after shoving restored text in to | |
744 | * split the current line. | |
745 | */ | |
746 | i++; | |
747 | if (c == 'p' && *cursor) | |
748 | cursor++; | |
749 | } else { | |
750 | /* | |
751 | * In whole line case, have to back up dot | |
752 | * for P; also want to clear cursor so | |
753 | * cursor will eventually be positioned | |
754 | * at the beginning of the first put line. | |
755 | */ | |
756 | cursor = 0; | |
757 | if (c == 'P') { | |
758 | dot--, vcline--; | |
759 | c = 'p'; | |
760 | } | |
761 | } | |
762 | killU(); | |
763 | ||
764 | /* | |
765 | * The call to putreg can potentially | |
766 | * bomb since there may be nothing in a named buffer. | |
767 | * We thus put a catch in here. If we didn't and | |
768 | * there was an error we would end up in command mode. | |
769 | */ | |
887e3e0d | 770 | addr = dol; /* old dol */ |
2281ca6a MH |
771 | CATCH |
772 | vremote(1, vreg ? putreg : put, vreg); | |
773 | ONERR | |
774 | if (vreg == -1) { | |
775 | splitw = 0; | |
776 | if (op == 'P') | |
777 | dot++, vcline++; | |
778 | goto pfixup; | |
779 | } | |
780 | ENDCATCH | |
781 | splitw = 0; | |
887e3e0d | 782 | nlput = dol - addr + 1; |
2281ca6a MH |
783 | if (!i) { |
784 | /* | |
785 | * Increment undap1, undap2 to make up | |
786 | * for their incorrect initialization in the | |
787 | * routine vremote before calling put/putreg. | |
788 | */ | |
887e3e0d MH |
789 | if (FIXUNDO) |
790 | undap1++, undap2++; | |
2281ca6a | 791 | vcline++; |
887e3e0d | 792 | nlput--; |
2281ca6a | 793 | |
887e3e0d MH |
794 | /* |
795 | * After a put want current line first line, | |
796 | * and dot was made the last line put in code | |
797 | * run so far. This is why we increment vcline | |
798 | * above and decrease dot here. | |
799 | */ | |
800 | dot -= nlput - 1; | |
801 | } | |
802 | #ifdef TRACE | |
803 | if (trace) | |
804 | fprintf(trace, "vreplace(%d, %d, %d), undap1=%d, undap2=%d, dot=%d\n", vcline, i, nlput, lineno(undap1), lineno(undap2), lineno(dot)); | |
805 | #endif | |
806 | vreplace(vcline, i, nlput); | |
2281ca6a MH |
807 | if (state != VISUAL) { |
808 | /* | |
809 | * Special case in open mode. | |
810 | * Force action on the screen when a single | |
811 | * line is put even if it is identical to | |
812 | * the current line, e.g. on YP; otherwise | |
813 | * you can't tell anything happened. | |
814 | */ | |
815 | vjumpto(dot, cursor, '.'); | |
816 | continue; | |
817 | } | |
818 | pfixup: | |
819 | vrepaint(cursor); | |
820 | vfixcurs(); | |
821 | continue; | |
822 | ||
823 | /* | |
5e950816 MH |
824 | * ^^ Return to previous file. |
825 | * Like a :e #, and thus can be used after a | |
2281ca6a | 826 | * "No Write" diagnostic. |
2281ca6a MH |
827 | */ |
828 | case CTRL(^): | |
10499a70 | 829 | forbid (hadcnt); |
2281ca6a MH |
830 | vsave(); |
831 | ckaw(); | |
832 | oglobp = globp; | |
44232d5b MH |
833 | if (value(AUTOWRITE)) |
834 | globp = "e! #"; | |
835 | else | |
836 | globp = "e #"; | |
2281ca6a MH |
837 | goto gogo; |
838 | ||
839 | /* | |
840 | * ^] Takes word after cursor as tag, and then does | |
841 | * tag command. Read ``go right to''. | |
842 | */ | |
843 | case CTRL(]): | |
844 | grabtag(); | |
845 | oglobp = globp; | |
846 | globp = "tag"; | |
847 | goto gogo; | |
848 | ||
849 | /* | |
850 | * & Like :& | |
851 | */ | |
852 | case '&': | |
853 | oglobp = globp; | |
854 | globp = "&"; | |
855 | goto gogo; | |
856 | ||
857 | /* | |
858 | * ^G Bring up a status line at the bottom of | |
859 | * the screen, like a :file command. | |
860 | * | |
861 | * BUG: Was ^S but doesn't work in cbreak mode | |
862 | */ | |
863 | case CTRL(g): | |
864 | oglobp = globp; | |
865 | globp = "file"; | |
866 | gogo: | |
867 | addr = dot; | |
868 | vsave(); | |
869 | goto doinit; | |
870 | ||
bdbbb330 | 871 | #ifdef SIGTSTP |
d266c416 MH |
872 | /* |
873 | * ^Z: suspend editor session and temporarily return | |
04379bab MH |
874 | * to shell. Only works with Berkeley/IIASA process |
875 | * control in kernel. | |
d266c416 MH |
876 | */ |
877 | case CTRL(z): | |
878 | forbid(dosusp == 0 || !ldisc); | |
879 | vsave(); | |
880 | oglobp = globp; | |
881 | globp = "stop"; | |
882 | goto gogo; | |
883 | #endif | |
884 | ||
2281ca6a MH |
885 | /* |
886 | * : Read a command from the echo area and | |
887 | * execute it in command mode. | |
888 | */ | |
889 | case ':': | |
10499a70 | 890 | forbid (hadcnt); |
2281ca6a MH |
891 | vsave(); |
892 | i = tchng; | |
893 | addr = dot; | |
894 | if (readecho(c)) { | |
895 | esave[0] = 0; | |
896 | goto fixup; | |
897 | } | |
d266c416 | 898 | getDOT(); |
2281ca6a MH |
899 | /* |
900 | * Use the visual undo buffer to store the global | |
901 | * string for command mode, since it is idle right now. | |
902 | */ | |
903 | oglobp = globp; strcpy(vutmp, genbuf+1); globp = vutmp; | |
904 | doinit: | |
905 | esave[0] = 0; | |
906 | fixech(); | |
907 | ||
908 | /* | |
909 | * Have to finagle around not to lose last | |
910 | * character after this command (when run from ex | |
911 | * command mode). This is clumsy. | |
912 | */ | |
913 | d = peekc; ungetchar(0); | |
887e3e0d MH |
914 | if (shouldpo) { |
915 | /* | |
916 | * So after a "Hit return..." ":", we do | |
917 | * another "Hit return..." the next time | |
918 | */ | |
919 | pofix(); | |
920 | shouldpo = 0; | |
921 | } | |
2281ca6a MH |
922 | CATCH |
923 | /* | |
924 | * Save old values of options so we can | |
925 | * notice when they change; switch into | |
926 | * cooked mode so we are interruptible. | |
927 | */ | |
928 | onumber = value(NUMBER); | |
929 | olist = value(LIST); | |
930 | OPline = Pline; | |
931 | OPutchar = Putchar; | |
932 | #ifndef CBREAK | |
933 | vcook(); | |
934 | #endif | |
935 | commands(1, 1); | |
936 | if (dot == zero && dol > zero) | |
937 | dot = one; | |
938 | #ifndef CBREAK | |
939 | vraw(); | |
940 | #endif | |
941 | ONERR | |
942 | #ifndef CBREAK | |
943 | vraw(); | |
944 | #endif | |
945 | copy(esave, vtube[WECHO], TUBECOLS); | |
946 | ENDCATCH | |
947 | fixol(); | |
948 | Pline = OPline; | |
949 | Putchar = OPutchar; | |
950 | ungetchar(d); | |
951 | globp = oglobp; | |
952 | ||
953 | /* | |
954 | * If we ended up with no lines in the buffer, make | |
955 | * a line, and don't consider the buffer changed. | |
956 | */ | |
957 | if (dot == zero) { | |
958 | fixzero(); | |
959 | sync(); | |
960 | } | |
961 | splitw = 0; | |
962 | ||
963 | /* | |
964 | * Special case: did list/number options change? | |
965 | */ | |
966 | if (onumber != value(NUMBER)) | |
967 | setnumb(value(NUMBER)); | |
968 | if (olist != value(LIST)) | |
969 | setlist(value(LIST)); | |
970 | ||
971 | fixup: | |
972 | /* | |
973 | * If a change occurred, other than | |
974 | * a write which clears changes, then | |
975 | * we should allow an undo even if . | |
976 | * didn't move. | |
977 | * | |
978 | * BUG: You can make this wrong by | |
979 | * tricking around with multiple commands | |
980 | * on one line of : escape, and including | |
981 | * a write command there, but its not | |
982 | * worth worrying about. | |
983 | */ | |
887e3e0d | 984 | if (FIXUNDO && tchng && tchng != i) |
2281ca6a MH |
985 | vundkind = VMANY, cursor = 0; |
986 | ||
987 | /* | |
988 | * If we are about to do another :, hold off | |
989 | * updating of screen. | |
990 | */ | |
991 | if (vcnt < 0 && Peekkey == ':') { | |
992 | getDOT(); | |
887e3e0d | 993 | shouldpo = 1; |
2281ca6a MH |
994 | continue; |
995 | } | |
887e3e0d | 996 | shouldpo = 0; |
2281ca6a MH |
997 | |
998 | /* | |
999 | * In the case where the file being edited is | |
1000 | * new; e.g. if the initial state hasn't been | |
1001 | * saved yet, then do so now. | |
1002 | */ | |
1003 | if (unddol == truedol) { | |
1004 | vundkind = VNONE; | |
1005 | Vlines = lineDOL(); | |
1006 | if (!inglobal) | |
1007 | savevis(); | |
1008 | addr = zero; | |
1009 | vcnt = 0; | |
1010 | if (esave[0] == 0) | |
1011 | copy(esave, vtube[WECHO], TUBECOLS); | |
1012 | } | |
1013 | ||
1014 | /* | |
1015 | * If the current line moved reset the cursor position. | |
1016 | */ | |
1017 | if (dot != addr) { | |
1018 | vmoving = 0; | |
1019 | cursor = 0; | |
1020 | } | |
1021 | ||
1022 | /* | |
1023 | * If current line is not on screen or if we are | |
1024 | * in open mode and . moved, then redraw. | |
1025 | */ | |
1026 | i = vcline + (dot - addr); | |
1027 | if (i < 0 || i >= vcnt && i >= -vcnt || state != VISUAL && dot != addr) { | |
1028 | if (state == CRTOPEN) | |
1029 | vup1(); | |
1030 | if (vcnt > 0) | |
1031 | vcnt = 0; | |
1032 | vjumpto(dot, (char *) 0, '.'); | |
1033 | } else { | |
1034 | /* | |
1035 | * Current line IS on screen. | |
1036 | * If we did a [Hit return...] then | |
1037 | * restore vcnt and clear screen if in visual | |
1038 | */ | |
1039 | vcline = i; | |
1040 | if (vcnt < 0) { | |
1041 | vcnt = -vcnt; | |
1042 | if (state == VISUAL) | |
1043 | vclear(); | |
887e3e0d | 1044 | else if (state == CRTOPEN) { |
2281ca6a | 1045 | vcnt = 0; |
887e3e0d | 1046 | } |
2281ca6a MH |
1047 | } |
1048 | ||
1049 | /* | |
1050 | * Limit max value of vcnt based on $ | |
1051 | */ | |
1052 | i = vcline + lineDOL() - lineDOT() + 1; | |
1053 | if (i < vcnt) | |
1054 | vcnt = i; | |
1055 | ||
1056 | /* | |
1057 | * Dirty and repaint. | |
1058 | */ | |
1059 | vdirty(0, LINES); | |
1060 | vrepaint(cursor); | |
1061 | } | |
1062 | ||
1063 | /* | |
1064 | * If in visual, put back the echo area | |
1065 | * if it was clobberred. | |
1066 | */ | |
1067 | if (state == VISUAL) { | |
1068 | int sdc = destcol, sdl = destline; | |
1069 | ||
1070 | splitw++; | |
1071 | vigoto(WECHO, 0); | |
1072 | for (i = 0; i < TUBECOLS - 1; i++) { | |
1073 | if (esave[i] == 0) | |
1074 | break; | |
1075 | vputchar(esave[i]); | |
1076 | } | |
1077 | splitw = 0; | |
1078 | vgoto(sdl, sdc); | |
1079 | } | |
1080 | continue; | |
1081 | ||
1082 | /* | |
1083 | * u undo the last changing command. | |
1084 | */ | |
1085 | case 'u': | |
d266c416 | 1086 | vundo(1); |
2281ca6a MH |
1087 | continue; |
1088 | ||
1089 | /* | |
1090 | * U restore current line to initial state. | |
1091 | */ | |
1092 | case 'U': | |
1093 | vUndo(); | |
1094 | continue; | |
1095 | ||
1096 | fonfon: | |
1097 | beep(); | |
1098 | vmacp = 0; | |
44232d5b | 1099 | inopen = 1; /* might have been -1 */ |
2281ca6a MH |
1100 | continue; |
1101 | } | |
1102 | ||
1103 | /* | |
1104 | * Rest of commands are decoded by the operate | |
1105 | * routine. | |
1106 | */ | |
1107 | operate(c, cnt); | |
1108 | } | |
1109 | } | |
1110 | ||
1111 | /* | |
1112 | * Grab the word after the cursor so we can look for it as a tag. | |
1113 | */ | |
1114 | grabtag() | |
1115 | { | |
1116 | register char *cp, *dp; | |
1117 | ||
1118 | cp = vpastwh(cursor); | |
1119 | if (*cp) { | |
1120 | dp = lasttag; | |
1121 | do { | |
1122 | if (dp < &lasttag[sizeof lasttag - 2]) | |
1123 | *dp++ = *cp; | |
1124 | cp++; | |
1125 | } while (isalpha(*cp) || isdigit(*cp) || *cp == '_'); | |
1126 | *dp++ = 0; | |
1127 | } | |
1128 | } | |
1129 | ||
1130 | /* | |
1131 | * Before appending lines, set up addr1 and | |
1132 | * the command mode undo information. | |
1133 | */ | |
1134 | prepapp() | |
1135 | { | |
1136 | ||
1137 | addr1 = dot; | |
1138 | deletenone(); | |
1139 | addr1++; | |
1140 | appendnone(); | |
1141 | } | |
1142 | ||
1143 | /* | |
1144 | * Execute function f with the address bounds addr1 | |
1145 | * and addr2 surrounding cnt lines starting at dot. | |
1146 | */ | |
1147 | vremote(cnt, f, arg) | |
1148 | int cnt, (*f)(), arg; | |
1149 | { | |
1150 | register int oing = inglobal; | |
1151 | ||
1152 | addr1 = dot; | |
1153 | addr2 = dot + cnt - 1; | |
2281ca6a | 1154 | inglobal = 0; |
887e3e0d MH |
1155 | if (FIXUNDO) |
1156 | undap1 = undap2 = dot; | |
2281ca6a MH |
1157 | (*f)(arg); |
1158 | inglobal = oing; | |
887e3e0d | 1159 | if (FIXUNDO) |
2281ca6a MH |
1160 | vundkind = VMANY; |
1161 | vmcurs = 0; | |
1162 | } | |
1163 | ||
1164 | /* | |
1165 | * Save the current contents of linebuf, if it has changed. | |
1166 | */ | |
1167 | vsave() | |
1168 | { | |
1169 | char temp[LBSIZE]; | |
1170 | ||
1171 | CP(temp, linebuf); | |
887e3e0d | 1172 | if (FIXUNDO && vundkind == VCHNG || vundkind == VCAPU) { |
2281ca6a MH |
1173 | /* |
1174 | * If the undo state is saved in the temporary buffer | |
1175 | * vutmp, then we sync this into the temp file so that | |
1176 | * we will be able to undo even after we have moved off | |
1177 | * the line. It would be possible to associate a line | |
1178 | * with vutmp but we assume that vutmp is only associated | |
1179 | * with line dot (e.g. in case ':') above, so beware. | |
1180 | */ | |
1181 | prepapp(); | |
1182 | strcLIN(vutmp); | |
1183 | putmark(dot); | |
1184 | vremote(1, yank, 0); | |
1185 | vundkind = VMCHNG; | |
1186 | notecnt = 0; | |
1187 | undkind = UNDCHANGE; | |
1188 | } | |
1189 | /* | |
1190 | * Get the line out of the temp file and do nothing if it hasn't | |
1191 | * changed. This may seem like a loss, but the line will | |
1192 | * almost always be in a read buffer so this may well avoid disk i/o. | |
1193 | */ | |
1194 | getDOT(); | |
1195 | if (strcmp(linebuf, temp) == 0) | |
1196 | return; | |
1197 | strcLIN(temp); | |
1198 | putmark(dot); | |
1199 | } | |
1200 | ||
1201 | #undef forbid | |
1202 | #define forbid(a) if (a) { beep(); return; } | |
1203 | ||
1204 | /* | |
1205 | * Do a z operation. | |
1206 | * Code here is rather long, and very uninteresting. | |
1207 | */ | |
1208 | vzop(hadcnt, cnt, c) | |
1209 | bool hadcnt; | |
1210 | int cnt; | |
1211 | register int c; | |
1212 | { | |
1213 | register line *addr; | |
1214 | ||
1215 | if (state != VISUAL) { | |
1216 | /* | |
1217 | * Z from open; always like a z=. | |
1218 | * This code is a mess and should be cleaned up. | |
1219 | */ | |
1220 | vmoveitup(1, 1); | |
1221 | vgoto(outline, 0); | |
1222 | ostop(normf); | |
1223 | setoutt(); | |
1224 | addr2 = dot; | |
1225 | vclear(); | |
1226 | destline = WECHO; | |
1227 | zop2(Xhadcnt ? Xcnt : value(WINDOW) - 1, '='); | |
1228 | if (state == CRTOPEN) | |
1229 | putnl(); | |
1230 | putNFL(); | |
1231 | termreset(); | |
1232 | Outchar = vputchar; | |
1233 | ignore(ostart()); | |
1234 | vcnt = 0; | |
1235 | outline = destline = 0; | |
1236 | vjumpto(dot, cursor, 0); | |
1237 | return; | |
1238 | } | |
1239 | if (hadcnt) { | |
1240 | addr = zero + cnt; | |
1241 | if (addr < one) | |
1242 | addr = one; | |
1243 | if (addr > dol) | |
1244 | addr = dol; | |
1245 | markit(addr); | |
1246 | } else | |
1247 | switch (c) { | |
1248 | ||
1249 | case '+': | |
1250 | addr = dot + vcnt - vcline; | |
1251 | break; | |
1252 | ||
1253 | case '^': | |
1254 | addr = dot - vcline - 1; | |
1255 | forbid (addr < one); | |
1256 | c = '-'; | |
1257 | break; | |
1258 | ||
1259 | default: | |
1260 | addr = dot; | |
1261 | break; | |
1262 | } | |
1263 | switch (c) { | |
1264 | ||
1265 | case '.': | |
1266 | case '-': | |
1267 | break; | |
1268 | ||
1269 | case '^': | |
1270 | forbid (addr <= one); | |
1271 | break; | |
1272 | ||
1273 | case '+': | |
1274 | forbid (addr >= dol); | |
1275 | /* fall into ... */ | |
1276 | ||
1277 | case CR: | |
1278 | case NL: | |
1279 | c = CR; | |
1280 | break; | |
1281 | ||
1282 | default: | |
1283 | beep(); | |
1284 | return; | |
1285 | } | |
1286 | vmoving = 0; | |
1287 | vjumpto(addr, NOSTR, c); | |
1288 | } |