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