view usr/src/cmd/vi/port/ex_cmdsub.c @ 13726:056b82d21d71

1450 Illumos should be buildable with GCC4 Reviewed by: Joshua M. Clulow <josh@sysmgr.org> Reviewed by: Keith Wesolowski <keith.wesolowski@joyent.com> Reviewed by: Gordon Ross <gwr@nexenta.com> Reviewed by: Albert Lee <trisk@nexenta.com> Approved by: Gordon Ross <gwr@nexenta.com>
author Richard Lowe <richlowe@richlowe.net>
date Sat, 14 Apr 2012 20:29:22 -0400
parents 48f2dbca79a2
children
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


/* Copyright (c) 1981 Regents of the University of California */

#include "ex.h"
#include "ex_argv.h"
#include "ex_temp.h"
#include "ex_tty.h"
#include "ex_vis.h"
#ifdef	STDIO
#include	<stdio.h>
#undef getchar
#undef putchar
#endif


/*
 * Command mode subroutines implementing
 *	append, args, copy, delete, join, move, put,
 *	shift, tag, yank, z and undo
 */

bool	endline = 1;
line	*tad1;
static int jnoop(void);
static void splitit(void);
int putchar(), getchar();
int tags_flag;

/*
 * Append after line a lines returned by function f.
 * Be careful about intermediate states to avoid scramble
 * if an interrupt comes in.
 */
int
append(int (*f)(), line *a)
{
	line *a1, *a2, *rdot;
	int nline;

	nline = 0;
	dot = a;
	if(FIXUNDO && !inopen && f!=getsub) {
		undap1 = undap2 = dot + 1;
		undkind = UNDCHANGE;
	}
	while ((*f)() == 0) {
		if (truedol >= endcore) {
			if (morelines() < 0) {
				if (FIXUNDO && f == getsub) {
					undap1 = addr1;
					undap2 = addr2 + 1;
				}
				error(value(vi_TERSE) ? gettext("Out of memory") :
gettext("Out of memory- too many lines in file"));
			}
		}
		nline++;
		a1 = truedol + 1;
		a2 = a1 + 1;
		dot++;
		undap2++;
		dol++;
		unddol++;
		truedol++;
		for (rdot = dot; a1 > rdot;)
			*--a2 = *--a1;
		*rdot = 0;
		putmark(rdot);
		if (f == gettty) {
			dirtcnt++;
			TSYNC();
		}
	}
	return (nline);
}

void
appendnone(void)
{

	if(FIXUNDO) {
		undkind = UNDCHANGE;
		undap1 = undap2 = addr1;
	}
}

/*
 * Print out the argument list, with []'s around the current name.
 */
void
pargs(void)
{
	unsigned char **av = argv0, *as = args0;
	int ac;

	for (ac = 0; ac < argc0; ac++) {
		if (ac != 0)
			putchar(' ');
		if (ac + argc == argc0 - 1)
			viprintf("[");
		lprintf("%s", as);
		if (ac + argc == argc0 - 1)
			viprintf("]");
		as = av ? *++av : strend(as) + 1;
	}
	noonl();
}

/*
 * Delete lines; two cases are if we are really deleting,
 * more commonly we are just moving lines to the undo save area.
 */
int
delete(bool hush)
{
	line *a1, *a2;

	nonzero();
	if(FIXUNDO) {
		void (*dsavint)();

#ifdef UNDOTRACE
		if (trace)
			vudump("before delete");
#endif
		change();
		dsavint = signal(SIGINT, SIG_IGN);
		undkind = UNDCHANGE;
		a1 = addr1;
		squish();
		a2 = addr2;
		if (a2++ != dol) {
			reverse(a1, a2);
			reverse(a2, dol + 1);
			reverse(a1, dol + 1);
		}
		dol -= a2 - a1;
		unddel = a1 - 1;
		if (a1 > dol)
			a1 = dol;
		dot = a1;
		pkill[0] = pkill[1] = 0;
		signal(SIGINT, dsavint);
#ifdef UNDOTRACE
		if (trace)
			vudump("after delete");
#endif
	} else {
		line *a3;
		int i;

		change();
		a1 = addr1;
		a2 = addr2 + 1;
		a3 = truedol;
		i = a2 - a1;
		unddol -= i;
		undap2 -= i;
		dol -= i;
		truedol -= i;
		do
			*a1++ = *a2++;
		while (a2 <= a3);
		a1 = addr1;
		if (a1 > dol)
			a1 = dol;
		dot = a1;
	}
	if (!hush)
		killed();
	return (0);
}

void
deletenone(void)
{

	if(FIXUNDO) {
		undkind = UNDCHANGE;
		squish();
		unddel = addr1;
	}
}

/*
 * Crush out the undo save area, moving the open/visual
 * save area down in its place.
 */
void
squish(void)
{
	line *a1 = dol + 1, *a2 = unddol + 1, *a3 = truedol + 1;

	if(FIXUNDO) {
		if (inopen == -1)
			return;
		if (a1 < a2 && a2 < a3)
			do
				*a1++ = *a2++;
			while (a2 < a3);
		truedol -= unddol - dol;
		unddol = dol;
	}
}

/*
 * Join lines.  Special hacks put in spaces, two spaces if
 * preceding line ends with '.', or no spaces if next line starts with ).
 */
static	int jcount;

