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