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