xref: /illumos-gate/usr/src/cmd/vi/port/ex_vops2.c (revision 004388ebfdfe2ed7dfd2d153a876dfcc22d2c006)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 
31 /* Copyright (c) 1981 Regents of the University of California */
32 
33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 #include "ex.h"
36 #include "ex_tty.h"
37 #include "ex_vis.h"
38 #ifndef PRESUNEUC
39 #include <wctype.h>
40 /* Undef putchar/getchar if they're defined. */
41 #ifdef putchar
42 #	undef putchar
43 #endif
44 #ifdef getchar
45 #	undef getchar
46 #endif
47 #endif /* PRESUNEUC */
48 
49 extern size_t strlcpy(char *, const char *, size_t);
50 
51 /*
52  * Low level routines for operations sequences,
53  * and mostly, insert mode (and a subroutine
54  * to read an input line, including in the echo area.)
55  */
56 extern unsigned char	*vUA1, *vUA2;		/* extern; also in ex_vops.c */
57 extern unsigned char	*vUD1, *vUD2;		/* extern; also in ex_vops.c */
58 
59 int vmaxrep(unsigned char, int);
60 static void imultlinerep(int, line *, int, int);
61 static void omultlinerep(int, line *, int);
62 static void fixdisplay(void);
63 
64 /*
65  * Obleeperate characters in hardcopy
66  * open with \'s.
67  */
68 void
69 bleep(int i, unsigned char *cp)
70 {
71 
72 	i -= lcolumn(nextchr(cp));
73 	do
74 		putchar('\\' | QUOTE);
75 	while (--i >= 0);
76 	rubble = 1;
77 }
78 
79 /*
80  * Common code for middle part of delete
81  * and change operating on parts of lines.
82  */
83 int
84 vdcMID(void)
85 {
86 	unsigned char *cp;
87 
88 	squish();
89 	setLAST();
90 	if (FIXUNDO)
91 		vundkind = VCHNG, CP(vutmp, linebuf);
92 	if (wcursor < cursor)
93 		cp = wcursor, wcursor = cursor, cursor = cp;
94 	vUD1 = vUA1 = vUA2 = cursor; vUD2 = wcursor;
95 	return (lcolumn(wcursor));
96 }
97 
98 /*
99  * Take text from linebuf and stick it
100  * in the VBSIZE buffer BUF.  Used to save
101  * deleted text of part of line.
102  */
103 void
104 takeout(unsigned char *BUF)
105 {
106 	unsigned char *cp;
107 
108 	if (wcursor < linebuf)
109 		wcursor = linebuf;
110 	if (cursor == wcursor) {
111 		(void) beep();
112 		return;
113 	}
114 	if (wcursor < cursor) {
115 		cp = wcursor;
116 		wcursor = cursor;
117 		cursor = cp;
118 	}
119 	setBUF(BUF);
120 	if ((unsigned char)BUF[128] == 0200)
121 		(void) beep();
122 }
123 
124 /*
125  * Are we at the end of the printed representation of the
126  * line?  Used internally in hardcopy open.
127  */
128 int
129 ateopr(void)
130 {
131 	wchar_t i, c;
132 	wchar_t *cp = vtube[destline] + destcol;
133 
134 	for (i = WCOLS - destcol; i > 0; i--) {
135 		c = *cp++;
136 		if (c == 0) {
137 			/*
138 			 * Optimization to consider returning early, saving
139 			 * CPU time.  We have to make a special check that
140 			 * we aren't missing a mode indicator.
141 			 */
142 			if (destline == WECHO && destcol < WCOLS-11 && vtube[WECHO][WCOLS-20])
143 				return 0;
144 			return (1);
145 		}
146 		if (c != ' ' && (c & QUOTE) == 0)
147 			return (0);
148 	}
149 	return (1);
150 }
151 
152 /*
153  * Append.
154  *
155  * This routine handles the top level append, doing work
156  * as each new line comes in, and arranging repeatability.
157  * It also handles append with repeat counts, and calculation
158  * of autoindents for new lines.
159  */
160 bool	vaifirst;
161 bool	gobbled;
162 unsigned char	*ogcursor;
163 
164 static int 	INSCDCNT; /* number of ^D's (backtabs) in insertion buffer */
165 
166 static int 	inscdcnt; /*
167 			   * count of ^D's (backtabs) not seen yet when doing
168 		 	   * repeat of insertion
169 			   */
170 
171 void
172 vappend(int ch, int cnt, int indent)
173 {
174 	int i;
175 	unsigned char *gcursor;
176 	bool escape;
177 	int repcnt, savedoomed;
178 	short oldhold = hold;
179 	int savecnt = cnt;
180 	line *startsrcline;
181 	int startsrccol, endsrccol;
182 	int gotNL = 0;
183 	int imultlinecnt = 0;
184 	int omultlinecnt = 0;
185 
186 	if ((savecnt > 1) && (ch == 'o' || ch == 'O')) {
187 		omultlinecnt = 1;
188 	}
189 #ifdef XPG6
190 	if ((savecnt > 1) && (ch == 'a' || ch == 'A' || ch == 'i' || ch == 'I'))
191 		imultlinecnt = 1;
192 #endif /* XPG6 */
193 
194 	/*
195 	 * Before a move in hardopen when the line is dirty
196 	 * or we are in the middle of the printed representation,
197 	 * we retype the line to the left of the cursor so the
198 	 * insert looks clean.
199 	 */
200 
201 	if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) {
202 		rubble = 1;
203 		gcursor = cursor;
204 		i = *gcursor;
205 		*gcursor = ' ';
206 		wcursor = gcursor;
207 		(void) vmove();
208 		*gcursor = i;
209 	}
210 	vaifirst = indent == 0;
211 
212 	/*
213 	 * Handle replace character by (eventually)
214 	 * limiting the number of input characters allowed
215 	 * in the vgetline routine.
216 	 */
217 	if (ch == 'r')
218 		repcnt = 2;
219 	else
220 		repcnt = 0;
221 
222 	/*
223 	 * If an autoindent is specified, then
224 	 * generate a mixture of blanks to tabs to implement
225 	 * it and place the cursor after the indent.
226 	 * Text read by the vgetline routine will be placed in genbuf,
227 	 * so the indent is generated there.
228 	 */
229 	if (value(vi_AUTOINDENT) && indent != 0) {
230 		unsigned char x;
231 		gcursor = genindent(indent);
232 		*gcursor = 0;
233 		vgotoCL(nqcolumn(lastchr(linebuf, cursor), genbuf));
234 	} else {
235 		gcursor = genbuf;
236 		*gcursor = 0;
237 		if (ch == 'o')
238 			vfixcurs();
239 	}
240 
241 	/*
242 	 * Prepare for undo.  Pointers delimit inserted portion of line.
243 	 */
244 	vUA1 = vUA2 = cursor;
245 
246 	/*
247 	 * If we are not in a repeated command and a ^@ comes in
248 	 * then this means the previous inserted text.
249 	 * If there is none or it was too long to be saved,
250 	 * then beep() and also arrange to undo any damage done
251 	 * so far (e.g. if we are a change.)
252 	 */
253 	switch (ch) {
254 	case 'r':
255 		break;
256 	case 'a':
257 		/*
258 		 * TRANSLATION_NOTE
259 		 *	"A" is a terse mode message corresponding to
260 		 *	"APPEND MODE".
261 		 *	Translated message of "A" must be 1 character (not byte).
262 		 *	Or, just leave it.
263 		 */
264 		if (value(vi_TERSE)) {
265 			vshowmode(gettext("A"));
266 		} else {
267 			vshowmode(gettext("APPEND MODE"));
268 		}
269 		break;
270 	case 's':
271 		/*
272 		 * TRANSLATION_NOTE
273 		 *	"S" is a terse mode message corresponding to
274 		 *	"SUBSTITUTE MODE".
275 		 *	Translated message of "S" must be 1 character (not byte).
276 		 *	Or, just leave it.
277 		 */
278 		if (value(vi_TERSE)) {
279 			vshowmode(gettext("S"));
280 		} else {
281 			vshowmode(gettext("SUBSTITUTE MODE"));
282 		}
283 		break;
284 	case 'c':
285 		/*
286 		 * TRANSLATION_NOTE
287 		 *	"C" is a terse mode message corresponding to
288 		 *	"CHANGE MODE".
289 		 *	Translated message of "C" must be 1 character (not byte).
290 		 *	Or, just leave it.
291 		 */
292 		if (value(vi_TERSE)) {
293 			vshowmode(gettext("C"));
294 		} else {
295 			vshowmode(gettext("CHANGE MODE"));
296 		}
297 		break;
298 	case 'R':
299 		/*
300 		 * TRANSLATION_NOTE
301 		 *	"R" is a terse mode message corresponding to
302 		 *	"REPLACE MODE".
303 		 *	Translated message of "R" must be 1 character (not byte).
304 		 *	Or, just leave it.
305 		 */
306 		if (value(vi_TERSE)) {
307 			vshowmode(gettext("R"));
308 		} else {
309 			vshowmode(gettext("REPLACE MODE"));
310 		}
311 		break;
312 	case 'o':
313 		/*
314 		 * TRANSLATION_NOTE
315 		 *	"O" is a terse mode message corresponding to
316 		 *	"OPEN MODE".
317 		 *	Translated message of "O" must be 1 character (not byte).
318 		 *	Or, just leave it.
319 		 */
320 		if (value(vi_TERSE)) {
321 			vshowmode(gettext("O"));
322 		} else {
323 			vshowmode(gettext("OPEN MODE"));
324 		}
325 		break;
326 	case 'i':
327 		/*
328 		 * TRANSLATION_NOTE
329 		 *	"I" is a terse mode message corresponding to
330 		 *	"INSERT MODE" and the following "INPUT MODE".
331 		 *	Translated message of "I" must be 1 character (not byte).
332 		 *	Or, just leave it.
333 		 */
334 		if (value(vi_TERSE)) {
335 			vshowmode(gettext("I"));
336 		} else {
337 			vshowmode(gettext("INSERT MODE"));
338 		}
339 		break;
340 	default:
341 		/*
342 		 * TRANSLATION_NOTE
343 		 *	"I" is a terse mode message corresponding to
344 		 *	"INPUT MODE" and the previous "INSERT MODE".
345 		 *	Translated message of "I" must be 1 character (not byte).
346 		 *	Or, just leave it.
347 		 */
348 		if (value(vi_TERSE)) {
349 			vshowmode(gettext("I"));
350 		} else {
351 			vshowmode(gettext("INPUT MODE"));
352 		}
353 	}
354 	ixlatctl(1);
355 	if ((vglobp && *vglobp == 0) || peekbr()) {
356 		if (INS[128] == 0200) {
357 			(void) beep();
358 			if (!splitw)
359 				ungetkey('u');
360 			doomed = 0;
361 			hold = oldhold;
362 			return;
363 		}
364 		/*
365 		 * Unread input from INS.
366 		 * An escape will be generated at end of string.
367 		 * Hold off n^^2 type update on dumb terminals.
368 		 */
369 		vglobp = INS;
370 		inscdcnt = INSCDCNT;
371 		hold |= HOLDQIK;
372 	} else if (vglobp == 0) {
373 		/*
374 		 * Not a repeated command, get
375 		 * a new inserted text for repeat.
376 		 */
377 		INS[0] = 0;
378 		INS[128] = 0;
379 		INSCDCNT = 0;
380 	}
381 
382 	/*
383 	 * For wrapmargin to hack away second space after a '.'
384 	 * when the first space caused a line break we keep
385 	 * track that this happened in gobblebl, which says
386 	 * to gobble up a blank silently.
387 	 */
388 	gobblebl = 0;
389 
390 	startsrcline = dot;
391 	startsrccol = cursor - linebuf;
392 
393 	/*
394 	 * Text gathering loop.
395 	 * New text goes into genbuf starting at gcursor.
396 	 * cursor preserves place in linebuf where text will eventually go.
397 	 */
398 	if (*cursor == 0 || state == CRTOPEN)
399 		hold |= HOLDROL;
400 	for (;;) {
401 		if (ch == 'r' && repcnt == 0)
402 			escape = 0;
403 		else {
404 			ixlatctl(1);
405 			gcursor = vgetline(repcnt, gcursor, &escape, ch);
406 
407 			/* vgetline() only returns when it got ESCAPE or NL */
408 			if (escape == '\n') {
409 				gotNL = 1;
410 			} else {
411 				/*
412 				 * Upon escape, gcursor is pointing to '\0'
413 				 * terminating the string in genbuf.
414 				 */
415 				endsrccol = gcursor - genbuf - 1;
416 			}
417 			ixlatctl(0);
418 
419 			/*
420 			 * After an append, stick information
421 			 * about the ^D's and ^^D's and 0^D's in
422 			 * the repeated text buffer so repeated
423 			 * inserts of stuff indented with ^D as backtab's
424 			 * can work.
425 			 */
426 			if (HADUP)
427 				addtext("^");
428 			else if (HADZERO)
429 				addtext("0");
430 			if(!vglobp)
431 				INSCDCNT = CDCNT;
432 			while (CDCNT > 0) {
433 				addtext("\004");
434 				CDCNT--;
435 			}
436 			if (gobbled)
437 				addtext(" ");
438 			addtext(ogcursor);
439 		}
440 		repcnt = 0;
441 
442 		/*
443 		 * Smash the generated and preexisting indents together
444 		 * and generate one cleanly made out of tabs and spaces
445 		 * if we are using autoindent.
446 		 */
447 		if (!vaifirst && value(vi_AUTOINDENT)) {
448 			i = fixindent(indent);
449 			if (!HADUP)
450 				indent = i;
451 			gcursor = strend(genbuf);
452 		}
453 
454 		/*
455 		 * Set cnt to 1 to avoid repeating the text on the same line.
456 		 * Do this for commands 'i', 'I', 'a', and 'A', if we're
457 		 * inserting anything with a newline for XPG6.  Always do this
458 		 * for commands 'o' and 'O'.
459 		 */
460 		if ((imultlinecnt && gotNL) || omultlinecnt) {
461 			cnt = 1;
462 		}
463 
464 		/*
465 		 * Limit the repetition count based on maximum
466 		 * possible line length; do output implied
467 		 * by further count (> 1) and cons up the new line
468 		 * in linebuf.
469 		 */
470 		cnt = vmaxrep(ch, cnt);
471 		CP(gcursor + 1, cursor);
472 		do {
473 			CP(cursor, genbuf);
474 			if (cnt > 1) {
475 				int oldhold = hold;
476 
477 				Outchar = vinschar;
478 				hold |= HOLDQIK;
479 				viprintf("%s", genbuf);
480 				hold = oldhold;
481 				Outchar = vputchar;
482 			}
483 			cursor += gcursor - genbuf;
484 		} while (--cnt > 0);
485 		endim();
486 		vUA2 = cursor;
487 		if (escape != '\n')
488 			CP(cursor, gcursor + 1);
489 
490 		/*
491 		 * If doomed characters remain, clobber them,
492 		 * and reopen the line to get the display exact.
493 		 */
494 		if (state != HARDOPEN) {
495 			DEPTH(vcline) = 0;
496 			savedoomed = doomed;
497 			if (doomed > 0) {
498 				int cind = cindent();
499 
500 				physdc(cind, cind + doomed);
501 				doomed = 0;
502 			}
503 			if(MB_CUR_MAX > 1)
504 				rewrite = _ON;
505 			i = vreopen(LINE(vcline), lineDOT(), vcline);
506 			if(MB_CUR_MAX > 1)
507 				rewrite = _OFF;
508 #ifdef TRACE
509 			if (trace)
510 				fprintf(trace, "restoring doomed from %d to %d\n", doomed, savedoomed);
511 #endif
512 			if (ch == 'R')
513 				doomed = savedoomed;
514 		}
515 
516 		/*
517 		 * All done unless we are continuing on to another line.
518 		 */
519 		if (escape != '\n') {
520 			vshowmode("");
521 			break;
522 		}
523 
524 		/*
525 		 * Set up for the new line.
526 		 * First save the current line, then construct a new
527 		 * first image for the continuation line consisting
528 		 * of any new autoindent plus the pushed ahead text.
529 		 */
530 		killU();
531 		addtext(gobblebl ? " " : "\n");
532 		vsave();
533 		cnt = 1;
534 		if (value(vi_AUTOINDENT)) {
535 			if (value(vi_LISP))
536 				indent = lindent(dot + 1);
537 			else
538 			     if (!HADUP && vaifirst)
539 				indent = whitecnt(linebuf);
540 			vaifirst = 0;
541 			strcLIN(vpastwh(gcursor + 1));
542 			gcursor = genindent(indent);
543 			*gcursor = 0;
544 			if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2])
545 				gcursor = genbuf;
546 			CP(gcursor, linebuf);
547 		} else {
548 			CP(genbuf, gcursor + 1);
549 			gcursor = genbuf;
550 		}
551 
552 		/*
553 		 * If we started out as a single line operation and are now
554 		 * turning into a multi-line change, then we had better yank
555 		 * out dot before it changes so that undo will work
556 		 * correctly later.
557 		 */
558 		if (FIXUNDO && vundkind == VCHNG) {
559 			vremote(1, yank, 0);
560 			undap1--;
561 		}
562 
563 		/*
564 		 * Now do the append of the new line in the buffer,
565 		 * and update the display.  If slowopen
566 		 * we don't do very much.
567 		 */
568 		vdoappend(genbuf);
569 		vundkind = VMANYINS;
570 		vcline++;
571 		if (state != VISUAL)
572 			vshow(dot, NOLINE);
573 		else {
574 			i += LINE(vcline - 1);
575 			vopen(dot, i);
576 			if (value(vi_SLOWOPEN))
577 				vscrap();
578 			else
579 				vsync1(LINE(vcline));
580 		}
581 		switch (ch) {
582 		case 'r':
583 			break;
584 		case 'a':
585 			if (value(vi_TERSE)) {
586 				vshowmode(gettext("A"));
587 			} else {
588 				vshowmode(gettext("APPEND MODE"));
589 			}
590 			break;
591 		case 's':
592 			if (value(vi_TERSE)) {
593 				vshowmode(gettext("S"));
594 			} else {
595 				vshowmode(gettext("SUBSTITUTE MODE"));
596 			}
597 			break;
598 		case 'c':
599 			if (value(vi_TERSE)) {
600 				vshowmode(gettext("C"));
601 			} else {
602 				vshowmode(gettext("CHANGE MODE"));
603 			}
604 			break;
605 		case 'R':
606 			if (value(vi_TERSE)) {
607 				vshowmode(gettext("R"));
608 			} else {
609 				vshowmode(gettext("REPLACE MODE"));
610 			}
611 			break;
612 		case 'i':
613 			if (value(vi_TERSE)) {
614 				vshowmode(gettext("I"));
615 			} else {
616 				vshowmode(gettext("INSERT MODE"));
617 			}
618 			break;
619 		case 'o':
620 			if (value(vi_TERSE)) {
621 				vshowmode(gettext("O"));
622 			} else {
623 				vshowmode(gettext("OPEN MODE"));
624 			}
625 			break;
626 		default:
627 			if (value(vi_TERSE)) {
628 				vshowmode(gettext("I"));
629 			} else {
630 				vshowmode(gettext("INPUT MODE"));
631 			}
632 		}
633 		strcLIN(gcursor);
634 		*gcursor = 0;
635 		cursor = linebuf;
636 		vgotoCL(nqcolumn(cursor - 1, genbuf));
637 	} /* end for (;;) loop in vappend() */
638 
639 	if (imultlinecnt && gotNL) {
640 		imultlinerep(savecnt, startsrcline, startsrccol, endsrccol);
641 	} else if (omultlinecnt) {
642 		omultlinerep(savecnt, startsrcline, endsrccol);
643 	}
644 
645 	/*
646 	 * All done with insertion, position the cursor
647 	 * and sync the screen.
648 	 */
649 	hold = oldhold;
650 	if ((imultlinecnt && gotNL) || omultlinecnt) {
651 		fixdisplay();
652 	} else if (cursor > linebuf) {
653 		cursor = lastchr(linebuf, cursor);
654 	}
655 	if (state != HARDOPEN)
656 		vsyncCL();
657 	else if (cursor > linebuf)
658 		back1();
659 	doomed = 0;
660 	wcursor = cursor;
661 	(void) vmove();
662 }
663 
664 /*
665  * XPG6
666  * To repeat multi-line input for [count]a, [count]A, [count]i, [count]I,
667  * or a subsequent [count]. :
668  * insert input count-1 more times.
669  */
670 
671 static void
672 imultlinerep(int savecnt, line *startsrcline, int startsrccol, int endsrccol)
673 {
674 	int tmpcnt = 2;	/* 1st insert counts as 1 repeat */
675 	line *srcline, *endsrcline;
676 	size_t destsize = LBSIZE - endsrccol - 1;
677 
678 	endsrcline = dot;
679 
680 	/* Save linebuf into temp file before moving off the line. */
681 	vsave();
682 
683 	/*
684 	 * At this point the temp file contains the first iteration of
685 	 * a multi-line insert, and we need to repeat it savecnt - 1
686 	 * more times in the temp file.  dot is the last line in the
687 	 * first iteration of the insert.  Decrement dot so that
688 	 * vdoappend() will append each new line before the last line.
689 	 */
690 	--dot;
691 	--vcline;
692 	/*
693 	 * Use genbuf to rebuild the last line in the 1st iteration
694 	 * of the repeated insert, then copy this line to the temp file.
695 	 */
696 	(void) strlcpy((char *)genbuf, (char *)linebuf, sizeof (genbuf));
697 	getline(*startsrcline);
698 	if (strlcpy((char *)(genbuf + endsrccol + 1),
699 	    (char *)(linebuf + startsrccol), destsize) >= destsize) {
700 		error(gettext("Line too long"));
701 	}
702 	vdoappend(genbuf);
703 	vcline++;
704 	/*
705 	 * Loop from the second line of the first iteration
706 	 * through endsrcline, appending after dot.
707 	 */
708 	++startsrcline;
709 
710 	while (tmpcnt <= savecnt) {
711 		for (srcline = startsrcline; srcline <= endsrcline;
712 		    ++srcline) {
713 			if ((tmpcnt == savecnt) &&
714 			    (srcline == endsrcline)) {
715 				/*
716 				 * The last line is already in place,
717 				 * just make it the current line.
718 				 */
719 				vcline++;
720 				dot++;
721 				getDOT();
722 				cursor = linebuf + endsrccol;
723 			} else {
724 				getline(*srcline);
725 				/* copy linebuf to temp file */
726 				vdoappend(linebuf);
727 				vcline++;
728 			}
729 		}
730 		++tmpcnt;
731 	}
732 }
733 
734 /*
735  * To repeat input for [count]o, [count]O, or a subsequent [count]. :
736  * append input count-1 more times to the end of the already added
737  * text, each time starting on a new line.
738  */
739 
740 static void
741 omultlinerep(int savecnt, line *startsrcline, int endsrccol)
742 {
743 	int tmpcnt = 2;	/* 1st insert counts as 1 repeat */
744 	line *srcline, *endsrcline;
745 
746 	endsrcline = dot;
747 	/* Save linebuf into temp file before moving off the line. */
748 	vsave();
749 
750 	/*
751 	 * Loop from the first line of the first iteration
752 	 * through endsrcline, appending after dot.
753 	 */
754 	while (tmpcnt <= savecnt) {
755 		for (srcline = startsrcline; srcline <= endsrcline; ++srcline) {
756 			getline(*srcline);
757 			/* copy linebuf to temp file */
758 			vdoappend(linebuf);
759 			vcline++;
760 		}
761 		++tmpcnt;
762 	}
763 	cursor = linebuf + endsrccol;
764 }
765 
766 /*
767  * Similiar to a ctrl-l, however always vrepaint() in case the last line
768  * of the repeat would exceed the bottom of the screen.
769  */
770 
771 static void
772 fixdisplay(void)
773 {
774 	vclear();
775 	vdirty(0, vcnt);
776 	if (state != VISUAL) {
777 		vclean();
778 		vcnt = 0;
779 		vmoveto(dot, cursor, 0);
780 	} else {
781 		vredraw(WTOP);
782 		vrepaint(cursor);
783 		vfixcurs();
784 	}
785 }
786 
787 /*
788  * Subroutine for vgetline to back up a single character position,
789  * backwards around end of lines (vgoto can't hack columns which are
790  * less than 0 in general).
791  */
792 void
793 back1(void)
794 {
795 
796 	vgoto(destline - 1, WCOLS + destcol - 1);
797 }
798 
799 /*
800  * Get a line into genbuf after gcursor.
801  * Cnt limits the number of input characters
802  * accepted and is used for handling the replace
803  * single character command.  Aescaped is the location
804  * where we stick a termination indicator (whether we
805  * ended with an ESCAPE or a newline/return.
806  *
807  * We do erase-kill type processing here and also
808  * are careful about the way we do this so that it is
809  * repeatable.  (I.e. so that your kill doesn't happen,
810  * when you repeat an insert if it was escaped with \ the
811  * first time you did it.  commch is the command character
812  * involved, including the prompt for readline.
813  */
814 unsigned char *
815 vgetline(cnt, gcursor, aescaped, commch)
816 	int cnt;
817 	unsigned char *gcursor;
818 	bool *aescaped;
819 	unsigned char commch;
820 {
821 	int c, ch;
822 	unsigned char *cp, *pcp;
823 	int x, y, iwhite, backsl=0;
824 	unsigned char *iglobp;
825 	int (*OO)() = Outchar;
826 	int length, width;
827 	unsigned char multic[MULTI_BYTE_MAX+1];
828 	wchar_t wchar = 0;
829 	unsigned char	*p;
830 	int	len;
831 
832 
833 	/*
834 	 * Clear the output state and counters
835 	 * for autoindent backwards motion (counts of ^D, etc.)
836 	 * Remember how much white space at beginning of line so
837 	 * as not to allow backspace over autoindent.
838 	 */
839 
840 	*aescaped = 0;
841 	ogcursor = gcursor;
842 	flusho();
843 	CDCNT = 0;
844 	HADUP = 0;
845 	HADZERO = 0;
846 	gobbled = 0;
847 	iwhite = whitecnt(genbuf);
848 	iglobp = vglobp;
849 
850 	/*
851 	 * Clear abbreviation recursive-use count
852 	 */
853 	abbrepcnt = 0;
854 	/*
855 	 * Carefully avoid using vinschar in the echo area.
856 	 */
857 	if (splitw)
858 		Outchar = vputchar;
859 	else {
860 		Outchar = vinschar;
861 		vprepins();
862 	}
863 	for (;;) {
864 		length = 0;
865 		backsl = 0;
866 		if (gobblebl)
867 			gobblebl--;
868 		if (cnt != 0) {
869 			cnt--;
870 			if (cnt == 0)
871 				goto vadone;
872 		}
873 		c = getkey();
874 		if (c != ATTN)
875 			c &= 0377;
876 		ch = c;
877 		maphopcnt = 0;
878 		if (vglobp == 0 && Peekkey == 0 && commch != 'r')
879 			while ((ch = map(c, immacs, commch)) != c) {
880 				c = ch;
881 				if (!value(vi_REMAP))
882 					break;
883 				if (++maphopcnt > 256)
884 					error(gettext("Infinite macro loop"));
885 			}
886 		if (!iglobp) {
887 
888 			/*
889 			 * Erase-kill type processing.
890 			 * Only happens if we were not reading
891 			 * from untyped input when we started.
892 			 * Map users erase to ^H, kill to -1 for switch.
893 			 */
894 			if (c == tty.c_cc[VERASE])
895 				c = CTRL('h');
896 			else if (c == tty.c_cc[VKILL])
897 				c = -1;
898 			switch (c) {
899 
900 			/*
901 			 * ^?		Interrupt drops you back to visual
902 			 *		command mode with an unread interrupt
903 			 *		still in the input buffer.
904 			 *
905 			 * ^\		Quit does the same as interrupt.
906 			 *		If you are a ex command rather than
907 			 *		a vi command this will drop you
908 			 *		back to command mode for sure.
909 			 */
910 			case ATTN:
911 			case QUIT:
912 				ungetkey(c);
913 				goto vadone;
914 
915 			/*
916 			 * ^H		Backs up a character in the input.
917 			 *
918 			 * BUG:		Can't back around line boundaries.
919 			 *		This is hard because stuff has
920 			 *		already been saved for repeat.
921 			 */
922 			case CTRL('h'):
923 bakchar:
924 				cp = lastchr(ogcursor, gcursor);
925 				if (cp < ogcursor) {
926 					if (splitw) {
927 						/*
928 						 * Backspacing over readecho
929 						 * prompt. Pretend delete but
930 						 * don't beep.
931 						 */
932 						ungetkey(c);
933 						goto vadone;
934 					}
935 					(void) beep();
936 					continue;
937 				}
938 				goto vbackup;
939 
940 			/*
941 			 * ^W		Back up a white/non-white word.
942 			 */
943 			case CTRL('w'):
944 				wdkind = 1;
945 				for (cp = gcursor; cp > ogcursor && isspace(cp[-1]); cp--)
946 					continue;
947 				pcp = lastchr(ogcursor, cp);
948 				for (c = wordch(pcp);
949 				    cp > ogcursor && wordof(c, pcp); cp = pcp, pcp = lastchr(ogcursor, cp))
950 					continue;
951 				goto vbackup;
952 
953 			/*
954 			 * users kill	Kill input on this line, back to
955 			 *		the autoindent.
956 			 */
957 			case -1:
958 				cp = ogcursor;
959 vbackup:
960 				if (cp == gcursor) {
961 					(void) beep();
962 					continue;
963 				}
964 				endim();
965 				*cp = 0;
966 				c = cindent();
967 				vgotoCL(nqcolumn(lastchr(linebuf, cursor), genbuf));
968 
969 				if (doomed >= 0)
970 					doomed += c - cindent();
971 				gcursor = cp;
972 				continue;
973 
974 			/*
975 			 * \		Followed by erase or kill
976 			 *		maps to just the erase or kill.
977 			 */
978 			case '\\':
979 				x = destcol, y = destline;
980 				putchar('\\');
981 				vcsync();
982 				c = getkey();
983 				if (c == tty.c_cc[VERASE]
984 				    || c == tty.c_cc[VKILL])
985 				{
986 					vgoto(y, x);
987 					if (doomed >= 0)
988 						doomed++;
989 					multic[0] = wchar = c;
990 					length = 1;
991 					goto def;
992 				}
993 				ungetkey(c), c = '\\';
994 				backsl = 1;
995 				break;
996 
997 			/*
998 			 * ^Q		Super quote following character
999 			 *		Only ^@ is verboten (trapped at
1000 			 *		a lower level) and \n forces a line
1001 			 *		split so doesn't really go in.
1002 			 *
1003 			 * ^V		Synonym for ^Q
1004 			 */
1005 			case CTRL('q'):
1006 			case CTRL('v'):
1007 				x = destcol, y = destline;
1008 				putchar('^');
1009 				vgoto(y, x);
1010 				c = getkey();
1011 #ifdef USG
1012 				if (c == ATTN)
1013 					c = tty.c_cc[VINTR];
1014 #endif
1015 				if (c != NL) {
1016 					if (doomed >= 0)
1017 						doomed++;
1018 					multic[0] = wchar = c;
1019 					length = 1;
1020 					goto def;
1021 				}
1022 				break;
1023 			}
1024 		}
1025 
1026 		/*
1027 		 * If we get a blank not in the echo area
1028 		 * consider splitting the window in the wrapmargin.
1029 		 */
1030 		if(!backsl) {
1031 			ungetkey(c);
1032 			if((length = _mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) {
1033 				(void) beep();
1034 				continue;
1035 			}
1036 		} else {
1037 			length = 1;
1038 			multic[0] = '\\';
1039 		}
1040 
1041 		if (c != NL && !splitw) {
1042 			if (c == ' ' && gobblebl) {
1043 				gobbled = 1;
1044 				continue;
1045 			}
1046 			if ((width = wcwidth(wchar)) <= 0)
1047 				width = (wchar <= 0177 ? 1 : 4);
1048 			if (value(vi_WRAPMARGIN) &&
1049 				(outcol + width - 1 >= OCOLUMNS - value(vi_WRAPMARGIN) ||
1050 				 backsl && outcol==0) &&
1051 				commch != 'r') {
1052 				/*
1053 				 * At end of word and hit wrapmargin.
1054 				 * Move the word to next line and keep going.
1055 				 */
1056 				unsigned char *wp;
1057 				int bytelength;
1058 #ifndef PRESUNEUC
1059 				unsigned char *tgcursor;
1060 				wchar_t wc1, wc2;
1061 				tgcursor = gcursor;
1062 #endif /* PRESUNEUC */
1063 				wdkind = 1;
1064 				strncpy(gcursor, multic, length);
1065 				gcursor += length;
1066 				if (backsl) {
1067 #ifdef PRESUNEUC
1068 					if((length = mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) {
1069 #else
1070 					if((length = _mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) {
1071 #endif /* PRESUNEUC */
1072 						(void) beep();
1073 						continue;
1074 					}
1075 					strncpy(gcursor, multic, length);
1076 					gcursor += length;
1077 				}
1078 				*gcursor = 0;
1079 				/*
1080 				 * Find end of previous word if we are past it.
1081 				 */
1082 				for (cp=gcursor; cp>ogcursor && isspace(cp[-1]); cp--)
1083 					;
1084 #ifdef PRESUNEUC
1085 				/* find screen width of previous word */
1086 				width = 0;
1087 				for(wp = cp; *wp; )
1088 #else
1089 				/* count screen width of pending characters */
1090 				width = 0;
1091 				for(wp = tgcursor; wp < cp;)
1092 #endif /* PRESUNEUC */
1093 					if((bytelength = mbtowc(&wchar, (char *)wp, MULTI_BYTE_MAX)) < 0) {
1094 						width+=4;
1095 						wp++;
1096 					} else {
1097 						int curwidth = wcwidth(wchar);
1098 						if(curwidth <= 0)
1099 							width += (*wp < 0200 ? 2 : 4);
1100 						else
1101 							width += curwidth;
1102 						wp += bytelength;
1103 					}
1104 
1105 #ifdef PRESUNEUC
1106 				if (outcol+(backsl?OCOLUMNS:0) - width >= OCOLUMNS - value(vi_WRAPMARGIN)) {
1107 #else
1108 				if (outcol+(backsl?OCOLUMNS:0) + width -1 >= OCOLUMNS - value(vi_WRAPMARGIN)) {
1109 #endif /* PRESUNEUC */
1110 					/*
1111 					 * Find beginning of previous word.
1112 					 */
1113 #ifdef PRESUNEUC
1114 					for (; cp>ogcursor && !isspace(cp[-1]); cp--)
1115 						;
1116 #else
1117 					wc1 = wc2 = 0;
1118 					while (cp>ogcursor) {
1119 						if (isspace(cp[-1])) {
1120 							break;
1121 						}
1122 						if (!multibyte) {
1123 							cp--;
1124 							continue;
1125 						}
1126 						wp = (unsigned char *)(cp -
1127 							MB_CUR_MAX);
1128 						if (wp < ogcursor)
1129 							wp = ogcursor;
1130 						while (cp > wp) {
1131 /* 7tabs */if (wc2) {
1132 /* 7tabs */	if ((bytelength = mbtowc(&wc1, (char *)wp, cp-wp)) != cp-wp) {
1133 /* 7tabs */		wp++;
1134 /* 7tabs */		wc1 = 0;
1135 /* 7tabs */		continue;
1136 /* 7tabs */	}
1137 /* 7tabs */} else {
1138 /* 7tabs */	if ((bytelength = mbtowc(&wc2, (char *)wp, cp-wp)) != cp-wp) {
1139 /* 7tabs */		wp++;
1140 /* 7tabs */		wc2 = 0;
1141 /* 7tabs */		continue;
1142 /* 7tabs */	}
1143 /* 7tabs */}
1144 /* 7tabs */if (wc1) {
1145 /* 7tabs */	if (wdbdg && (!iswascii(wc1) || !iswascii(wc2))) {
1146 /* 7tabs */		if ((*wdbdg)(wc1, wc2, 2) < 5) {
1147 /* 7tabs */			goto ws;
1148 /* 7tabs */		}
1149 /* 7tabs */	}
1150 /* 7tabs */	wc2 = wc1;
1151 /* 7tabs */	wc1 = 0;
1152 /* 7tabs */	cp -= bytelength - 1;
1153 /* 7tabs */	break;
1154 /* 7tabs */} else {
1155 /* 7tabs */	cp -= bytelength - 1;
1156 /* 7tabs */	break;
1157 /* 7tabs */}
1158 						}
1159 						cp--;
1160 					}
1161 ws:
1162 #endif /* PRESUNEUC */
1163 					if (cp <= ogcursor) {
1164 						/*
1165 						 * There is a single word that
1166 						 * is too long to fit.  Just
1167 						 * let it pass, but beep for
1168 						 * each new letter to warn
1169 						 * the luser.
1170 						 */
1171 						gcursor -= length;
1172 						c = *gcursor;
1173 						*gcursor = 0;
1174 						(void) beep();
1175 						goto dontbreak;
1176 					}
1177 					/*
1178 					 * Save it for next line.
1179 					 */
1180 					macpush(cp, 0);
1181 #ifdef PRESUNEUC
1182 					cp--;
1183 #endif /* PRESUNEUC */
1184 				}
1185 				macpush("\n", 0);
1186 				/*
1187 				 * Erase white space before the word.
1188 				 */
1189 				while (cp > ogcursor && isspace(cp[-1]))
1190 					cp--;	/* skip blank */
1191 				gobblebl = 3;
1192 				goto vbackup;
1193 			}
1194 		dontbreak:;
1195 		}
1196 
1197 		/*
1198 		 * Word abbreviation mode.
1199 		 */
1200 		if (anyabbrs && gcursor > ogcursor && !wordch(multic) && wordch(lastchr(ogcursor, gcursor))) {
1201 				int wdtype, abno;
1202 
1203 				multic[length] = 0;
1204 				wdkind = 1;
1205 				cp = lastchr(ogcursor, gcursor);
1206 				pcp = lastchr(ogcursor, cp);
1207 				for (wdtype = wordch(pcp);
1208 				    cp > ogcursor && wordof(wdtype, pcp); cp = pcp, pcp = lastchr(ogcursor, pcp))
1209 					;
1210 				*gcursor = 0;
1211 				for (abno=0; abbrevs[abno].mapto; abno++) {
1212 					if (eq(cp, abbrevs[abno].cap)) {
1213 						if(abbrepcnt == 0) {
1214 							if(reccnt(abbrevs[abno].cap, abbrevs[abno].mapto))
1215 								abbrepcnt = 1;
1216 							macpush(multic, 0);
1217 							macpush(abbrevs[abno].mapto);
1218 							goto vbackup;
1219 						} else
1220 							abbrepcnt = 0;
1221 					}
1222 				}
1223 		}
1224 
1225 		switch (c) {
1226 
1227 		/*
1228 		 * ^M		Except in repeat maps to \n.
1229 		 */
1230 		case CR:
1231 			if (vglobp) {
1232 				multic[0] = wchar = c;
1233 				length = 1;
1234 				goto def;
1235 			}
1236 			c = '\n';
1237 			/* presto chango ... */
1238 
1239 		/*
1240 		 * \n		Start new line.
1241 		 */
1242 		case NL:
1243 			*aescaped = c;
1244 			goto vadone;
1245 
1246 		/*
1247 		 * escape	End insert unless repeat and more to repeat.
1248 		 */
1249 		case ESCAPE:
1250 			if (lastvgk) {
1251 				multic[0] = wchar = c;
1252 				length = 1;
1253 				goto def;
1254 			}
1255 			goto vadone;
1256 
1257 		/*
1258 		 * ^D		Backtab.
1259 		 * ^T		Software forward tab.
1260 		 *
1261 		 *		Unless in repeat where this means these
1262 		 *		were superquoted in.
1263 		 */
1264 		case CTRL('t'):
1265 			if (vglobp) {
1266 				multic[0] = wchar = c;
1267 				length = 1;
1268 				goto def;
1269 			}
1270 			/* fall into ... */
1271 
1272 			*gcursor = 0;
1273 			cp = vpastwh(genbuf);
1274 			c = whitecnt(genbuf);
1275 			if (ch == CTRL('t')) {
1276 				/*
1277 				 * ^t just generates new indent replacing
1278 				 * current white space rounded up to soft
1279 				 * tab stop increment.
1280 				 */
1281 				if (cp != gcursor)
1282 					/*
1283 					 * BUG:		Don't hack ^T except
1284 					 *		right after initial
1285 					 *		white space.
1286 					 */
1287 					continue;
1288 				cp = genindent(iwhite = backtab(c + value(vi_SHIFTWIDTH) + 1));
1289 				ogcursor = cp;
1290 				goto vbackup;
1291 			}
1292 			/*
1293 			 * ^D works only if we are at the (end of) the
1294 			 * generated autoindent.  We count the ^D for repeat
1295 			 * purposes.
1296 			 */
1297 		case CTRL('d'):
1298 			/* check if ^d was superquoted in */
1299 			if(vglobp && inscdcnt <= 0) {
1300 				multic[0] = wchar = c;
1301 				length = 1;
1302 				goto def;
1303 			}
1304 			if(vglobp)
1305 				inscdcnt--;
1306 			*gcursor = 0;
1307 			cp = vpastwh(genbuf);
1308 			c = whitecnt(genbuf);
1309 			if (c == iwhite && c != 0)
1310 				if (cp == gcursor) {
1311 					iwhite = backtab(c);
1312 					CDCNT++;
1313 					ogcursor = cp = genindent(iwhite);
1314 					goto vbackup;
1315 				} else if (&cp[1] == gcursor &&
1316 				    (*cp == '^' || *cp == '0')) {
1317 					/*
1318 					 * ^^D moves to margin, then back
1319 					 * to current indent on next line.
1320 					 *
1321 					 * 0^D moves to margin and then
1322 					 * stays there.
1323 					 */
1324 					HADZERO = *cp == '0';
1325 					ogcursor = cp = genbuf;
1326 					HADUP = 1 - HADZERO;
1327 					CDCNT = 1;
1328 					endim();
1329 					back1();
1330 					(void) vputchar(' ');
1331 					goto vbackup;
1332 				}
1333 
1334 			if (vglobp && vglobp - iglobp >= 2) {
1335 				if ((p = vglobp - MB_CUR_MAX) < iglobp)
1336 					p = iglobp;
1337 				for ( ; p < &vglobp[-2]; p += len) {
1338 					if ((len = mblen((char *)p, MB_CUR_MAX)) <= 0)
1339 						len = 1;
1340 				}
1341 				if ((p == &vglobp[-2]) &&
1342 			            (*p == '^' || *p == '0') &&
1343 			            gcursor == ogcursor + 1)
1344 					goto bakchar;
1345 			}
1346 			continue;
1347 
1348 		default:
1349 			/*
1350 			 * Possibly discard control inputs.
1351 			 */
1352 			if (!vglobp && junk(c)) {
1353 				(void) beep();
1354 				continue;
1355 			}
1356 def:
1357 			if (!backsl) {
1358 				putchar(wchar);
1359 				flush();
1360 			}
1361 			if (gcursor + length - 1 > &genbuf[LBSIZE - 2])
1362 				error(gettext("Line too long"));
1363 			(void)strncpy(gcursor, multic, length);
1364 			gcursor += length;
1365 			vcsync();
1366 			if (value(vi_SHOWMATCH) && !iglobp)
1367 				if (c == ')' || c == '}')
1368 					lsmatch(gcursor);
1369 			continue;
1370 		}
1371 	}
1372 vadone:
1373 	*gcursor = 0;
1374 	if (Outchar != termchar)
1375 		Outchar = OO;
1376 	endim();
1377 	return (gcursor);
1378 }
1379 
1380 int	vgetsplit();
1381 unsigned char	*vsplitpt;
1382 
1383 /*
1384  * Append the line in buffer at lp
1385  * to the buffer after dot.
1386  */
1387 void
1388 vdoappend(unsigned char *lp)
1389 {
1390 	int oing = inglobal;
1391 
1392 	vsplitpt = lp;
1393 	inglobal = 1;
1394 	(void)append(vgetsplit, dot);
1395 	inglobal = oing;
1396 }
1397 
1398 /*
1399  * Subroutine for vdoappend to pass to append.
1400  */
1401 int
1402 vgetsplit(void)
1403 {
1404 
1405 	if (vsplitpt == 0)
1406 		return (EOF);
1407 	strcLIN(vsplitpt);
1408 	vsplitpt = 0;
1409 	return (0);
1410 }
1411 
1412 /*
1413  * Vmaxrep determines the maximum repetition factor
1414  * allowed that will yield total line length less than
1415  * LBSIZE characters and also does hacks for the R command.
1416  */
1417 int
1418 vmaxrep(unsigned char ch, int cnt)
1419 {
1420 	int len;
1421 	unsigned char *cp;
1422 	int repcnt, oldcnt, replen;
1423 	if (cnt > LBSIZE - 2)
1424 		cnt = LBSIZE - 2;
1425 	if (ch == 'R') {
1426 		len = strlen(cursor);
1427 		oldcnt = 0;
1428 		for(cp = cursor; *cp; ) {
1429 			oldcnt++;
1430 			cp = nextchr(cp);
1431 		}
1432 		repcnt = 0;
1433 		for(cp = genbuf; *cp; ) {
1434 			repcnt++;
1435 			cp = nextchr(cp);
1436 		}
1437 		/*
1438 		 * if number of characters in replacement string
1439 		 * (repcnt) is less than number of characters following
1440 		 * cursor (oldcnt), find end of repcnt
1441 		 * characters after cursor
1442 		 */
1443 		if(repcnt < oldcnt) {
1444 			for(cp = cursor; repcnt > 0; repcnt--)
1445 				cp = nextchr(cp);
1446 			len = cp - cursor;
1447 		}
1448 		CP(cursor, cursor + len);
1449 		vUD2 += len;
1450 	}
1451 	len = strlen(linebuf);
1452 	replen = strlen(genbuf);
1453 	if (len + cnt * replen <= LBSIZE - 2)
1454 		return (cnt);
1455 	cnt = (LBSIZE - 2 - len) / replen;
1456 	if (cnt == 0) {
1457 		vsave();
1458 		error(gettext("Line too long"));
1459 	}
1460 	return (cnt);
1461 }
1462 
1463 /*
1464  * Determine how many occurrences of word 'CAP' are in 'MAPTO'.  To be
1465  * considered an occurrence there must be both a nonword-prefix, a
1466  * complete match of 'CAP' within 'MAPTO', and a nonword-suffix.
1467  * Note that the beginning and end of 'MAPTO' are considered to be
1468  * valid nonword delimiters.
1469  */
1470 int
1471 reccnt(unsigned char *cap, unsigned char *mapto)
1472 {
1473 	int i, cnt, final;
1474 
1475 	cnt = 0;
1476 	final = strlen(mapto) - strlen(cap);
1477 
1478 	for (i=0; i <= final; i++)
1479 	  if ((strncmp(cap, mapto+i, strlen(cap)) == 0)       /* match */
1480 	  && (i == 0     || !wordch(&mapto[i-1]))	      /* prefix ok */
1481 	  && (i == final || !wordch(&mapto[i+strlen(cap)])))  /* suffix ok */
1482 		cnt++;
1483 	return (cnt);
1484 }
1485 
1486