xref: /original-bsd/usr.bin/ex/ex_voper.c (revision 56abee86)
1 /* Copyright (c) 1980 Regents of the University of California */
2 static char *sccsid = "@(#)ex_voper.c	6.2 10/23/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 		vmacchng(1);
110 		vrep(cnt);
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 #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
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);
252 		forbid (Xhadcnt);
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);
269 		i = lastFCHR;
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;
289 		i = lastFCHR;
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':
308 		if (!subop) {
309 			i = getesc();
310 			if (i == 0)
311 				return;
312 			*lastcp++ = i;
313 		}
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':
328 fixup:
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 ... */
415 deleteit:
416 	/*
417 	 * x		Delete character at cursor, leaving cursor where it is.
418 	 */
419 	case 'x':
420 		if (margin())
421 			goto errlab;
422 		vmacchng(1);
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]) {
435 errlab:
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);
567 		forbid (Xhadcnt);
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 '?':
604 		forbid (Xhadcnt);
605 		vsave();
606 		ocurs = cursor;
607 		odot = dot;
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;
614 		d = peekc;
615 fromsemi:
616 		ungetchar(0);
617 		fixech();
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
633 slerr:
634 			globp = oglobp;
635 			dot = odot;
636 			cursor = ocurs;
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;
647 		if (*globp == ';') {
648 			/* /foo/;/bar/ */
649 			globp++;
650 			dot = addr;
651 			cursor = loc1;
652 			goto fromsemi;
653 		}
654 		dot = odot;
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';
664 		if (any(*globp, "^+-."))
665 			c = *globp++;
666 		if (*globp) {
667 			/* random junk after the pattern */
668 			beep();
669 			goto slerr;
670 		}
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  */
716 find(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  */
733 word(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  */
784 eend(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  */
811 wordof(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  */
825 wordch(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  */
837 edge()
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  */
851 margin()
852 {
853 
854 	return (wcursor < linebuf || wcursor[0] == 0);
855 }
856