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