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