/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is	*
 * provided to you without charge, and with no warranty.  You may give	*
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.						*
 ************************************************************************/

#define NO_PROCDECL

#include "tune.h"	/* to allow definition of debug flags there. */

#ifdef DEBUG		/* General debug flag, turn on file-specific flag. */
#   ifndef EXTEND_DEBUG
#	define EXTEND_DEBUG	1
#   endif
#else
#   if EXTEND_DEBUG	/* File-specific debug flag, turn on general flag. */
#	define DEBUG
#   endif
#endif

#include "jove.h"

RCS("$Id: extend.c,v 14.32.0.12 1994/06/24 17:55:02 tom Exp tom $")

#include "ctype.h"
#include "io.h"
#include "maps.h"
#include "process.h"
#ifndef NO_PROCDECL
#include "re.h"
#include "screen.h"
#endif

#ifndef TINY
private const char *const TypeName[] = {
	NullStr,
	"Command",			/* FUNCTION */
	"Variable",			/* VARIABLE */
	"Macro",			/* MACRO */
	"Keymap",			/* KEYMAP */
};
#endif

private const char	Help[] = "*Help*";

private void
	do_help __(( const data_obj *_(dp) ));
#ifdef COLOR
private int
	ask_color __(( int _(def), const char *_(prompt) ));
#endif
private	const char *
	vpr_aux __(( const Variable *_(vp) ));


int	InJoverc ZERO;
DEF_INT( "quiet", Quiet, V_BOOL ) ZERO;

/*=====	Command/Macro auto-execute. ==========================================*/

private struct autoexec {
	struct autoexec	*a_next;
	data_obj	*a_cmd;
	char		*a_pattern;
} *AutoExecs ZERO;

DEF_CMD( "auto-execute-command", DefAutoExec, ARG(FUNCTION) );
DEF_CMD( "auto-execute-macro",	 DefAutoExec, ARG(MACRO) )
{
	register struct autoexec *ap, **hp;
	register data_obj	*dp;
	register char		*pattern;

#ifndef SMALL
	if (exp_p) {
		if (ap = AutoExecs) {
			register const char *fmt = "%-30s %s";

			TOstart(Help, TRUE);
			Typeout(fmt, "Command", "Pattern");
			Typeout(fmt, "-------", "-------");
			do {
				Typeout(fmt, ap->a_cmd->Name, ap->a_pattern);
			} while (ap = ap->a_next);
			TOstop();
		}
	}
#endif /* !SMALL */

	dp = FindObj(ObjArg(LastCmd), ProcFmt);
	pattern = re_ask((char *) 0, YES, ProcArgFmt, dp->Name);

	for (hp = &AutoExecs; ap = (*hp); hp = &ap->a_next) {
		if ((ap->a_cmd == dp) && (strcmp(pattern, ap->a_pattern) == 0))
			return;		/* Eliminate duplicates. */
	}

	ap = (struct autoexec *) emalloc(sizeof(*ap));
	ap->a_pattern = copystr(pattern);
	ap->a_cmd = dp;
	ap->a_next = NULL;
	(*hp) = ap;
}

/* DoAutoExec: NEW and OLD are file names, and if NEW and OLD aren't the
   same kind of file (i.e., match the same pattern) or OLD is 0 and it
   matches, we execute the command associated with that kind of file. */

public void
DoAutoExec(newfile, oldfile)
const char	*newfile,
		*oldfile;
{
	register struct autoexec *ap;
	register const char	*new,
				*old = oldfile;

	if ((new = newfile) == NULL)
		return;

	if (ap = AutoExecs) do {
		if ((LookingAt(ap->a_pattern, new, 0)) &&
		    (old == NULL || !LookingAt(ap->a_pattern, old, 0)))
			DoTimes(ExecNow(ap->a_cmd), 1);
				/* So minor modes don't toggle.
				   We always want them on. */
	} while (ap = ap->a_next);
}

/*===== Get an additional character. =========================================*/

addgetc()
{
	register int	c;

	if (!InJoverc) {
		Asking = strlen(mesgbuf);
		c = getch();
		Asking = 0;
		add_mess("%p ", c);
	}
	else switch (c = getch()) {	/* interpret input from .joverc */

	case '\n':
		return EOF;		/* this isn't part of the sequence */

	case '\\':			/* literal character */
		if ((c = getch()) != '\n')
			break;

		complain("[Premature end of line]");

	case '^':			/* control character */
		if (isalpha(c = getch()))
			return (c & 037);	/* ^A - ^Z, ^a - ^z */
#ifdef FUNCKEYS
		if (c == ':') {			/* function key id */
			union {
				char	name[2];
				short	id;
			} u;
			register short	*kmp = &FKeyMap[NFKEYS];

			if ((u.name[0] = getch()) != '\n') {
				u.name[1] = getch();
				c = u.id;

				do {
					if (c == *--kmp)
						return (kmp - FKeyMap +
							MAPSIZE - NFKEYS);

				} while (kmp != FKeyMap);

				complain("[Unknown function key name]");
			}
		}
#endif /* FUNCKEYS */
		if (index("@[\\]^_?", c))	/* others */
			return (c ^ '@');

		complain("[Unknown control sequence]");

	default:
		break;
	}
	return c;
}

/*===== Describe key. ========================================================*/

DEF_CMD( "describe-key", KeyDesc, NO )
{
	register const data_obj	*dp;

	s_mess(ProcFmt);
	if (dp = *MapKey(active_map(), MAP_FOLLOW_ACTIVE_LIST|MAP_ALLOW_PREFIX)) {
#ifdef TINY
		add_mess("is bound to %s.", dp->Name);
#else
		add_mess("is bound to the %s %s.",
			 TypeName[dp->Type & REALTYPEMASK], dp->Name);
#endif
		if (exp_p)
			do_help(dp);
	} else {
		add_mess("is unbound.");
	}
}

/*===== Describe a data-object. ==============================================*/