int
join(int c)
{
	line *a1;
	unsigned char *cp, *cp1;
#ifndef PRESUNEUC
	unsigned char *pcp;
	wchar_t *delim;
	wchar_t wc1, wc2;
	int n;
#endif /* PRESUNEUC */

	cp = genbuf;
	*cp = 0;
	for (a1 = addr1; a1 <= addr2; a1++) {
		getaline(*a1);
		cp1 = linebuf;
		if (a1 != addr1 && c == 0) {
			while (*cp1 == ' ' || *cp1 == '\t')
				cp1++;
			if (*cp1 && cp > genbuf && cp[-1] != ' ' && cp[-1] != '\t') {
#ifndef PRESUNEUC
				/*
				 * insert locale-specific word delimiter if
				 * either of end-of-former-line or
				 * top-of-latter-line is non-ASCII.
				 */
				if (wddlm && *cp1 != ')' && cp[-1] != '.') {
					if ((pcp = cp - MB_CUR_MAX) < genbuf)
						pcp = genbuf;;
					for ( ; pcp <= cp-1; pcp++) {
						if ((n = mbtowc(&wc1,
						    (char *)pcp, cp - pcp)) ==
						    cp - pcp)
							goto gotprev;
					}
					goto mberror;
gotprev:
					if (!isascii(wc2 = *cp1)) {
						if (mbtowc(&wc2, (char *) cp1,
					    		   MB_CUR_MAX) <= 0)
							goto mberror;
					}
					delim = (*wddlm)(wc1,wc2,2);
					while (*delim)
						cp += wctomb((char *)cp,
						      *delim++);
					*cp = 0;
				} else
mberror:
#endif /* PRESUNEUC */
				if (*cp1 != ')') {
					*cp++ = ' ';
					if (cp[-2] == '.')
						*cp++ = ' ';
				}
			}
		}
		while (*cp++ = *cp1++)
			if (cp > &genbuf[LBSIZE-2])
				error(value(vi_TERSE) ? gettext("Line overflow") :
gettext("Result line of join would be too long"));
		cp--;
	}
	strcLIN(genbuf);
	(void) delete(0);
	jcount = 1;
	if (FIXUNDO)
		undap1 = undap2 = addr1;
	(void)append(jnoop, --addr1);
	if (FIXUNDO)
		vundkind = VMANY;
	return (0);
}

static int
jnoop(void)
{

	return(--jcount);
}

/*
 * Move and copy lines.  Hard work is done by move1 which
 * is also called by undo.
 */
int	getcopy();

void
vi_move(void)
{
	line *adt;
	bool iscopy = 0;

	if (Command[0] == 'm') {
		setdot1();
		markpr(addr2 == dot ? addr1 - 1 : addr2 + 1);
	} else {
		iscopy++;
		setdot();
	}
	nonzero();
	adt = address((char*)0);
	if (adt == 0)
		serror(value(vi_TERSE) ?
		    (unsigned char *)gettext("%s where?") :
		    (unsigned char *)gettext("%s requires a trailing address"),
		    Command);
	donewline();
	move1(iscopy, adt);
	killed();
}

void
move1(int cflag, line *addrt)
{
	line *adt, *ad1, *ad2;
	int nlines;

	adt = addrt;
	nlines = (addr2 - addr1) + 1;
	if (cflag) {
		tad1 = addr1;
		ad1 = dol;
		(void)append(getcopy, ad1++);
		ad2 = dol;
	} else {
		ad2 = addr2;
		for (ad1 = addr1; ad1 <= ad2;)
			*ad1++ &= ~01;
		ad1 = addr1;
	}
	ad2++;
	if (adt < ad1) {
		if (adt + 1 == ad1 && !cflag && !inglobal)
			error(gettext("That move would do nothing!"));
		dot = adt + (ad2 - ad1);
		if (++adt != ad1) {
			reverse(adt, ad1);
			reverse(ad1, ad2);
			reverse(adt, ad2);
		}
	} else if (adt >= ad2) {
		dot = adt++;
		reverse(ad1, ad2);
		reverse(ad2, adt);
		reverse(ad1, adt);
	} else
		error(gettext("Move to a moved line"));
	change();
	if (!inglobal)
		if(FIXUNDO) {
			if (cflag) {
				undap1 = addrt + 1;
				undap2 = undap1 + nlines;
				deletenone();
			} else {
				undkind = UNDMOVE;
				undap1 = addr1;
				undap2 = addr2;
				unddel = addrt;
				squish();
			}
		}
}

int
getcopy(void)
{

	if (tad1 > addr2)
		return (EOF);
	getaline(*tad1++);
	return (0);
}

/*
 * Put lines in the buffer from the undo save area.
 */
int
getput(void)
{

	if (tad1 > unddol)
		return (EOF);
	getaline(*tad1++);
	tad1++;
	return (0);
}

int
put(void)
{
	int cnt;

	if (!FIXUNDO)
		error(gettext("Cannot put inside global/macro"));
	cnt = unddol - dol;
	if (cnt && inopen && pkill[0] && pkill[1]) {
		pragged(1);
		return (0);
	}
	tad1 = dol + 1;
	(void)append(getput, addr2);
	undkind = UNDPUT;
	notecnt = cnt;
	netchange(cnt);
	return (0);
}

/*
 * A tricky put, of a group of lines in the middle
 * of an existing line.  Only from open/visual.
 * Argument says pkills have meaning, e.g. called from
 * put; it is 0 on calls from putreg.
 */
void
pragged(bool kill)
{
	extern unsigned char *cursor;
#ifdef XPG4
	extern int P_cursor_offset;
#endif
	unsigned char *gp = &genbuf[cursor - linebuf];

	/*
	 * Assume the editor has:
	 *
	 *	cursor is on 'c'
	 *
	 *	file is:	1) abcd
	 *			2) efgh
	 *
	 *	undo area:	3) 1
	 *			4) 2
	 *			5) 3
	 */

	if (!kill)
		getDOT();
	
	/*
	 * Copy "abcd" into genbuf.
	 * Note that gp points to 'c'.
	 */

	strcpy(genbuf, linebuf);
	
	/*
	 * Get last line of undo area ("3") into linebuf.
	 */

	getaline(*unddol);
	if (kill)
		*pkill[1] = 0;
	

	/*
	 * Concatenate trailing end of current line
	 * into the last line of undo area:
	 *	linebuf = "3cd"
	 */

	strcat(linebuf, gp);
#ifdef XPG4
	P_cursor_offset = strlen(linebuf) - strlen(gp) - 1;
#endif

	/*
	 * Replace the last line with what is now in linebuf.
	 * So unddol = "3cd"
	 */

	putmark(unddol);

	/*
	 * Get the first line of the undo save area into linebuf.
	 * So linebuf = "1"
	 */

	getaline(dol[1]);
	if (kill)
		strcLIN(pkill[0]);
	
	/*
	 * Copy the first line of the undo save area
	 * over what is pointed to by sp.
	 *	genbuf = "ab1"
	 */

	strcpy(gp, linebuf);
	
	/*
	 * Now copy genbuf back into linebuf.
	 *	linebuf = "ab1"
	 */

	strcLIN(genbuf);

	/*
	 * Now put linebuf back into the first line
	 * of the undo save area.
	 */

	putmark(dol+1);

	/*
	 * Prepare to perform an undo which will actually
	 * do a put of multiple lines in the middle of
	 * the current line.
	 */

	undkind = UNDCHANGE;
	undap1 = dot;
	undap2 = dot + 1;
	unddel = dot - 1;
	undo(1);
}

