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