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