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