release 3.4, June 24, 1980
[unix-history] / usr / src / usr.bin / ex / ex_voper.c
CommitLineData
e4283c49
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#define blank() isspace(wcursor[0])
7#define forbid(a) if (a) goto errlab;
8
9char vscandir[2] = { '/', 0 };
10
11/*
12 * Decode an operator/operand type command.
13 * Eventually we switch to an operator subroutine in ex_vops.c.
14 * The work here is setting up a function variable to point
15 * to the routine we want, and manipulation of the variables
16 * wcursor and wdot, which mark the other end of the affected
17 * area. If wdot is zero, then the current line is the other end,
18 * and if wcursor is zero, then the first non-blank location of the
19 * other line is implied.
20 */
21operate(c, cnt)
22 register int c, cnt;
23{
24 register int i;
25 int (*moveop)(), (*deleteop)();
26 register int (*opf)();
27 bool subop = 0;
28 char *oglobp, *ocurs;
29 register line *addr;
887e3e0d 30 line *odot;
e4283c49
MH
31 static char lastFKND, lastFCHR;
32 char d;
33
34 moveop = vmove, deleteop = vdelete;
35 wcursor = cursor;
36 wdot = NOLINE;
37 notecnt = 0;
38 dir = 1;
39 switch (c) {
40
41 /*
42 * d delete operator.
43 */
44 case 'd':
45 moveop = vdelete;
46 deleteop = beep;
47 break;
48
49 /*
50 * s substitute characters, like c\040, i.e. change space.
51 */
52 case 's':
53 ungetkey(' ');
54 subop++;
55 /* fall into ... */
56
57 /*
58 * c Change operator.
59 */
60 case 'c':
61 if (c == 'c' && workcmd[0] == 'C' || workcmd[0] == 'S')
62 subop++;
63 moveop = vchange;
64 deleteop = beep;
65 break;
66
67 /*
68 * ! Filter through a UNIX command.
69 */
70 case '!':
71 moveop = vfilter;
72 deleteop = beep;
73 break;
74
75 /*
76 * y Yank operator. Place specified text so that it
77 * can be put back with p/P. Also yanks to named buffers.
78 */
79 case 'y':
80 moveop = vyankit;
81 deleteop = beep;
82 break;
83
84 /*
85 * = Reformat operator (for LISP).
86 */
87#ifdef LISPCODE
88 case '=':
89 forbid(!value(LISP));
90 /* fall into ... */
91#endif
92
93 /*
94 * > Right shift operator.
95 * < Left shift operator.
96 */
97 case '<':
98 case '>':
99 moveop = vshftop;
100 deleteop = beep;
101 break;
102
103 /*
104 * r Replace character under cursor with single following
105 * character.
106 */
107 case 'r':
108 vrep(cnt);
d266c416 109 vmacchng(1);
e4283c49
MH
110 return;
111
112 default:
113 goto nocount;
114 }
d266c416 115 vmacchng(1);
e4283c49
MH
116 /*
117 * Had an operator, so accept another count.
118 * Multiply counts together.
119 */
120 if (isdigit(peekkey()) && peekkey() != '0') {
121 cnt *= vgetcnt();
122 Xcnt = cnt;
123 forbid (cnt <= 0);
124 }
125
126 /*
127 * Get next character, mapping it and saving as
128 * part of command for repeat.
129 */
130 c = map(getesc(),arrows);
131 if (c == 0)
132 return;
133 if (!subop)
134 *lastcp++ = c;
135nocount:
136 opf = moveop;
137 switch (c) {
138
139 /*
140 * b Back up a word.
141 * B Back up a word, liberal definition.
142 */
143 case 'b':
144 case 'B':
145 dir = -1;
146 /* fall into ... */
147
148 /*
149 * w Forward a word.
150 * W Forward a word, liberal definition.
151 */
152 case 'W':
153 case 'w':
154 wdkind = c & ' ';
155 forbid(lfind(2, cnt, opf, 0) < 0);
156 vmoving = 0;
157 break;
158
159 /*
160 * E to end of following blank/nonblank word
161 */
162 case 'E':
163 wdkind = 0;
164 goto ein;
165
166 /*
167 * e To end of following word.
168 */
169 case 'e':
170 wdkind = 1;
171ein:
172 forbid(lfind(3, cnt - 1, opf, 0) < 0);
173 vmoving = 0;
174 break;
175
176 /*
177 * ( Back an s-expression.
178 */
179 case '(':
180 dir = -1;
181 /* fall into... */
182
183 /*
184 * ) Forward an s-expression.
185 */
186 case ')':
187 forbid(lfind(0, cnt, opf, (line *) 0) < 0);
188 markDOT();
189 break;
190
191 /*
192 * { Back an s-expression, but don't stop on atoms.
193 * In text mode, a paragraph. For C, a balanced set
194 * of {}'s.
195 */
196 case '{':
197 dir = -1;
198 /* fall into... */
199
200 /*
201 * } Forward an s-expression, but don't stop on atoms.
202 * In text mode, back paragraph. For C, back a balanced
203 * set of {}'s.
204 */
205 case '}':
206 forbid(lfind(1, cnt, opf, (line *) 0) < 0);
207 markDOT();
208 break;
209
210 /*
211 * % To matching () or {}. If not at ( or { scan for
212 * first such after cursor on this line.
213 */
214 case '%':
215 vsave();
216 i = lmatchp((line *) 0);
217 getDOT();
218 forbid(!i);
219 if (opf != vmove)
220 if (dir > 0)
221 wcursor++;
222 else
223 cursor++;
224 else
225 markDOT();
226 vmoving = 0;
227 break;
228
229 /*
230 * [ Back to beginning of defun, i.e. an ( in column 1.
231 * For text, back to a section macro.
232 * For C, back to a { in column 1 (~~ beg of function.)
233 */
234 case '[':
235 dir = -1;
236 /* fall into ... */
237
238 /*
239 * ] Forward to next defun, i.e. a ( in column 1.
240 * For text, forward section.
241 * For C, forward to a } in column 1 (if delete or such)
242 * or if a move to a { in column 1.
243 */
244 case ']':
245 if (!vglobp)
246 forbid(getkey() != c);
247 if (Xhadcnt)
248 vsetsiz(Xcnt);
249 vsave();
250 i = lbrack(c, opf);
251 getDOT();
252 forbid(!i);
253 markDOT();
254 if (ospeed > B300)
255 hold |= HOLDWIG;
256 break;
257
258 /*
259 * , Invert last find with f F t or T, like inverse
260 * of ;.
261 */
262 case ',':
263 forbid (lastFKND == 0);
264 c = isupper(lastFKND) ? tolower(lastFKND) : toupper(lastFKND);
887e3e0d 265 i = lastFCHR;
e4283c49
MH
266 if (vglobp == 0)
267 vglobp = "";
268 subop++;
269 goto nocount;
270
271 /*
272 * 0 To beginning of real line.
273 */
274 case '0':
275 wcursor = linebuf;
276 vmoving = 0;
277 break;
278
279 /*
280 * ; Repeat last find with f F t or T.
281 */
282 case ';':
283 forbid (lastFKND == 0);
284 c = lastFKND;
887e3e0d 285 i = lastFCHR;
e4283c49
MH
286 subop++;
287 goto nocount;
288
289 /*
290 * F Find single character before cursor in current line.
291 * T Like F, but stops before character.
292 */
293 case 'F': /* inverted find */
294 case 'T':
295 dir = -1;
296 /* fall into ... */
297
298 /*
299 * f Find single character following cursor in current line.
300 * t Like f, but stope before character.
301 */
302 case 'f': /* find */
303 case 't':
887e3e0d
MH
304 if (!subop) {
305 i = getesc();
306 if (i == 0)
307 return;
e4283c49 308 *lastcp++ = i;
887e3e0d 309 }
e4283c49
MH
310 if (vglobp == 0)
311 lastFKND = c, lastFCHR = i;
312 for (; cnt > 0; cnt--)
313 forbid (find(i) == 0);
314 vmoving = 0;
315 switch (c) {
316
317 case 'T':
318 wcursor++;
319 break;
320
321 case 't':
322 wcursor--;
323 case 'f':
324fixup:
325 if (moveop != vmove)
326 wcursor++;
327 break;
328 }
329 break;
330
331 /*
332 * | Find specified print column in current line.
333 */
334 case '|':
335 if (Pline == numbline)
336 cnt += 8;
337 vmovcol = cnt;
338 vmoving = 1;
339 wcursor = vfindcol(cnt);
340 break;
341
342 /*
343 * ^ To beginning of non-white space on line.
344 */
345 case '^':
346 wcursor = vskipwh(linebuf);
347 vmoving = 0;
348 break;
349
350 /*
351 * $ To end of line.
352 */
353 case '$':
354 if (opf == vmove) {
355 vmoving = 1;
356 vmovcol = 20000;
357 } else
358 vmoving = 0;
359 if (cnt > 1) {
360 if (opf == vmove) {
361 wcursor = 0;
362 cnt--;
363 } else
364 wcursor = linebuf;
365 /* This is wrong at EOF */
366 wdot = dot + cnt;
367 break;
368 }
369 if (linebuf[0]) {
370 wcursor = strend(linebuf) - 1;
371 goto fixup;
372 }
373 wcursor = linebuf;
374 break;
375
376 /*
377 * h Back a character.
378 * ^H Back a character.
379 */
380 case 'h':
381 case CTRL(h):
382 dir = -1;
383 /* fall into ... */
384
385 /*
386 * space Forward a character.
387 */
388 case 'l':
389 case ' ':
390 forbid (margin() || opf == vmove && edge());
391 while (cnt > 0 && !margin())
392 wcursor += dir, cnt--;
393 if (margin() && opf == vmove || wcursor < linebuf)
394 wcursor -= dir;
395 vmoving = 0;
396 break;
397
398 /*
399 * D Delete to end of line, short for d$.
400 */
401 case 'D':
402 cnt = INF;
403 goto deleteit;
404
405 /*
406 * X Delete character before cursor.
407 */
408 case 'X':
409 dir = -1;
410 /* fall into ... */
411deleteit:
412 /*
413 * x Delete character at cursor, leaving cursor where it is.
414 */
415 case 'x':
416 if (margin())
417 goto errlab;
d266c416 418 vmacchng(1);
e4283c49
MH
419 while (cnt > 0 && !margin())
420 wcursor += dir, cnt--;
421 opf = deleteop;
422 vmoving = 0;
423 break;
424
425 default:
426 /*
427 * Stuttered operators are equivalent to the operator on
428 * a line, thus turn dd into d_.
429 */
430 if (opf == vmove || c != workcmd[0]) {
431errlab:
432 beep();
433 vmacp = 0;
434 return;
435 }
436 /* fall into ... */
437
438 /*
439 * _ Target for a line or group of lines.
440 * Stuttering is more convenient; this is mostly
441 * for aesthetics.
442 */
443 case '_':
444 wdot = dot + cnt - 1;
445 vmoving = 0;
446 wcursor = 0;
447 break;
448
449 /*
450 * H To first, home line on screen.
451 * Count is for count'th line rather than first.
452 */
453 case 'H':
454 wdot = (dot - vcline) + cnt - 1;
455 if (opf == vmove)
456 markit(wdot);
457 vmoving = 0;
458 wcursor = 0;
459 break;
460
461 /*
462 * - Backwards lines, to first non-white character.
463 */
464 case '-':
465 wdot = dot - cnt;
466 vmoving = 0;
467 wcursor = 0;
468 break;
469
470 /*
471 * ^P To previous line same column. Ridiculous on the
472 * console of the VAX since it puts console in LSI mode.
473 */
474 case 'k':
475 case CTRL(p):
476 wdot = dot - cnt;
477 if (vmoving == 0)
478 vmoving = 1, vmovcol = column(cursor);
479 wcursor = 0;
480 break;
481
482 /*
483 * L To last line on screen, or count'th line from the
484 * bottom.
485 */
486 case 'L':
487 wdot = dot + vcnt - vcline - cnt;
488 if (opf == vmove)
489 markit(wdot);
490 vmoving = 0;
491 wcursor = 0;
492 break;
493
494 /*
495 * M To the middle of the screen.
496 */
497 case 'M':
498 wdot = dot + ((vcnt + 1) / 2) - vcline - 1;
499 if (opf == vmove)
500 markit(wdot);
501 vmoving = 0;
502 wcursor = 0;
503 break;
504
505 /*
506 * + Forward line, to first non-white.
507 *
508 * CR Convenient synonym for +.
509 */
510 case '+':
511 case CR:
512 wdot = dot + cnt;
513 vmoving = 0;
514 wcursor = 0;
515 break;
516
517 /*
518 * ^N To next line, same column if possible.
519 *
520 * LF Linefeed is a convenient synonym for ^N.
521 */
522 case CTRL(n):
523 case 'j':
524 case NL:
525 wdot = dot + cnt;
526 if (vmoving == 0)
527 vmoving = 1, vmovcol = column(cursor);
528 wcursor = 0;
529 break;
530
531 /*
532 * n Search to next match of current pattern.
533 */
534 case 'n':
535 vglobp = vscandir;
536 c = *vglobp++;
537 goto nocount;
538
539 /*
540 * N Like n but in reverse direction.
541 */
542 case 'N':
543 vglobp = vscandir[0] == '/' ? "?" : "/";
544 c = *vglobp++;
545 goto nocount;
546
547 /*
548 * ' Return to line specified by following mark,
549 * first white position on line.
550 *
551 * ` Return to marked line at remembered column.
552 */
553 case '\'':
554 case '`':
555 d = c;
556 c = getesc();
557 if (c == 0)
558 return;
559 c = markreg(c);
560 forbid (c == 0);
561 wdot = getmark(c);
562 forbid (wdot == NOLINE);
563 if (Xhadcnt)
564 vsetsiz(Xcnt);
565 vmoving = 0;
566 wcursor = d == '`' ? ncols[c - 'a'] : 0;
567 if (opf == vmove && (wdot != dot || (d == '`' && wcursor != cursor)))
568 markDOT();
569 if (wcursor) {
570 vsave();
571 getline(*wdot);
572 if (wcursor > strend(linebuf))
573 wcursor = 0;
574 getDOT();
575 }
576 if (ospeed > B300)
577 hold |= HOLDWIG;
578 break;
579
580 /*
581 * G Goto count'th line, or last line if no count
582 * given.
583 */
584 case 'G':
585 if (!Xhadcnt)
586 cnt = lineDOL();
587 wdot = zero + cnt;
588 forbid (wdot < one || wdot > dol);
589 if (opf == vmove)
590 markit(wdot);
591 vmoving = 0;
592 wcursor = 0;
593 break;
594
595 /*
596 * / Scan forward for following re.
597 * ? Scan backward for following re.
598 */
599 case '/':
600 case '?':
601 if (Xhadcnt)
602 vsetsiz(Xcnt);
603 vsave();
604 ocurs = cursor;
887e3e0d 605 odot = dot;
e4283c49
MH
606 wcursor = 0;
607 if (readecho(c))
608 return;
609 if (!vglobp)
610 vscandir[0] = genbuf[0];
611 oglobp = globp; CP(vutmp, genbuf); globp = vutmp;
887e3e0d
MH
612 d = peekc;
613fromsemi:
614 ungetchar(0);
615 fixech();
e4283c49
MH
616 CATCH
617#ifndef CBREAK
618 /*
619 * Lose typeahead (ick).
620 */
621 vcook();
622#endif
623 addr = address(cursor);
624#ifndef CBREAK
625 vraw();
626#endif
627 ONERR
628#ifndef CBREAK
629 vraw();
630#endif
887e3e0d 631slerr:
e4283c49 632 globp = oglobp;
887e3e0d
MH
633 dot = odot;
634 cursor = ocurs;
e4283c49
MH
635 ungetchar(d);
636 splitw = 0;
637 vclean();
638 vjumpto(dot, ocurs, 0);
639 return;
640 ENDCATCH
641 if (globp == 0)
642 globp = "";
643 else if (peekc)
644 --globp;
887e3e0d
MH
645 if (*globp == ';') {
646 /* /foo/;/bar/ */
647 globp++;
648 dot = addr;
649 cursor = loc1;
650 goto fromsemi;
651 }
652 dot = odot;
e4283c49
MH
653 ungetchar(d);
654 c = 0;
655 if (*globp == 'z')
656 globp++, c = '\n';
657 if (any(*globp, "^+-."))
658 c = *globp++;
659 i = 0;
660 while (isdigit(*globp))
661 i = i * 10 + *globp++ - '0';
887e3e0d 662 if (any(*globp, "^+-."))
e4283c49 663 c = *globp++;
887e3e0d
MH
664 if (*globp) {
665 /* random junk after the pattern */
666 beep();
667 goto slerr;
668 }
e4283c49
MH
669 globp = oglobp;
670 splitw = 0;
671 vmoving = 0;
672 wcursor = loc1;
673 if (i != 0)
674 vsetsiz(i);
675 if (opf == vmove) {
676 if (state == ONEOPEN || state == HARDOPEN)
677 outline = destline = WBOT;
678 if (addr != dot || loc1 != cursor)
679 markDOT();
680 if (loc1 > linebuf && *loc1 == 0)
681 loc1--;
682 if (c)
683 vjumpto(addr, loc1, c);
684 else {
685 vmoving = 0;
686 if (loc1) {
687 vmoving++;
688 vmovcol = column(loc1);
689 }
690 getDOT();
691 if (state == CRTOPEN && addr != dot)
692 vup1();
693 vupdown(addr - dot, NOSTR);
694 }
695 return;
696 }
697 lastcp[-1] = 'n';
698 getDOT();
699 wdot = addr;
700 break;
701 }
702 /*
703 * Apply.
704 */
705 if (vreg && wdot == 0)
706 wdot = dot;
707 (*opf)(c);
708 wdot = NOLINE;
709}
710
711/*
712 * Find single character c, in direction dir from cursor.
713 */
714find(c)
715 char c;
716{
717
718 for(;;) {
719 if (edge())
720 return (0);
721 wcursor += dir;
722 if (*wcursor == c)
723 return (1);
724 }
725}
726
727/*
728 * Do a word motion with operator op, and cnt more words
729 * to go after this.
730 */
731word(op, cnt)
732 register int (*op)();
733 int cnt;
734{
735 register int which;
736 register char *iwc;
737 register line *iwdot = wdot;
738
739 if (dir == 1) {
740 iwc = wcursor;
741 which = wordch(wcursor);
742 while (wordof(which, wcursor)) {
743 if (cnt == 1 && op != vmove && wcursor[1] == 0) {
744 wcursor++;
745 break;
746 }
747 if (!lnext())
748 return (0);
749 if (wcursor == linebuf)
750 break;
751 }
752 /* Unless last segment of a change skip blanks */
753 if (op != vchange || cnt > 1)
754 while (!margin() && blank())
755 wcursor++;
756 else
757 if (wcursor == iwc && iwdot == wdot && *iwc)
758 wcursor++;
759 if (op == vmove && margin())
760 wcursor--;
761 } else {
762 if (!lnext())
763 return (0);
764 while (blank())
765 if (!lnext())
766 return (0);
767 if (!margin()) {
768 which = wordch(wcursor);
769 while (!margin() && wordof(which, wcursor))
770 wcursor--;
771 }
772 if (wcursor < linebuf || !wordof(which, wcursor))
773 wcursor++;
774 }
775 return (1);
776}
777
778/*
779 * To end of word, with operator op and cnt more motions
780 * remaining after this.
781 */
782eend(op)
783 register int (*op)();
784{
785 register int which;
786
787 if (!lnext())
788 return;
789 while (blank())
790 if (!lnext())
791 return;
792 which = wordch(wcursor);
793 while (wordof(which, wcursor)) {
794 if (wcursor[1] == 0) {
795 wcursor++;
796 break;
797 }
798 if (!lnext())
799 return;
800 }
801 if (op != vchange && op != vdelete && wcursor > linebuf)
802 wcursor--;
803}
804
805/*
806 * Wordof tells whether the character at *wc is in a word of
807 * kind which (blank/nonblank words are 0, conservative words 1).
808 */
809wordof(which, wc)
810 char which;
811 register char *wc;
812{
813
814 if (isspace(*wc))
815 return (0);
816 return (!wdkind || wordch(wc) == which);
817}
818
819/*
820 * Wordch tells whether character at *wc is a word character
821 * i.e. an alfa, digit, or underscore.
822 */
823wordch(wc)
824 char *wc;
825{
826 register int c;
827
828 c = wc[0];
829 return (isalpha(c) || isdigit(c) || c == '_');
830}
831
832/*
833 * Edge tells when we hit the last character in the current line.
834 */
835edge()
836{
837
838 if (linebuf[0] == 0)
839 return (1);
840 if (dir == 1)
841 return (wcursor[1] == 0);
842 else
843 return (wcursor == linebuf);
844}
845
846/*
847 * Margin tells us when we have fallen off the end of the line.
848 */
849margin()
850{
851
852 return (wcursor < linebuf || wcursor[0] == 0);
853}