/*
 * Shift lines, based on c.
 * If c is neither < nor >, then this is a lisp aligning =.
 */
void
shift(int c, int cnt)
{
	line *addr;
	unsigned char *cp;
	unsigned char *dp;
	int i;

	if(FIXUNDO)
		save12(), undkind = UNDCHANGE;
	cnt *= value(vi_SHIFTWIDTH);
	for (addr = addr1; addr <= addr2; addr++) {
		dot = addr;
		if (c == '=' && addr == addr1 && addr != addr2)
			continue;
		getDOT();
		i = whitecnt(linebuf);
		switch (c) {

		case '>':
			if (linebuf[0] == 0)
				continue;
			cp = genindent(i + cnt);
			break;

		case '<':
			if (i == 0)
				continue;
			i -= cnt;
			cp = i > 0 ? genindent(i) : genbuf;
			break;

		default:
			i = lindent(addr);
			getDOT();
			cp = genindent(i);
			break;
		}
		if (cp + strlen(dp = vpastwh(linebuf)) >= &genbuf[LBSIZE - 2])
			error(value(vi_TERSE) ? gettext("Line too long") :
gettext("Result line after shift would be too long"));
		CP(cp, dp);
		strcLIN(genbuf);
		putmark(addr);
	}
	killed();
}

/*
 * Find a tag in the tags file.
 * Most work here is in parsing the tags file itself.
 */
