xref: /original-bsd/usr.bin/ex/ex_voper.c (revision 3705696b)
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  */
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  */
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  */
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  */
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  */
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  */
861 margin()
862 {
863 
864 	return (wcursor < linebuf || wcursor[0] == 0);
865 }
866