DEF_CMD( "describe-macro",    Desc, ARG(MACRO) );
DEF_CMD( "describe-variable", Desc, ARG(VARIABLE) );
DEF_CMD( "describe-command",  Desc, ARG(FUNCTION) )
{
	register const data_obj *dp = FindObj(ObjArg(LastCmd), ProcFmt);

	s_mess(ProcArgFmt, dp->Name);
	do_help(dp);
}

private void
do_help(dp)
register const data_obj *	dp;
{
	static char	entry_pat[] = "^:entry \"%s\" \"%s\"$";
	register File	*fp;
	register char	*sstr;
	register int	Type = (dp->Type & REALTYPEMASK);
#ifndef TINY
	/* first try user's help file, then system-wide command database. */
	extern char	JoveHlp[];
	char		orgmesg[MESG_SIZE];
	char		fnamebuf[FILESIZE];
	register const char
			*helpfile = PathParse(JoveHlp, fnamebuf);

	strcpy(orgmesg, mesgbuf);
#else
#	define helpfile	CmdDb
#endif
	DrawMesg(NO);
	curstoLL();
	flusho();

    for (;;) {
	if (fp = open_file(helpfile, iobuff, F_READ|F_TEXT|F_QUIET)) {

		sstr = sprint(entry_pat, dp->Name,
			      ((Type)==VARIABLE ? "Variable" : "Command"));

		f_bsearch(fp, &sstr[1], 8, (int) strlen(sstr) - 2);
				/*      ^	^^^^^^^^^^^^^^^^^	   */
				/* |:entry "|	|:entry "<name>" "<Type>"| */

		if (re_fsearch(fp, sstr))
			break;

		s_mess("There is no documentation for \"%s\".", dp->Name);
#ifndef TINY
		f_close(fp);
	}
	if (helpfile != CmdDb) { /* retry with system-wide command database */
		message(orgmesg);
		helpfile = CmdDb;
		continue;
#endif
#undef helpfile
	}
	complain((char *)0);
    }
	/* we only get here when an entry was found. */

	TOstart(Help, TRUE);

	if (find_binds(dp, genbuf) || Type != VARIABLE) {
		/* command or macro, or variable bound to a key. */
		if (genbuf[0] == '\0')		/* i.e. !find_binds() */
			sprintf(genbuf, "ESC X %s%s<cr>",
				(Type == MACRO) ? "execute-macro " : NullStr,
				dp->Name);

		Typeout("Type \"%s\" to invoke %s.", genbuf, dp->Name);
	} else {
		Typeout("%s => %s", dp->Name, vpr_aux((Variable *) dp));
	}
	Typeout(NULL);

	while (f_gets(fp, genbuf, LBSIZE) == 0 &&
	       numcomp(genbuf, &sstr[1]) < 6)
		Typeout("%s", genbuf);

	TOstop();
	f_close(fp);
}

/*===== Describe bindings. ===================================================*/

private int mpr_aux __(( char *_(buf), const Keymap *_(map),
			 int _(c1), const data_obj *_(d1) ));
private int
mpr_aux(buf, map, c1, d1)
char			*buf;
register const Keymap	*map;
register int		c1;
const data_obj		*d1;	/* "d1 == map[c1]" */
{
	register int	c2 = c1,
			last_c = -1;
	register char	*fmt = "%p";

	/* only print lowercase bindings when different from uppercase */
	if (isfunckey(c1) || !islower(c1) || get_bind(map, toupper(c1)) != d1) {

		while (++c2 < MAPSIZE && get_bind(map, c2) == d1)
			;
		last_c = --c2;

		switch (c2 - c1) {
		case 0:
			/* use default format if upper- and lowercase
			   bindings differ */
			if (isfunckey(c1) || !isupper(c1) ||
			    get_bind(map, c2 = tolower(c1)) != d1)
				break;
		case 1:
			fmt = "{%p,%p}";
			break;
		default:
			fmt = "[%p-%p]";
			break;
		}
		sprintf(buf, fmt, c1, c2);
	}
	return last_c;
}

private void DescMap __(( Keymap *_(initial_map), char *_(buf) ));
private void
DescMap(initial_map, buf)
Keymap	*initial_map;
char	*buf;
{
	register Keymap	*map;
	register int	c = 0;
	register char	*bp;
	register data_obj *dp;
	register int	newc;

	bp = buf;
	while (*bp++) ; --bp;

	do {
		map = initial_map;
		do {
			/*
			 * This inner loop is an optimization of:
			 *	if (dp = get_bind(map, c)) { ... }
			 */
			if ((dp = get_shallow_bind(map, c)) == NULL)
				continue;

			if ((newc = mpr_aux(bp, initial_map, c, dp)) < 0)
				break;

			c = newc;

			/* also print "prefix-n" bindings */
			Typeout("%-14s%s", buf, dp->Name);
			if (TOabort)
				return;

			if (map = IsPrefix(dp)) {
				/*
				 * describe prefixed maps only once
				 * also inhibits infinite recursion.
				 */
				if (!(map->Type & VISITED)) {
					map->Type |= VISITED;
					strcat(bp, " ");
					DescMap(map, buf);
				}
			}
			break;

		} while (K_ACTIVE_NEXT(map));

	} while (++c < MAPSIZE);
}

DEF_CMD( "describe-bindings", DescBindings, NO )
{
	map_init_visit();
	genbuf[0] = '\0';
	TOstart("*Key Bindings*", TRUE);
	DescMap(active_map(), genbuf);
	TOstop();
}

/* [TRH changed March 1990]
 * fb_aux describes bindings from a prefixed map.
 * If a single binding is found, it is appended to buf;
 * multiple bindings are collected in a comma-separated list
 * surrounded by { } braces.
 * fb_aux returns the number of bindings found.
 */