void
tagfind(quick)
	bool quick;
{
	unsigned char cmdbuf[BUFSIZE];
	unsigned char filebuf[FNSIZE];
	unsigned char tagfbuf[BUFSIZE];
	int c, d;
	bool samef = 1;
	int tfcount = 0;
	int omagic, tl;
	unsigned char *fn, *fne;
#ifdef STDIO		/* was VMUNIX */
	/*
	 * We have lots of room so we bring in stdio and do
	 * a binary search on the tags file.
	 */
	FILE *iof;
	unsigned char iofbuf[BUFSIZE];
	off64_t mid;	/* assumed byte offset */
	off64_t top, bot;	/* length of tag file */
	struct stat64 sbuf;
#endif

	omagic = value(vi_MAGIC);
	tl = value(vi_TAGLENGTH);
	if (!skipend()) {
		unsigned char *lp = lasttag;

		while (!iswhite(peekchar()) && !endcmd(peekchar()))
			if (lp < &lasttag[sizeof lasttag - 2])
				*lp++ = getchar();
			else
				ignchar();
		*lp++ = 0;
		if (!endcmd(peekchar()))
badtag:
			error(value(vi_TERSE) ? gettext("Bad tag") :
				gettext("Give one tag per line"));
	} else if (lasttag[0] == 0)
		error(gettext("No previous tag"));
	c = getchar();
	if (!endcmd(c))
		goto badtag;
	if (c == EOF)
		ungetchar(c);
	clrstats();

	/*
	 * Loop once for each file in tags "path".
	 * 
	 * System tags array limits to 4k (tags[ONMSZ]) long, 
	 * therefore, tagfbuf should be able to hold all tags.
	 */

	CP(tagfbuf, svalue(vi_TAGS));
	fne = tagfbuf - 1;
	while (fne) {
		fn = ++fne;
		while (*fne && *fne != ' ')
			fne++;
		if (*fne == 0)
			fne = 0;	/* done, quit after this time */
		else
			*fne = 0;	/* null terminate filename */
#ifdef STDIO		/* was VMUNIX */
		iof = fopen((char *)fn, "r");
		if (iof == NULL)
			continue;
		tfcount++;
		setbuf(iof, (char *)iofbuf);
		fstat64(fileno(iof), &sbuf);
		top = sbuf.st_size;
		if (top == 0L || iof == NULL)
			top = -1L;
		bot = 0L;
		while (top >= bot) {
			/* loop for each tags file entry */
			unsigned char *cp = linebuf;
			unsigned char *lp = lasttag;
			unsigned char *oglobp;

			mid = (top + bot) / 2;
			fseeko64(iof, mid, 0);
			if (mid > 0)	/* to get first tag in file to work */
				/* scan to next \n */
				if(fgets((char *)linebuf, sizeof linebuf, iof)==NULL)
					goto goleft;
			/* get the line itself */
			if(fgets((char *)linebuf, sizeof linebuf, iof)==NULL)
				goto goleft;
			linebuf[strlen(linebuf)-1] = 0;	/* was '\n' */
			while (*cp && *lp == *cp)
				cp++, lp++;
			/*
			 * This if decides whether there is a tag match.
			 *  A positive taglength means that a
			 *  match is found if the tag given matches at least
			 *  taglength chars of the tag found.
			 *  A taglength of greater than 511 means that a
			 *  match is found even if the tag given is a proper
			 *  prefix of the tag found.  i.e. "ab" matches "abcd"
			 */
			if ( *lp == 0 && (iswhite(*cp) || tl > 511 || tl > 0 && lp-lasttag >= tl) ) {
				/*
				 * Found a match.  Force selection to be
				 *  the first possible.
				 */
				if ( mid == bot  &&  mid == top ) {
					; /* found first possible match */
				}
				else {
					/* postpone final decision. */
					top = mid;
					continue;
				}
			}
			else {
				if ((int)*lp > (int)*cp)
					bot = mid + 1;
				else
goleft:
					top = mid - 1;
				continue;
			}
			/*
			 * We found the tag.  Decode the line in the file.
			 */
			fclose(iof);

			/* Rest of tag if abbreviated */
			while (!iswhite(*cp))
				cp++;

			/* name of file */
			while (*cp && iswhite(*cp))
				cp++;
			if (!*cp)
badtags:
				serror((unsigned char *)
				    gettext("%s: Bad tags file entry"),
				    lasttag);
			lp = filebuf;
			while (*cp && *cp != ' ' && *cp != '\t') {
				if (lp < &filebuf[sizeof filebuf - 2])
					*lp++ = *cp;
				cp++;
			}
			*lp++ = 0;

			if (*cp == 0)
				goto badtags;
			if (dol != zero) {
				/*
				 * Save current position in 't for ^^ in visual.
				 */
				names['t'-'a'] = *dot &~ 01;
				if (inopen) {
					extern unsigned char *ncols['z'-'a'+2];
					extern unsigned char *cursor;

					ncols['t'-'a'] = cursor;
				}
			}
#ifdef TAG_STACK
                        if (*savedfile) {
				savetag((char *)savedfile);
                        }
#endif 
			strcpy(cmdbuf, cp);
			if (strcmp(filebuf, savedfile) || !edited) {
				unsigned char cmdbuf2[sizeof filebuf + 10];

				/* Different file.  Do autowrite & get it. */
				if (!quick) {
					ckaw();
					if (chng && dol > zero) {
#ifdef TAG_STACK
                                                unsavetag();
#endif
						error(value(vi_TERSE) ?
gettext("No write") : gettext("No write since last change (:tag! overrides)"));
					}
				}
				oglobp = globp;
				strcpy(cmdbuf2, "e! ");
				strcat(cmdbuf2, filebuf);
				globp = cmdbuf2;
				d = peekc; ungetchar(0);
				commands(1, 1);
				peekc = d;
				globp = oglobp;
				value(vi_MAGIC) = omagic;
				samef = 0;
			}

			/*
			 * Look for pattern in the current file.
			 */
			oglobp = globp;
			globp = cmdbuf;
			d = peekc; ungetchar(0);
			if (samef)
				markpr(dot);
			/*
			 * BUG: if it isn't found (user edited header
			 * line) we get left in nomagic mode.
			 */
			value(vi_MAGIC) = 0;
			commands(1, 1);
			peekc = d;
			globp = oglobp;
			value(vi_MAGIC) = omagic;
			return;
		}	/* end of "for each tag in file" */
#endif	/* STDIO */
		/*
		 * Binary search failed, so try linear search if -S is on.
		 * -S is needed for tags files that are not sorted.
		 */

		/*
		 * Avoid stdio and scan tag file linearly.
		 */
		if (tags_flag == 0)
			continue;
		io = open(fn, 0);
		if (io < 0)
			continue;
		/* tfcount++; */
		while (getfile() == 0) {
			/* loop for each tags file entry */
			unsigned char *cp = linebuf;
			unsigned char *lp = lasttag;
			unsigned char *oglobp;

			while (*cp && *lp == *cp)
				cp++, lp++;
			/*
			 * This if decides whether there is a tag match.
			 *  A positive taglength means that a
			 *  match is found if the tag given matches at least
			 *  taglength chars of the tag found.
			 *  A taglength of greater than 511 means that a
			 *  match is found even if the tag given is a proper
			 *  prefix of the tag found.  i.e. "ab" matches "abcd"
			 */
			if ( *lp == 0 && (iswhite(*cp) || tl > 511 || tl > 0 && lp-lasttag >= tl) ) {
				; /* Found it. */
			}
			else {
				/* Not this tag.  Try the next */
				continue;
			}
			/*
			 * We found the tag.  Decode the line in the file.
			 */
			close(io);
			/* Rest of tag if abbreviated */
			while (!iswhite(*cp))
				cp++;

			/* name of file */
			while (*cp && iswhite(*cp))
				cp++;
			if (!*cp)
badtags2:
				serror((unsigned char *)
				    gettext("%s: Bad tags file entry"),
				    lasttag);
			lp = filebuf;
			while (*cp && *cp != ' ' && *cp != '\t') {
				if (lp < &filebuf[sizeof filebuf - 2])
					*lp++ = *cp;
				cp++;
			}
			*lp++ = 0;

			if (*cp == 0)
				goto badtags2;
			if (dol != zero) {
				/*
				 * Save current position in 't for ^^ in visual.
				 */
				names['t'-'a'] = *dot &~ 01;
				if (inopen) {
					extern unsigned char *ncols['z'-'a'+2];
					extern unsigned char *cursor;

					ncols['t'-'a'] = cursor;
				}
			}
#ifdef TAG_STACK
                        if (*savedfile) {
				savetag((char *)savedfile);
                        }
#endif 
			strcpy(cmdbuf, cp);
			if (strcmp(filebuf, savedfile) || !edited) {
				unsigned char cmdbuf2[sizeof filebuf + 10];

				/* Different file.  Do autowrite & get it. */
				if (!quick) {
					ckaw();
					if (chng && dol > zero) {
#ifdef TAG_STACK
                                                unsavetag();
#endif
						error(value(vi_TERSE) ?
gettext("No write") : gettext("No write since last change (:tag! overrides)"));
					}
				}
				oglobp = globp;
				strcpy(cmdbuf2, "e! ");
				strcat(cmdbuf2, filebuf);
				globp = cmdbuf2;
				d = peekc; ungetchar(0);
				commands(1, 1);
				peekc = d;
				globp = oglobp;
				value(vi_MAGIC) = omagic;
				samef = 0;
			}

			/*
			 * Look for pattern in the current file.
			 */
			oglobp = globp;
			globp = cmdbuf;
			d = peekc; ungetchar(0);
			if (samef)
				markpr(dot);
			/*
			 * BUG: if it isn't found (user edited header
			 * line) we get left in nomagic mode.
			 */
			value(vi_MAGIC) = 0;
			commands(1, 1);
			peekc = d;
			globp = oglobp;
			value(vi_MAGIC) = omagic;
			return;
		}	/* end of "for each tag in file" */

		/*
		 * No such tag in this file.  Close it and try the next.
		 */
#ifdef STDIO		/* was VMUNIX */
		fclose(iof);
#else
		close(io);
#endif
	}	/* end of "for each file in path" */
	if (tfcount <= 0)
		error(gettext("No tags file"));
	else
		serror(value(vi_TERSE) ?
		    (unsigned char *)gettext("%s: No such tag") :
		    (unsigned char *)gettext("%s: No such tag in tags file"),
		    lasttag);
}

