release 3.3, Feb 2, 1980
[unix-history] / usr / src / usr.bin / ex / ex_vops2.c
CommitLineData
09035cec
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 * Low level routines for operations sequences,
8 * and mostly, insert mode (and a subroutine
9 * to read an input line, including in the echo area.)
10 */
11char *vUA1, *vUA2;
12char *vUD1, *vUD2;
13
14/*
15 * Obleeperate characters in hardcopy
16 * open with \'s.
17 */
18bleep(i, cp)
19 register int i;
20 char *cp;
21{
22
23 i -= column(cp);
24 do
25 putchar('\\' | QUOTE);
26 while (--i >= 0);
27 rubble = 1;
28}
29
30/*
31 * Common code for middle part of delete
32 * and change operating on parts of lines.
33 */
34vdcMID()
35{
36 register char *cp;
37
38 squish();
39 setLAST();
887e3e0d
MH
40 if (FIXUNDO)
41 vundkind = VCHNG, CP(vutmp, linebuf);
09035cec
MH
42 if (wcursor < cursor)
43 cp = wcursor, wcursor = cursor, cursor = cp;
44 vUD1 = vUA1 = vUA2 = cursor; vUD2 = wcursor;
45 return (column(wcursor - 1));
46}
47
48/*
49 * Take text from linebuf and stick it
50 * in the VBSIZE buffer BUF. Used to save
51 * deleted text of part of line.
52 */
53takeout(BUF)
54 char *BUF;
55{
56 register char *cp;
57
58 if (wcursor < linebuf)
59 wcursor = linebuf;
60 if (cursor == wcursor) {
61 beep();
62 return;
63 }
64 if (wcursor < cursor) {
65 cp = wcursor;
66 wcursor = cursor;
67 cursor = cp;
68 }
69 setBUF(BUF);
70 if ((BUF[0] & (QUOTE|TRIM)) == OVERBUF)
71 beep();
72}
73
74/*
75 * Are we at the end of the printed representation of the
76 * line? Used internally in hardcopy open.
77 */
78ateopr()
79{
80 register int i, c;
81 register char *cp = vtube[destline] + destcol;
82
83 for (i = WCOLS - destcol; i > 0; i--) {
84 c = *cp++;
85 if (c == 0)
86 return (1);
87 if (c != ' ' && (c & QUOTE) == 0)
88 return (0);
89 }
90 return (1);
91}
92
93/*
94 * Append.
95 *
96 * This routine handles the top level append, doing work
97 * as each new line comes in, and arranging repeatability.
98 * It also handles append with repeat counts, and calculation
99 * of autoindents for new lines.
100 */
101bool vaifirst;
102bool gobbled;
103char *ogcursor;
104
105vappend(ch, cnt, indent)
106 char ch;
107 int cnt, indent;
108{
109 register int i;
110 register char *gcursor;
111 bool escape;
112 int repcnt;
113 short oldhold = hold;
114
115 /*
116 * Before a move in hardopen when the line is dirty
117 * or we are in the middle of the printed representation,
118 * we retype the line to the left of the cursor so the
119 * insert looks clean.
120 */
121 if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) {
122 rubble = 1;
123 gcursor = cursor;
124 i = *gcursor;
125 *gcursor = ' ';
126 wcursor = gcursor;
127 vmove();
128 *gcursor = i;
129 }
130 vaifirst = indent == 0;
131
132 /*
133 * Handle replace character by (eventually)
134 * limiting the number of input characters allowed
135 * in the vgetline routine.
136 */
137 if (ch == 'r')
138 repcnt = 2;
139 else
140 repcnt = 0;
141
142 /*
143 * If an autoindent is specified, then
144 * generate a mixture of blanks to tabs to implement
145 * it and place the cursor after the indent.
146 * Text read by the vgetline routine will be placed in genbuf,
147 * so the indent is generated there.
148 */
149 if (value(AUTOINDENT) && indent != 0) {
150 gcursor = genindent(indent);
151 *gcursor = 0;
152 vgotoCL(qcolumn(cursor - 1, genbuf));
153 } else {
154 gcursor = genbuf;
155 *gcursor = 0;
156 if (ch == 'o')
157 vfixcurs();
158 }
159
160 /*
161 * Prepare for undo. Pointers delimit inserted portion of line.
162 */
163 vUA1 = vUA2 = cursor;
164
165 /*
166 * If we are not in a repeated command and a ^@ comes in
167 * then this means the previous inserted text.
168 * If there is none or it was too long to be saved,
169 * then beep() and also arrange to undo any damage done
170 * so far (e.g. if we are a change.)
171 */
172 if ((vglobp && *vglobp == 0) || peekbr()) {
173 if ((INS[0] & (QUOTE|TRIM)) == OVERBUF) {
174 beep();
175 if (!splitw)
176 ungetkey('u');
177 doomed = 0;
178 hold = oldhold;
179 return;
180 }
181 /*
182 * Unread input from INS.
183 * An escape will be generated at end of string.
184 * Hold off n^^2 type update on dumb terminals.
185 */
186 vglobp = INS;
187 hold |= HOLDQIK;
188 } else if (vglobp == 0)
189 /*
190 * Not a repeated command, get
191 * a new inserted text for repeat.
192 */
193 INS[0] = 0;
194
195 /*
196 * For wrapmargin to hack away second space after a '.'
197 * when the first space caused a line break we keep
198 * track that this happened in gobblebl, which says
199 * to gobble up a blank silently.
200 */
201 gobblebl = 0;
202
203 /*
204 * Text gathering loop.
205 * New text goes into genbuf starting at gcursor.
206 * cursor preserves place in linebuf where text will eventually go.
207 */
208 if (*cursor == 0 || state == CRTOPEN)
209 hold |= HOLDROL;
210 for (;;) {
211 if (ch == 'r' && repcnt == 0)
212 escape = 0;
213 else {
214 gcursor = vgetline(repcnt, gcursor, &escape);
215
216 /*
217 * After an append, stick information
218 * about the ^D's and ^^D's and 0^D's in
219 * the repeated text buffer so repeated
220 * inserts of stuff indented with ^D as backtab's
221 * can work.
222 */
223 if (HADUP)
224 addtext("^");
225 else if (HADZERO)
226 addtext("0");
227 while (CDCNT > 0)
228 addtext("\204"), CDCNT--;
229 if (gobbled)
230 addtext(" ");
231 addtext(ogcursor);
232 }
233 repcnt = 0;
234
235 /*
236 * Smash the generated and preexisting indents together
237 * and generate one cleanly made out of tabs and spaces
238 * if we are using autoindent.
239 */
240 if (!vaifirst && value(AUTOINDENT)) {
241 i = fixindent(indent);
242 if (!HADUP)
243 indent = i;
244 gcursor = strend(genbuf);
245 }
246
247 /*
248 * Limit the repetition count based on maximum
249 * possible line length; do output implied
250 * by further count (> 1) and cons up the new line
251 * in linebuf.
252 */
253 cnt = vmaxrep(ch, cnt);
254 CP(gcursor + 1, cursor);
255 do {
256 CP(cursor, genbuf);
257 if (cnt > 1) {
258 int oldhold = hold;
259
260 Outchar = vinschar;
261 hold |= HOLDQIK;
262 printf("%s", genbuf);
263 hold = oldhold;
264 Outchar = vputchar;
265 }
266 cursor += gcursor - genbuf;
267 } while (--cnt > 0);
268 endim();
269 vUA2 = cursor;
270 if (escape != '\n')
271 CP(cursor, gcursor + 1);
272
273 /*
274 * If doomed characters remain, clobber them,
275 * and reopen the line to get the display exact.
276 */
277 if (state != HARDOPEN) {
278 DEPTH(vcline) = 0;
279 if (doomed > 0) {
280 register int cind = cindent();
281
282 physdc(cind, cind + doomed);
283 doomed = 0;
284 }
285 i = vreopen(LINE(vcline), lineDOT(), vcline);
286 }
287
288 /*
289 * All done unless we are continuing on to another line.
290 */
291 if (escape != '\n')
292 break;
293
294 /*
295 * Set up for the new line.
296 * First save the current line, then construct a new
297 * first image for the continuation line consisting
298 * of any new autoindent plus the pushed ahead text.
299 */
300 killU();
301 addtext(gobblebl ? " " : "\n");
302 vsave();
303 cnt = 1;
304 if (value(AUTOINDENT)) {
305#ifdef LISPCODE
306 if (value(LISP))
307 indent = lindent(dot + 1);
308 else
309#endif
310 if (!HADUP && vaifirst)
311 indent = whitecnt(linebuf);
312 vaifirst = 0;
313 strcLIN(vpastwh(gcursor + 1));
314 gcursor = genindent(indent);
315 *gcursor = 0;
316 if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2])
317 gcursor = genbuf;
318 CP(gcursor, linebuf);
319 } else {
320 CP(genbuf, gcursor + 1);
321 gcursor = genbuf;
322 }
323
324 /*
325 * If we started out as a single line operation and are now
326 * turning into a multi-line change, then we had better yank
327 * out dot before it changes so that undo will work
328 * correctly later.
329 */
887e3e0d 330 if (FIXUNDO && vundkind == VCHNG) {
09035cec
MH
331 vremote(1, yank, 0);
332 undap1--;
333 }
334
335 /*
336 * Now do the append of the new line in the buffer,
337 * and update the display. If slowopen
338 * we don't do very much.
339 */
340 vdoappend(genbuf);
341 vundkind = VMANYINS;
342 vcline++;
343 if (state != VISUAL)
344 vshow(dot, NOLINE);
345 else {
346 i += LINE(vcline - 1);
347 vopen(dot, i);
348 if (value(SLOWOPEN))
349 vscrap();
350 else
351 vsync1(LINE(vcline));
352 }
353 strcLIN(gcursor);
354 *gcursor = 0;
355 cursor = linebuf;
356 vgotoCL(qcolumn(cursor - 1, genbuf));
357 }
358
359 /*
360 * All done with insertion, position the cursor
361 * and sync the screen.
362 */
363 hold = oldhold;
364 if (cursor > linebuf)
365 cursor--;
366 if (state != HARDOPEN)
367 vsyncCL();
368 else if (cursor > linebuf)
369 back1();
370 doomed = 0;
371 wcursor = cursor;
372 vmove();
373}
374
375/*
376 * Subroutine for vgetline to back up a single character position,
377 * backwards around end of lines (vgoto can't hack columns which are
378 * less than 0 in general).
379 */
380back1()
381{
382
383 vgoto(destline - 1, WCOLS + destcol - 1);
384}
385
386/*
387 * Get a line into genbuf after gcursor.
388 * Cnt limits the number of input characters
389 * accepted and is used for handling the replace
390 * single character command. Aescaped is the location
391 * where we stick a termination indicator (whether we
392 * ended with an ESCAPE or a newline/return.
393 *
394 * We do erase-kill type processing here and also
395 * are careful about the way we do this so that it is
396 * repeatable. (I.e. so that your kill doesn't happen,
397 * when you repeat an insert if it was escaped with \ the
398 * first time you did it.
399 */
400char *
401vgetline(cnt, gcursor, aescaped)
402 int cnt;
403 register char *gcursor;
404 bool *aescaped;
405{
406 register int c, ch;
407 register char *cp;
408 int x, y, iwhite;
409 char *iglobp;
410 int (*OO)() = Outchar;
411
412 /*
413 * Clear the output state and counters
414 * for autoindent backwards motion (counts of ^D, etc.)
415 * Remember how much white space at beginning of line so
416 * as not to allow backspace over autoindent.
417 */
418 *aescaped = 0;
419 ogcursor = gcursor;
420 flusho();
421 CDCNT = 0;
422 HADUP = 0;
423 HADZERO = 0;
424 gobbled = 0;
425 iwhite = whitecnt(genbuf);
426 iglobp = vglobp;
427
428 /*
429 * Carefully avoid using vinschar in the echo area.
430 */
431 if (splitw)
432 Outchar = vputchar;
433 else {
434 Outchar = vinschar;
435 vprepins();
436 }
437 for (;;) {
438 if (gobblebl)
439 gobblebl--;
440 if (cnt != 0) {
441 cnt--;
442 if (cnt == 0)
443 goto vadone;
444 }
445 ch = c = getkey() & (QUOTE|TRIM);
887e3e0d
MH
446 if (vglobp == 0 && Peekkey == 0)
447 while ((ch = map(c, immacs)) != c) {
09035cec 448 c = ch;
887e3e0d
MH
449 if (!value(REMAP))
450 break;
451 }
09035cec
MH
452 if (!iglobp) {
453
454 /*
455 * Erase-kill type processing.
456 * Only happens if we were not reading
457 * from untyped input when we started.
458 * Map users erase to ^H, kill to -1 for switch.
459 */
460 if (c == tty.sg_erase)
461 c = CTRL(h);
462 else if (c == tty.sg_kill)
463 c = -1;
464 switch (c) {
465
466 /*
467 * ^? Interrupt drops you back to visual
468 * command mode with an unread interrupt
469 * still in the input buffer.
470 *
471 * ^\ Quit does the same as interrupt.
472 * If you are a ex command rather than
473 * a vi command this will drop you
474 * back to command mode for sure.
475 */
476 case ATTN:
477 case QUIT:
478 ungetkey(c);
479 goto vadone;
480
481 /*
482 * ^H Backs up a character in the input.
483 *
484 * BUG: Can't back around line boundaries.
485 * This is hard because stuff has
486 * already been saved for repeat.
487 */
488 case CTRL(h):
489bakchar:
490 cp = gcursor - 1;
491 if (cp < ogcursor) {
492 if (splitw) {
493 /*
494 * Backspacing over readecho
495 * prompt. Pretend delete but
496 * don't beep.
497 */
498 ungetkey(c);
499 goto vadone;
500 }
501 beep();
502 continue;
503 }
504 goto vbackup;
505
506 /*
507 * ^W Back up a white/non-white word.
508 */
509 case CTRL(w):
510 wdkind = 1;
511 for (cp = gcursor; cp > ogcursor && isspace(cp[-1]); cp--)
512 continue;
513 for (c = wordch(cp - 1);
514 cp > ogcursor && wordof(c, cp - 1); cp--)
515 continue;
516 goto vbackup;
517
518 /*
519 * users kill Kill input on this line, back to
520 * the autoindent.
521 */
522 case -1:
523 cp = ogcursor;
524vbackup:
525 if (cp == gcursor) {
526 beep();
527 continue;
528 }
529 endim();
530 *cp = 0;
531 c = cindent();
532 vgotoCL(qcolumn(cursor - 1, genbuf));
533 if (doomed >= 0)
534 doomed += c - cindent();
535 gcursor = cp;
536 continue;
537
538 /*
539 * \ Followed by erase or kill
540 * maps to just the erase or kill.
541 */
542 case '\\':
543 x = destcol, y = destline;
544 putchar('\\');
545 vcsync();
546 c = getkey();
547 if (c == tty.sg_erase || c == tty.sg_kill) {
548 vgoto(y, x);
549 if (doomed >= 0)
550 doomed++;
551 goto def;
552 }
553 ungetkey(c), c = '\\';
554 goto noput;
555
556 /*
557 * ^Q Super quote following character
558 * Only ^@ is verboten (trapped at
559 * a lower level) and \n forces a line
560 * split so doesn't really go in.
561 *
562 * ^V Synonym for ^Q
563 */
564 case CTRL(q):
565 case CTRL(v):
566 x = destcol, y = destline;
567 putchar('^');
568 vgoto(y, x);
569 c = getkey();
570#ifdef TIOCSETC
571 if (c == ATTN)
572 c = nttyc.t_intrc;
573#endif
574 if (c != NL) {
575 if (doomed >= 0)
576 doomed++;
577 goto def;
578 }
579 break;
580 }
581 }
582
583 /*
584 * If we get a blank not in the echo area
585 * consider splitting the window in the wrapmargin.
586 */
587 if (c == ' ' && !splitw) {
588 if (gobblebl) {
589 gobbled = 1;
590 continue;
591 }
592 if (value(WRAPMARGIN) && outcol >= OCOLUMNS - value(WRAPMARGIN)) {
593 c = NL;
594 gobblebl = 2;
595 }
596 }
597 switch (c) {
598
599 /*
600 * ^M Except in repeat maps to \n.
601 */
602 case CR:
603 if (vglobp)
604 goto def;
605 c = '\n';
606 /* presto chango ... */
607
608 /*
609 * \n Start new line.
610 */
611 case NL:
612 *aescaped = c;
613 goto vadone;
614
615 /*
616 * escape End insert unless repeat and more to repeat.
617 */
618 case ESCAPE:
619 if (lastvgk)
620 goto def;
621 goto vadone;
622
623 /*
624 * ^D Backtab.
625 * ^T Software forward tab.
626 *
627 * Unless in repeat where this means these
628 * were superquoted in.
629 */
630 case CTRL(d):
631 case CTRL(t):
632 if (vglobp)
633 goto def;
634 /* fall into ... */
635
636 /*
637 * ^D|QUOTE Is a backtab (in a repeated command).
638 */
639 case CTRL(d) | QUOTE:
640 *gcursor = 0;
641 cp = vpastwh(genbuf);
642 c = whitecnt(genbuf);
643 if (ch == CTRL(t)) {
644 /*
645 * ^t just generates new indent replacing
646 * current white space rounded up to soft
647 * tab stop increment.
648 */
649 if (cp != gcursor)
650 /*
651 * BUG: Don't hack ^T except
652 * right after initial
653 * white space.
654 */
655 continue;
656 cp = genindent(iwhite = backtab(c + value(SHIFTWIDTH) + 1));
657 ogcursor = cp;
658 goto vbackup;
659 }
660 /*
661 * ^D works only if we are at the (end of) the
662 * generated autoindent. We count the ^D for repeat
663 * purposes.
664 */
665 if (c == iwhite && c != 0)
666 if (cp == gcursor) {
667 iwhite = backtab(c);
668 CDCNT++;
669 ogcursor = cp = genindent(iwhite);
670 goto vbackup;
671 } else if (&cp[1] == gcursor &&
672 (*cp == '^' || *cp == '0')) {
673 /*
674 * ^^D moves to margin, then back
675 * to current indent on next line.
676 *
677 * 0^D moves to margin and then
678 * stays there.
679 */
680 HADZERO = *cp == '0';
681 ogcursor = cp = genbuf;
682 HADUP = 1 - HADZERO;
683 CDCNT = 1;
684 endim();
685 back1();
887e3e0d 686 vputchar(' ');
09035cec
MH
687 goto vbackup;
688 }
689 if (vglobp && vglobp - iglobp >= 2 &&
690 (vglobp[-2] == '^' || vglobp[-2] == '0')
691 && gcursor == ogcursor + 1)
692 goto bakchar;
693 continue;
694
695 default:
696 /*
697 * Possibly discard control inputs.
698 */
699 if (!vglobp && junk(c)) {
700 beep();
701 continue;
702 }
703def:
704 putchar(c);
705noput:
706 if (gcursor > &genbuf[LBSIZE - 2])
707 error("Line too long");
708 *gcursor++ = c & TRIM;
709 vcsync();
710#ifdef LISPCODE
711 if (value(SHOWMATCH) && !iglobp)
712 if (c == ')' || c == '}')
713 lsmatch(gcursor);
714#endif
715 continue;
716 }
717 }
718vadone:
719 *gcursor = 0;
720 Outchar = OO;
721 endim();
722 return (gcursor);
723}
724
725int vgetsplit();
726char *vsplitpt;
727
728/*
729 * Append the line in buffer at lp
730 * to the buffer after dot.
731 */
732vdoappend(lp)
733 char *lp;
734{
735 register int oing = inglobal;
736
737 vsplitpt = lp;
738 inglobal = 1;
739 ignore(append(vgetsplit, dot));
740 inglobal = oing;
741}
742
743/*
744 * Subroutine for vdoappend to pass to append.
745 */
746vgetsplit()
747{
748
749 if (vsplitpt == 0)
750 return (EOF);
751 strcLIN(vsplitpt);
752 vsplitpt = 0;
753 return (0);
754}
755
756/*
757 * Vmaxrep determines the maximum repetitition factor
758 * allowed that will yield total line length less than
44232d5b 759 * LBSIZE characters and also does hacks for the R command.
09035cec
MH
760 */
761vmaxrep(ch, cnt)
762 char ch;
763 register int cnt;
764{
765 register int len, replen;
766
767 if (cnt > LBSIZE - 2)
768 cnt = LBSIZE - 2;
769 replen = strlen(genbuf);
770 if (ch == 'R') {
771 len = strlen(cursor);
772 if (replen < len)
773 len = replen;
774 CP(cursor, cursor + len);
775 vUD2 += len;
776 }
777 len = strlen(linebuf);
778 if (len + cnt * replen <= LBSIZE - 2)
779 return (cnt);
780 cnt = (LBSIZE - 2 - len) / replen;
781 if (cnt == 0) {
782 vsave();
783 error("Line too long");
784 }
785 return (cnt);
786}