private int fb_aux __(( char *_(buf), Keymap *_(thismap), const data_obj *_(dp) ));
private int
fb_aux(buf, thismap, dp)
char		*buf;
Keymap		*thismap;
const data_obj	*dp;
{
	register char	*bp = buf;
	register Keymap	*map = thismap;
	int		n = 0;

	if (!(map->Type & VISITED)) {

		map->Type |= VISITED;

		*bp++ = '{';
#ifndef FIXED_MAPS
	    if (map->Type & SPARSE) {
		register sparsemap	*sp = (sparsemap *) map->k_bind;

		do {
			if (sp->s_cmd != dp)
				continue;

			bp += sprintf(bp, "%p,", sp->s_chr);
			++n;
		} while ((++sp)->s_chr >= 0);
	    }
	    else
#endif
	    {
		register data_obj *const *cp = map->k_bind;
		register int		c = 0;

		do {
			if (*cp++ != dp)
				continue;

			bp += sprintf(bp, "%p,", c);
			++n;
		} while (++c < MAPSIZE);
	    }
		*--bp = '\0';	/* remove trailing ',' */
		if (n)
			if (n == 1) {	/* chuck leading '{' */
				register const char	*sp;

				for (bp = buf, sp = bp + 1;  *bp++ = *sp++; )
					continue;
			}
			else
				*bp++ = '}', *bp = '\0';
	}
	return n;
}

/*
 * BUG: find_binds should handle nested Keymaps recursively.
 */
int
find_binds(dp, buf)
const data_obj	*dp;
char		*buf;
{
	register int	c = 0;
	register Keymap *map;
	register char	*bp = buf;
	register data_obj *cp;
	register int	newc;

	map_init_visit();

	do {
		map = activemap;
		do {
			/*
			 * This inner loop is an optimization of:
			 *	if (dp = get_bind(map, c)) { ... }
			 */
			if ((cp = get_shallow_bind(map, c)) == NULL)
				continue;

			if (cp == dp) {
				if ((newc = mpr_aux(bp, activemap, c, dp)) < 0)
					break;
				c = newc;
			}
			else if (!(map = IsPrefix(cp)) ||
				 !fb_aux(bp + sprintf(bp, "%p ", c), map, dp)) {
				break;
			}
			/* only get here on a match */
			while (*bp++) ; --bp;
			*bp++ = ',', *bp++ = ' ';	/* append ", " */
			break;

		} while (K_ACTIVE_NEXT(map));

	} while (++c < MAPSIZE);

	if (bp >= buf + 2)
		bp -= 2;		/* remove superfluous ", " */
	*bp = '\0';

	return (int) buf[0];
}

DEF_CMD( "apropos", Apropos, NO )
{
	extern char	compbuf[];	/* just to be sure... */
	register int	any = 0;
	register char	*bp = genbuf;

	re_ask(NullStr, UseRE, "%N: %f (keyword) ");

	TOstart(Help, TRUE);
    {
	register const Command *cp = commands;

	do {
		if (re_sindex(cp->Name, 0, compbuf)) {
			if (!any) {
				any |= Bit(FUNCTION);
				Typeout("Commands");
				Typeout("--------");
			}
			Typeout((find_binds((data_obj *) cp, bp)) ?
				": %-35s(%s)" : ": %s", cp->Name, bp);
		}
	} while ((++cp)->Name && !TOabort);
    }
    {
	register const Variable *v = variables;

	do {
		if (re_sindex(v->Name, 0, compbuf)) {
			if (!(any & Bit(VARIABLE))) {
				if (any)
					Typeout(NullStr);
				any |= Bit(VARIABLE);
				Typeout("Variables");
				Typeout("---------");
			}
			Typeout(": set %-30s %s", v->Name, vpr_aux(v));
		}
	} while ((++v)->Name && !TOabort);
    }
    {
	register Macro	*m = macros;	/* keyboard-macro always present */

	do {
		if (re_sindex(m->Name, 0, compbuf)) {
			if (!(any & Bit(MACRO))) {
				if (any)
					Typeout(NullStr);
				any |= Bit(MACRO);
				Typeout("Macros");
				Typeout("------");
			}
			Typeout((find_binds((data_obj *) m, bp)) ?
				": %-35s(%s)" : ": %s",	m->Name, bp);
		}
	} while ((m = m->m_nextm) && !TOabort);
    }
	TOstop();
}

/*=====	Prompt for a command and execute it. =================================*/

DEF_CMD( "execute-named-command", Extend, ARG(FUNCTION) )
{
	ExecCmd(findcom("%N: "));
}

/*=====	Invoke macros, set variables. ========================================*/

DEF_CMD( "execute-macro", Invoke, ARG(MACRO) );
DEF_CMD( "set", Invoke, ARG(VARIABLE) )
{
	ExecCmd(FindObj(ObjArg(LastCmd), ProcFmt));
}

/*===== Handle Variables. ====================================================*/

private /* deliberately non-const */ char bool_val[2][4] = { "off", "on" };

private const char *
vpr_aux(vp)
register const Variable	*vp;
{
	register int	*v_value = vp->v_value;
	static char	buffer[20];
	register char	*buf = buffer;

	switch (V_TYPE(vp)) {
	case V_BASE10:
		sprintf(buf, "%d", *v_value);
		break;

	case V_BASE8:
		sprintf(buf, "%o", *v_value);
		break;

	case V_BOOL:
		if (True(*v_value))
			buf = bool_val[YES];
		else
			buf = bool_val[NO];
		break;

	case V_STRING:
	case V_FILENAME:
	case V_REGEXP:
		buf = *(char **) v_value;
		break;

	case V_CHAR:
		sprintf(buf, "%p", *v_value);
		break;
#ifdef COLOR
	case V_COLOR:
	    {
		extern const char * const color_names[];
		register int	color;

		if ((color = *v_value) == 0)
			color = DefColor;
		sprintf(buf, "%s on %s", color_names[FG_COLOR(color)],
			color_names[BG_COLOR(color)]);
	    }
		break;
#endif
	}
	return buf;
}

