xref: /illumos-gate/usr/src/cmd/vi/port/ex_cmdsub.c (revision 7014882c6a3672fd0e5d60200af8643ae53c5928)
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_argv.h"
34 #include "ex_temp.h"
35 #include "ex_tty.h"
36 #include "ex_vis.h"
37 #ifdef	STDIO
38 #include	<stdio.h>
39 #undef getchar
40 #undef putchar
41 #endif
42 
43 
44 /*
45  * Command mode subroutines implementing
46  *	append, args, copy, delete, join, move, put,
47  *	shift, tag, yank, z and undo
48  */
49 
50 bool	endline = 1;
51 line	*tad1;
52 static int jnoop(void);
53 static void splitit(void);
54 int putchar(), getchar();
55 int tags_flag;
56 
57 /*
58  * Append after line a lines returned by function f.
59  * Be careful about intermediate states to avoid scramble
60  * if an interrupt comes in.
61  */
62 int
63 append(int (*f)(), line *a)
64 {
65 	line *a1, *a2, *rdot;
66 	int nline;
67 
68 	nline = 0;
69 	dot = a;
70 	if(FIXUNDO && !inopen && f!=getsub) {
71 		undap1 = undap2 = dot + 1;
72 		undkind = UNDCHANGE;
73 	}
74 	while ((*f)() == 0) {
75 		if (truedol >= endcore) {
76 			if (morelines() < 0) {
77 				if (FIXUNDO && f == getsub) {
78 					undap1 = addr1;
79 					undap2 = addr2 + 1;
80 				}
81 				error(value(vi_TERSE) ? gettext("Out of memory") :
82 gettext("Out of memory- too many lines in file"));
83 			}
84 		}
85 		nline++;
86 		a1 = truedol + 1;
87 		a2 = a1 + 1;
88 		dot++;
89 		undap2++;
90 		dol++;
91 		unddol++;
92 		truedol++;
93 		for (rdot = dot; a1 > rdot;)
94 			*--a2 = *--a1;
95 		*rdot = 0;
96 		putmark(rdot);
97 		if (f == gettty) {
98 			dirtcnt++;
99 			TSYNC();
100 		}
101 	}
102 	return (nline);
103 }
104 
105 void
106 appendnone(void)
107 {
108 
109 	if(FIXUNDO) {
110 		undkind = UNDCHANGE;
111 		undap1 = undap2 = addr1;
112 	}
113 }
114 
115 /*
116  * Print out the argument list, with []'s around the current name.
117  */
118 void
119 pargs(void)
120 {
121 	unsigned char **av = argv0, *as = args0;
122 	int ac;
123 
124 	for (ac = 0; ac < argc0; ac++) {
125 		if (ac != 0)
126 			putchar(' ');
127 		if (ac + argc == argc0 - 1)
128 			viprintf("[");
129 		lprintf("%s", as);
130 		if (ac + argc == argc0 - 1)
131 			viprintf("]");
132 		as = av ? *++av : strend(as) + 1;
133 	}
134 	noonl();
135 }
136 
137 /*
138  * Delete lines; two cases are if we are really deleting,
139  * more commonly we are just moving lines to the undo save area.
140  */
141 int
142 delete(bool hush)
143 {
144 	line *a1, *a2;
145 
146 	nonzero();
147 	if(FIXUNDO) {
148 		void (*dsavint)();
149 
150 #ifdef UNDOTRACE
151 		if (trace)
152 			vudump("before delete");
153 #endif
154 		change();
155 		dsavint = signal(SIGINT, SIG_IGN);
156 		undkind = UNDCHANGE;
157 		a1 = addr1;
158 		squish();
159 		a2 = addr2;
160 		if (a2++ != dol) {
161 			reverse(a1, a2);
162 			reverse(a2, dol + 1);
163 			reverse(a1, dol + 1);
164 		}
165 		dol -= a2 - a1;
166 		unddel = a1 - 1;
167 		if (a1 > dol)
168 			a1 = dol;
169 		dot = a1;
170 		pkill[0] = pkill[1] = 0;
171 		signal(SIGINT, dsavint);
172 #ifdef UNDOTRACE
173 		if (trace)
174 			vudump("after delete");
175 #endif
176 	} else {
177 		line *a3;
178 		int i;
179 
180 		change();
181 		a1 = addr1;
182 		a2 = addr2 + 1;
183 		a3 = truedol;
184 		i = a2 - a1;
185 		unddol -= i;
186 		undap2 -= i;
187 		dol -= i;
188 		truedol -= i;
189 		do
190 			*a1++ = *a2++;
191 		while (a2 <= a3);
192 		a1 = addr1;
193 		if (a1 > dol)
194 			a1 = dol;
195 		dot = a1;
196 	}
197 	if (!hush)
198 		killed();
199 	return (0);
200 }
201 
202 void
203 deletenone(void)
204 {
205 
206 	if(FIXUNDO) {
207 		undkind = UNDCHANGE;
208 		squish();
209 		unddel = addr1;
210 	}
211 }
212 
213 /*
214  * Crush out the undo save area, moving the open/visual
215  * save area down in its place.
216  */
217 void
218 squish(void)
219 {
220 	line *a1 = dol + 1, *a2 = unddol + 1, *a3 = truedol + 1;
221 
222 	if(FIXUNDO) {
223 		if (inopen == -1)
224 			return;
225 		if (a1 < a2 && a2 < a3)
226 			do
227 				*a1++ = *a2++;
228 			while (a2 < a3);
229 		truedol -= unddol - dol;
230 		unddol = dol;
231 	}
232 }
233 
234 /*
235  * Join lines.  Special hacks put in spaces, two spaces if
236  * preceding line ends with '.', or no spaces if next line starts with ).
237  */
238 static	int jcount;
239 
240 int
241 join(int c)
242 {
243 	line *a1;
244 	unsigned char *cp, *cp1;
245 #ifndef PRESUNEUC
246 	unsigned char *pcp;
247 	wchar_t *delim;
248 	wchar_t wc1, wc2;
249 	int n;
250 #endif /* PRESUNEUC */
251 
252 	cp = genbuf;
253 	*cp = 0;
254 	for (a1 = addr1; a1 <= addr2; a1++) {
255 		getaline(*a1);
256 		cp1 = linebuf;
257 		if (a1 != addr1 && c == 0) {
258 			while (*cp1 == ' ' || *cp1 == '\t')
259 				cp1++;
260 			if (*cp1 && cp > genbuf && cp[-1] != ' ' && cp[-1] != '\t') {
261 #ifndef PRESUNEUC
262 				/*
263 				 * insert locale-specific word delimiter if
264 				 * either of end-of-former-line or
265 				 * top-of-latter-line is non-ASCII.
266 				 */
267 				if (wddlm && *cp1 != ')' && cp[-1] != '.') {
268 					if ((pcp = cp - MB_CUR_MAX) < genbuf)
269 						pcp = genbuf;;
270 					for ( ; pcp <= cp-1; pcp++) {
271 						if ((n = mbtowc(&wc1,
272 						    (char *)pcp, cp - pcp)) ==
273 						    cp - pcp)
274 							goto gotprev;
275 					}
276 					goto mberror;
277 gotprev:
278 					if (!isascii(wc2 = *cp1)) {
279 						if (mbtowc(&wc2, (char *) cp1,
280 					    		   MB_CUR_MAX) <= 0)
281 							goto mberror;
282 					}
283 					delim = (*wddlm)(wc1,wc2,2);
284 					while (*delim)
285 						cp += wctomb((char *)cp,
286 						      *delim++);
287 					*cp = 0;
288 				} else
289 mberror:
290 #endif /* PRESUNEUC */
291 				if (*cp1 != ')') {
292 					*cp++ = ' ';
293 					if (cp[-2] == '.')
294 						*cp++ = ' ';
295 				}
296 			}
297 		}
298 		while (*cp++ = *cp1++)
299 			if (cp > &genbuf[LBSIZE-2])
300 				error(value(vi_TERSE) ? gettext("Line overflow") :
301 gettext("Result line of join would be too long"));
302 		cp--;
303 	}
304 	strcLIN(genbuf);
305 	(void) delete(0);
306 	jcount = 1;
307 	if (FIXUNDO)
308 		undap1 = undap2 = addr1;
309 	(void)append(jnoop, --addr1);
310 	if (FIXUNDO)
311 		vundkind = VMANY;
312 	return (0);
313 }
314 
315 static int
316 jnoop(void)
317 {
318 
319 	return(--jcount);
320 }
321 
322 /*
323  * Move and copy lines.  Hard work is done by move1 which
324  * is also called by undo.
325  */
326 int	getcopy();
327 
328 void
329 vi_move(void)
330 {
331 	line *adt;
332 	bool iscopy = 0;
333 
334 	if (Command[0] == 'm') {
335 		setdot1();
336 		markpr(addr2 == dot ? addr1 - 1 : addr2 + 1);
337 	} else {
338 		iscopy++;
339 		setdot();
340 	}
341 	nonzero();
342 	adt = address((char*)0);
343 	if (adt == 0)
344 		serror(value(vi_TERSE) ?
345 		    (unsigned char *)gettext("%s where?") :
346 		    (unsigned char *)gettext("%s requires a trailing address"),
347 		    Command);
348 	donewline();
349 	move1(iscopy, adt);
350 	killed();
351 }
352 
353 void
354 move1(int cflag, line *addrt)
355 {
356 	line *adt, *ad1, *ad2;
357 	int nlines;
358 
359 	adt = addrt;
360 	nlines = (addr2 - addr1) + 1;
361 	if (cflag) {
362 		tad1 = addr1;
363 		ad1 = dol;
364 		(void)append(getcopy, ad1++);
365 		ad2 = dol;
366 	} else {
367 		ad2 = addr2;
368 		for (ad1 = addr1; ad1 <= ad2;)
369 			*ad1++ &= ~01;
370 		ad1 = addr1;
371 	}
372 	ad2++;
373 	if (adt < ad1) {
374 		if (adt + 1 == ad1 && !cflag && !inglobal)
375 			error(gettext("That move would do nothing!"));
376 		dot = adt + (ad2 - ad1);
377 		if (++adt != ad1) {
378 			reverse(adt, ad1);
379 			reverse(ad1, ad2);
380 			reverse(adt, ad2);
381 		}
382 	} else if (adt >= ad2) {
383 		dot = adt++;
384 		reverse(ad1, ad2);
385 		reverse(ad2, adt);
386 		reverse(ad1, adt);
387 	} else
388 		error(gettext("Move to a moved line"));
389 	change();
390 	if (!inglobal)
391 		if(FIXUNDO) {
392 			if (cflag) {
393 				undap1 = addrt + 1;
394 				undap2 = undap1 + nlines;
395 				deletenone();
396 			} else {
397 				undkind = UNDMOVE;
398 				undap1 = addr1;
399 				undap2 = addr2;
400 				unddel = addrt;
401 				squish();
402 			}
403 		}
404 }
405 
406 int
407 getcopy(void)
408 {
409 
410 	if (tad1 > addr2)
411 		return (EOF);
412 	getaline(*tad1++);
413 	return (0);
414 }
415 
416 /*
417  * Put lines in the buffer from the undo save area.
418  */
419 int
420 getput(void)
421 {
422 
423 	if (tad1 > unddol)
424 		return (EOF);
425 	getaline(*tad1++);
426 	tad1++;
427 	return (0);
428 }
429 
430 int
431 put(void)
432 {
433 	int cnt;
434 
435 	if (!FIXUNDO)
436 		error(gettext("Cannot put inside global/macro"));
437 	cnt = unddol - dol;
438 	if (cnt && inopen && pkill[0] && pkill[1]) {
439 		pragged(1);
440 		return (0);
441 	}
442 	tad1 = dol + 1;
443 	(void)append(getput, addr2);
444 	undkind = UNDPUT;
445 	notecnt = cnt;
446 	netchange(cnt);
447 	return (0);
448 }
449 
450 /*
451  * A tricky put, of a group of lines in the middle
452  * of an existing line.  Only from open/visual.
453  * Argument says pkills have meaning, e.g. called from
454  * put; it is 0 on calls from putreg.
455  */
456 void
457 pragged(bool kill)
458 {
459 	extern unsigned char *cursor;
460 #ifdef XPG4
461 	extern int P_cursor_offset;
462 #endif
463 	unsigned char *gp = &genbuf[cursor - linebuf];
464 
465 	/*
466 	 * Assume the editor has:
467 	 *
468 	 *	cursor is on 'c'
469 	 *
470 	 *	file is:	1) abcd
471 	 *			2) efgh
472 	 *
473 	 *	undo area:	3) 1
474 	 *			4) 2
475 	 *			5) 3
476 	 */
477 
478 	if (!kill)
479 		getDOT();
480 
481 	/*
482 	 * Copy "abcd" into genbuf.
483 	 * Note that gp points to 'c'.
484 	 */
485 
486 	strcpy(genbuf, linebuf);
487 
488 	/*
489 	 * Get last line of undo area ("3") into linebuf.
490 	 */
491 
492 	getaline(*unddol);
493 	if (kill)
494 		*pkill[1] = 0;
495 
496 
497 	/*
498 	 * Concatenate trailing end of current line
499 	 * into the last line of undo area:
500 	 *	linebuf = "3cd"
501 	 */
502 
503 	strcat(linebuf, gp);
504 #ifdef XPG4
505 	P_cursor_offset = strlen(linebuf) - strlen(gp) - 1;
506 #endif
507 
508 	/*
509 	 * Replace the last line with what is now in linebuf.
510 	 * So unddol = "3cd"
511 	 */
512 
513 	putmark(unddol);
514 
515 	/*
516 	 * Get the first line of the undo save area into linebuf.
517 	 * So linebuf = "1"
518 	 */
519 
520 	getaline(dol[1]);
521 	if (kill)
522 		strcLIN(pkill[0]);
523 
524 	/*
525 	 * Copy the first line of the undo save area
526 	 * over what is pointed to by sp.
527 	 *	genbuf = "ab1"
528 	 */
529 
530 	strcpy(gp, linebuf);
531 
532 	/*
533 	 * Now copy genbuf back into linebuf.
534 	 *	linebuf = "ab1"
535 	 */
536 
537 	strcLIN(genbuf);
538 
539 	/*
540 	 * Now put linebuf back into the first line
541 	 * of the undo save area.
542 	 */
543 
544 	putmark(dol+1);
545 
546 	/*
547 	 * Prepare to perform an undo which will actually
548 	 * do a put of multiple lines in the middle of
549 	 * the current line.
550 	 */
551 
552 	undkind = UNDCHANGE;
553 	undap1 = dot;
554 	undap2 = dot + 1;
555 	unddel = dot - 1;
556 	undo(1);
557 }
558 
559 /*
560  * Shift lines, based on c.
561  * If c is neither < nor >, then this is a lisp aligning =.
562  */
563 void
564 shift(int c, int cnt)
565 {
566 	line *addr;
567 	unsigned char *cp;
568 	unsigned char *dp;
569 	int i;
570 
571 	if(FIXUNDO)
572 		save12(), undkind = UNDCHANGE;
573 	cnt *= value(vi_SHIFTWIDTH);
574 	for (addr = addr1; addr <= addr2; addr++) {
575 		dot = addr;
576 		if (c == '=' && addr == addr1 && addr != addr2)
577 			continue;
578 		getDOT();
579 		i = whitecnt(linebuf);
580 		switch (c) {
581 
582 		case '>':
583 			if (linebuf[0] == 0)
584 				continue;
585 			cp = genindent(i + cnt);
586 			break;
587 
588 		case '<':
589 			if (i == 0)
590 				continue;
591 			i -= cnt;
592 			cp = i > 0 ? genindent(i) : genbuf;
593 			break;
594 
595 		default:
596 			i = lindent(addr);
597 			getDOT();
598 			cp = genindent(i);
599 			break;
600 		}
601 		if (cp + strlen(dp = vpastwh(linebuf)) >= &genbuf[LBSIZE - 2])
602 			error(value(vi_TERSE) ? gettext("Line too long") :
603 gettext("Result line after shift would be too long"));
604 		CP(cp, dp);
605 		strcLIN(genbuf);
606 		putmark(addr);
607 	}
608 	killed();
609 }
610 
611 /*
612  * Find a tag in the tags file.
613  * Most work here is in parsing the tags file itself.
614  */
615 void
616 tagfind(quick)
617 	bool quick;
618 {
619 	unsigned char cmdbuf[BUFSIZE];
620 	unsigned char filebuf[FNSIZE];
621 	unsigned char tagfbuf[BUFSIZE];
622 	int c, d;
623 	bool samef = 1;
624 	int tfcount = 0;
625 	int omagic, tl;
626 	unsigned char *fn, *fne;
627 #ifdef STDIO		/* was VMUNIX */
628 	/*
629 	 * We have lots of room so we bring in stdio and do
630 	 * a binary search on the tags file.
631 	 */
632 	FILE *iof;
633 	unsigned char iofbuf[BUFSIZE];
634 	off64_t mid;	/* assumed byte offset */
635 	off64_t top, bot;	/* length of tag file */
636 	struct stat64 sbuf;
637 #endif
638 
639 	omagic = value(vi_MAGIC);
640 	tl = value(vi_TAGLENGTH);
641 	if (!skipend()) {
642 		unsigned char *lp = lasttag;
643 
644 		while (!iswhite(peekchar()) && !endcmd(peekchar()))
645 			if (lp < &lasttag[sizeof lasttag - 2])
646 				*lp++ = getchar();
647 			else
648 				ignchar();
649 		*lp++ = 0;
650 		if (!endcmd(peekchar()))
651 badtag:
652 			error(value(vi_TERSE) ? gettext("Bad tag") :
653 				gettext("Give one tag per line"));
654 	} else if (lasttag[0] == 0)
655 		error(gettext("No previous tag"));
656 	c = getchar();
657 	if (!endcmd(c))
658 		goto badtag;
659 	if (c == EOF)
660 		ungetchar(c);
661 	clrstats();
662 
663 	/*
664 	 * Loop once for each file in tags "path".
665 	 *
666 	 * System tags array limits to 4k (tags[ONMSZ]) long,
667 	 * therefore, tagfbuf should be able to hold all tags.
668 	 */
669 
670 	CP(tagfbuf, svalue(vi_TAGS));
671 	fne = tagfbuf - 1;
672 	while (fne) {
673 		fn = ++fne;
674 		while (*fne && *fne != ' ')
675 			fne++;
676 		if (*fne == 0)
677 			fne = 0;	/* done, quit after this time */
678 		else
679 			*fne = 0;	/* null terminate filename */
680 #ifdef STDIO		/* was VMUNIX */
681 		iof = fopen((char *)fn, "r");
682 		if (iof == NULL)
683 			continue;
684 		tfcount++;
685 		setbuf(iof, (char *)iofbuf);
686 		fstat64(fileno(iof), &sbuf);
687 		top = sbuf.st_size;
688 		if (top == 0L || iof == NULL)
689 			top = -1L;
690 		bot = 0L;
691 		while (top >= bot) {
692 			/* loop for each tags file entry */
693 			unsigned char *cp = linebuf;
694 			unsigned char *lp = lasttag;
695 			unsigned char *oglobp;
696 
697 			mid = (top + bot) / 2;
698 			fseeko64(iof, mid, 0);
699 			if (mid > 0)	/* to get first tag in file to work */
700 				/* scan to next \n */
701 				if(fgets((char *)linebuf, sizeof linebuf, iof)==NULL)
702 					goto goleft;
703 			/* get the line itself */
704 			if(fgets((char *)linebuf, sizeof linebuf, iof)==NULL)
705 				goto goleft;
706 			linebuf[strlen(linebuf)-1] = 0;	/* was '\n' */
707 			while (*cp && *lp == *cp)
708 				cp++, lp++;
709 			/*
710 			 * This if decides whether there is a tag match.
711 			 *  A positive taglength means that a
712 			 *  match is found if the tag given matches at least
713 			 *  taglength chars of the tag found.
714 			 *  A taglength of greater than 511 means that a
715 			 *  match is found even if the tag given is a proper
716 			 *  prefix of the tag found.  i.e. "ab" matches "abcd"
717 			 */
718 			if ( *lp == 0 && (iswhite(*cp) || tl > 511 || tl > 0 && lp-lasttag >= tl) ) {
719 				/*
720 				 * Found a match.  Force selection to be
721 				 *  the first possible.
722 				 */
723 				if ( mid == bot  &&  mid == top ) {
724 					; /* found first possible match */
725 				}
726 				else {
727 					/* postpone final decision. */
728 					top = mid;
729 					continue;
730 				}
731 			}
732 			else {
733 				if ((int)*lp > (int)*cp)
734 					bot = mid + 1;
735 				else
736 goleft:
737 					top = mid - 1;
738 				continue;
739 			}
740 			/*
741 			 * We found the tag.  Decode the line in the file.
742 			 */
743 			fclose(iof);
744 
745 			/* Rest of tag if abbreviated */
746 			while (!iswhite(*cp))
747 				cp++;
748 
749 			/* name of file */
750 			while (*cp && iswhite(*cp))
751 				cp++;
752 			if (!*cp)
753 badtags:
754 				serror((unsigned char *)
755 				    gettext("%s: Bad tags file entry"),
756 				    lasttag);
757 			lp = filebuf;
758 			while (*cp && *cp != ' ' && *cp != '\t') {
759 				if (lp < &filebuf[sizeof filebuf - 2])
760 					*lp++ = *cp;
761 				cp++;
762 			}
763 			*lp++ = 0;
764 
765 			if (*cp == 0)
766 				goto badtags;
767 			if (dol != zero) {
768 				/*
769 				 * Save current position in 't for ^^ in visual.
770 				 */
771 				names['t'-'a'] = *dot &~ 01;
772 				if (inopen) {
773 					extern unsigned char *ncols['z'-'a'+2];
774 					extern unsigned char *cursor;
775 
776 					ncols['t'-'a'] = cursor;
777 				}
778 			}
779 #ifdef TAG_STACK
780                         if (*savedfile) {
781 				savetag((char *)savedfile);
782                         }
783 #endif
784 			strcpy(cmdbuf, cp);
785 			if (strcmp(filebuf, savedfile) || !edited) {
786 				unsigned char cmdbuf2[sizeof filebuf + 10];
787 
788 				/* Different file.  Do autowrite & get it. */
789 				if (!quick) {
790 					ckaw();
791 					if (chng && dol > zero) {
792 #ifdef TAG_STACK
793                                                 unsavetag();
794 #endif
795 						error(value(vi_TERSE) ?
796 gettext("No write") : gettext("No write since last change (:tag! overrides)"));
797 					}
798 				}
799 				oglobp = globp;
800 				strcpy(cmdbuf2, "e! ");
801 				strcat(cmdbuf2, filebuf);
802 				globp = cmdbuf2;
803 				d = peekc; ungetchar(0);
804 				commands(1, 1);
805 				peekc = d;
806 				globp = oglobp;
807 				value(vi_MAGIC) = omagic;
808 				samef = 0;
809 			}
810 
811 			/*
812 			 * Look for pattern in the current file.
813 			 */
814 			oglobp = globp;
815 			globp = cmdbuf;
816 			d = peekc; ungetchar(0);
817 			if (samef)
818 				markpr(dot);
819 			/*
820 			 * BUG: if it isn't found (user edited header
821 			 * line) we get left in nomagic mode.
822 			 */
823 			value(vi_MAGIC) = 0;
824 			commands(1, 1);
825 			peekc = d;
826 			globp = oglobp;
827 			value(vi_MAGIC) = omagic;
828 			return;
829 		}	/* end of "for each tag in file" */
830 #endif	/* STDIO */
831 		/*
832 		 * Binary search failed, so try linear search if -S is on.
833 		 * -S is needed for tags files that are not sorted.
834 		 */
835 
836 		/*
837 		 * Avoid stdio and scan tag file linearly.
838 		 */
839 		if (tags_flag == 0)
840 			continue;
841 		io = open(fn, 0);
842 		if (io < 0)
843 			continue;
844 		/* tfcount++; */
845 		while (getfile() == 0) {
846 			/* loop for each tags file entry */
847 			unsigned char *cp = linebuf;
848 			unsigned char *lp = lasttag;
849 			unsigned char *oglobp;
850 
851 			while (*cp && *lp == *cp)
852 				cp++, lp++;
853 			/*
854 			 * This if decides whether there is a tag match.
855 			 *  A positive taglength means that a
856 			 *  match is found if the tag given matches at least
857 			 *  taglength chars of the tag found.
858 			 *  A taglength of greater than 511 means that a
859 			 *  match is found even if the tag given is a proper
860 			 *  prefix of the tag found.  i.e. "ab" matches "abcd"
861 			 */
862 			if ( *lp == 0 && (iswhite(*cp) || tl > 511 || tl > 0 && lp-lasttag >= tl) ) {
863 				; /* Found it. */
864 			}
865 			else {
866 				/* Not this tag.  Try the next */
867 				continue;
868 			}
869 			/*
870 			 * We found the tag.  Decode the line in the file.
871 			 */
872 			close(io);
873 			/* Rest of tag if abbreviated */
874 			while (!iswhite(*cp))
875 				cp++;
876 
877 			/* name of file */
878 			while (*cp && iswhite(*cp))
879 				cp++;
880 			if (!*cp)
881 badtags2:
882 				serror((unsigned char *)
883 				    gettext("%s: Bad tags file entry"),
884 				    lasttag);
885 			lp = filebuf;
886 			while (*cp && *cp != ' ' && *cp != '\t') {
887 				if (lp < &filebuf[sizeof filebuf - 2])
888 					*lp++ = *cp;
889 				cp++;
890 			}
891 			*lp++ = 0;
892 
893 			if (*cp == 0)
894 				goto badtags2;
895 			if (dol != zero) {
896 				/*
897 				 * Save current position in 't for ^^ in visual.
898 				 */
899 				names['t'-'a'] = *dot &~ 01;
900 				if (inopen) {
901 					extern unsigned char *ncols['z'-'a'+2];
902 					extern unsigned char *cursor;
903 
904 					ncols['t'-'a'] = cursor;
905 				}
906 			}
907 #ifdef TAG_STACK
908                         if (*savedfile) {
909 				savetag((char *)savedfile);
910                         }
911 #endif
912 			strcpy(cmdbuf, cp);
913 			if (strcmp(filebuf, savedfile) || !edited) {
914 				unsigned char cmdbuf2[sizeof filebuf + 10];
915 
916 				/* Different file.  Do autowrite & get it. */
917 				if (!quick) {
918 					ckaw();
919 					if (chng && dol > zero) {
920 #ifdef TAG_STACK
921                                                 unsavetag();
922 #endif
923 						error(value(vi_TERSE) ?
924 gettext("No write") : gettext("No write since last change (:tag! overrides)"));
925 					}
926 				}
927 				oglobp = globp;
928 				strcpy(cmdbuf2, "e! ");
929 				strcat(cmdbuf2, filebuf);
930 				globp = cmdbuf2;
931 				d = peekc; ungetchar(0);
932 				commands(1, 1);
933 				peekc = d;
934 				globp = oglobp;
935 				value(vi_MAGIC) = omagic;
936 				samef = 0;
937 			}
938 
939 			/*
940 			 * Look for pattern in the current file.
941 			 */
942 			oglobp = globp;
943 			globp = cmdbuf;
944 			d = peekc; ungetchar(0);
945 			if (samef)
946 				markpr(dot);
947 			/*
948 			 * BUG: if it isn't found (user edited header
949 			 * line) we get left in nomagic mode.
950 			 */
951 			value(vi_MAGIC) = 0;
952 			commands(1, 1);
953 			peekc = d;
954 			globp = oglobp;
955 			value(vi_MAGIC) = omagic;
956 			return;
957 		}	/* end of "for each tag in file" */
958 
959 		/*
960 		 * No such tag in this file.  Close it and try the next.
961 		 */
962 #ifdef STDIO		/* was VMUNIX */
963 		fclose(iof);
964 #else
965 		close(io);
966 #endif
967 	}	/* end of "for each file in path" */
968 	if (tfcount <= 0)
969 		error(gettext("No tags file"));
970 	else
971 		serror(value(vi_TERSE) ?
972 		    (unsigned char *)gettext("%s: No such tag") :
973 		    (unsigned char *)gettext("%s: No such tag in tags file"),
974 		    lasttag);
975 }
976 
977 /*
978  * Save lines from addr1 thru addr2 as though
979  * they had been deleted.
980  */
981 int
982 yank(void)
983 {
984 
985 	if (!FIXUNDO)
986 		error(gettext("Can't yank inside global/macro"));
987 	save12();
988 	undkind = UNDNONE;
989 	killcnt(addr2 - addr1 + 1);
990 	return (0);
991 }
992 
993 /*
994  * z command; print windows of text in the file.
995  *
996  * If this seems unreasonably arcane, the reasons
997  * are historical.  This is one of the first commands
998  * added to the first ex (then called en) and the
999  * number of facilities here were the major advantage
1000  * of en over ed since they allowed more use to be
1001  * made of fast terminals w/o typing .,.22p all the time.
1002  */
1003 bool	zhadpr;
1004 bool	znoclear;
1005 short	zweight;
1006 
1007 void
1008 zop(int hadpr)
1009 {
1010 	int c, nlines, op;
1011 	bool excl;
1012 
1013 	zhadpr = hadpr;
1014 	notempty();
1015 	znoclear = 0;
1016 	zweight = 0;
1017 	excl = exclam();
1018 	switch (c = op = getchar()) {
1019 
1020 	case '^':
1021 		zweight = 1;
1022 	case '-':
1023 	case '+':
1024 		while (peekchar() == op) {
1025 			ignchar();
1026 			zweight++;
1027 		}
1028 	case '=':
1029 	case '.':
1030 		c = getchar();
1031 		break;
1032 
1033 	case EOF:
1034 		znoclear++;
1035 		break;
1036 
1037 	default:
1038 		op = 0;
1039 		break;
1040 	}
1041 	if (isdigit(c)) {
1042 		nlines = c - '0';
1043 		for(;;) {
1044 			c = getchar();
1045 			if (!isdigit(c))
1046 				break;
1047 			nlines *= 10;
1048 			nlines += c - '0';
1049 		}
1050 		if (nlines < lines)
1051 			znoclear++;
1052 		value(vi_WINDOW) = nlines;
1053 		if (op == '=')
1054 			nlines += 2;
1055 	}
1056 	else {
1057 		nlines = op == EOF ? value(vi_SCROLL) :
1058 			excl ? lines - 1 : value(vi_WINDOW);
1059 	}
1060 	if (inopen || c != EOF) {
1061 		ungetchar(c);
1062 		donewline();
1063 	}
1064 	addr1 = addr2;
1065 	if (addr2 == 0 && dot < dol && op == 0)
1066 		addr1 = addr2 = dot+1;
1067 	setdot();
1068 	zop2(nlines, op);
1069 }
1070 
1071 void
1072 zop2(int nlines, int op)
1073 {
1074 	line *split;
1075 
1076 	split = NULL;
1077 	switch (op) {
1078 
1079 	case EOF:
1080 		if (addr2 == dol)
1081 			error(gettext("\nAt EOF"));
1082 	case '+':
1083 		if (addr2 == dol)
1084 			error(gettext("At EOF"));
1085 		addr2 += nlines * zweight;
1086 		if (addr2 > dol)
1087 			error(gettext("Hit BOTTOM"));
1088 		addr2++;
1089 	default:
1090 		addr1 = addr2;
1091 		addr2 += nlines-1;
1092 		dot = addr2;
1093 		break;
1094 
1095 	case '=':
1096 	case '.':
1097 		znoclear = 0;
1098 		nlines--;
1099 		nlines >>= 1;
1100 		if (op == '=')
1101 			nlines--;
1102 		addr1 = addr2 - nlines;
1103 		if (op == '=')
1104 			dot = split = addr2;
1105 		addr2 += nlines;
1106 		if (op == '.') {
1107 			markDOT();
1108 			dot = addr2;
1109 		}
1110 		break;
1111 
1112 	case '^':
1113 	case '-':
1114 		addr2 -= nlines * zweight;
1115 		if (addr2 < one)
1116 			error(gettext("Hit TOP"));
1117 		nlines--;
1118 		addr1 = addr2 - nlines;
1119 		dot = addr2;
1120 		break;
1121 	}
1122 	if (addr1 <= zero)
1123 		addr1 = one;
1124 	if (addr2 > dol)
1125 		addr2 = dol;
1126 	if (dot > dol)
1127 		dot = dol;
1128 	if (addr1 > addr2)
1129 		return;
1130 	if (op == EOF && zhadpr) {
1131 		getaline(*addr1);
1132 		putchar((int)('\r' | QUOTE));
1133 		shudclob = 1;
1134 	} else if (znoclear == 0 && clear_screen != NOSTR && !inopen) {
1135 		flush1();
1136 		vclear();
1137 	}
1138 	if (addr2 - addr1 > 1)
1139 		pstart();
1140 	if (split) {
1141 		plines(addr1, split - 1, 0);
1142 		splitit();
1143 		plines(split, split, 0);
1144 		splitit();
1145 		addr1 = split + 1;
1146 	}
1147 	plines(addr1, addr2, 0);
1148 }
1149 
1150 static void
1151 splitit(void)
1152 {
1153 	int l;
1154 
1155 	for (l = columns > 80 ? 40 : columns / 2; l > 0; l--)
1156 		putchar('-');
1157 	putnl();
1158 }
1159 
1160 void
1161 plines(line *adr1, line *adr2, bool movedot)
1162 {
1163 	line *addr;
1164 
1165 	pofix();
1166 	for (addr = adr1; addr <= adr2; addr++) {
1167 		getaline(*addr);
1168 		pline(lineno(addr));
1169 		if (inopen)
1170 			putchar((int)('\n' | QUOTE));
1171 		if (movedot)
1172 			dot = addr;
1173 	}
1174 }
1175 
1176 void
1177 pofix(void)
1178 {
1179 
1180 	if (inopen && Outchar != termchar) {
1181 		vnfl();
1182 		setoutt();
1183 	}
1184 }
1185 
1186 /*
1187  * Command level undo works easily because
1188  * the editor has a unique temporary file
1189  * index for every line which ever existed.
1190  * We don't have to save large blocks of text,
1191  * only the indices which are small.  We do this
1192  * by moving them to after the last line in the
1193  * line buffer array, and marking down info
1194  * about whence they came.
1195  *
1196  * Undo is its own inverse.
1197  */
1198 void
1199 undo(bool c)
1200 {
1201 	int i, k;
1202 	line *jp, *kp, *j;
1203 	line *dolp1, *newdol, *newadot;
1204 
1205 #ifdef UNDOTRACE
1206 	if (trace)
1207 		vudump("before undo");
1208 #endif
1209 	if (inglobal && inopen <= 0)
1210 		error(value(vi_TERSE) ? gettext("Can't undo in global") :
1211 			gettext("Can't undo in global commands"));
1212 
1213 	/*
1214 	 * Unless flag indicates a forced undo, make sure
1215 	 * there really was a change before trying to undo it.
1216 	 */
1217 
1218 	if (!c)
1219 		somechange();
1220 
1221 	/*
1222 	 * Update change flags.
1223 	 */
1224 
1225 	pkill[0] = pkill[1] = 0;
1226 	change();
1227 	if (undkind == UNDMOVE) {
1228  		/*
1229 		 * Command to be undone is a move command.
1230 		 * This is handled as a special case by noting that
1231 		 * a move "a,b m c" can be inverted by another move.
1232 		 */
1233 		if ((i = (jp = unddel) - undap2) > 0) {
1234 			/*
1235 			 * when c > b inverse is a+(c-b),c m a-1
1236 			 */
1237 			addr2 = jp;
1238 			addr1 = (jp = undap1) + i;
1239 			unddel = jp-1;
1240 		} else {
1241 			/*
1242 			 * when b > c inverse is  c+1,c+1+(b-a) m b
1243 			 */
1244 			addr1 = ++jp;
1245 			addr2 = jp + ((unddel = undap2) - undap1);
1246 		}
1247 		kp = undap1;
1248 		move1(0, unddel);
1249 		dot = kp;
1250 		Command = (unsigned char *)"move";
1251 		killed();
1252 	} else {
1253 		int cnt;
1254 
1255 		newadot = dot;
1256 		cnt = lineDOL();
1257 		newdol = dol;
1258 		dolp1 = dol + 1;
1259 		/*
1260 		 * Command to be undone is a non-move.
1261 		 * All such commands are treated as a combination of
1262 		 * a delete command and a append command.
1263 		 * We first move the lines appended by the last command
1264 		 * from undap1 to undap2-1 so that they are just before the
1265 		 * saved deleted lines.
1266 		 *
1267 		 * Assume the editor has:
1268 		 *
1269 		 * 	cursor is on 'c'
1270 		 *
1271 		 *	(just change lines 5-8)
1272 		 *
1273 		 *	file is:	1) ab
1274 		 *			2) cd
1275 		 *			3) ef
1276 		 *			4) gh
1277 		 *	undap1:		5) 12
1278 		 *			6) 34
1279 		 *			7) 56
1280 		 *			8) 78
1281 		 *	undap2:		9) qr
1282 		 *		       10) st
1283 		 *		       11) uv
1284 		 *		       12) wx
1285 		 *	dol:	       13) yz
1286 		 *
1287 		 *	    UNDO AREA:
1288 		 *	dol+1:		5) ij
1289 		 *			6) kl
1290 		 *			7) mn
1291 		 *	unddol:		8) op
1292 		 */
1293 
1294 		/*
1295 		 * If this is a change (not a delete/put),
1296 		 * then we must move the text between undap1 and undap2
1297 		 * and it must not be at the bottom of the file
1298 		 */
1299 
1300 		if ((i = (kp = undap2) - (jp = undap1)) > 0) {
1301 			if (kp != dolp1) {
1302 
1303 		/*
1304 		 * FILE:     LINE    INITIAL   REV1   REV2   REV3
1305 		 *
1306 		 *	      1)       ab	ab     ab     ab
1307 		 *	      2)       cd       cd     cd     cd
1308 		 *            3)       ef       ef     ef     ef
1309 		 * unddel:    4)       gh       gh     gh     gh
1310 		 * undap1:    5)       12       78     78     qr
1311 		 *            6)       34       56     56     st
1312 		 *            7)       56       34     34     uv
1313 		 *            8)       78       12     12     wx
1314 		 * undap2:    9)       qr       qr     yz     yz
1315 		 *           10)       st       st     wx     12
1316 		 *           11)       uv       uv     uv     34
1317 		 *           12)       wx       wx     st     56
1318 		 * dol:      13)       yz       yz     qr     78
1319 		 *
1320 		 *	UNDO AREA:
1321 		 * dol+1:     5)       ij       ij     ij     ij
1322 		 *            6)       kl       kl     kl     kl
1323 		 *	      7)       mn       mn     mn     mn
1324 		 * unddol:    8)       op       op     op     op
1325 		 */
1326 
1327 				reverse(jp, kp);
1328 				reverse(kp, dolp1);
1329 				reverse(jp, dolp1);
1330 			}
1331 			/*
1332 			 * Unddel, the line just before the spot where this
1333 			 * test was deleted, may have moved. Account for
1334 			 * this in restoration of saved deleted lines.
1335 			 */
1336 			if (unddel >= jp)
1337 				unddel -= i;
1338 
1339 			/*
1340 			 * The last line (dol) may have changed,
1341 			 * account for this.
1342 			 */
1343 			 newdol -= i;
1344 
1345 			/*
1346 			 * For the case where no lines are restored, dot
1347 			 * is the line before the first line deleted.
1348 			 */
1349 			dot = jp-1;
1350 		}
1351 		/*
1352 		 * Now put the deleted lines, if any, back where they were.
1353 		 * Basic operation is: dol+1,unddol m unddel
1354 		 */
1355 		if (undkind == UNDPUT) {
1356 			unddel = undap1 - 1;
1357 			squish();
1358 		}
1359 
1360 		/*
1361 		 * Set jp to the line where deleted text is to be added.
1362 		 */
1363 		jp = unddel + 1;
1364 
1365 		/*
1366 		 * Set kp to end of undo save area.
1367 		 *
1368 		 * If there is any deleted text to be added, do reverses.
1369 		 */
1370 
1371 		if ((i = (kp = unddol) - dol) > 0) {
1372 
1373 			/*
1374 			 * If deleted lines are not to be appended
1375 			 * to the bottom of the file...
1376 			 */
1377 
1378 			 if (jp != dolp1) {
1379 				/*
1380 				 * FILE:   LINE   START   REV1   REV2   REV3
1381 				 *          1)     ab      ab     ab     ab
1382 				 *          2)     cd      cd     cd     cd
1383 				 *          3)     ef      ef     ef     ef
1384 				 * unddel:  4)     gh      gh     gh     gh
1385 				 * undap1:  5)     qr      78     78     ij
1386 				 *          6)     st      56     56     kl
1387 				 *          7)     uv      34     34     mn
1388 				 *          8)     wx      12     12     op
1389 				 * undap2:  9)     yz      yz     yz     qr
1390 				 *         10)     12      wx     wx     st
1391 				 *         11)     34      uv     uv     uv
1392 				 *         12)     56      st     st     wx
1393 				 * dol:    13)     78      qr     qr     yz
1394 				 *
1395 				 * UNDO AREA:
1396 				 * dol+1:  5)      ij      ij     op     12
1397 				 *         6)      kl      kl     mn     34
1398 				 *         7)      mn      mn     kl     56
1399 				 * unddol: 8)      op      op     ij     78
1400 				 */
1401 
1402 				 reverse(jp, dolp1);
1403 				reverse(dolp1, ++kp);
1404 				reverse(jp, kp);
1405 			}
1406 			/*
1407 			 * Account for possible forward motion of the target
1408 			 * (where the deleted lines were restored) for after
1409 			 * restoration of the deleted lines.
1410 			 */
1411 			if (undap1 >= jp)
1412 				undap1 += i;
1413 			/*
1414 			 * Dot is the first resurrected line.
1415 			 */
1416 			dot = jp;
1417 
1418 			/*
1419 			 * Account for a shift in the last line (dol).
1420 			 */
1421 
1422 			 newdol += i;
1423 		}
1424 		/*
1425 		 * Clean up so we are invertible
1426 		 */
1427 		unddel = undap1 - 1;
1428 		undap1 = jp;
1429 		undap2 = jp + i;
1430 		dol = newdol;
1431 		netchHAD(cnt);
1432 		if (undkind == UNDALL) {
1433 			dot = undadot;
1434 			undadot = newadot;
1435 		} else
1436 			undkind = UNDCHANGE;
1437  		/*
1438  		 * Now relocate all marks for lines that were modified,
1439  		 * since the marks point to lines whose address has
1440  		 * been modified from the save area to the current
1441  		 * area
1442  		 */
1443 
1444  		for (j=unddol; j> dol; j--)
1445  			for (k=0; k<=25; k++)
1446  				if (names[k] == *(j))
1447  					names[k]= *((undap1+(j-dolp1)) );
1448 	}
1449 	/*
1450 	 * Defensive programming - after a munged undadot.
1451 	 * Also handle empty buffer case.
1452 	 */
1453 	if ((dot <= zero || dot > dol) && dot != dol)
1454 		dot = one;
1455 #ifdef UNDOTRACE
1456 	if (trace)
1457 		vudump("after undo");
1458 #endif
1459 }
1460 
1461 /*
1462  * Be (almost completely) sure there really
1463  * was a change, before claiming to undo.
1464  */
1465 void
1466 somechange(void)
1467 {
1468 	line *ip, *jp;
1469 
1470 	switch (undkind) {
1471 
1472 	case UNDMOVE:
1473 		return;
1474 
1475 	case UNDCHANGE:
1476 		if (undap1 == undap2 && dol == unddol)
1477 			break;
1478 		return;
1479 
1480 	case UNDPUT:
1481 		if (undap1 != undap2)
1482 			return;
1483 		break;
1484 
1485 	case UNDALL:
1486 		if (unddol - dol != lineDOL())
1487 			return;
1488 		for (ip = one, jp = dol + 1; ip <= dol; ip++, jp++)
1489 			if ((*ip &~ 01) != (*jp &~ 01))
1490 				return;
1491 		break;
1492 
1493 	case UNDNONE:
1494 		error(gettext("Nothing to undo"));
1495 	}
1496 	error(value(vi_TERSE) ? gettext("Nothing changed") :
1497 		gettext("Last undoable command didn't change anything"));
1498 }
1499 
1500 /*
1501  * Map command:
1502  * map src dest
1503  *
1504  * un is true if this is unmap command
1505  * ab is true if this is abbr command
1506  */
1507 void
1508 mapcmd(int un, int ab)
1509 {
1510 	unsigned char lhs[100], rhs[100];	/* max sizes resp. */
1511 	unsigned char *p;
1512 	int c;		/* char --> int */
1513 	unsigned char *dname;
1514 	struct maps *mp;	/* the map structure we are working on */
1515 
1516 	mp = ab ? abbrevs : exclam() ? immacs : arrows;
1517 	if (skipend()) {
1518 		int i;
1519 
1520 		/* print current mapping values */
1521 		if (peekchar() != EOF)
1522 			ignchar();
1523 		if (un)
1524 			error(gettext("Missing lhs"));
1525 		if (inopen)
1526 			pofix();
1527 		for (i=0; i< MAXNOMACS && mp[i].mapto; i++)
1528 			if (mp[i].cap) {
1529 				lprintf("%s", mp[i].descr);
1530 				putchar('\t');
1531 				lprintf("%s", mp[i].cap);
1532 				putchar('\t');
1533 				lprintf("%s", mp[i].mapto);
1534 				putNFL();
1535 			}
1536 		return;
1537 	}
1538 
1539 	(void)skipwh();
1540 	for (p=lhs; ; ) {
1541 		c = getchar();
1542 		if (c == CTRL('v')) {
1543 			c = getchar();
1544 		} else if (!un && any(c, " \t")) {
1545 			/* End of lhs */
1546 			break;
1547 		} else if (endcmd(c) && c!='"') {
1548 			ungetchar(c);
1549 			if (un) {
1550 				donewline();
1551 				*p = 0;
1552 				addmac(lhs, (unsigned char *)NOSTR,
1553 				    (unsigned char *)NOSTR, mp);
1554 				return;
1555 			} else
1556 				error(gettext("Missing rhs"));
1557 		}
1558 		*p++ = c;
1559 	}
1560 	*p = 0;
1561 
1562 	if (skipend())
1563 		error(gettext("Missing rhs"));
1564 	for (p=rhs; ; ) {
1565 		c = getchar();
1566 		if (c == CTRL('v')) {
1567 			c = getchar();
1568 		} else if (endcmd(c) && c!='"') {
1569 			ungetchar(c);
1570 			break;
1571 		}
1572 		*p++ = c;
1573 	}
1574 	*p = 0;
1575 	donewline();
1576 	/*
1577 	 * Special hack for function keys: #1 means key f1, etc.
1578 	 * If the terminal doesn't have function keys, we just use #1.
1579 	 */
1580 	if (lhs[0] == '#') {
1581 		unsigned char *fnkey;
1582 		unsigned char *fkey();
1583 		unsigned char funkey[3];
1584 
1585 		fnkey = fkey(lhs[1] - '0');
1586 		funkey[0] = 'f'; funkey[1] = lhs[1]; funkey[2] = 0;
1587 		if (fnkey)
1588 			strcpy(lhs, fnkey);
1589 		dname = funkey;
1590 	} else {
1591 		dname = lhs;
1592 	}
1593 	addmac(lhs,rhs,dname,mp);
1594 }
1595 
1596 /*
1597  * Add a macro definition to those that already exist. The sequence of
1598  * chars "src" is mapped into "dest". If src is already mapped into something
1599  * this overrides the mapping. There is no recursion. Unmap is done by
1600  * using NOSTR for dest.  Dname is what to show in listings.  mp is
1601  * the structure to affect (arrows, etc).
1602  */
1603 void
1604 addmac(unsigned char *src, unsigned char *dest, unsigned char *dname,
1605     struct maps *mp)
1606 {
1607 	int slot, zer;
1608 
1609 #ifdef UNDOTRACE
1610 	if (trace)
1611 		fprintf(trace, "addmac(src='%s', dest='%s', dname='%s', mp=%x\n", src, dest, dname, mp);
1612 #endif
1613 	if (dest && mp==arrows) {
1614 		/*
1615 		 * Prevent tail recursion. We really should be
1616 		 * checking to see if src is a suffix of dest
1617 		 * but this makes mapping involving escapes that
1618 		 * is reasonable mess up.
1619 		 */
1620 		if (src[1] == 0 && src[0] == dest[strlen(dest)-1])
1621 			error(gettext("No tail recursion"));
1622 		/*
1623 		 * We don't let the user rob himself of ":", and making
1624 		 * multi char words is a bad idea so we don't allow it.
1625 		 * Note that if user sets mapinput and maps all of return,
1626 		 * linefeed, and escape, he can hurt himself. This is
1627 		 * so weird I don't bother to check for it.
1628 		 */
1629 		if (isalpha(src[0])  && isascii(src[0]) && src[1] || any(src[0],":"))
1630 			error(gettext("Too dangerous to map that"));
1631 	}
1632 	else if (dest) {
1633 		/* check for tail recursion in input mode: fussier */
1634 		if (eq(src, dest+strlen(dest)-strlen(src)))
1635 			error(gettext("No tail recursion"));
1636 	}
1637 	/*
1638 	 * If the src were null it would cause the dest to
1639 	 * be mapped always forever. This is not good.
1640 	 */
1641 	if (src == (unsigned char *)NOSTR || src[0] == 0)
1642 		error(gettext("Missing lhs"));
1643 
1644 	/* see if we already have a def for src */
1645 	zer = -1;
1646 	for (slot=0; slot < MAXNOMACS && mp[slot].mapto; slot++) {
1647 		if (mp[slot].cap) {
1648 			if (eq(src, mp[slot].cap) || eq(src, mp[slot].mapto))
1649 				break;	/* if so, reuse slot */
1650 		} else {
1651 			zer = slot;	/* remember an empty slot */
1652 		}
1653 	}
1654 
1655 	if (slot >= MAXNOMACS)
1656 		error(gettext("Too many macros"));
1657 
1658 	if (dest == (unsigned char *)NOSTR) {
1659 		/* unmap */
1660 		if (mp[slot].cap) {
1661 			mp[slot].cap = (unsigned char *)NOSTR;
1662 			mp[slot].descr = (unsigned char *)NOSTR;
1663 		} else {
1664 			error(value(vi_TERSE) ? gettext("Not mapped") :
1665 				gettext("That macro wasn't mapped"));
1666 		}
1667 		return;
1668 	}
1669 
1670 	/* reuse empty slot, if we found one and src isn't already defined */
1671 	if (zer >= 0 && mp[slot].mapto == 0)
1672 		slot = zer;
1673 
1674 	/* if not, append to end */
1675 	if (msnext == 0)	/* first time */
1676 		msnext = mapspace;
1677 	/* Check is a bit conservative, we charge for dname even if reusing src */
1678 	if (msnext - mapspace + strlen(dest) + strlen(src) + strlen(dname) + 3 > MAXCHARMACS)
1679 		error(gettext("Too much macro text"));
1680 	CP(msnext, src);
1681 	mp[slot].cap = msnext;
1682 	msnext += strlen(src) + 1;	/* plus 1 for null on the end */
1683 	CP(msnext, dest);
1684 	mp[slot].mapto = msnext;
1685 	msnext += strlen(dest) + 1;
1686 	if (dname) {
1687 		CP(msnext, dname);
1688 		mp[slot].descr = msnext;
1689 		msnext += strlen(dname) + 1;
1690 	} else {
1691 		/* default descr to string user enters */
1692 		mp[slot].descr = src;
1693 	}
1694 }
1695 
1696 /*
1697  * Implements macros from command mode. c is the buffer to
1698  * get the macro from.
1699  */
1700 void
1701 cmdmac(c)
1702 unsigned char c;
1703 {
1704 	unsigned char macbuf[BUFSIZE];
1705 	line *ad, *a1, *a2;
1706 	unsigned char *oglobp;
1707 	short pk;
1708 	bool oinglobal;
1709 
1710 	lastmac = c;
1711 	oglobp = globp;
1712 	oinglobal = inglobal;
1713 	pk = peekc; peekc = 0;
1714 	if (inglobal < 2)
1715 		inglobal = 1;
1716 	regbuf(c, macbuf, sizeof(macbuf));
1717 	a1 = addr1; a2 = addr2;
1718 	for (ad=a1; ad<=a2; ad++) {
1719 		globp = macbuf;
1720 		dot = ad;
1721 		commands(1,1);
1722 	}
1723 	globp = oglobp;
1724 	inglobal = oinglobal;
1725 	peekc = pk;
1726 }
1727 
1728 unsigned char *
1729 vgetpass(prompt)
1730 char *prompt;
1731 {
1732 	unsigned char *p;
1733 	int c;
1734 	static unsigned char pbuf[9];
1735 
1736 	/* In ex mode, let the system hassle with setting no echo */
1737 	if (!inopen)
1738 		return (unsigned char *)getpass(prompt);
1739 	viprintf("%s", prompt); flush();
1740 	for (p=pbuf; (c = getkey())!='\n' && c!=EOF && c!='\r';) {
1741 		if (p < &pbuf[8])
1742 			*p++ = c;
1743 	}
1744 	*p = '\0';
1745 	return(pbuf);
1746 }
1747 
1748 
1749 #ifdef TAG_STACK
1750 #define TSTACKSIZE 20
1751 struct tagstack {
1752 	line *tag_line;
1753 	char *tag_file;
1754 } tagstack[TSTACKSIZE];
1755 static int tag_depth = 0;
1756 
1757 static char tag_buf[ 1024 ];
1758 static char *tag_end = tag_buf;
1759 
1760 void
1761 savetag(char *name)	/* saves location where we are */
1762 {
1763 	if( !value(vi_TAGSTACK) )
1764 		return;
1765 	if(tag_depth >= TSTACKSIZE) {
1766 		error(gettext("Tagstack too deep."));
1767 	}
1768 	if( strlen( name ) + 1 + tag_end >= &tag_buf[1024]) {
1769 		error(gettext("Too many tags."));
1770 	}
1771 	tagstack[tag_depth].tag_line = dot;
1772 	tagstack[tag_depth++].tag_file = tag_end;
1773 	while(*tag_end++ = *name++)
1774 		;
1775 }
1776 
1777 /*
1778  * Undo a "savetag".
1779  */
1780 void
1781 unsavetag(void)
1782 {
1783 	if (!value(vi_TAGSTACK))
1784 		return;
1785 	if (tag_depth > 0)
1786 		tag_end = tagstack[--tag_depth].tag_file;
1787 }
1788 
1789 void
1790 poptag(quick)	/* puts us back where we came from */
1791 bool quick;
1792 {
1793 	unsigned char cmdbuf[100];
1794 	unsigned char *oglobp;
1795 	int d;
1796 
1797 	if (!value(vi_TAGSTACK)) {	/* reset the stack */
1798 		tag_end = tag_buf;
1799 		d = tag_depth;
1800 		tag_depth = 0;
1801 		if (d == 0)
1802 			error(gettext("Tagstack not enabled."));
1803 		else
1804 			return;
1805 	}
1806 	if (!tag_depth)
1807 		error(gettext("Tagstack empty."));
1808 
1809 	/* change to old file */
1810 	if (strcmp(tagstack[tag_depth-1].tag_file, savedfile) ) {
1811 		if (!quick) {
1812 			ckaw();
1813 			if (chng && dol > zero)
1814 				error(value(vi_TERSE) ?
1815 gettext("No write") : gettext("No write since last change (:pop! overrides)"));
1816 		}
1817 		oglobp = globp;
1818 		strcpy(cmdbuf, "e! ");
1819 		strcat(cmdbuf, tagstack[tag_depth-1].tag_file);
1820 		globp = cmdbuf;
1821 		d = peekc; ungetchar(0);
1822 		commands(1, 1);
1823 		peekc = d;
1824 		globp = oglobp;
1825 	}
1826 		markpr(dot);
1827 	/* set line number */
1828 	dot = tagstack[--tag_depth].tag_line;
1829 	tag_end = tagstack[tag_depth].tag_file;
1830 }
1831 #endif
1832