/*
 * Save lines from addr1 thru addr2 as though
 * they had been deleted.
 */
int
yank(void)
{

	if (!FIXUNDO)
		error(gettext("Can't yank inside global/macro"));
	save12();
	undkind = UNDNONE;
	killcnt(addr2 - addr1 + 1);
	return (0);
}

/*
 * z command; print windows of text in the file.
 *
 * If this seems unreasonably arcane, the reasons
 * are historical.  This is one of the first commands
 * added to the first ex (then called en) and the
 * number of facilities here were the major advantage
 * of en over ed since they allowed more use to be
 * made of fast terminals w/o typing .,.22p all the time.
 */
bool	zhadpr;
bool	znoclear;
short	zweight;

void
zop(int hadpr)
{
	int c, nlines, op;
	bool excl;

	zhadpr = hadpr;
	notempty();
	znoclear = 0;
	zweight = 0;
	excl = exclam();
	switch (c = op = getchar()) {

	case '^':
		zweight = 1;
	case '-':
	case '+':
		while (peekchar() == op) {
			ignchar();
			zweight++;
		}
	case '=':
	case '.':
		c = getchar();
		break;

	case EOF:
		znoclear++;
		break;

	default:
		op = 0;
		break;
	}
	if (isdigit(c)) {
		nlines = c - '0';
		for(;;) {
			c = getchar();
			if (!isdigit(c))
				break;
			nlines *= 10;
			nlines += c - '0';
		}
		if (nlines < lines)
			znoclear++;
		value(vi_WINDOW) = nlines;
		if (op == '=')
			nlines += 2;
	}
	else {
		nlines = op == EOF ? value(vi_SCROLL) :
			excl ? lines - 1 : value(vi_WINDOW);
	}
	if (inopen || c != EOF) {
		ungetchar(c);
		donewline();
	}
	addr1 = addr2;
	if (addr2 == 0 && dot < dol && op == 0)
		addr1 = addr2 = dot+1;
	setdot();
	zop2(nlines, op);
}

void
zop2(int nlines, int op)
{
	line *split;

	split = NULL;
	switch (op) {

	case EOF:
		if (addr2 == dol)
			error(gettext("\nAt EOF"));
	case '+':
		if (addr2 == dol)
			error(gettext("At EOF"));
		addr2 += nlines * zweight;
		if (addr2 > dol)
			error(gettext("Hit BOTTOM"));
		addr2++;
	default:
		addr1 = addr2;
		addr2 += nlines-1;
		dot = addr2;
		break;

	case '=':
	case '.':
		znoclear = 0;
		nlines--;
		nlines >>= 1;
		if (op == '=')
			nlines--;
		addr1 = addr2 - nlines;
		if (op == '=')
			dot = split = addr2;
		addr2 += nlines;
		if (op == '.') {
			markDOT();
			dot = addr2;
		}
		break;

	case '^':
	case '-':
		addr2 -= nlines * zweight;
		if (addr2 < one)
			error(gettext("Hit TOP"));
		nlines--;
		addr1 = addr2 - nlines;
		dot = addr2;
		break;
	}
	if (addr1 <= zero)
		addr1 = one;
	if (addr2 > dol)
		addr2 = dol;
	if (dot > dol)
		dot = dol;
	if (addr1 > addr2)
		return;
	if (op == EOF && zhadpr) {
		getaline(*addr1);
		putchar((int)('\r' | QUOTE));
		shudclob = 1;
	} else if (znoclear == 0 && clear_screen != NOSTR && !inopen) {
		flush1();
		vclear();
	}
	if (addr2 - addr1 > 1)
		pstart();
	if (split) {
		plines(addr1, split - 1, 0);
		splitit();
		plines(split, split, 0);
		splitit();
		addr1 = split + 1;
	}
	plines(addr1, addr2, 0);
}

static void
splitit(void)
{
	int l;

	for (l = columns > 80 ? 40 : columns / 2; l > 0; l--)
		putchar('-');
	putnl();
}

void
plines(line *adr1, line *adr2, bool movedot)
{
	line *addr;

	pofix();
	for (addr = adr1; addr <= adr2; addr++) {
		getaline(*addr);
		pline(lineno(addr));
		if (inopen)
			putchar((int)('\n' | QUOTE));
		if (movedot)
			dot = addr;
	}
}

void
pofix(void)
{

	if (inopen && Outchar != termchar) {
		vnfl();
		setoutt();
	}
}

/*
 * Command level undo works easily because
 * the editor has a unique temporary file
 * index for every line which ever existed.
 * We don't have to save large blocks of text,
 * only the indices which are small.  We do this
 * by moving them to after the last line in the
 * line buffer array, and marking down info
 * about whence they came.
 *
 * Undo is its own inverse.
 */