DEF_CMD( "print", PrVar, NO )
{
	register const Variable *vp = (Variable *) findvar(ProcFmt);
	register const char	*p = vpr_aux(vp);

	if (exp < 0)
		ins_str(p, NO);
	else
		s_mess("%N: %f %s => %s", vp->Name, p);
}

void
DoSetVar(vp)
register const Variable	*vp;
{
	static const char	SetPrompt[] = "%N: set %f ";
	register int		*v_value = vp->v_value;
	register int		v_type = V_TYPE(vp);

	char			fbuf[FILESIZE],
				promptbuf[MESG_SIZE];
	register const char	*prompt = promptbuf;
	register const char	*on_off;

	LastCmd = (data_obj *) vp;	/* so we can use "%f" */

	if (V_FLAGS(vp) == V_CONST)
		complain("[Can't modify constant \"%f\"]");

	if (v_type == V_BOOL)
		on_off = True(*v_value) ? bool_val[OFF] : bool_val[ON];

	if (!InJoverc)
		sprintf((char *) prompt, "%N: set %f (default %s) ",
		        (v_type != V_BOOL) ? vpr_aux(vp) : on_off);
		/* cannot use `sprint' here due to race condition. */

	switch (v_type) {
	case V_BASE10:
	case V_BASE8:
	    {
		register int	v_max;
#		define	value	v_type	/* alias! */

		if (exp_p == NO)
			value = ask_int(prompt, (v_type == V_BASE10) ? 10 : 8, *v_value);
		else if ((value = exp) < 0)
			if ((value += *v_value) < 0)
				value = 0;

		if ((v_max = V_LENGTH(vp)) && value > v_max)
			value = v_max;		/* silently adjust to bound */

		*v_value = value;
		break;
#		undef	value		/* unalias */
	    }

	case V_BOOL:
	    {
#		define	value	v_type	/* alias! */

		if (exp_p) {
			if (exp_p == YES)
				*v_value = (exp > 0);
			else
				*v_value += exp;
				/* allows you to save/restore modes in macros */
			break;
		}
		on_off = strlwr(ask(on_off, prompt));
		value = 2;
		do {
			if (--value < 0)
				complain("Boolean variables must be ON or OFF.");
		} while	(strcmp(on_off, bool_val[value]) != 0);
		*v_value = value;
		s_mess("%s%s", prompt, on_off);
		break;
#		undef	value		/* unalias */
	    }

	case V_FILENAME:
	    {
		set_str((char **) v_value,
			ask_file(prompt, *(char **) v_value, fbuf));
		break;
	    }

	case V_STRING:
	    {
		register const char	*str;

		/* Do_ask() so you can set string to "" if you so desire. */
		if ((str = do_ask("\r\n", (int(*)()) 0, *(char **) v_value,
				  SetPrompt)) == NULL)
			str = NullStr;
		set_str((char **) v_value, str);
		break;
	    }

	case V_REGEXP:
	    {
		set_str((char **) v_value,
			re_ask(*(char **) v_value, YES, SetPrompt));
		break;
	    }

	case V_CHAR:
	    {
#		define	value	v_type	/* alias! */

		if (exp_p) {
			*v_value = exp & 0377;
			break;
		}
		message(prompt);
		value = addgetc();
		if (value != AbortChar)
			*v_value = value;
		break;
#		undef value		/* unalias */
	    }
#ifdef COLOR
	case V_COLOR:
		*v_value = ask_color(*v_value, prompt);
		break;
#endif
	}

	switch (V_FLAGS(vp)) {
	case V_MODELINE:
		updmode();
		break;
	case V_CLRSCREEN:
		ClAndRedraw();
		break;
	case V_TTY_RESET:
		tty_reset();
		break;
	}
#ifdef MENUS
	Modechange++;
#endif
}

/*=====	Command completion. ==================================================*/

/* Command completion - possible is an array of strings, prompt is
   the prompt to use, and flags are ... well read jove.h.
   [TRH] the UNIQUE code seems obsolete.

   If flags are RET_STATE, and the user hits <return> what they typed
   so far is in the Minibuf string. */

DEF_INT( "completion-auto-help", ComplAutoHelp, V_BOOL ) ZERO;

private const char	* const *Possible;
private int	comp_value,
		comp_flags;

private int	minmatch,	/* some useful values for command completion */
		maxmatch,
		lenmatch,
		lastmatch;

match(possible, what)
const char	* const *possible;
const char	*what;
{
	register const char	*pp;
	register int		i,
				len,
				found;

	lenmatch = strlen(what);
	minmatch = LBSIZE;	/* big */
	maxmatch = 0;
	found = ORIGINAL;

	for (i = 0;  pp = possible[i];  i++) {
		if (*pp != *what)		/* optimization */
			len = 0;
		else if ((len = numcomp(pp, what)) > maxmatch)
			maxmatch = len;

		if (len != lenmatch)		/* no match */
			continue;

		if (*(pp += len) == '\0') {	/* exact match */
			minmatch = len;
			return (lastmatch = i);
		}
		if (found == ORIGINAL) {	/* first partial match */
			minmatch = len + strlen(pp);
			found = i;
		} else {			/* ambiguous match */
			len += numcomp(&possible[lastmatch][len], pp);
			if (len < minmatch)
				minmatch = len;
			found = AMBIGUOUS;
		}
		lastmatch = i;
	}
	return found;
}

