Release 3.5, 8/28/80, previous "release" not released
[unix-history] / usr / src / usr.bin / ex / ex_voper.c
CommitLineData
7c4625ef 1/* Copyright (c) 1980 Regents of the University of California */
10499a70 2static char *sccsid = "@(#)ex_voper.c 5.1 %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
MH
32 static char lastFKND, lastFCHR;
33 char d;
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':
109 vrep(cnt);
d266c416 110 vmacchng(1);
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);
218 getDOT();
219 forbid(!i);
220 if (opf != vmove)
221 if (dir > 0)
222 wcursor++;
223 else
224 cursor++;
225 else
226 markDOT();
227 vmoving = 0;
228 break;
229
230 /*
231 * [ Back to beginning of defun, i.e. an ( in column 1.
232 * For text, back to a section macro.
233 * For C, back to a { in column 1 (~~ beg of function.)
234 */
235 case '[':
236 dir = -1;
237 /* fall into ... */
238
239 /*
240 * ] Forward to next defun, i.e. a ( in column 1.
241 * For text, forward section.
242 * For C, forward to a } in column 1 (if delete or such)
243 * or if a move to a { in column 1.
244 */
245 case ']':
246 if (!vglobp)
247 forbid(getkey() != c);
10499a70 248 forbid (Xhadcnt);
e4283c49
MH
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);
10499a70 563 forbid (Xhadcnt);
e4283c49
MH
564 vmoving = 0;
565 wcursor = d == '`' ? ncols[c - 'a'] : 0;
566 if (opf == vmove && (wdot != dot || (d == '`' && wcursor != cursor)))
567 markDOT();
568 if (wcursor) {
569 vsave();
570 getline(*wdot);
571 if (wcursor > strend(linebuf))
572 wcursor = 0;
573 getDOT();
574 }
575 if (ospeed > B300)
576 hold |= HOLDWIG;
577 break;
578
579 /*
580 * G Goto count'th line, or last line if no count
581 * given.
582 */
583 case 'G':
584 if (!Xhadcnt)
585 cnt = lineDOL();
586 wdot = zero + cnt;
587 forbid (wdot < one || wdot > dol);
588 if (opf == vmove)
589 markit(wdot);
590 vmoving = 0;
591 wcursor = 0;
592 break;
593
594 /*
595 * / Scan forward for following re.
596 * ? Scan backward for following re.
597 */
598 case '/':
599 case '?':
10499a70 600 forbid (Xhadcnt);
e4283c49
MH
601 vsave();
602 ocurs = cursor;
887e3e0d 603 odot = dot;
e4283c49
MH
604 wcursor = 0;
605 if (readecho(c))
606 return;
607 if (!vglobp)
608 vscandir[0] = genbuf[0];
609 oglobp = globp; CP(vutmp, genbuf); globp = vutmp;
887e3e0d
MH
610 d = peekc;
611fromsemi:
612 ungetchar(0);
613 fixech();
e4283c49
MH
614 CATCH
615#ifndef CBREAK
616 /*
617 * Lose typeahead (ick).
618 */
619 vcook();
620#endif
621 addr = address(cursor);
622#ifndef CBREAK
623 vraw();
624#endif
625 ONERR
626#ifndef CBREAK
627 vraw();
628#endif
887e3e0d 629slerr:
e4283c49 630 globp = oglobp;
887e3e0d
MH
631 dot = odot;
632 cursor = ocurs;
e4283c49
MH
633 ungetchar(d);
634 splitw = 0;
635 vclean();
636 vjumpto(dot, ocurs, 0);
637 return;
638 ENDCATCH
639 if (globp == 0)
640 globp = "";
641 else if (peekc)
642 --globp;
887e3e0d
MH
643 if (*globp == ';') {
644 /* /foo/;/bar/ */
645 globp++;
646 dot = addr;
647 cursor = loc1;
648 goto fromsemi;
649 }
650 dot = odot;
e4283c49
MH
651 ungetchar(d);
652 c = 0;
653 if (*globp == 'z')
654 globp++, c = '\n';
655 if (any(*globp, "^+-."))
656 c = *globp++;
657 i = 0;
658 while (isdigit(*globp))
659 i = i * 10 + *globp++ - '0';
887e3e0d 660 if (any(*globp, "^+-."))
e4283c49 661 c = *globp++;
887e3e0d
MH
662 if (*globp) {
663 /* random junk after the pattern */
664 beep();
665 goto slerr;
666 }
e4283c49
MH
667 globp = oglobp;
668 splitw = 0;
669 vmoving = 0;
670 wcursor = loc1;
671 if (i != 0)
672 vsetsiz(i);
673 if (opf == vmove) {
674 if (state == ONEOPEN || state == HARDOPEN)
675 outline = destline = WBOT;
676 if (addr != dot || loc1 != cursor)
677 markDOT();
678 if (loc1 > linebuf && *loc1 == 0)
679 loc1--;
680 if (c)
681 vjumpto(addr, loc1, c);
682 else {
683 vmoving = 0;
684 if (loc1) {
685 vmoving++;
686 vmovcol = column(loc1);
687 }
688 getDOT();
689 if (state == CRTOPEN && addr != dot)
690 vup1();
691 vupdown(addr - dot, NOSTR);
692 }
693 return;
694 }
695 lastcp[-1] = 'n';
696 getDOT();
697 wdot = addr;
698 break;
699 }
700 /*
701 * Apply.
702 */
703 if (vreg && wdot == 0)
704 wdot = dot;
705 (*opf)(c);
706 wdot = NOLINE;
707}
708
709/*
710 * Find single character c, in direction dir from cursor.
711 */
712find(c)
713 char c;
714{
715
716 for(;;) {
717 if (edge())
718 return (0);
719 wcursor += dir;
720 if (*wcursor == c)
721 return (1);
722 }
723}
724
725/*
726 * Do a word motion with operator op, and cnt more words
727 * to go after this.
728 */
729word(op, cnt)
730 register int (*op)();
731 int cnt;
732{
733 register int which;
734 register char *iwc;
735 register line *iwdot = wdot;
736
737 if (dir == 1) {
738 iwc = wcursor;
739 which = wordch(wcursor);
740 while (wordof(which, wcursor)) {
741 if (cnt == 1 && op != vmove && wcursor[1] == 0) {
742 wcursor++;
743 break;
744 }
745 if (!lnext())
746 return (0);
747 if (wcursor == linebuf)
748 break;
749 }
750 /* Unless last segment of a change skip blanks */
751 if (op != vchange || cnt > 1)
752 while (!margin() && blank())
753 wcursor++;
754 else
755 if (wcursor == iwc && iwdot == wdot && *iwc)
756 wcursor++;
757 if (op == vmove && margin())
758 wcursor--;
759 } else {
760 if (!lnext())
761 return (0);
762 while (blank())
763 if (!lnext())
764 return (0);
765 if (!margin()) {
766 which = wordch(wcursor);
767 while (!margin() && wordof(which, wcursor))
768 wcursor--;
769 }
770 if (wcursor < linebuf || !wordof(which, wcursor))
771 wcursor++;
772 }
773 return (1);
774}
775
776/*
777 * To end of word, with operator op and cnt more motions
778 * remaining after this.
779 */
780eend(op)
781 register int (*op)();
782{
783 register int which;
784
785 if (!lnext())
786 return;
787 while (blank())
788 if (!lnext())
789 return;
790 which = wordch(wcursor);
791 while (wordof(which, wcursor)) {
792 if (wcursor[1] == 0) {
793 wcursor++;
794 break;
795 }
796 if (!lnext())
797 return;
798 }
799 if (op != vchange && op != vdelete && wcursor > linebuf)
800 wcursor--;
801}
802
803/*
804 * Wordof tells whether the character at *wc is in a word of
805 * kind which (blank/nonblank words are 0, conservative words 1).
806 */
807wordof(which, wc)
808 char which;
809 register char *wc;
810{
811
812 if (isspace(*wc))
813 return (0);
814 return (!wdkind || wordch(wc) == which);
815}
816
817/*
818 * Wordch tells whether character at *wc is a word character
819 * i.e. an alfa, digit, or underscore.
820 */
821wordch(wc)
822 char *wc;
823{
824 register int c;
825
826 c = wc[0];
827 return (isalpha(c) || isdigit(c) || c == '_');
828}
829
830/*
831 * Edge tells when we hit the last character in the current line.
832 */
833edge()
834{
835
836 if (linebuf[0] == 0)
837 return (1);
838 if (dir == 1)
839 return (wcursor[1] == 0);
840 else
841 return (wcursor == linebuf);
842}
843
844/*
845 * Margin tells us when we have fallen off the end of the line.
846 */
847margin()
848{
849
850 return (wcursor < linebuf || wcursor[0] == 0);
851}