void
undo(bool c)
{
	int i, k;
	line *jp, *kp, *j;
	line *dolp1, *newdol, *newadot;

#ifdef UNDOTRACE
	if (trace)
		vudump("before undo");
#endif
	if (inglobal && inopen <= 0)
		error(value(vi_TERSE) ? gettext("Can't undo in global") :
			gettext("Can't undo in global commands"));
	
	/*
	 * Unless flag indicates a forced undo, make sure
	 * there really was a change before trying to undo it.
	 */

	if (!c)
		somechange();
	
	/*
	 * Update change flags.
	 */

	pkill[0] = pkill[1] = 0;
	change();
	if (undkind == UNDMOVE) {
 		/*
		 * Command to be undone is a move command.
		 * This is handled as a special case by noting that
		 * a move "a,b m c" can be inverted by another move.
		 */
		if ((i = (jp = unddel) - undap2) > 0) {
			/*
			 * when c > b inverse is a+(c-b),c m a-1
			 */
			addr2 = jp;
			addr1 = (jp = undap1) + i;
			unddel = jp-1;
		} else {
			/*
			 * when b > c inverse is  c+1,c+1+(b-a) m b
			 */
			addr1 = ++jp;
			addr2 = jp + ((unddel = undap2) - undap1);
		}
		kp = undap1;
		move1(0, unddel);
		dot = kp;
		Command = (unsigned char *)"move";
		killed();
	} else {
		int cnt;

		newadot = dot;
		cnt = lineDOL();
		newdol = dol;
		dolp1 = dol + 1;
		/*
		 * Command to be undone is a non-move.
		 * All such commands are treated as a combination of
		 * a delete command and a append command.
		 * We first move the lines appended by the last command
		 * from undap1 to undap2-1 so that they are just before the
		 * saved deleted lines.
		 *
		 * Assume the editor has:
		 *
		 * 	cursor is on 'c'
		 *
		 *	(just change lines 5-8)
		 *
		 *	file is:	1) ab
		 *			2) cd
		 *			3) ef
		 *			4) gh
		 *	undap1:		5) 12
		 *			6) 34
		 *			7) 56
		 *			8) 78
		 *	undap2:		9) qr
		 *		       10) st
		 *		       11) uv
		 *		       12) wx
		 *	dol:	       13) yz
		 *
		 *	    UNDO AREA:
		 *	dol+1:		5) ij
		 *			6) kl
		 *			7) mn
		 *	unddol:		8) op
		 */

		/*
		 * If this is a change (not a delete/put),
		 * then we must move the text between undap1 and undap2
		 * and it must not be at the bottom of the file
		 */
		
		if ((i = (kp = undap2) - (jp = undap1)) > 0) {
			if (kp != dolp1) {

		/*
		 * FILE:     LINE    INITIAL   REV1   REV2   REV3
		 *
		 *	      1)       ab	ab     ab     ab
		 *	      2)       cd       cd     cd     cd
		 *            3)       ef       ef     ef     ef
		 * unddel:    4)       gh       gh     gh     gh
		 * undap1:    5)       12       78     78     qr
		 *            6)       34       56     56     st
		 *            7)       56       34     34     uv
		 *            8)       78       12     12     wx
		 * undap2:    9)       qr       qr     yz     yz
		 *           10)       st       st     wx     12
		 *           11)       uv       uv     uv     34
		 *           12)       wx       wx     st     56
		 * dol:      13)       yz       yz     qr     78
		 *
		 *	UNDO AREA:
		 * dol+1:     5)       ij       ij     ij     ij
		 *            6)       kl       kl     kl     kl
		 *	      7)       mn       mn     mn     mn
		 * unddol:    8)       op       op     op     op
		 */

				reverse(jp, kp);
				reverse(kp, dolp1);
				reverse(jp, dolp1);
			}
			/*
			 * Unddel, the line just before the spot where this
			 * test was deleted, may have moved. Account for 
			 * this in restoration of saved deleted lines.
			 */
			if (unddel >= jp)
				unddel -= i;
			
			/*
			 * The last line (dol) may have changed,
			 * account for this.
			 */
			 newdol -= i;
			
			/*
			 * For the case where no lines are restored, dot
			 * is the line before the first line deleted.
			 */
			dot = jp-1;
		}
		/*
		 * Now put the deleted lines, if any, back where they were.
		 * Basic operation is: dol+1,unddol m unddel
		 */
		if (undkind == UNDPUT) {
			unddel = undap1 - 1;
			squish();
		}
		
		/*
		 * Set jp to the line where deleted text is to be added.
		 */
		jp = unddel + 1;
		
		/*
		 * Set kp to end of undo save area.
		 *
		 * If there is any deleted text to be added, do reverses.
		 */
		
		if ((i = (kp = unddol) - dol) > 0) {
			
			/*
			 * If deleted lines are not to be appended
			 * to the bottom of the file...
			 */
			 
			 if (jp != dolp1) {
				/*
				 * FILE:   LINE   START   REV1   REV2   REV3
				 *          1)     ab      ab     ab     ab
				 *          2)     cd      cd     cd     cd
				 *          3)     ef      ef     ef     ef
				 * unddel:  4)     gh      gh     gh     gh
				 * undap1:  5)     qr      78     78     ij
				 *          6)     st      56     56     kl
				 *          7)     uv      34     34     mn
				 *          8)     wx      12     12     op
				 * undap2:  9)     yz      yz     yz     qr
				 *         10)     12      wx     wx     st
				 *         11)     34      uv     uv     uv
				 *         12)     56      st     st     wx
				 * dol:    13)     78      qr     qr     yz
				 *
				 * UNDO AREA:
				 * dol+1:  5)      ij      ij     op     12
				 *         6)      kl      kl     mn     34
				 *         7)      mn      mn     kl     56
				 * unddol: 8)      op      op     ij     78
				 */
				 
				 reverse(jp, dolp1);
				reverse(dolp1, ++kp);
				reverse(jp, kp);
			}
			/*
			 * Account for possible forward motion of the target
			 * (where the deleted lines were restored) for after
			 * restoration of the deleted lines.
			 */
			if (undap1 >= jp)
				undap1 += i;
			/*
			 * Dot is the first resurrected line.
			 */
			dot = jp;
			
			/*
			 * Account for a shift in the last line (dol).
			 */
			 
			 newdol += i;
		}
		/*
		 * Clean up so we are invertible
		 */
		unddel = undap1 - 1;
		undap1 = jp;
		undap2 = jp + i;
		dol = newdol;
		netchHAD(cnt);
		if (undkind == UNDALL) {
			dot = undadot;
			undadot = newadot;
		} else
			undkind = UNDCHANGE;
 		/*
 		 * Now relocate all marks for lines that were modified,
 		 * since the marks point to lines whose address has 
 		 * been modified from the save area to the current 
 		 * area
 		 */
 			
 		for (j=unddol; j> dol; j--) 
 			for (k=0; k<=25; k++) 
 				if (names[k] == *(j)) 
 					names[k]= *((undap1+(j-dolp1)) );
	}
	/*
	 * Defensive programming - after a munged undadot.
	 * Also handle empty buffer case.
	 */
	if ((dot <= zero || dot > dol) && dot != dol)
		dot = one;
#ifdef UNDOTRACE
	if (trace)
		vudump("after undo");
#endif
}