private int aux_complete __(( int _(c) ));
private int
aux_complete(c)
register int	c;
{
	register const char	* const *possible = Possible;
	register int		command;

	if (comp_flags & CASEIND)
		strlwr(linebuf);

	/*
	 * c is one of:
	 *	?	show possible completions
	 *	LF,CR	complete command, return if unique match,
	 *		else return literal string if RET_STATE flag set.
	 *	space	complete command, return if unique match.
	 *	tab	just complete the command.
	 */
	switch (c) {
	case EOF:		/* only if bad .joverc */
		complain("[Premature end of line]");

	case '\r':
	case '\n':
		c = 0;
	default:
		if ((command = match(possible, linebuf)) >= 0) {
			if (c != '\t' || InJoverc)  /* non-ambiguous match */
				break;
		}
		else if ((comp_flags & RET_STATE) && (c == 0 || InJoverc)) {
			if (linebuf[0] == '\0')
				command = NULLSTRING;
			break;
		}
		else if (command != AMBIGUOUS) {
			if (InJoverc)
				complain("[\"%s\" unknown]", linebuf);
			/* If we're not in the .joverc then let's do
			   something helpful for the user. */
			linebuf[maxmatch] = '\0';
			rbell();
			Eol();
			return 1;
		}
		else if (c == 0) {			/* ambiguous match */
			if (InJoverc)
				complain("[\"%s\" ambiguous]", linebuf);
		}

		/* complete the command if we get here */
		null_ncpy(linebuf, possible[lastmatch], minmatch);
		Eol();
		if (minmatch != lenmatch)
			return 1;

		/* No difference; ring the bell, and eventually list
		   completions iff requested and we were ambiguous. */
		rbell();

		if (command >= 0 || False(ComplAutoHelp))
			return 1;
		/* fall through... */
	case '?':
		if (InJoverc)
			complain((char *)0);

#	define	length	command	/* alias! */

		/* kludge: copy because if we're using UseBuffers,
		   linebuf gets written all over */

		length = appcpy(Minibuf, linebuf) - Minibuf;	/* Optimization */

		TOstart("*Completion*", TRUE);	/* for now ... */
		for (;  *possible;  *possible++)
			if (numcomp(*possible, Minibuf) >= length) {
				Typeout("%s", *possible);
				if (TOabort)
					break;
			}
#	undef	length

		TOstop();

		return 1;
	}
	comp_value = command;
	return 0;
}

/*
 * Apr-89 [TRH] modified calling sequence
 */
/* VARARGS3 */

int
DEFVARG(complete, (const char * const possible[], int flags, const char *fmt, ...),
		  (possible, flags, fmt, va_alist)
	const char	* const possible[];
	const char	*fmt;)
{
	va_register va_list	ap;
	register const char	* const *o_possible = Possible; /* to make it re-entrant */
	register int		o_flags = comp_flags;
	VA_INIT_PROPAGATE

	Possible = possible;
	comp_flags = flags;

	va_begin(ap, fmt);
	do_ask("\r\n \t?", aux_complete, NullStr, VA_PROPAGATE(fmt, ap));
	va_end(ap);

	Possible = o_possible;
	comp_flags = o_flags;
	return comp_value;
}

/*=====	Execute a ".joverc" command file. ====================================*/

DEF_CMD( "source", Source, NO )
{
	char			buf[FILESIZE];
	register const char	*com = ask_file((char *) 0, JoveRc, buf);

	if (joverc(com) == NIL)
		complain(IOerr("read", com));
}

/*
 * Put caught error message (in mesgbuf) in a buffer.
 * Format a la "grep" so we can use error parsing on it.
 */
void
PutErrInBuf(bufname, filename, lno, lbuf)
const char	*bufname,
		*filename,
		*lbuf;
{
	static Buffer	*save ZERO;
	register Buffer	*errbuf;
	register char	*file_line = sprint((lno) ? "%s:%d:" : "%s:",
					    pr_name(filename), lno);

	if (save != NULL)	/* we got an error in here */
		f_mess("%s%s", file_line, lbuf);
	else {
		save = curbuf;
		SetBuf(errbuf = do_select((Window *) 0, bufname));
		SETBUFTYPE(errbuf, B_SCRATCH);
		ToLast();
		/*
		 * Don't use a single sprintf here since that has a limited
		 * maximum string length.
		 */
		ins_str(file_line, NO);
		ins_str(lbuf, YES);
		if (!bolp(errbuf))
			LineInsert(1);
		ins_str(sprint("\t%s\n", mesgbuf), NO);
	}
	SetBuf(save);
	save = NULL;
}

private do_if __(( char *_(cmd) ));
private
do_if(cmd)
char	*cmd;
{
	register int	status;
#if unix
	register int	pid;

#ifdef IPROCS
#   ifdef PIPEPROCS
	int state = kbd(OFF);
#   else
	sighold(SIGCHLD);
#   endif
#endif
	if ((pid = vfork()) <= 0) {
		char		*args[12];
		register char	*cp = cmd,
				**ap = args;
#ifdef IPROCS
#   ifndef PIPEPROCS
		sigrelse(SIGCHLD);
#   endif
#endif
		if (pid < 0)
#ifdef IPROCS
#   ifdef PIPEPROCS
			kbd(state),
#   endif
#endif
			complain("[Fork failed]");

		/* build argument vector */
		*ap++ = cp;
		while (*cp) {
			if (*cp++ != ' ')
				continue;
			cp[-1] = '\0';
			*ap++ = cp;
		}
		*ap = 0;
		ap = args;
		close(0);	/*	we want reads to fail */
		/* close(1);	 but not writes or ioctl's
		close(2);    */
		execvp(ap[0], ap);
		exec_fail((char *)0);	/* signals exec error (see below) */
	}
	status = dowait(pid);
#ifdef IPROCS
#   ifdef PIPEPROCS
	kbd(state);
#   else
	sigrelse(SIGCHLD);
#   endif
#endif
	if (status == -ENOEXEC)
		complain("[Exec failed]");
#else /* unix */
	status = system(cmd);
#endif /* unix */
	if (status < 0)
		complain("[Exit %d]", status);
	return (status == EXIT_SUCCESS);
}

DEF_REF( "digit", DigitCmd );

private void do_command __(( char *_(lp) ));
private void
do_command(lp)
register char	*lp;
{
	register int	c = *lp;	/* look ahead */

	Inputp = lp;
	exp = 1; exp_p = NO;
	if (isdigit(c) || c == '-') {
		getch();		/* i.e., LastKeyStruck = c, Inputp++; */
		ExecCmd(DigitCmd);
		getch();		/* skip Ungetc'd char */
	}
	Extend();
}

