xref: /illumos-gate/usr/src/cmd/vi/port/ex_vops.c (revision cb6207858a9fcc2feaee22e626912fba281ac969)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 
30 /* Copyright (c) 1981 Regents of the University of California */
31 
32 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 #include "ex.h"
35 #include "ex_tty.h"
36 #include "ex_vis.h"
37 
38 void fixundo(void);
39 
40 /*
41  * This file defines the operation sequences which interface the
42  * logical changes to the file buffer with the internal and external
43  * display representations.
44  */
45 
46 /*
47  * Undo.
48  *
49  * Undo is accomplished in two ways.  We often for small changes in the
50  * current line know how (in terms of a change operator) how the change
51  * occurred.  Thus on an intelligent terminal we can undo the operation
52  * by another such operation, using insert and delete character
53  * stuff.  The pointers vU[AD][12] index the buffer vutmp when this
54  * is possible and provide the necessary information.
55  *
56  * The other case is that the change involved multiple lines or that
57  * we have moved away from the line or forgotten how the change was
58  * accomplished.  In this case we do a redisplay and hope that the
59  * low level optimization routines (which don't look for winning
60  * via insert/delete character) will not lose too badly.
61  */
62 unsigned char	*vUA1, *vUA2;
63 unsigned char	*vUD1, *vUD2;
64 
65 void
66 vUndo(void)
67 {
68 
69 	/*
70 	 * Avoid UU which clobbers ability to do u.
71 	 */
72 	if (vundkind == VNONE || vundkind == VCAPU || vUNDdot != dot) {
73 		(void) beep();
74 		return;
75 	}
76 	CP(vutmp, linebuf);
77 	vUD1 = linebuf; vUD2 = strend(linebuf);
78 	putmk1(dot, vUNDsav);
79 	getDOT();
80 	vUA1 = linebuf; vUA2 = strend(linebuf);
81 	vundkind = VCAPU;
82 	if (state == ONEOPEN || state == HARDOPEN) {
83 		vjumpto(dot, vUNDcurs, 0);
84 		return;
85 	}
86 	vdirty(vcline, 1);
87 	if(MB_CUR_MAX > 1)
88 		rewrite = _ON;
89 	vsyncCL();
90 	if(MB_CUR_MAX > 1)
91 		rewrite = _OFF;
92 	cursor = linebuf;
93 	vfixcurs();
94 }
95 
96 void
97 vundo(show)
98 bool show;	/* if true update the screen */
99 {
100 	int cnt;
101 	line *addr;
102 	unsigned char *cp;
103 	unsigned char temp[LBSIZE];
104 	bool savenote;
105 	int (*OO)();
106 	short oldhold = hold;
107 	unsigned multic[MULTI_BYTE_MAX];
108 	int length;
109 	wchar_t wchar;
110 
111 	switch (vundkind) {
112 
113 	case VMANYINS:
114 		wcursor = 0;
115 		addr1 = undap1;
116 		addr2 = undap2 - 1;
117 		vsave();
118 		(void) YANKreg('1');
119 		notecnt = 0;
120 		/* fall into ... */
121 
122 	case VMANY:
123 	case VMCHNG:
124 		vsave();
125 		addr = dot - vcline;
126 		notecnt = 1;
127 		if (undkind == UNDPUT && undap1 == undap2) {
128 			(void) beep();
129 			break;
130 		}
131 		/*
132 		 * Undo() call below basically replaces undap1 to undap2-1
133 		 * with dol through unddol-1.  Hack screen image to
134 		 * reflect this replacement.
135 		 */
136 		if (show)
137 			if (undkind == UNDMOVE)
138 				vdirty(0, lines);
139 			else
140 				vreplace(undap1 - addr, undap2 - undap1,
141 				    undkind == UNDPUT ? 0 : unddol - dol);
142 		savenote = notecnt;
143 		undo(1);
144 		if (show && (vundkind != VMCHNG || addr != dot))
145 			killU();
146 		vundkind = VMANY;
147 		cnt = dot - addr;
148 		if (cnt < 0 || cnt > vcnt || state != VISUAL) {
149 			if (show)
150 				vjumpto(dot, (unsigned char *)NOSTR, '.');
151 			break;
152 		}
153 		if (!savenote)
154 			notecnt = 0;
155 		if (show) {
156 			vcline = cnt;
157 			if(MB_CUR_MAX > 1)
158 				rewrite = _ON;
159 			vrepaint(vmcurs);
160 			if(MB_CUR_MAX > 1)
161 				rewrite = _OFF;
162 		}
163 		vmcurs = 0;
164 		break;
165 
166 	case VCHNG:
167 	case VCAPU:
168 		vundkind = VCHNG;
169 		strcpy(temp, vutmp);
170 		strcpy(vutmp, linebuf);
171 		doomed = lcolumn(vUA2) - lcolumn(vUA1);
172 		strcLIN(temp);
173 		cp = vUA1; vUA1 = vUD1; vUD1 = cp;
174 		cp = vUA2; vUA2 = vUD2; vUD2 = cp;
175 		if (!show)
176 			break;
177 		cursor = vUD1;
178 		if (state == HARDOPEN) {
179 			doomed = 0;
180 			vsave();
181 			vopen(dot, WBOT);
182 			vnline(cursor);
183 			break;
184 		}
185 		/*
186 		 * Pseudo insert command.
187 		 */
188 		vcursat(cursor);
189 		OO = Outchar; Outchar = vinschar; hold |= HOLDQIK;
190 		vprepins();
191 		temp[vUA2 - linebuf] = 0;
192 		for (cp = &temp[vUA1 - linebuf]; *cp;) {
193 			length = mbtowc(&wchar, (char *)cp, MULTI_BYTE_MAX);
194 			if(length < 0) {
195 				putoctal = 1;
196 				putchar(*cp++);
197 				putoctal = 0;
198 			} else {
199 				putchar(wchar);
200 				cp += length;
201 			}
202 		}
203 		Outchar = OO; hold = oldhold;
204 		endim();
205 		physdc(cindent(), cindent() + doomed);
206 		doomed = 0;
207 		vdirty(vcline, 1);
208 		if(MB_CUR_MAX > 1)
209 			rewrite = _ON;
210 		vsyncCL();
211 		if(MB_CUR_MAX > 1)
212 			rewrite = _OFF;
213 		if (cursor > linebuf && cursor >= strend(linebuf))
214 			cursor = lastchr(linebuf, cursor);
215 		vfixcurs();
216 		break;
217 
218 	case VNONE:
219 		(void) beep();
220 		break;
221 	}
222 }
223 
224 /*
225  * Routine to handle a change inside a macro.
226  * Fromvis is true if we were called from a visual command (as
227  * opposed to an ex command).  This has nothing to do with being
228  * in open/visual mode as :s/foo/bar is not fromvis.
229  */
230 void
231 vmacchng(fromvis)
232 bool fromvis;
233 {
234 	line *savedot, *savedol;
235 	unsigned char *savecursor;
236 	unsigned char savelb[LBSIZE];
237 	int nlines, more;
238 	line *a1, *a2;
239 	unsigned char ch;	/* DEBUG */
240 	int copyw(), copywR();
241 
242 	if (!inopen)
243 		return;
244 	if (!vmacp)
245 		vch_mac = VC_NOTINMAC;
246 #ifdef UNDOTRACE
247 	if (trace)
248 		fprintf(trace, "vmacchng, vch_mac=%d, linebuf='%s', *dot=%o\n", vch_mac, linebuf, *dot);
249 #endif
250 	if (vmacp && fromvis)
251 		vsave();
252 #ifdef UNDOTRACE
253 	if (trace)
254 		fprintf(trace, "after vsave, linebuf='%s', *dot=%o\n", linebuf, *dot);
255 #endif
256 	switch(vch_mac) {
257 	case VC_NOCHANGE:
258 		vch_mac = VC_ONECHANGE;
259 		break;
260 	case VC_ONECHANGE:
261 		/* Save current state somewhere */
262 #ifdef UNDOTRACE
263 		vudump("before vmacchng hairy case");
264 #endif
265 		savedot = dot; savedol = dol; savecursor = cursor;
266 		CP(savelb, linebuf);
267 		nlines = dol - zero;
268 		while ((line *) endcore - truedol < nlines)
269 			if (morelines() < 0)
270 				return;	/* or could be fatal error */
271 		copyw(truedol+1, zero+1, nlines);
272 		truedol += nlines;
273 
274 #ifdef UNDOTRACE
275 		visdump("before vundo");
276 #endif
277 		/* Restore state as it was at beginning of macro */
278 		vundo(0);
279 #ifdef UNDOTRACE
280 		visdump("after vundo");
281 		vudump("after vundo");
282 #endif
283 
284 		/* Do the saveall we should have done then */
285 		saveall();
286 #ifdef UNDOTRACE
287 		vudump("after saveall");
288 #endif
289 
290 		/* Restore current state from where saved */
291 		more = savedol - dol; /* amount we shift everything by */
292 		if (more)
293 			(*(more>0 ? copywR : copyw))(savedol+1, dol+1, truedol-dol);
294 		unddol += more; truedol += more; undap2 += more;
295 
296 		truedol -= nlines;
297 		copyw(zero+1, truedol+1, nlines);
298 		dot = savedot; dol = savedol ; cursor = savecursor;
299 		CP(linebuf, savelb);
300 		vch_mac = VC_MANYCHANGE;
301 
302 		/* Arrange that no further undo saving happens within macro */
303 		otchng = tchng;	/* Copied this line blindly - bug? */
304 		inopen = -1;	/* no need to save since it had to be 1 or -1 before */
305 		vundkind = VMANY;
306 #ifdef UNDOTRACE
307 		vudump("after vmacchng");
308 #endif
309 		break;
310 	case VC_NOTINMAC:
311 	case VC_MANYCHANGE:
312 		/* Nothing to do for various reasons. */
313 		break;
314 	}
315 }
316 
317 /*
318  * Initialize undo information before an append.
319  */
320 void
321 vnoapp(void)
322 {
323 	vUD1 = vUD2 = cursor;
324 	/*
325 	 * XPG6 assertion 273: Set vmcurs so that undo positions the
326 	 * cursor column correctly when we've moved off the initial
327 	 * line that was changed with the A, a, i, and R commands,
328 	 * eg: when G has moved us off the line, or when a
329 	 * multi-line change was done.
330 	 */
331 	if (lastcmd[0] == 'A' || lastcmd[0] == 'a' || lastcmd[0] == 'i' ||
332 	    lastcmd[0] == 'R') {
333 		vmcurs = cursor;
334 	}
335 }
336 
337 /*
338  * All the rest of the motion sequences have one or more
339  * cases to deal with.  In the case wdot == 0, operation
340  * is totally within current line, from cursor to wcursor.
341  * If wdot is given, but wcursor is 0, then operation affects
342  * the inclusive line range.  The hardest case is when both wdot
343  * and wcursor are given, then operation affects from line dot at
344  * cursor to line wdot at wcursor.
345  */
346 
347 /*
348  * Move is simple, except for moving onto new lines in hardcopy open mode.
349  */
350 int
351 vmove(void)
352 {
353 	int cnt;
354 
355 	if (wdot) {
356 		if (wdot < one || wdot > dol) {
357 			(void) beep();
358 			return (0);
359 		}
360 		cnt = wdot - dot;
361 		wdot = NOLINE;
362 		if (cnt)
363 			killU();
364 		vupdown(cnt, wcursor);
365 		return (0);
366 	}
367 
368 	/*
369 	 * When we move onto a new line, save information for U undo.
370 	 */
371 	if (vUNDdot != dot) {
372 		vUNDsav = *dot;
373 		vUNDcurs = wcursor;
374 		vUNDdot = dot;
375 	}
376 
377 	/*
378 	 * In hardcopy open, type characters to left of cursor
379 	 * on new line, or back cursor up if its to left of where we are.
380 	 * In any case if the current line is ``rubbled'' i.e. has trashy
381 	 * looking overstrikes on it or \'s from deletes, we reprint
382 	 * so it is more comprehensible (and also because we can't work
383 	 * if we let it get more out of sync since column() won't work right.
384 	 */
385 	if (state == HARDOPEN) {
386 		unsigned char *cp;
387 		if (rubble) {
388 			int c;
389 			int oldhold = hold;
390 
391 			sethard();
392 			cp = wcursor;
393 			c = *cp;
394 			*cp = 0;
395 			hold |= HOLDDOL;
396 			(void) vreopen(WTOP, lineDOT(), vcline);
397 			hold = oldhold;
398 			*cp = c;
399 		} else if (wcursor > cursor) {
400 			int length;
401 			char multic[MULTI_BYTE_MAX];
402 			wchar_t wchar;
403 			vfixcurs();
404 			for (cp = cursor; *cp && cp < wcursor;) {
405 				length = mbtowc(&wchar, (char *)cp, MULTI_BYTE_MAX);
406 				if(length == 0)
407 					putchar(' ');
408 				else if(length < 0) {
409 					putoctal = 1;
410 					putchar(*cp++);
411 					putoctal = 0;
412 				} else {
413 					cp += length;
414 					putchar(wchar);
415 				}
416 			}
417 		}
418 	}
419 	vsetcurs(wcursor);
420 	return (0);
421 }
422 
423 /*
424  * Delete operator.
425  *
426  * Hard case of deleting a range where both wcursor and wdot
427  * are specified is treated as a special case of change and handled
428  * by vchange (although vchange may pass it back if it degenerates
429  * to a full line range delete.)
430  */
431 int
432 vdelete(unsigned char c)
433 {
434 	unsigned char *cp;
435 	int i;
436 
437 	if (wdot) {
438 		if (wcursor) {
439 			(void) vchange('d');
440 			return (0);
441 		}
442 		if ((i = xdw()) < 0)
443 			return (0);
444 		if (state != VISUAL) {
445 			vgoto(LINE(0), 0);
446 			(void) vputchar('@');
447 		}
448 		wdot = dot;
449 		vremote(i, delete, 0);
450 		notenam = (unsigned char *)"delete";
451 		DEL[0] = 0;
452 		killU();
453 		vreplace(vcline, i, 0);
454 		if (wdot > dol)
455 			vcline--;
456 		vrepaint(NOSTR);
457 		return (0);
458 	}
459 	if (wcursor < linebuf)
460 		wcursor = linebuf;
461 	if (cursor == wcursor) {
462 		(void) beep();
463 		return (0);
464 	}
465 	i = vdcMID();
466 	cp = cursor;
467 	setDEL();
468 	CP(cp, wcursor);
469 	if (cp > linebuf && (cp[0] == 0 || c == '#'))
470 		cp = lastchr(linebuf, cp);
471 	if (state == HARDOPEN) {
472 		bleep(i, cp);
473 		cursor = cp;
474 		return (0);
475 	}
476 	physdc(lcolumn(cursor), i);
477 	DEPTH(vcline) = 0;
478 	if(MB_CUR_MAX > 1)
479 		rewrite = _ON;
480 	(void) vreopen(LINE(vcline), lineDOT(), vcline);
481 	if(MB_CUR_MAX > 1)
482 		rewrite = _OFF;
483 	vsyncCL();
484 	vsetcurs(cp);
485 	return (0);
486 }
487 
488 /*
489  * Change operator.
490  *
491  * In a single line we mark the end of the changed area with '$'.
492  * On multiple whole lines, we clear the lines first.
493  * Across lines with both wcursor and wdot given, we delete
494  * and sync then append (but one operation for undo).
495  */
496 int
497 vchange(unsigned char c)
498 {
499 	unsigned char *cp;
500 	int i, ind, cnt;
501 	line *addr;
502 
503 	if (wdot) {
504 		/*
505 		 * Change/delete of lines or across line boundaries.
506 		 */
507 		if ((cnt = xdw()) < 0)
508 			return (0);
509 		getDOT();
510 		if (wcursor && cnt == 1) {
511 			/*
512 			 * Not really.
513 			 */
514 			wdot = 0;
515 			if (c == 'd') {
516 				(void) vdelete(c);
517 				return (0);
518 			}
519 			goto smallchange;
520 		}
521 		if (cursor && wcursor) {
522 			/*
523 			 * Across line boundaries, but not
524 			 * necessarily whole lines.
525 			 * Construct what will be left.
526 			 */
527 			*cursor = 0;
528 			strcpy(genbuf, linebuf);
529 			getline(*wdot);
530 			if (strlen(genbuf) + strlen(wcursor) > LBSIZE - 2) {
531 				getDOT();
532 				(void) beep();
533 				return (0);
534 			}
535 			strcat(genbuf, wcursor);
536 			if (c == 'd' && *vpastwh(genbuf) == 0) {
537 				/*
538 				 * Although this is a delete
539 				 * spanning line boundaries, what
540 				 * would be left is all white space,
541 				 * so take it all away.
542 				 */
543 				wcursor = 0;
544 				getDOT();
545 				op = 0;
546 				notpart(lastreg);
547 				notpart('1');
548 				(void) vdelete(c);
549 				return (0);
550 			}
551 			ind = -1;
552 		} else if (c == 'd' && wcursor == 0) {
553 			(void) vdelete(c);
554 			return (0);
555 		} else
556 			/*
557 			 * We are just substituting text for whole lines,
558 			 * so determine the first autoindent.
559 			 */
560 			if (value(vi_LISP) && value(vi_AUTOINDENT))
561 				ind = lindent(dot);
562 			else
563 				ind = whitecnt(linebuf);
564 		i = vcline >= 0 ? LINE(vcline) : WTOP;
565 
566 		/*
567 		 * Delete the lines from the buffer,
568 		 * and remember how the partial stuff came about in
569 		 * case we are told to put.
570 		 */
571 		addr = dot;
572 		vremote(cnt, delete, 0);
573 		setpk();
574 		notenam = (unsigned char *)"delete";
575 		if (c != 'd')
576 			notenam = (unsigned char *)"change";
577 		/*
578 		 * If DEL[0] were nonzero, put would put it back
579 		 * rather than the deleted lines.
580 		 */
581 		DEL[0] = 0;
582 		if (cnt > 1)
583 			killU();
584 
585 		/*
586 		 * Now hack the screen image coordination.
587 		 */
588 		vreplace(vcline, cnt, 0);
589 		wdot = NOLINE;
590 		noteit(0);
591 		vcline--;
592 		if (addr <= dol)
593 			dot--;
594 
595 		/*
596 		 * If this is a across line delete/change,
597 		 * cursor stays where it is; just splice together the pieces
598 		 * of the new line.  Otherwise generate a autoindent
599 		 * after a S command.
600 		 */
601 		if (ind >= 0) {
602 			/*
603 			 * XPG6 assertion 273: Set vmcurs so that cursor
604 			 * column will be set by undo.
605 			 */
606 			fixundo();
607 			*genindent(ind) = 0;
608 			vdoappend(genbuf);
609 		} else {
610 			vmcurs = cursor;
611 			strcLIN(genbuf);
612 			vdoappend(linebuf);
613 		}
614 
615 		/*
616 		 * Indicate a change on hardcopies by
617 		 * erasing the current line.
618 		 */
619 		if (c != 'd' && state != VISUAL && state != HARDOPEN) {
620 			int oldhold = hold;
621 
622 			hold |= HOLDAT, vclrlin(i, dot), hold = oldhold;
623 		}
624 
625 		/*
626 		 * Open the line (logically) on the screen, and
627 		 * update the screen tail.  Unless we are really a delete
628 		 * go off and gather up inserted characters.
629 		 */
630 		vcline++;
631 		if (vcline < 0)
632 			vcline = 0;
633 		vopen(dot, i);
634 		vsyncCL();
635 		noteit(1);
636 		if (c != 'd') {
637 			if (ind >= 0) {
638 				cursor = linebuf;
639 				/*
640 				 * XPG6 assertion 273: Set vmcurs so that
641 				 * cursor column will be set by undo.  When
642 				 * undo is preceded by 'S' or 'O' command,
643 				 * white space isn't skipped in vnline(vmcurs).
644 				 */
645 				fixundo();
646 				linebuf[0] = 0;
647 				vfixcurs();
648 			} else {
649 				ind = 0;
650 				/*
651 				 * XPG6 assertion 273: Set vmcurs so that
652 				 * cursor column will be set by undo.
653 				 */
654 				fixundo();
655 				vcursat(cursor);
656 			}
657 			vappend('x', 1, ind);
658 			return (0);
659 		}
660 		if (*cursor == 0 && cursor > linebuf)
661 			cursor = lastchr(linebuf, cursor);
662 		vrepaint(cursor);
663 		return (0);
664 	}
665 
666 smallchange:
667 	/*
668 	 * The rest of this is just low level hacking on changes
669 	 * of small numbers of characters.
670 	 */
671 	if (wcursor < linebuf)
672 		wcursor = linebuf;
673 	if (cursor == wcursor) {
674 		(void) beep();
675 		return (0);
676 	}
677 	i = vdcMID();
678 	cp = cursor;
679 	if (state != HARDOPEN)
680 		vfixcurs();
681 
682 	/*
683 	 * Put out the \\'s indicating changed text in hardcopy,
684 	 * or mark the end of the change with $ if not hardcopy.
685 	 */
686 	if (state == HARDOPEN)
687 		bleep(i, cp);
688 	else {
689 		vcursbef(wcursor);
690 		putchar('$');
691 		i = cindent();
692 	}
693 
694 	/*
695 	 * Remember the deleted text for possible put,
696 	 * and then prepare and execute the input portion of the change.
697 	 */
698 	cursor = cp;
699 	setDEL();
700 	CP(cursor, wcursor);
701 	/*
702 	 * XPG6 assertion 273: Set vmcurs so that cursor column will be
703 	 * set by undo.
704 	 */
705 	fixundo();
706 	if (state != HARDOPEN) {
707 		/* place cursor at beginning of changing text */
708 		vgotoCL(lcolumn(cp));
709 		doomed = i - cindent();
710 	} else {
711 /*
712 		sethard();
713 		wcursor = cursor;
714 		cursor = linebuf;
715 		vgoto(outline, value(vi_NUMBER) << 3);
716 		vmove();
717 */
718 		doomed = 0;
719 	}
720 	prepapp();
721 	vappend('c', 1, 0);
722 	return (0);
723 }
724 
725 /*
726  * Open new lines.
727  */
728 void
729 voOpen(int c, int cnt)
730 {
731 	int ind = 0, i;
732 	short oldhold = hold;
733 
734 	vsave();
735 	setLAST();
736 	if (value(vi_AUTOINDENT))
737 		ind = whitecnt(linebuf);
738 	if (c == 'O') {
739 		vcline--;
740 		dot--;
741 		if (dot > zero)
742 			getDOT();
743 	}
744 	if (value(vi_AUTOINDENT)) {
745 		if (value(vi_LISP))
746 			ind = lindent(dot + 1);
747 	}
748 	killU();
749 	prepapp();
750 	if (FIXUNDO)
751 		vundkind = VMANY;
752 	if (state != VISUAL)
753 		c = WBOT + 1;
754 	else {
755 		c = vcline < 0 ? WTOP - cnt : LINE(vcline) + DEPTH(vcline);
756 		if (c < ZERO)
757 			c = ZERO;
758 		i = LINE(vcline + 1) - c;
759 		if (i < cnt && c <= WBOT && (!insert_line || !delete_line))
760 			vinslin(c, cnt - i, vcline);
761 	}
762 	*genindent(ind) = 0;
763 	vdoappend(genbuf);
764 	vcline++;
765 	oldhold = hold;
766 	hold |= HOLDROL;
767 	vopen(dot, c);
768 	hold = oldhold;
769 	if (value(vi_SLOWOPEN))
770 		/*
771 		 * Oh, so lazy!
772 		 */
773 		vscrap();
774 	else
775 		vsync1(LINE(vcline));
776 	cursor = linebuf;
777 	/*
778 	 * XPG6 assertion 273: Set vmcurs so that cursor column will be
779 	 * set by undo.  For undo preceded by 'o' command, white space
780 	 * isn't skipped in vnline(vmcurs).
781 	 */
782 	fixundo();
783 	linebuf[0] = 0;
784 	vappend('o', cnt, ind);
785 }
786 
787 /*
788  * > < and = shift operators.
789  *
790  * Note that =, which aligns lisp, is just a ragged sort of shift,
791  * since it never distributes text between lines.
792  */
793 unsigned char	vshnam[2] = { 'x', 0 };
794 
795 int
796 vshftop(void)
797 {
798 	line *addr;
799 	int cnt;
800 
801 	if ((cnt = xdw()) < 0)
802 		return (0);
803 	addr = dot;
804 	vremote(cnt, vshift, 0);
805 	vshnam[0] = op;
806 	notenam = vshnam;
807 	dot = addr;
808 	vreplace(vcline, cnt, cnt);
809 	if (state == HARDOPEN)
810 		vcnt = 0;
811 	vrepaint(NOSTR);
812 	return (0);
813 }
814 
815 /*
816  * !.
817  *
818  * Filter portions of the buffer through unix commands.
819  */
820 int
821 vfilter(void)
822 {
823 	line *addr;
824 	int cnt;
825 	unsigned char *oglobp;
826 	short d;
827 
828 	if ((cnt = xdw()) < 0)
829 		return (0);
830 	if (vglobp)
831 		vglobp = (unsigned char *)uxb;
832 	if (readecho('!'))
833 		return (0);
834 	oglobp = globp; globp = genbuf + 1;
835 	d = peekc; ungetchar(0);
836 	CATCH
837 		fixech();
838 		unix0(0, 0);
839 	ONERR
840 		splitw = 0;
841 		ungetchar(d);
842 		vrepaint(cursor);
843 		globp = oglobp;
844 		return (0);
845 	ENDCATCH
846 	ungetchar(d); globp = oglobp;
847 	addr = dot;
848 	CATCH
849 		vgoto(WECHO, 0); flusho();
850 		vremote(cnt, vi_filter, 2);
851 	ONERR
852 		vdirty(0, lines);
853 	ENDCATCH
854 	if (dot == zero && dol > zero)
855 		dot = one;
856 	splitw = 0;
857 	notenam = (unsigned char *)"";
858 	/*
859 	 * BUG: we shouldn't be depending on what undap2 and undap1 are,
860 	 * since we may be inside a macro.  What's really wanted is the
861 	 * number of lines we read from the filter.  However, the mistake
862 	 * will be an overestimate so it only results in extra work,
863 	 * it shouldn't cause any real mess-ups.
864 	 */
865 	vreplace(vcline, cnt, undap2 - undap1);
866 	dot = addr;
867 	if (dot > dol) {
868 		dot--;
869 		vcline--;
870 	}
871 	vrepaint(NOSTR);
872 	return (0);
873 }
874 
875 /*
876  * Xdw exchanges dot and wdot if appropriate and also checks
877  * that wdot is reasonable.  Its name comes from
878  *	xchange dotand wdot
879  */
880 int
881 xdw(void)
882 {
883 	unsigned char *cp;
884 	int cnt;
885 /*
886 	register int notp = 0;
887  */
888 
889 	if (wdot == NOLINE || wdot < one || wdot > dol) {
890 		(void) beep();
891 		return (-1);
892 	}
893 	vsave();
894 	setLAST();
895 	if (dot > wdot || (dot == wdot && wcursor != 0 && cursor > wcursor)) {
896 		line *addr;
897 
898 		vcline -= dot - wdot;
899 		addr = dot; dot = wdot; wdot = addr;
900 		cp = cursor; cursor = wcursor; wcursor = cp;
901 	}
902 	/*
903 	 * If a region is specified but wcursor is at the beginning
904 	 * of the last line, then we move it to be the end of the
905 	 * previous line (actually off the end).
906 	 */
907 	if (cursor && wcursor == linebuf && wdot > dot) {
908 		wdot--;
909 		getDOT();
910 		if (vpastwh(linebuf) >= cursor)
911 			wcursor = 0;
912 		else {
913 			getline(*wdot);
914 			wcursor = strend(linebuf);
915 			getDOT();
916 		}
917 		/*
918 		 * Should prepare in caller for possible dot == wdot.
919 		 */
920 	}
921 	cnt = wdot - dot + 1;
922 	if (vreg) {
923 		vremote(cnt, YANKreg, vreg);
924 /*
925 		if (notp)
926 			notpart(vreg);
927  */
928 	}
929 
930 	/*
931 	 * Kill buffer code.  If delete operator is c or d, then save
932 	 * the region in numbered buffers.
933 	 *
934 	 * BUG:			This may be somewhat inefficient due
935 	 *			to the way named buffer are implemented,
936 	 *			necessitating some optimization.
937 	 */
938 	vreg = 0;
939 	/* XPG6 assertion 194 and 264: use numeric buffers for 'C' and 'S' */
940 	if (any(op, (unsigned char *)"cdCS")) {
941 		vremote(cnt, YANKreg, '1');
942 /*
943 		if (notp)
944 			notpart('1');
945  */
946 	}
947 	return (cnt);
948 }
949 
950 /*
951  * Routine for vremote to call to implement shifts.
952  */
953 int
954 vshift(void)
955 {
956 
957 	shift(op, 1);
958 	return (0);
959 }
960 
961 /*
962  * Replace a single character with the next input character.
963  * A funny kind of insert.
964  */
965 void
966 vrep(int cnt)
967 {
968 	int i, c;
969 	unsigned char *endcurs;
970 	endcurs = cursor;
971 	/* point endcurs to last char entered */
972 	for(i = 1; i <= cnt; i++) {
973 		if(!*endcurs) {
974 			(void) beep();
975 			return;
976 		}
977 		endcurs = nextchr(endcurs);
978 	}
979 	i = lcolumn(endcurs);
980 	vcursat(cursor);
981 	doomed = i - cindent();
982 	/*
983 	 * TRANSLATION_NOTE
984 	 *	"r" is a terse mode message that corresponds to
985 	 *	"REPLACE 1 CHAR".
986 	 *	Translated message of "r" must be 1 character (not byte).
987 	 *	Or, just leave it.
988 	 */
989 	if(value(vi_TERSE))
990 		vshowmode(gettext("r"));
991 	else
992 		vshowmode(gettext("REPLACE 1 CHAR"));
993 	if (!vglobp) {
994 		/* get a key using getkey() */
995 		c = getesc();
996 		if (c == 0) {
997 			vshowmode("");
998 			vfixcurs();
999 			return;
1000 		}
1001 		ungetkey(c);
1002 	}
1003 	CP(vutmp, linebuf);
1004 	if (FIXUNDO)
1005 		vundkind = VCHNG;
1006 	wcursor = endcurs;
1007 	vUD1 = cursor; vUD2 = wcursor;
1008 	CP(cursor, wcursor);
1009 	/* before appending lines, set addr1 and undo information */
1010 	prepapp();
1011 	vappend('r', cnt, 0);
1012 	*lastcp++ = INS[0];
1013 	setLAST();
1014 }
1015 
1016 /*
1017  * Yank.
1018  *
1019  * Yanking to string registers occurs for free (essentially)
1020  * in the routine xdw().
1021  */
1022 int
1023 vyankit(void)
1024 {
1025 	int cnt;
1026 
1027 	if (wdot) {
1028 		if ((cnt = xdw()) < 0)
1029 			return (0);
1030 		vremote(cnt, yank, 0);
1031 		setpk();
1032 		notenam = (unsigned char *)"yank";
1033 		if (FIXUNDO)
1034 			vundkind = VNONE;
1035 		DEL[0] = 0;
1036 		wdot = NOLINE;
1037 		if (notecnt <= vcnt - vcline && notecnt < value(vi_REPORT))
1038 			notecnt = 0;
1039 		vrepaint(cursor);
1040 		return (0);
1041 	} else {
1042 		/*
1043 		 * For one line y<motion> commands, eg. 2yw, save the
1044 		 * command for a subsequent [count].
1045 		 */
1046 		setLAST();
1047 	}
1048 	takeout(DEL);
1049 	return (0);
1050 
1051 }
1052 
1053 /*
1054  * Set pkill variables so a put can
1055  * know how to put back partial text.
1056  * This is necessary because undo needs the complete
1057  * line images to be saved, while a put wants to trim
1058  * the first and last lines.  The compromise
1059  * is for put to be more clever.
1060  */
1061 void
1062 setpk(void)
1063 {
1064 
1065 	if (wcursor) {
1066 		pkill[0] = cursor;
1067 		pkill[1] = wcursor;
1068 	}
1069 }
1070 
1071 /*
1072  * XPG6 assertion 273 : If the command is C, c, o, R, S, or s, set vmcurs
1073  * so that the cursor column will be set by undo.
1074  */
1075 void
1076 fixundo(void)
1077 {
1078 	if (lastcmd[0] == 'C' || lastcmd[0] == 'c' || lastcmd[0] == 'o' ||
1079 	    lastcmd[0] == 'R' || lastcmd[0] == 'S' || lastcmd[0] == 's') {
1080 		vmcurs = cursor;
1081 	}
1082 }
1083