/*
 * Be (almost completely) sure there really
 * was a change, before claiming to undo.
 */
void
somechange(void)
{
	line *ip, *jp;

	switch (undkind) {

	case UNDMOVE:
		return;

	case UNDCHANGE:
		if (undap1 == undap2 && dol == unddol)
			break;
		return;

	case UNDPUT:
		if (undap1 != undap2)
			return;
		break;

	case UNDALL:
		if (unddol - dol != lineDOL())
			return;
		for (ip = one, jp = dol + 1; ip <= dol; ip++, jp++)
			if ((*ip &~ 01) != (*jp &~ 01))
				return;
		break;

	case UNDNONE:
		error(gettext("Nothing to undo"));
	}
	error(value(vi_TERSE) ? gettext("Nothing changed") :
		gettext("Last undoable command didn't change anything"));
}

/*
 * Map command:
 * map src dest
 *
 * un is true if this is unmap command
 * ab is true if this is abbr command
 */
void
mapcmd(int un, int ab)
{
	unsigned char lhs[100], rhs[100];	/* max sizes resp. */
	unsigned char *p;
	int c;		/* char --> int */
	unsigned char *dname;
	struct maps *mp;	/* the map structure we are working on */

	mp = ab ? abbrevs : exclam() ? immacs : arrows;
	if (skipend()) {
		int i;

		/* print current mapping values */
		if (peekchar() != EOF)
			ignchar();
		if (un)
			error(gettext("Missing lhs"));
		if (inopen)
			pofix();
		for (i=0; i< MAXNOMACS && mp[i].mapto; i++)
			if (mp[i].cap) {
				lprintf("%s", mp[i].descr);
				putchar('\t');
				lprintf("%s", mp[i].cap);
				putchar('\t');
				lprintf("%s", mp[i].mapto);
				putNFL();
			}
		return;
	}

	(void)skipwh();
	for (p=lhs; ; ) {
		c = getchar();
		if (c == CTRL('v')) {
			c = getchar();
		} else if (!un && any(c, " \t")) {
			/* End of lhs */
			break;
		} else if (endcmd(c) && c!='"') {
			ungetchar(c);
			if (un) {
				donewline();
				*p = 0;
				addmac(lhs, (unsigned char *)NOSTR,
				    (unsigned char *)NOSTR, mp);
				return;
			} else
				error(gettext("Missing rhs"));
		}
		*p++ = c;
	}
	*p = 0;

	if (skipend())
		error(gettext("Missing rhs"));
	for (p=rhs; ; ) {
		c = getchar();
		if (c == CTRL('v')) {
			c = getchar();
		} else if (endcmd(c) && c!='"') {
			ungetchar(c);
			break;
		}
		*p++ = c;
	}
	*p = 0;
	donewline();
	/*
	 * Special hack for function keys: #1 means key f1, etc.
	 * If the terminal doesn't have function keys, we just use #1.
	 */
	if (lhs[0] == '#') {
		unsigned char *fnkey;
		unsigned char *fkey();
		unsigned char funkey[3];

		fnkey = fkey(lhs[1] - '0');
		funkey[0] = 'f'; funkey[1] = lhs[1]; funkey[2] = 0;
		if (fnkey)
			strcpy(lhs, fnkey);
		dname = funkey;
	} else {
		dname = lhs;
	}
	addmac(lhs,rhs,dname,mp);
}

/*
 * Add a macro definition to those that already exist. The sequence of
 * chars "src" is mapped into "dest". If src is already mapped into something
 * this overrides the mapping. There is no recursion. Unmap is done by
 * using NOSTR for dest.  Dname is what to show in listings.  mp is
 * the structure to affect (arrows, etc).
 */
void
addmac(unsigned char *src, unsigned char *dest, unsigned char *dname,
    struct maps *mp)
{
	int slot, zer;

#ifdef UNDOTRACE
	if (trace)
		fprintf(trace, "addmac(src='%s', dest='%s', dname='%s', mp=%x\n", src, dest, dname, mp);
#endif
	if (dest && mp==arrows) {
		/*
		 * Prevent tail recursion. We really should be
		 * checking to see if src is a suffix of dest
		 * but this makes mapping involving escapes that
		 * is reasonable mess up.
		 */
		if (src[1] == 0 && src[0] == dest[strlen(dest)-1])
			error(gettext("No tail recursion"));
		/*
		 * We don't let the user rob himself of ":", and making
		 * multi char words is a bad idea so we don't allow it.
		 * Note that if user sets mapinput and maps all of return,
		 * linefeed, and escape, he can hurt himself. This is
		 * so weird I don't bother to check for it.
		 */
		if (isalpha(src[0])  && isascii(src[0]) && src[1] || any(src[0],":"))
			error(gettext("Too dangerous to map that"));
	}
	else if (dest) {
		/* check for tail recursion in input mode: fussier */
		if (eq(src, dest+strlen(dest)-strlen(src)))
			error(gettext("No tail recursion"));
	}
	/*
	 * If the src were null it would cause the dest to
	 * be mapped always forever. This is not good.
	 */
	if (src == (unsigned char *)NOSTR || src[0] == 0)
		error(gettext("Missing lhs"));