joverc(file)
const char	*file;
{
	char		buf[BLKSIZ];
	register char	*lp;

	/* The following weird construct tries to outsmart optimizing
	   compilers (like ST Turbo C) that puts as many variables in
	   registers as possible. That would be okay weren't it for the
	   longjmp() restoring the context to the situation at the time of
	   the initializing setjmp().  So we want these variables on the
	   stack!  (BTW the volatile modifier has no effect (for ST Turbo
	   C at least) but maybe I just don't understand what this
	   modifier *should* do). */

	volatile struct { int i; } /* so that they are forced on the stack */
			_lnum,
			_IfNesting,
			_IfStatus;
#	define lnum		_lnum.i
#	define IfNesting	_IfNesting.i
#	define IfStatus		_IfStatus.i

	/* If States (other than FALSE and TRUE) */
#	define DONE		2
#	define SAWELSE		4
#	define IfTrue(s)	((s)&TRUE)
#	define IfDone(s)	((s)&DONE)
#	define IfElseSeen(s)	((s)&SAWELSE)

#	define MaxIfNesting	10	/* size of `if' nesting stack */

	volatile int	IfStack[MaxIfNesting];

	register File	*fp;
	int		s_quiet = Quiet;
	Savejmp 	savejmp;

	lp = buf;
	if ((fp = open_file(PathParse(file, lp), lp, F_READ|F_TEXT|F_QUIET)) == NULL)
		return NIL;

	/* [TRH] increment InJoverc instead of absolute assignment,
	   to allow nested "source" directives */

	InJoverc++;

	lnum = 0;
	IfStatus = TRUE;
	IfNesting = 0;
	Quiet = NO;

	/* Catch any errors, here, and do the right thing with them,
	   and then restore the error handler to whoever did a setjmp
	   last. */

	switch (push_env(savejmp)) {

	case QUIT:
		pop_env(savejmp);
		Leave();

	default:
		if (False(Quiet))
			PutErrInBuf("*RC errors*", file, lnum, genbuf);
		Asking = 0;

	case 0:
		break;
	}

	/* [TRH] As far as I can see, it is safe to use genbuf as the
	   line buffer, since at most a single command is executed from
	   it, and each command parses its arguments BEFORE it does
	   anything else with genbuf. (this saves some stack space;
           which only matters on DOS systems...) */

	while (f_gets(fp, lp = genbuf, LBSIZE - 1) == 0 /*!EOF*/) {
	    {
		static const struct builtin {
			char	bi_name[6];
			short	bi_len;
		} Builtins[] = {
			"if",		2,
			"elif",		4,
			"else",		4,
			"endif",	5
		};
#		define builtin_cases	case 'i': case 'e'
#		define IF	0
#		define ELIF	1
#		define ELSE	2
#		define ENDIF	3
#		define CMD	4
		register const struct builtin	*cmd;

		lnum++;
		while (isspace(*lp++)) ;	/* skip leading blanks */

		switch (tolower(*--lp)) {
		case '\0':	/* empty line */
		case '#':	/* comment */
			continue;

		builtin_cases:
			/* to speed things up, only lookup directives
			   if there is a possibility for a match. */
			cmd = Builtins;
			do {
				if (casencmp(lp, cmd->bi_name, cmd->bi_len) == 0)
					goto found_builtin;	/* break out
								   of switch */
			} while (++cmd != &Builtins[CMD]);

		default:
			if (IfTrue(IfStatus))
				do_command(lp);
			continue;
		}
	found_builtin:
		if (cmd == &Builtins[IF]) {
			if (IfNesting == MaxIfNesting)
				complain("[`if's nested too deeply]");
			if (!IfTrue(IfStack[IfNesting++] = IfStatus)) {
				IfStatus = DONE;
				/* ...to prevent nested `elif's in a false
				   branch from being evaluated. */
				continue;
			}
		}
		else {
			if (!IfNesting)
				complain("[Unexpected `%s']", cmd->bi_name);

			if (cmd == &Builtins[ENDIF]) {
				IfStatus = IfStack[--IfNesting];
				continue;
			}
#	ifndef TINY
			if (IfElseSeen(IfStatus))
				complain("[Unexpected `%s']", cmd->bi_name);
#	endif
			if (IfDone(IfStatus))
				continue;

			/* transition: FALSE => TRUE and TRUE => DONE */
			if (IfDone(++IfStatus))
				continue;

			if (cmd == &Builtins[ELSE]) {
#	ifndef TINY
				IfStatus += SAWELSE;
#	endif
				continue;
			}
		}
		/* only get here on (IF || ELIF) && IsTrue(IfStatus) */

		/* so we won't be screwed by complain */
		IfStatus = DONE;

		/*
		 * This rather intimidating Regular Expression defines the
		 * conditions we are prepared to handle.
		 *  1. (<variable> in <regexp>)
		 *	- true if value of <variable> matches <regexp>
		 *  2. (<variable> <relop> <value>)
		 *	- true if STRING comparison satisfies <relop>
		 *  3. (<variable>)
		 *	- test presence of ENVIRONMENT variables,
		 *	  OR non-zeroness of numeric JOVE variables,
		 *	  OR truth value of boolean JOVE variables,
		 *  4. term in <regexp>
		 *	- special case for backward compatibility
		 *  5. command [parameters]
		 *	- test exit status of command.
		 *  6. ! <any of the above>
		 *	- logical NOT operator.
		 * If <variable> starts with a dollar sign `$' it is
		 * considered an environment variable; anything else is
		 * thought an internal JOVE variable or -constant.
		 */
		if (!LookingAt("\
 *\\(!?\\) *(\\([^ )=<>!]+\\)\
 *\\{\\(\\{in,==,!=,<=,<,>=,>\\}\\) *\\(.+\\)),)\\}[\t ]*\\{#,$\\}\
\\|\
 *\\(!?\\) *\\{\\(term\\) +\\(in\\) +,\\}\\([^(].*\\)$",
			       lp, cmd->bi_len))
			complain("[`If' syntax error]");
	    }
	    {
		char			not[2], op[3];
		register const char	*var;

		putmatch(1, not, sizeof not);
		putmatch(2, (char *)(var = Minibuf), sizeof Minibuf);
		putmatch(3, op, sizeof op);
		putmatch(4, lp, LBSIZE);

		if (var[0]) {		/* "(var op value)" or "(var)" */

			if (var[0] == '$') {	/* environment variable */
				if ((var = getenv(&var[1])) == NULL) {
					var = "0";
					if (!isdigit(lp[0]))
						var = NullStr;
				}
			}
			else {			/* internal JOVE variable */
				Inputp = (char *) var;
				var = vpr_aux((Variable *) findvar(NullStr));

				if (!op[0]) {		/* (var) */
					lp = "0";
					if (!isdigit(var[0])) {
						lp = bool_val[YES];
						not[0] ^= '!';
					}
				}
			}

			/* (how ad-hoc can you get...) */

			if (op[0] == 'i') {		/* in */
				IfStatus = LookingAt(lp, var, 0);
			}
			else {
				IfStatus = strcmp(var, lp);

				switch (op[0]) {
				case '=':		/* == */
					not[0] ^= '!';
					break;
#	if EXTEND_DEBUG
				case '!':		/* != */
				case '\0':		/* (var), implicit != */
					break;
				default:
					complain("BUG: unknown `if' operator \"%s\"", op);
#	endif
				case '>':		/* >, >= */
					IfStatus = -IfStatus;
				case '<':		/* <, <= */
					if (op[1])
						IfStatus--;
					if (IfStatus > 0)
						IfStatus = 0;
					/*
					 * We can get away with this since:
					 *   1a. (a > 0)   <=>  (-a < 0)
					 *   1b. (a >= 0)  <=>  (-a <= 0)
					 * and, with integer arithmetic:
					 *   2.  (a <= 0)  <=>  (a-1 < 0)
					 */
				}
			}
			if (IfStatus)
				IfStatus = 1;
		}
		else {
			IfStatus = do_if(lp);
		}
		if (not[0])
			IfStatus ^= 1;	/* negate */
	    }
	}
	if (IfNesting) {
		IfNesting = 0;
		complain("[Missing `endif']");
	}

	f_close(fp);
	pop_env(savejmp);
	Inputp = 0;
	Asking = 0;
	InJoverc--;
	Quiet = s_quiet;

	return !NIL;

#undef lnum
#undef IfNesting
#undef IfStatus

#undef DONE
#undef SAWELSE
#undef IfTrue
#undef IfDone
#undef IfElseSeen

#undef MaxIfNesting
#undef builtin
#undef IF
#undef ELIF
#undef ELSE
#undef ENDIF
#undef CMD
}

