xref: /original-bsd/usr.bin/ex/ex_voper.c (revision cc77da92)
1 /* Copyright (c) 1980 Regents of the University of California */
2 static char *sccsid = "@(#)ex_voper.c	5.1 08/28/80";
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 
10 char	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  */
22 operate(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;
31 	line *odot;
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);
110 		vmacchng(1);
111 		return;
112 
113 	default:
114 		goto nocount;
115 	}
116 	vmacchng(1);
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;
136 nocount:
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;
172 ein:
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);
248 		forbid (Xhadcnt);
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);
265 		i = lastFCHR;
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;
285 		i = lastFCHR;
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':
304 		if (!subop) {
305 			i = getesc();
306 			if (i == 0)
307 				return;
308 			*lastcp++ = i;
309 		}
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':
324 fixup:
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 ... */
411 deleteit:
412 	/*
413 	 * x		Delete character at cursor, leaving cursor where it is.
414 	 */
415 	case 'x':
416 		if (margin())
417 			goto errlab;
418 		vmacchng(1);
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]) {
431 errlab:
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 		forbid (Xhadcnt);
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 '?':
600 		forbid (Xhadcnt);
601 		vsave();
602 		ocurs = cursor;
603 		odot = dot;
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;
610 		d = peekc;
611 fromsemi:
612 		ungetchar(0);
613 		fixech();
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
629 slerr:
630 			globp = oglobp;
631 			dot = odot;
632 			cursor = ocurs;
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;
643 		if (*globp == ';') {
644 			/* /foo/;/bar/ */
645 			globp++;
646 			dot = addr;
647 			cursor = loc1;
648 			goto fromsemi;
649 		}
650 		dot = odot;
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';
660 		if (any(*globp, "^+-."))
661 			c = *globp++;
662 		if (*globp) {
663 			/* random junk after the pattern */
664 			beep();
665 			goto slerr;
666 		}
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  */
712 find(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  */
729 word(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  */
780 eend(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  */
807 wordof(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  */
821 wordch(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  */
833 edge()
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  */
847 margin()
848 {
849 
850 	return (wcursor < linebuf || wcursor[0] == 0);
851 }
852