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