/*=====	Command-line option handling. ========================================*/

#ifdef CLIOPTIONS

typedef struct option Option;
struct option {
	char	*o_option;	/* option name--must start with '-' or '+' */
	char	*o_cmd;		/* command to be executed. */
	short	o_force;	/* force read of last buffer */
	Option	*o_next;	/* link */
};

private Option	*CliOpts;

/*
 * Define a command-line option. This is only of any use from within
 * your ".joverc" file since command-line options are (obviously) never
 * used after the command line is processed.
 * Key escape sequences in the command string are recognized;
 * occurrences of $1 .. $9 are replaced with the Nth next argument from
 * the command line; $$ is a single literal dollar; in all other cases
 * the $ is left undisturbed.
 */
DEF_CMD( "command-line-option", CliDefine, NO ) _IF(def CLIOPTIONS)
{
	register char	*s;
	register Option	*op;
	register int	c;

	if (!InJoverc) {		/* not useful when not in .joverc */
#   ifdef SMALL
		rbell();
#   else
		if (op = CliOpts) {
			TOstart(Help, YES);
			Typeout("  Option   Command  (* means force read current buffer)");
			Typeout("  ------   -------");
			do {
				Typeout("%8s %c %s",
					op->o_option,
					(op->o_force) ? '*' : ' ',
					op->o_cmd);
			} while (op = op->o_next);
			TOstop();
		}
		else
			message("[No %fs defined]");
#   endif
		return;
	}

	s = do_ask(" ", (int(*)())0, (char *)0, ProcFmt);
#   ifndef TINY
	if (!(*s == '-' || *s == '+'))
		complain("[Option must start with `-' or `+']");
#   endif
	op = (Option *) emalloc(sizeof(Option));
	op->o_option = copystr(s);

	/* addgetc does the key sequence interpretation */
	for (s = genbuf; (c = addgetc()) > 0; *s++ = c)
		;
	*s = '\0';
	op->o_cmd = copystr(genbuf);
	op->o_force = exp_p;
	op->o_next = CliOpts;
	CliOpts = op;
}

/*
 * Delete CLI option definitions.
 * This is called by the command-argument interpreter after all
 * arguments have been processed.
 */
void
CliDelete()
{
#   ifdef SMALL
	register Option	*op;

	while (op = CliOpts) {
		CliOpts = op->o_next;
		free(op->o_cmd);
		free(op->o_option);
		free(op);
	}
#   else
	/* Keep them around to support help. */
#   endif
}

/*
 * Scan for command line option. If one is found, execute the
 * command line associated with it.
 * Returns the number of arguments consumed, or -1 if option does not
 * match any user-defined option.
 */