	/* see if we already have a def for src */
	zer = -1;
	for (slot=0; slot < MAXNOMACS && mp[slot].mapto; slot++) {
		if (mp[slot].cap) {
			if (eq(src, mp[slot].cap) || eq(src, mp[slot].mapto))
				break;	/* if so, reuse slot */
		} else {
			zer = slot;	/* remember an empty slot */
		}
	}
	
	if (slot >= MAXNOMACS)
		error(gettext("Too many macros"));

	if (dest == (unsigned char *)NOSTR) {
		/* unmap */
		if (mp[slot].cap) {
			mp[slot].cap = (unsigned char *)NOSTR;
			mp[slot].descr = (unsigned char *)NOSTR;
		} else {
			error(value(vi_TERSE) ? gettext("Not mapped") :
				gettext("That macro wasn't mapped"));
		}
		return;
	}

	/* reuse empty slot, if we found one and src isn't already defined */
	if (zer >= 0 && mp[slot].mapto == 0)
		slot = zer;

	/* if not, append to end */
	if (msnext == 0)	/* first time */
		msnext = mapspace;
	/* Check is a bit conservative, we charge for dname even if reusing src */
	if (msnext - mapspace + strlen(dest) + strlen(src) + strlen(dname) + 3 > MAXCHARMACS)
		error(gettext("Too much macro text"));
	CP(msnext, src);
	mp[slot].cap = msnext;
	msnext += strlen(src) + 1;	/* plus 1 for null on the end */
	CP(msnext, dest);
	mp[slot].mapto = msnext;
	msnext += strlen(dest) + 1;
	if (dname) {
		CP(msnext, dname);
		mp[slot].descr = msnext;
		msnext += strlen(dname) + 1;
	} else {
		/* default descr to string user enters */
		mp[slot].descr = src;
	}
}

/*
 * Implements macros from command mode. c is the buffer to
 * get the macro from.
 */
void
cmdmac(c)
unsigned char c;
{
	unsigned char macbuf[BUFSIZE];
	line *ad, *a1, *a2;
	unsigned char *oglobp;
	short pk;
	bool oinglobal;

	lastmac = c;
	oglobp = globp;
	oinglobal = inglobal;
	pk = peekc; peekc = 0;
	if (inglobal < 2)
		inglobal = 1;
	regbuf(c, macbuf, sizeof(macbuf));
	a1 = addr1; a2 = addr2;
	for (ad=a1; ad<=a2; ad++) {
		globp = macbuf;
		dot = ad;
		commands(1,1);
	}
	globp = oglobp;
	inglobal = oinglobal;
	peekc = pk;
}

unsigned char *
vgetpass(prompt)
char *prompt;
{
	unsigned char *p;
	int c;
	static unsigned char pbuf[9];

	/* In ex mode, let the system hassle with setting no echo */
	if (!inopen)
		return (unsigned char *)getpass(prompt);
	viprintf("%s", prompt); flush();
	for (p=pbuf; (c = getkey())!='\n' && c!=EOF && c!='\r';) {
		if (p < &pbuf[8])
			*p++ = c;
	}
	*p = '\0';
	return(pbuf);
}


#ifdef TAG_STACK
#define TSTACKSIZE 20
struct tagstack {
	line *tag_line;
	char *tag_file;
} tagstack[TSTACKSIZE];
static int tag_depth = 0;

static char tag_buf[ 1024 ];
static char *tag_end = tag_buf;

void
savetag(char *name)	/* saves location where we are */
{
	if( !value(vi_TAGSTACK) )
		return;
	if(tag_depth >= TSTACKSIZE) {
		error(gettext("Tagstack too deep."));
	}
	if( strlen( name ) + 1 + tag_end >= &tag_buf[1024]) {
		error(gettext("Too many tags."));
	}
	tagstack[tag_depth].tag_line = dot;
	tagstack[tag_depth++].tag_file = tag_end;
	while(*tag_end++ = *name++)
		;
}

/*
 * Undo a "savetag".
 */
void
unsavetag(void)
{
	if (!value(vi_TAGSTACK))
		return;
	if (tag_depth > 0)
		tag_end = tagstack[--tag_depth].tag_file;
}

void
poptag(quick)	/* puts us back where we came from */
bool quick;
{
	unsigned char cmdbuf[100];
	unsigned char *oglobp;
	int d;

	if (!value(vi_TAGSTACK)) {	/* reset the stack */
		tag_end = tag_buf;
		d = tag_depth;
		tag_depth = 0;
		if (d == 0)
			error(gettext("Tagstack not enabled."));
		else
			return;
	}
	if (!tag_depth)
		error(gettext("Tagstack empty."));
	
	/* change to old file */
	if (strcmp(tagstack[tag_depth-1].tag_file, savedfile) ) {
		if (!quick) {
			ckaw();
			if (chng && dol > zero)
				error(value(vi_TERSE) ?
gettext("No write") : gettext("No write since last change (:pop! overrides)"));
		}
		oglobp = globp;
		strcpy(cmdbuf, "e! ");
		strcat(cmdbuf, tagstack[tag_depth-1].tag_file);
		globp = cmdbuf;
		d = peekc; ungetchar(0);
		commands(1, 1);
		peekc = d;
		globp = oglobp;
	}
		markpr(dot);
	/* set line number */
	dot = tagstack[--tag_depth].tag_line;
	tag_end = tagstack[tag_depth].tag_file;
}
#endif