int
CliLookup(opt, argc, argv)
char	*opt;			/* option to match */
int	argc;			/* number of args left (including current arg!)*/
char	**argv;			/* points to next arg */
{
	char		lbuf[LBSIZE];
	register Option	*op;
	Savejmp		savejmp;
	Buffer		*savebuf;
	int		nargs;		/* number of arguments to skip */

	if (op = CliOpts) do {

		if (strcmp(op->o_option, opt) != 0)
			continue;

		/* Force read of most-recently created buffer */
		if (op->o_force) {
			register Buffer	*b = curbuf;
			savebuf = b;
			while (b->b_next)
				b = b->b_next;
			SetBuf(b);
		}
		/* build command; do dollar argument substitutions */
	    {
		register char	*s = op->o_cmd,
				*d = lbuf;
		register int	i;

		nargs = 0;

		/* do dollar substitutions */
		while(*d = *s++) {
			if (*d++ != '$')
				continue;
			if ((i = *s++) == '\0')
				break;
			if (i == '$')		/* $$ => literal dollar */
				continue;
			if ((unsigned)(i -= '0') > 9)	/* not $1 .. $9 */
				continue;
			--d;			/* backup over '$' */
			if (i > nargs)
				nargs = i;
			if (i >= argc)		/* out of args; complain later */
				break;
			d = appcpy(d, argv[i - 1]);
		}
		*d++ = '\n';	/* make sure cmd ends with newline */
		*d = '\0';
	    }

		switch(push_env(savejmp)) {

		case QUIT:
			pop_env(savejmp);
			Leave();

		default:
			PutErrInBuf("*CLI errors*", op->o_option, nargs, lbuf);
			break;

		case 0:
			if (nargs >= argc)
				complain("[missing argument]");
		    {
			register char	*lp = lbuf;

			do do_command(lp); while ((lp = Inputp) && *lp);
		    }
		}
		pop_env(savejmp);
		Inputp = NULL;
		Asking = 0;
		if (op->o_force)
			SetBuf(savebuf);

		return nargs;

	} while (op = op->o_next);

	return -1;
}
#endif /* CLIOPTIONS */

/*===== Color user interface. ================================================*/

#ifdef COLOR
const char * const color_names[] = {
	"black",
	"red",
	"green",
	"yellow",
	"blue",
	"magenta",
	"cyan",
	"white",
	NullStr,	/* for default */
	NULL
};

#if 0 /* don't expand these because they don't obey C-syntax */
DEF_INT( "color-of-file-buffers",    Gcolor[B_FILE], V_COLOR )	  _IF(def COLOR)
DEF_INT( "color-of-message-line",    Gcolor[0], V_COLOR )	  _IF(def COLOR)
DEF_INT( "color-of-process-buffers", Gcolor[B_PROCESS], V_COLOR ) _IF(def COLOR)
DEF_INT( "color-of-scratch-buffers", Gcolor[B_SCRATCH], V_COLOR ) _IF(def COLOR)
DEF_INT( "color-of-i-process-buffers", Gcolor[B_IPROCESS], V_COLOR ) _IF(def COLOR)_IF(def IPROCS)
#endif
DEF_INT( "exit-color", ExitColor, V_COLOR ) _IF(def COLOR) ZERO;
DEF_INT( "default-color", DefColor, V_COLOR ) _IF(def COLOR) = DEF_COLOR;

public int	Gcolor[1 + B_NTYPES] ZERO;

private int
ask_color(def, prompt)
const char	*prompt;
{
	if (def == 0)
		def = DefColor;
    {
	register int	bg_color = BG_COLOR(def),
			fg_color = FG_COLOR(def),
			n = exp;
	register unsigned i;

	if (exp_p == YES) {
		if (n < 0)
			bg_color = -n - 1;
		else
			fg_color = n - 1;
	} else {
		if (exp_p == NO)
			n = 0;

		if (n >= 0 &&
		    (i = complete(color_names, CASEIND, "%sText: ", prompt)) < NCOLORS)
			fg_color = i;

		if (n <= 0 &&
		    (i = complete(color_names, CASEIND, "%sBackground: ", prompt)) < NCOLORS)
			bg_color = i;
	}
	return MK_COLOR(fg_color, bg_color);
    }
}

DEF_CMD( "color", NonExisting, NO ) _IF(ndef COLOR);
DEF_CMD( "color", BufColor, NO ) _IF(def COLOR)
{
	register int	def;
	char		prompt[132];

	if ((def = curbuf->b_color) == 0)
		def = DefColor;
	sprintf(prompt, "%N: %f (default %s on %s) ", color_names[FG_COLOR(def)],
		color_names[BG_COLOR(def)]);

	curbuf->b_color = ask_color(def, prompt);
}
#endif /* COLOR */

/*======================================================================
 * $Log: extend.c,v $
 * Revision 14.32.0.12  1994/06/24  17:55:02  tom
 * (Help[],DescBindings,aux_complete,joverc,CliLookup):
 *  fix name of scratch buffer.
 *
 * Revision 14.32.0.10  1994/04/22  18:24:22  tom
 * (DefAutoExec): store new definitions at end of list;
 * (complete): use `va_register va_list'.
 *
 * Revision 14.32.0.8  1993/11/01  18:05:42  tom
 * (Apropos): use UseRE in call to re_ask.
 *
 * Revision 14.32.0.1  1993/07/07  12:20:55  tom
 * (F_TEXT): new option for f_open et al.
 *
 * Revision 14.32  1993/06/22  03:55:53  tom
 * (addgetc, do_help, joverc): some cleanup.
 *
 * Revision 14.31  1993/02/17  01:07:03  tom
 * standardize DEBUG flags; add help typeout to DefAutoExec(); add sanity
 * check for command-line options; remove (void) casts; lotsa random
 * optimizations.
 *
 * Revision 14.30  1993/02/05  00:07:27  tom
 * cleanup whitespace; some random optimizations; improve integration of
 * regular keymaps and sparsemaps.
 *
 * Revision 14.29  1992/12/29  14:09:46  tom
 * avoid NULL-pointer bug.
 *
 * Revision 14.28  1992/10/02  17:18:05  tom
 * "describe-key" message format changed; integrate sparsemaps with full-size
 * keymaps; support user-variable "default-color".
 *
 * Revision 14.27  1992/09/21  14:11:23  tom
 * use DEF_REF().
 *
 * Revision 14.26  1992/08/26  23:56:52  tom
 * display current variable's value in do_help(); make vpr_aux() private;
 * add RCS directives.
 *
 */
