ftp.nice.ch/pub/next/unix/editor/xvile-7.0.N.bs.tar.gz#/xvile-7.0.N.bs/bind.c

This is bind.c in view mode; [Download] [Up]

/*	This file is for functions having to do with key bindings,
 *	descriptions, help commands and startup file.
 *
 *	written 11-feb-86 by Daniel Lawrence
 *
 * $Header: /home/tom/src/vile/RCS/bind.c,v 1.147 1997/03/01 01:33:42 tom Exp $
 *
 */

#include	"estruct.h"
#include	"edef.h"
#include	"nefunc.h"

#define SHORT_CMD_LEN 4	/* command names longer than this are preferred
				over those shorter.  e.g. display "quit"
				instead of "q" if possible */

extern const int nametblsize;

static	KBIND *	kcode2kbind ( int code );

#if OPT_CASELESS
#define Strcmp(s,d)      cs_strcmp(case_insensitive, s, d)
#define StrNcmp(s,d,len) cs_strncmp(case_insensitive, s, d, len)
#else
#define Strcmp(s,d)      strcmp(s, d)
#define StrNcmp(s,d,len) strncmp(s, d, len)
#endif

#if OPT_REBIND
#define	isSpecialCmd(k) \
		( (k == &f_cntl_a_func)\
		||(k == &f_cntl_x_func)\
		||(k == &f_poundc_func)\
		||(k == &f_unarg_func)\
		||(k == &f_esc_func)\
		)

static	char *	kcod2prc (int c, char *seq);
static	int	install_bind (int c, const CMDFUNC *kcmd, const CMDFUNC **oldfunc);
static	int	key_to_bind ( const CMDFUNC *kcmd );
static	int	rebind_key ( int c, const CMDFUNC *kcmd );
static	int	strinc (char *sourc, char *sub);
static	int	unbindchar ( int c );
static	int	update_binding_list ( BUFFER *bp );
static	void	makebindlist (LIST_ARGS);

#endif	/* OPT_REBIND */

#if OPT_EVAL || OPT_REBIND
static	const NTAB * fnc2ntab ( const CMDFUNC *cfp );
static	int	prc2kcod ( const char *kk );
#endif

#if OPT_REBIND
static	KBIND	*KeyBindings = kbindtbl;
#endif

/*----------------------------------------------------------------------------*/

int
no_such_function(const char * fnp)
{
	mlforce("[No such function \"%s\"]", fnp != 0 ? fnp : "");
	return FALSE;
}

/* give me some help!!!! bring up a buffer and read the help file into it */
/* ARGSUSED */
int
help(int f, int n)
{
	register BUFFER *bp;	/* buffer pointer to help */
	const char *fname;	/* ptr to file returned by flook() */
	int alreadypopped;

	/* first check if we are already here */
	bp = bfind(HELP_BufName, BFSCRTCH);
	if (bp == NULL)
		return FALSE;

	if (bp->b_active == FALSE) { /* never been used */
		fname = flook(helpfile, FL_ANYWHERE|FL_READABLE);
		if (fname == NULL) {
			mlforce("[Sorry, can't find the help information]");
			(void)zotbuf(bp);
			return(FALSE);
		}
		alreadypopped = (bp->b_nwnd != 0);
		/* and read the stuff in */
		if (readin(fname, 0, bp, TRUE) == FALSE ||
				popupbuff(bp) == FALSE) {
			(void)zotbuf(bp);
			return(FALSE);
		}
		set_bname(bp, HELP_BufName);
		set_rdonly(bp, fname, MDVIEW);

		make_local_b_val(bp,MDIGNCASE); /* easy to search, */
		set_b_val(bp,MDIGNCASE,TRUE);
		b_set_scratch(bp);
		if (!alreadypopped)
			shrinkwrap();
	}

	if (!swbuffer(bp))
		return FALSE;

	if (help_at >= 0) {
		if (!gotoline(TRUE, help_at))
			return FALSE;
		mlwrite("[Type '1G' to return to start of help information]");
		help_at = -1;  /* until zotbuf is called, we let normal
				DOT tracking keep our position */
	}
	return TRUE;
}

#if OPT_REBIND

#if OPT_TERMCHRS

	/* patch: this table and the corresponding initializations should be
	 * generated by 'mktbls'.  In any case, the table must be sorted to use
	 * name-completion on it.
	 */
static	const struct {
		const char *name;
		int	*value;
		char	how_to;
	} TermChrs[] = {
		{"backspace",		&backspc,	's'},
		{"interrupt",		&intrc,		's'},
		{"line-kill",		&killc,		's'},
		{"name-complete",	&name_cmpl,	0},
		{"quote-next",		&quotec,	0},
		{"start-output",	&startc,	's'},
		{"stop-output",		&stopc,		's'},
		{"suspend",		&suspc,		's'},
		{"test-completions",	&test_cmpl,	0},
		{"word-kill",		&wkillc,	's'},
		{0}
	};

/*----------------------------------------------------------------------------*/

/* list the current chrs into the current buffer */
/* ARGSUSED */
static void
makechrslist(int dum1, void *ptr)
{
	register int i;
	char	temp[NLINE];

	bprintf("--- Terminal Character Settings %*P\n", term.t_ncol-1, '-');
	for (i = 0; TermChrs[i].name != 0; i++) {
		bprintf("\n%s = %s",
			TermChrs[i].name,
			kcod2prc(*(TermChrs[i].value), temp));
	}
}

/*
 * Find a special-character definition, given the name
 */
static int
chr_lookup(char *name)
{
	register int	j;
	for (j = 0; TermChrs[j].name != 0; j++)
		if (!strcmp(name, TermChrs[j].name))
			return j;
	return -1;
}

/*
 * The 'chr_complete()' and 'chr_eol()' functions are invoked from
 * 'kbd_reply()' to setup the mode-name completion and query displays.
 */
static int
chr_complete(int c, char *buf, int *pos)
{
	return kbd_complete(FALSE, c, buf, pos, (const char *)&TermChrs[0],
		sizeof(TermChrs[0]));
}

static int
/*ARGSUSED*/
chr_eol(char * buffer, int cpos, int c, int eolchar)
{
	return isspace(c);
}

#if OPT_UPBUFF
/* ARGSUSED */
static int
update_termchrs(BUFFER *bp)
{
	return show_termchrs(FALSE,1);
}
#endif

/* ARGSUSED */
int
set_termchrs(int f, int n)
{
	register int s, j;
	char	name[NLINE];
	int c;

	/* get the table-entry */
	*name = EOS;
	if ((s = kbd_reply("Terminal setting: ", name, sizeof(name), chr_eol,
		' ', 0, chr_complete)) == TRUE) {

		j = chr_lookup(name);
		switch (TermChrs[j].how_to) {
		case 's':
		default:
			c = key_to_bind((CMDFUNC *)0);
			if (c < 0)
				return(FALSE);
			*(TermChrs[j].value) = c;
			break;
		}
		update_scratch(TERMINALCHARS_BufName, update_termchrs);
	}
	return s;
}

/* ARGSUSED */
int
show_termchrs(int f, int n)
{
	return liststuff(TERMINALCHARS_BufName, FALSE, makechrslist, 0, (void *)0);
}
#endif /* OPT_TERMCHRS */

static void
ostring(	/* output a string of output characters */
char *s)	/* string to output */
{
	if (discmd)
		kbd_puts(s);
}

/* bindkey:	add a new key to the key binding table		*/

/* ARGSUSED */
int
bindkey(int f, int n)
{
	register const CMDFUNC *kcmd; /* ptr to the requested function to bind to */
	char cmd[NLINE];
	char *fnp;

	/* prompt the user to type in a key to bind */
	/* and get the function name to bind it to */
	fnp = kbd_engl("Bind function whose full name is: ", cmd);

	if (fnp == NULL || (kcmd = engl2fnc(fnp)) == NULL) {
		return no_such_function(fnp);
	}

	return rebind_key(key_to_bind(kcmd), kcmd);
}

/*
 * Prompt-for and return the key-code to bind.
 */
static int
key_to_bind(register const CMDFUNC *kcmd)
{
	char outseq[NLINE];	/* output buffer for keystroke sequence */
	register int c;

	mlprompt("...to keyboard sequence (type it exactly): ");

	/* get the command sequence to bind */
	if (clexec) {
		char tok[NSTRING];
		macarg(tok);	/* get the next token */
		c = prc2kcod(tok);
	} else {
		/* perhaps we only want a single key, not a sequence */
		/* 	(see more comments below) */
		if (isSpecialCmd(kcmd))
			c = keystroke();
		else
			c = kbd_seq();
	}

	if (c >= 0) {
		/* change it to something we can print as well */
		ostring(kcod2prc(c, outseq));
		hst_append(outseq, FALSE);
	} else {
		mlforce("[Not a proper key-sequence]");
	}
	return c;
}

/*
 * Given a key-code and a command-function pointer, rebind the key-code to
 * the command-function.
 */
static int
rebind_key (
register int	c,
register const CMDFUNC *kcmd)
{
	static const CMDFUNC ignored = { unimpl }, *old = &ignored;
	return install_bind (c, kcmd, &old);
}

/*
 * Prefix-keys can be only bound to one value. This procedure tests the
 * argument 'kcmd' to see if it is a prefix key, and if so, unbinds the
 * key, and sets the corresponding global variable to the new value.
 * The calling procedure will then do the binding per se.
 */
static void
reset_prefix (
register int	c,
register const CMDFUNC *kcmd)
{
	if (isSpecialCmd(kcmd)) {
		register int j;
		/* search for an existing binding for the prefix key */
		if ((j = fnc2kcod(kcmd)) >= 0)
			(void)unbindchar(j);
		/* reset the appropriate global prefix variable */
		if (kcmd == &f_cntl_a_func)
			cntl_a = c;
		if (kcmd == &f_cntl_x_func)
			cntl_x = c;
		if (kcmd == &f_poundc_func)
			poundc = c;
		if (kcmd == &f_unarg_func)
			reptc = c;
		if (kcmd == &f_esc_func)
			abortc = c;
	}
}

/*
 * Bind a command-function pointer to a given key-code (saving the old
 * value of the function-pointer via an pointer given by the caller).
 */
static int
install_bind (
register int	c,
register const CMDFUNC *kcmd,
const CMDFUNC **oldfunc)
{
	register KBIND *kbp;	/* pointer into a binding table */

	if (c < 0)
		return FALSE;	/* not a legal key-code */

	/* if the function is a prefix key, i.e. we're changing the definition
		of a prefix key, then they typed a dummy function name, which
		has been translated into a dummy function pointer */
	*oldfunc = kcod2fnc(c);
	reset_prefix(-1, *oldfunc);
	reset_prefix(c, kcmd);

	if (!isspecial(c)) {
		asciitbl[c] = (CMDFUNC *)kcmd;
	} else {
		if ((kbp = kcode2kbind(c)) != 0) { /* change it in place */
			kbp->k_cmd = kcmd;
		} else {
			if ((kbp = typealloc(KBIND)) == 0) {
				return no_memory("Key-Binding");
			}
			kbp->k_link = KeyBindings;
			kbp->k_code = (short)c;	/* add keycode */
			kbp->k_cmd  = kcmd;	/* and func pointer */
			KeyBindings = kbp;
		}
	}
	update_scratch(BINDINGLIST_BufName, update_binding_list);
	return(TRUE);
}

/* unbindkey:	delete a key from the key binding table	*/

/* ARGSUSED */
int
unbindkey(int f, int n)
{
	register int c;		/* command key to unbind */
	char outseq[NLINE];	/* output buffer for keystroke sequence */

	/* prompt the user to type in a key to unbind */
	mlprompt("Unbind this key sequence: ");

	/* get the command sequence to unbind */
	if (clexec) {
		char tok[NSTRING];
		macarg(tok);	/* get the next token */
		c = prc2kcod(tok);
		if (c < 0) {
			mlforce("[Illegal key-sequence \"%s\"]",tok);
			return FALSE;
		}
	} else {
		c = kbd_seq();
		if (c < 0) {
			mlforce("[Not a bindable key-sequence]");
			return(FALSE);
		}
	}

	/* change it to something we can print as well */
	ostring(kcod2prc(c, outseq));

	/* if it isn't bound, bitch */
	if (unbindchar(c) == FALSE) {
		mlforce("[Key not bound]");
		return(FALSE);
	}
	update_scratch(BINDINGLIST_BufName, update_binding_list);
	return(TRUE);
}

static int
unbindchar(int c)		/* command key to unbind */
{
	register KBIND *kbp;	/* pointer into the command table */
	register KBIND *skbp;	/* saved pointer into the command table */

	if (!isspecial(c)) {
		asciitbl[c] = NULL;
	} else {
		/* search the table to see if the key exists */
		if ((kbp = kcode2kbind(c)) == 0)
			return(FALSE);

		/* save the pointer and scan to the end of the table */
		skbp = kbp;
		while (kbp->k_cmd != NULL)
			++kbp;
		--kbp;		/* backup to the last legit entry */

		/* copy the last entry to the current one */
		skbp->k_code = kbp->k_code;
		skbp->k_cmd  = kbp->k_cmd;

		/* null out the last one */
		kbp->k_code = 0;
		kbp->k_cmd = NULL;
	}
	return TRUE;
}

/* describe bindings bring up a fake buffer and list the key bindings
		   into it with view mode			*/

/* remember whether we last did "apropos" or "describe-bindings" */
static char *last_apropos_string;
static CMDFLAGS last_whichcmds;
static append_to_binding_list;

/* ARGSUSED */
static int
update_binding_list(BUFFER *bp)
{
	return liststuff(BINDINGLIST_BufName, append_to_binding_list,
		makebindlist, (int)last_whichcmds, (void *)last_apropos_string);
}

/* ARGSUSED */
int
desbind(int f, int n)
{
	last_apropos_string = (char *)0;
	last_whichcmds = 0;

	return update_binding_list((BUFFER *)0);
}

/* ARGSUSED */
int
desmotions(int f, int n)
{
	last_apropos_string = (char *)0;
	last_whichcmds = MOTION;
	return update_binding_list((BUFFER *)0);
}

/* ARGSUSED */
int
desopers(int f, int n)
{
	last_apropos_string = (char *)0;
	last_whichcmds = OPER;
	return update_binding_list((BUFFER *)0);
}

/* ARGSUSED */
int
desapro(int f, int n)	/* Apropos (List functions that match a substring) */
{
	register int    s;
	static char mstring[NSTRING];	/* string to match cmd names to */

	s = mlreply("Apropos string: ", mstring, sizeof(mstring));
	if (s != TRUE)
		return(s);

	last_apropos_string = mstring;
	last_whichcmds = 0;
	return update_binding_list((BUFFER *)0);
}

static char described_cmd[NLINE+1];	/* string to match cmd names to */

/* ARGSUSED */
int
desfunc(int f, int n)	/* describe-function */
{
	register int    s;
	char *fnp;

	/* force an exact match by strinc() later on from makefuncdesc() */
	described_cmd[0] = '^';

	fnp = kbd_engl("Describe function whose full name is: ", 
							described_cmd+1);
	if (fnp == NULL || engl2fnc(fnp) == NULL) {
		return no_such_function(fnp);
	}

	last_apropos_string = described_cmd;
	last_whichcmds = 0;
	append_to_binding_list = TRUE;
	s = update_binding_list((BUFFER *)0);
	append_to_binding_list = FALSE;
	return s;
}

/* ARGSUSED */
int
deskey(int f, int n)	/* describe the command for a certain key */
{
	register int c;		/* key to describe */
	char outseq[NSTRING];	/* output buffer for command sequence */
	const NTAB *nptr;	/* name table pointer */
	int s;

	/* prompt the user to type us a key to describe */
	mlprompt("Describe the function bound to this key sequence: ");

	/* get the command sequence to describe
	   change it to something we can print as well */

	/* check to see if we are executing a command line */
	if (clexec) {
		char tok[NSTRING];
		macarg(tok);	/* get the next token */
		c = prc2kcod(tok);
		if (c < 0) {
			mlforce("[Illegal key-sequence \"%s\"]",tok);
			return(FALSE);
		}
	} else {
		c = kbd_seq();
		if (c < 0) {
			mlforce("[Not a bindable key-sequence]");
			return(FALSE);
		}
	}

	(void)kcod2prc(c, outseq);
	hst_append(outseq, EOS); /* cannot replay this, but can see it */

	/* find the right ->function */
	if ((nptr = fnc2ntab(kcod2fnc(c))) == NULL) {
		mlwrite("Key sequence '%s' is not bound to anything.",
					outseq);
		return TRUE;
	}

	/* describe it */
	described_cmd[0] = '^';
	(void)strcpy(described_cmd + 1, nptr->n_name);
	last_apropos_string = described_cmd;
	last_whichcmds = 0;
	append_to_binding_list = TRUE;
	s = update_binding_list((BUFFER *)0);
	append_to_binding_list = FALSE;

	mlwrite("Key sequence '%s' is bound to function \"%s\"",
				outseq, nptr->n_name);

	return s;
}

/* returns a name in double-quotes */
static char *
quoted(char *dst, char *src)
{
	return strcat(strcat(strcpy(dst, "\""), src), "\"");
}

/* returns the number of columns used by the given string */
static int
converted_len(register char *buffer)
{
	register int len = 0, c;
	while ((c = *buffer++) != EOS) {
		if (c == '\t')
			len |= 7;
		len++;
	}
	return len;
}

/* force the buffer to a tab-stop if needed */
static char *
to_tabstop(char *buffer)
{
	register int	cpos = converted_len(buffer);
	if (cpos & 7)
		(void)strcat(buffer, "\t");
	return strend(buffer);
}

/* convert a key binding, padding to the next multiple of 8 columns */
static void
convert_kcode(int c, char *buffer)
{
	(void)kcod2prc(c, to_tabstop(buffer));
}

/* fully describe a function into the current buffer, given a pointer to
 * its name table entry */
static int
makefuncdesc(int j, char *listed)
{
	register KBIND *kbp;	/* pointer into a key binding table */
	int i;
	const CMDFUNC *cmd = nametbl[j].n_cmd;
	char outseq[NLINE];	/* output buffer for keystroke sequence */

	/* add in the command name */
	(void)quoted(outseq, nametbl[j].n_name);
	while (converted_len(outseq) < 32)
		(void)strcat(outseq, "\t");

	/* look in the simple ascii binding table first */
	for (i = 0; i < N_chars; i++)
		if (asciitbl[i] == cmd)
			convert_kcode(i, outseq);

	/* then look in the multi-key table */
#if OPT_REBIND
	for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
		if (kbp->k_cmd == cmd)
			convert_kcode(kbp->k_code, outseq);
	}
#endif
	for (kbp = kbindtbl; kbp->k_cmd; kbp++)
		if (kbp->k_cmd == cmd)
			convert_kcode(kbp->k_code, outseq);

	/* dump the line */
	if (!addline(curbp,outseq,-1))
		return FALSE;

	/* then look for synonyms */
	(void)strcpy(outseq, "  or\t");
	for (i = 0; nametbl[i].n_name != 0; i++) {
		/* if it's the one we're on, skip */
		if (i == j)
			continue;
		/* if it's already been listed, skip */
		if (listed[i])
			continue;
		/* if it's not a synonym, skip */
		if (nametbl[i].n_cmd != cmd)
			continue;
		(void)quoted(outseq+5, nametbl[i].n_name);
		if (!addline(curbp,outseq,-1))
			return FALSE;
	}

#if OPT_ONLINEHELP
	if (cmd->c_help && cmd->c_help[0])
		(void)lsprintf(outseq,"  (%s %s )",
		(cmd->c_flags & MOTION) ? "motion: " :
			(cmd->c_flags & OPER) ? "operator: " : "",
		cmd->c_help);
	else
		(void)lsprintf(outseq,"  ( no help for this command )");
	if (!addline(curbp,outseq,-1))
		return FALSE;
	if (cmd->c_flags & GLOBOK) {
		if (!addline(curbp,"  (may follow global command)",-1))
			return FALSE;
	}
#endif
	/* blank separator */
	if (!addline(curbp,"",-1))
		return FALSE;

	return TRUE;
}

/* build a binding list (limited or full) */
/* ARGSUSED */
static void
makebindlist(
int whichmask,
void *mstring)		/* match string if partial list, NULL to list all */
{
	int pass;
	int j;
	int ok = TRUE;		/* reset if out-of-memory, etc. */
	char *listed = calloc(sizeof(char), (ALLOC_T)nametblsize);

	if (listed == 0) {
		(void)no_memory(BINDINGLIST_BufName);
		return;
	}

	/* let us know this is in progress */
	mlwrite("[Building binding list]");

	/* build the contents of this window, inserting it line by line */
	for (pass = 0; pass < 2; pass++) {
	    for (j = 0; nametbl[j].n_name != 0; j++) {

		/* if we've already described this one, move on */
		if (listed[j])
			continue;

		/* are we interested in this type of command? */
		if (whichmask && !(nametbl[j].n_cmd->c_flags & whichmask))
			continue;

		/* try to avoid alphabetizing by the real short names */
		if (pass == 0 && (int)strlen(nametbl[j].n_name) <= SHORT_CMD_LEN)
			continue;

		/* if we are executing an apropos command
		   and current string doesn't include the search string */
		if (mstring
		 && (strinc(nametbl[j].n_name, (char *)mstring) == FALSE))
			continue;

		ok = makefuncdesc(j, listed);
		if (!ok)
			break;

		listed[j] = TRUE; /* mark it as already listed */
	    }
	}

	if (ok)
		mlerase();	/* clear the message line */
	free(listed);
}

/* much like the "standard" strstr, but if the substring starts
	with a '^', we discard it and force an exact match.  */
static int
strinc(		/* does source include sub? */
char *sourc,	/* string to search in */
char *sub)	/* substring to look for */
{
	char *sp;	/* ptr into source */
	char *nxtsp;	/* next ptr into source */
	char *tp;	/* ptr into substring */
	int exact = (*sub == '^');

	if (exact)
		sub++;

	/* for each character in the source string */
	sp = sourc;
	while (*sp) {
		tp = sub;
		nxtsp = sp;

		/* is the substring here? */
		while (*tp) {
			if (*nxtsp++ != *tp)
				break;
			tp++;
		}

		if ((*tp == EOS) && (!exact || *nxtsp == EOS))
			return(TRUE);

		if (exact) /* we only get one chance */
			break;

		/* no, onward */
		sp++;
	}
	return(FALSE);
}

#endif /* OPT_REBIND */


/* execute the startup file */

int
startup(
const char *sfname)	/* name of startup file  */
{
	const char *fname;	/* resulting file name to execute */

	/* look up the startup file */
	fname = flook(sfname, (FL_HERE|FL_HOME)|FL_READABLE);

	/* if it isn't around, don't sweat it */
	if (fname == NULL) {
		mlforce("[Can't find startup file %s]",sfname);
		return(TRUE);
	}

	/* otherwise, execute the sucker */
	return(dofile(fname));
}

/*	Look up the existence of a file along the normal or PATH
	environment variable. Look first in the HOME directory if
	asked and possible
*/

const char *
flook(
const char *fname,	/* base file name to search for */
int hflag)		/* Look in the HOME environment variable first? */
{
	register char *home;	/* path to home directory */
#if ENVFUNC && OPT_PATHLOOKUP
	register const char *path; /* environmental PATH variable */
#endif
	static char fspec[NSTRING];	/* full path spec to search */
#if SYS_VMS
	register char *sp;	/* pointer into path spec */
	static TBUFF *myfiles;
#endif
	int	mode = (hflag & (FL_EXECABLE|FL_WRITEABLE|FL_READABLE));

	/* take care of special cases */
	if (!fname || !fname[0] || isspace(fname[0]))
		return NULL;
	else if (isShellOrPipe(fname))
		return fname;

	if (hflag & FL_HERE) {
		if (ffaccess(fname, mode)) {
			return(fname);
		}
	}

#if ENVFUNC

	if (hflag & FL_HOME) {
		home = getenv("HOME");
		if (home != NULL) {
			/* try home dir file spec */
			if (ffaccess(pathcat(fspec,home,fname), mode)) {
				return(fspec);
			}
		}
	}

#endif	/* ENVFUNC */

	if (hflag & FL_EXECDIR) { /* is it where we found the executable? */
		if (exec_pathname
		 && exec_pathname[0] != EOS
		 && ffaccess(pathcat(fspec, exec_pathname, fname), mode))
			return(fspec);
	}

	if (hflag & FL_TABLE) {
		/* then look it up via the table method */
		path = startup_path;
		while ((path = parse_pathlist(path, fspec)) != 0) {
			if (ffaccess(pathcat(fspec, fspec, fname), mode)) {
				return(fspec);
			}
		}
	}

	if (hflag & FL_PATH) {

#if ENVFUNC
#if OPT_PATHLOOKUP
		/* then look along $PATH */
#if SYS_VMS
		/* On VAX/VMS, the PATH environment variable is only the
		 * current-dir.  Fake up an acceptable alternative.
		 */
		if (!tb_length(myfiles)) {
			char	mypath[NFILEN];

			(void)strcpy(mypath, prog_arg);
			if ((sp = vms_pathleaf(mypath)) == mypath)
				(void)strcpy(mypath, current_directory(FALSE));
			else
				*sp = EOS;

			if (!tb_init(&myfiles, EOS)
			 || !tb_sappend(&myfiles, mypath)
			 || !tb_sappend(&myfiles, ",SYS$SYSTEM:,SYS$LIBRARY:")
			 || !tb_append(&myfiles, EOS))
			return NULL;
		}
		path = tb_values(myfiles);
#else	/* UNIX or MSDOS */
		path = getenv("PATH");	/* get the PATH variable */
#endif
		while ((path = parse_pathlist(path, fspec)) != 0) {
			if (ffaccess(pathcat(fspec, fspec, fname), mode)) {
				return(fspec);
			}
		}
#endif	/* OPT_PATHLOOKUP */
#endif	/* ENVFUNC */

	}

	return NULL;	/* no such luck */
}

/* translate a keycode to its binding-string */
char *
kcod2pstr(
int c,		/* sequence to translate */
char *seq)	/* destination string for sequence */
{
	seq[0] = (char)kcod2escape_seq(c, &seq[1]);
	return seq;
}

/* Translate a 16-bit keycode to a string that will replay into the same
 * code.
 */
int
kcod2escape_seq (
int	c,
char *	ptr)
{
	char	*base = ptr;

	/* ...just for completeness */
	if (c & CTLA)		*ptr++ = (char)cntl_a;
	else if (c & CTLX)	*ptr++ = (char)cntl_x;
	else if (c & SPEC)	*ptr++ = (char)poundc;

	*ptr++ = (char)c;
	*ptr = EOS;
	return (int)(ptr - base);
}


/* translates a binding string into printable form */
#if OPT_REBIND
static char *
bytes2prc(char *dst, char *src, int n)
{
	char	*base = dst;
	register int	c;
	register const char *tmp;

	for ( ; n != 0; dst++, src++, n--) {

		c = *src;

		tmp = 0;

		if (c & HIGHBIT) {
			*dst++ = 'M';
			*dst++ = '-';
			c &= ~HIGHBIT;
		}
		if (c == ' ') {
			tmp = "<space>";
		} else if (iscntrl(c)) {
			*dst++ = '^';
			*dst = tocntrl(c);
		} else {
			*dst = (char)c;
		}

		if (tmp != 0) {
			while ((*dst++ = *tmp++) != EOS)
				;
			dst -= 2;	/* point back to last nonnull */
		}

		if (n > 1) {
			*++dst = '-';
		}
	}
	*dst = EOS;
	return base;
}

/* translate a 10-bit keycode to its printable name (like "M-j")  */
static char *
kcod2prc(
int c,		/* sequence to translate */
char *seq)	/* destination string for sequence */
{
	char	temp[NSTRING];
	(void)kcod2pstr(c,temp);
	return bytes2prc(seq, temp + 1, *temp);
}
#endif


/* kcode2kbind: translate a 10-bit key-binding to the table-pointer
 */
static KBIND *
kcode2kbind(register int code)
{
	register KBIND	*kbp;	/* pointer into a binding table */

#if OPT_REBIND
	for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
		if (kbp->k_code == code)
			return kbp;
	}
#endif
	for (kbp = kbindtbl; kbp->k_cmd; kbp++) {
		if (kbp->k_code == code)
			return kbp;
	}
	return 0;
}

/* kcod2fnc:  translate a 10-bit keycode to a function pointer */
/*	(look a key binding up in the binding table)		*/
const CMDFUNC *
kcod2fnc(
int c)	/* key to find what is bound to it */
{
	if (isspecial(c)) {
		register KBIND *kp = kcode2kbind(c);
		return (kp != 0) ? kp->k_cmd : 0;
	}
	return asciitbl[c];
}

#if !SMALLER
/* fnc2kcod: translate a function pointer to a keycode */
int
fnc2kcod(const CMDFUNC *f)
{
	register KBIND *kbp;
	register int	c;

	for (c = 0; c < N_chars; c++)
		if (f == asciitbl[c])
			return c;

#if OPT_REBIND
	for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
		if (kbp->k_cmd == f)
			return kbp->k_code;
	}
#endif
	for (kbp = kbindtbl; kbp->k_cmd != 0; kbp++) {
		if (kbp->k_cmd == f)
			return kbp->k_code;
	}

	return -1;	/* none found */
}
#endif

/* fnc2pstr: translate a function pointer to a pascal-string that a user
	could enter.  returns a pointer to a static array */
#if DISP_X11
char *
fnc2pstr(const CMDFUNC *f)
{
	register int	c;
	static char seq[10];

	c = fnc2kcod(f);

	if (c == -1)
		return NULL;

	return kcod2pstr(c, seq);
}
#endif

/* fnc2engl: translate a function pointer to the english name for
		that function
*/
#if OPT_EVAL || OPT_REBIND
static const NTAB *
fnc2ntab(const CMDFUNC *cfp)
{
	register const NTAB *nptr;	/* pointer into the name table */
	register const NTAB *shortnptr = NULL; /* pointer into the name table */

	/* skim through the table, looking for a match */
	for (nptr = nametbl; nptr->n_cmd; nptr++) {
		if (nptr->n_cmd == cfp) {
			/* if it's a long name, return it */
			if ((int)strlen(nptr->n_name) > SHORT_CMD_LEN)
				return nptr;
			/* remember the first short name, in case there's
				no long name */
			if (!shortnptr)
				shortnptr = nptr;
		}
	}
	if (shortnptr)
		return shortnptr;

	return NULL;
}

static char *
fnc2engl(const CMDFUNC *cfp)
{
	register const NTAB *nptr = fnc2ntab(cfp);
	return nptr ? nptr->n_name : NULL;
}

#endif

/* engl2fnc: match name to a function in the names table
	translate english name to function pointer
 		 return any match or NULL if none
 */
#define BINARY_SEARCH_IS_BROKEN 0
#if BINARY_SEARCH_IS_BROKEN  /* then use the old linear look-up */
const CMDFUNC *
engl2fnc(const char *fname)	/* name to attempt to match */
{
	register NTAB *nptr;	/* pointer to entry in name binding table */
	register SIZE_T len = strlen(fname);

	if (len != 0) {	/* scan through the table, returning any match */
		nptr = nametbl;
		while (nptr->n_cmd != NULL) {
			if (strncmp(fname, nptr->n_name, len) == 0)
				return nptr->n_cmd;
			++nptr;
		}
	}
	return NULL;
}
#else
/* this runs 10 times faster for 'nametbl[]' */
const CMDFUNC *
engl2fnc(const char *fname)	/* name to attempt to match */
{
	int lo, hi, cur;
	int r;
	register SIZE_T len = strlen(fname);

	if (len == 0)
		return NULL;

	/* scan through the table, returning any match */
	lo = 0;
	hi = nametblsize - 2;	/* don't want last entry -- it's NULL */

	while (lo <= hi) {
		cur = (lo + hi) >> 1;
		if ((r = strncmp(fname, nametbl[cur].n_name, len)) == 0) {
			/* Now find earliest matching entry */
			while (cur > lo 
			    && strncmp(fname, nametbl[cur-1].n_name, len) == 0)
				cur--;
			return nametbl[cur].n_cmd;

		} else if (r > 0) {
			lo = cur+1;
		} else {
			hi = cur-1;
		}
	}
	return NULL;
}
#endif	/* binary vs linear */

/* prc2kcod: translate printable code to 10 bit keycode */
#if OPT_EVAL || OPT_REBIND
static int
prc2kcod(
const char *kk)		/* name of key to translate to Command key form */
{
	register UINT c;	/* key sequence to return */
	register UINT pref = 0;	/* key prefixes */
	register int len = strlen(kk);
	register const UCHAR *k = (const UCHAR *)kk;

	if (len > 3 && *(k+2) == '-') {
		if (*k == '^') {
			if (iscntrl(cntl_a) && *(k+1) == toalpha(cntl_a))
				pref = CTLA;
			if (iscntrl(cntl_x) && *(k+1) == toalpha(cntl_x))
				pref = CTLX;
			if (iscntrl(poundc) && *(k+1) == toalpha(poundc))
				pref = SPEC;
		} else if (!strncmp((const char *)k, "FN", (SIZE_T)2)) {
			pref = SPEC;
		}
		if (pref != 0)
			k += 3;
	} else if (len > 2 && !strncmp((const char *)k, "M-", (SIZE_T)2)) {
		pref = HIGHBIT;
		k += 2;
	} else if (len > 1) {
		if (*k == cntl_a)
			pref = CTLA;
		else if (*k == cntl_x)
			pref = CTLX;
		else if (*k == poundc)
			pref = SPEC;
		if (pref != 0) {
			k++;
			if (len > 2 && *k == '-')
				k++;
		}
	}

	/* a control char? */
	if (*k == '^' && *(k+1) != EOS) {
		c = *(k+1);
		if (islower(c)) c = toupper(c);
		c = tocntrl(c);
		k += 2;
	} else {		/* any single char, control or not */
		c = *k++;
	}

	if (*k != EOS)		/* we should have eaten the whole thing */
		return -1;

	return (int)(pref|c);
}
#endif


#if OPT_EVAL
/* translate printable code (like "M-r") to english command name */
const char *
prc2engl(		/* string key name to binding name.... */
const char *skey)	/* name of key to get binding for */
{
	const char *bindname;
	int c;

	c = prc2kcod(skey);
	if (c < 0)
		return "ERROR";

	bindname = fnc2engl(kcod2fnc(c));
	if (bindname == NULL)
		bindname = "ERROR";

	return bindname;
}
#endif

/*
 * Get an english command name from the user
 */
char *
kbd_engl(
const char *prompt,	/* null pointer to splice calls */
char *buffer)
{
	if (kbd_engl_stat(prompt, buffer) == TRUE)
		return buffer;
	return NULL;
}

/* sound the alarm! */
void
kbd_alarm(void)
{
	if (global_g_val(GMDERRORBELLS)) {
		TTbeep();
		TTflush();
	}
	warnings++;
}

/* put a character to the keyboard-prompt, updating 'ttcol' */
void
kbd_putc(int c)
{
	beginDisplay;
	if ((kbd_expand <= 0) && isreturn(c)) {
		TTputc(c);
		ttcol = 0;
	} else if (isprint(c)) {
		if (ttcol < term.t_ncol-1) /* -1 to avoid auto-wrap problems */
			TTputc(c);
		ttcol++;
	} else if ((kbd_expand < 0) && (c == '\t')) {
		kbd_putc(' ');
	} else {
		if (c & HIGHBIT) {
			kbd_putc('\\');
			if (global_w_val(WMDNONPRINTOCTAL)) {
				kbd_putc(((c>>6)&3)+'0');
				kbd_putc(((c>>3)&7)+'0');
				kbd_putc(((c   )&7)+'0');
			} else {
				kbd_putc('x');
				kbd_putc(hexdigits[(c>>4) & 0xf]);
				kbd_putc(hexdigits[(c   ) & 0xf]);
			}
		} else {
			kbd_putc('^');
			kbd_putc(toalpha(c));
		}
	}
	endofDisplay;
}

/* put a string to the keyboard-prompt */
void
kbd_puts(const char *s)
{
	while (*s)
		kbd_putc(*s++);
}

/* erase a character from the display by wiping it out */
void
kbd_erase(void)
{
	beginDisplay;
	if (ttcol > 0) {
		if (--ttcol < term.t_ncol-1) {
			TTputc('\b');
			TTputc(' ');
			TTputc('\b');
		}
	} else
		ttcol = 0;
	endofDisplay;
}

#if OPT_CASELESS
static int
cs_strcmp(
int case_insensitive,
const char *s1,
const char *s2)
{
	if (case_insensitive)
		return stricmp(s1, s2);
	return strcmp(s1, s2);
}

static int
cs_strncmp(
int case_insensitive,
const char *s1,
const char *s2,
SIZE_T n)
{
	if (case_insensitive)
		return strnicmp(s1, s2, n);
	return strncmp(s1, s2, n);
}
#endif	/* OPT_CASELESS */

/* definitions for name-completion */
#define	NEXT_DATA(p)	((p)+size_entry)
#define	PREV_DATA(p)	((p)-size_entry)

#ifdef	lint
static	/*ARGSUSED*/
const char *	THIS_NAME(const char *p) { return 0; }
#else
#define	THIS_NAME(p)	(*(const char *const *)(p))
#endif
#define	NEXT_NAME(p)	THIS_NAME(NEXT_DATA(p))

/*
 * Scan down until we no longer match the current input, or reach the end of
 * the symbol table.
 */
/*ARGSUSED*/
static const char *
skip_partial(
int	case_insensitive,
char	*buf,
SIZE_T	len,
const char *table,
SIZE_T	size_entry)
{
	register const char * next = NEXT_DATA(table);
	register const char *	sp;

	while ((sp = THIS_NAME(next)) != 0) {
		if (StrNcmp(buf, sp, len) != 0)
			break;
		next = NEXT_DATA(next);
	}
	return next;
}

/*
 * Shows a partial-match.  This is invoked in the symbol table at a partial
 * match, and the user wants to know what characters could be typed next.
 * If there is more than one possibility, they are shown in square-brackets.
 * If there is only one possibility, it is shown in curly-braces.
 */
static void
show_partial(
int	case_insensitive,
char	*buf,
SIZE_T	len,
const char *table,
SIZE_T	size_entry)
{
	register const char *next = skip_partial(case_insensitive, buf, len, table, size_entry);
	register const char *last = PREV_DATA(next);
	register int	c;

	if (THIS_NAME(table)[len] == THIS_NAME(last)[len]) {
		kbd_putc('{');
		while ((c = THIS_NAME(table)[len]) != 0) {
			if (c == THIS_NAME(last)[len]) {
				kbd_putc(c);
				len++;
			} else
				break;
		}
		kbd_putc('}');
	}
	if (next != NEXT_DATA(table)) {
		c = TESTC;	/* shouldn't be in the table! */
		kbd_putc('[');
		while (table != next) {
			register const char *sp = THIS_NAME(table);
			if (c != sp[len]) {
				c = sp[len];
				kbd_putc(c ? c : '$');
			}
			table = NEXT_DATA(table);
		}
		kbd_putc(']');
	}
	TTflush();
}

#if OPT_POPUPCHOICE
/*
 * makecmpllist is called from liststuff to display the possible completions.
 */
struct compl_rec {
    char *buf;
    SIZE_T len;
    const char *table;
    SIZE_T size_entry;
};

#ifdef lint
#define c2ComplRec(c) ((struct compl_rec *)0)
#else
#define c2ComplRec(c) ((struct compl_rec *)c)
#endif

/*ARGSUSED*/
static void
makecmpllist(
    int case_insensitive,
    void *cinfop)
{
    char * buf		= c2ComplRec(cinfop)->buf;
    SIZE_T len		= c2ComplRec(cinfop)->len;
    const char * first	= c2ComplRec(cinfop)->table;
    SIZE_T size_entry	= c2ComplRec(cinfop)->size_entry;
    register const char *last = skip_partial(case_insensitive, buf, len, first, size_entry);
    register const char *p;
    SIZE_T maxlen;
    int slashcol;
    int cmpllen;
    int cmplcols;
    int cmplrows;
    int nentries;
    int i, j;

    for (p = NEXT_DATA(first), maxlen = strlen(THIS_NAME(first));
         p != last;
	 p = NEXT_DATA(p)) {
	SIZE_T l = strlen(THIS_NAME(p));
	if (l > maxlen)
	    maxlen = l;
    }

    slashcol = (int)(pathleaf(buf) - buf);
    if (slashcol != 0) {
        char b[NLINE];
        (void)strncpy(b, buf, (SIZE_T)slashcol);
        (void)strncpy(&b[slashcol], &(THIS_NAME(first))[slashcol],
			(len-slashcol));
        b[slashcol+(len-slashcol)] = EOS;
        bprintf("Completions prefixed by %s:\n", b);
    }

    cmplcols = term.t_ncol / (maxlen - slashcol + 1);

    if (cmplcols == 0)
	cmplcols = 1;

    nentries = (int)(last - first) / size_entry;
    cmplrows = nentries / cmplcols;
    cmpllen  = term.t_ncol / cmplcols;
    if (cmplrows * cmplcols < nentries)
	cmplrows++;

    for (i = 0; i < cmplrows; i++) {
	for (j = 0; j < cmplcols; j++) {
	    int idx = cmplrows * j + i;
	    if (idx < nentries) {
		const char *s = THIS_NAME(first+(idx*size_entry))+slashcol;
		if (j == cmplcols-1)
		    bprintf("%s\n", s);
		else
		    bprintf("%*s", cmpllen, s);
	    }
	    else {
		bprintf("\n");
		break;
	    }
	}
    }
}

/*
 * Pop up a window and show the possible completions.
 */
static void
show_completions(
int	case_insensitive,
char	*buf,
SIZE_T	len,
const char *table,
SIZE_T	size_entry)
{
    struct compl_rec cinfo;
    BUFFER *bp;
    int alreadypopped = 0;

    /*
     * Find out if completions buffer exists; so we can take the time to
     * shrink/grow the window to the latest size.
     */
    if ((bp = find_b_name(COMPLETIONS_BufName)) != NULL) {
	alreadypopped = (bp->b_nwnd != 0);
    }

    cinfo.buf = buf;
    cinfo.len = len;
    cinfo.table = table;
    cinfo.size_entry = size_entry;
    liststuff(COMPLETIONS_BufName, FALSE, makecmpllist, case_insensitive, (void *) &cinfo);

    if (alreadypopped)
	shrinkwrap();

    (void)update(TRUE);
}

/*
 * Scroll the completions window wrapping around back to the beginning
 * of the buffer once it has been completely scrolled.  If the completions
 * buffer is missing for some reason, we will call show_completions to pop
 * it (back) up.
 */
static void
scroll_completions(
    int		case_insensitive,
    char	*buf,
    SIZE_T	len,
    const char	*table,
    SIZE_T	size_entry)
{
    BUFFER *bp = find_b_name(COMPLETIONS_BufName);
    if (bp == NULL)
	show_completions(case_insensitive, buf, len, table, size_entry);
    else {
	LINEPTR lp;
	swbuffer(bp);
	(void)gotoeos(FALSE, 1);
	lp = DOT.l;
	(void)forwhpage(FALSE, 1);
	if (lp == DOT.l)
	    (void)gotobob(FALSE, 0);
	(void)update(TRUE);
    }
}

void
popdown_completions(void)
{
    BUFFER *bp;
    if ((bp = find_b_name(COMPLETIONS_BufName)) != NULL)
	zotwp(bp);
}
#endif /* OPT_POPUPCHOICE */

/*
 * Attempt to partial-complete the string, char at a time
 */
static int
fill_partial(
int	case_insensitive,
char	*buf,
SIZE_T	pos,
const char *first,
const char *last,
SIZE_T	size_entry)
{
	register const char *p;
	register int	n = pos;
	const char *this_name = THIS_NAME(first);

#if 0 /* case insensitive reply correction doesn't work reliably yet */
	if (!clexec && case_insensitive) {
		int spos = pos;

		while (spos > 0 && buf[spos - 1] != SLASHC) {
			kbd_erase();
			spos--;
		}
		while (spos < pos) {
			kbd_putc(this_name[spos]);
			spos++;
		}
	}
#endif

	for_ever {
		buf[n] = this_name[n];	/* add the next char in */
		buf[n+1] = EOS;

		/* scan through the candidates */
		for (p = NEXT_DATA(first); p != last; p = NEXT_DATA(p)) {
			if (StrNcmp(&THIS_NAME(p)[n], &buf[n], 1) != 0) {
				buf[n] = EOS;
				if (n == pos
#if OPT_POPUPCHOICE
# if OPT_ENUM_MODES
				 && !global_g_val(GVAL_POPUP_CHOICES)
# else
				 && !global_g_val(GMDPOPUP_CHOICES)
# endif
#endif
				)
					kbd_alarm();
				TTflush(); /* force out alarm or partial completion */
				return n;
			}
		}

		if (!clexec)
			kbd_putc(buf[n]); /* add the character */
		n++;
	}
}

static	int	testcol;	/* records the column when TESTC is decoded */
#if OPT_POPUPCHOICE
/*
 * cmplcol is used to record the column number (on the message line) after
 * name completion.  Its value is used to decide whether or not to display
 * a completion list if the name completion character (tab) is pressed
 * twice in succession.  Once the completion list has been displayed, its
 * value will be changed to the additive inverse of the column number in
 * order to determine whether to scroll if tab is pressed yet again.  We
 * assume that 0 will never be a valid column number.  So long as we always
 * display some sort of prompt prior to reading from the message line, this
 * is a good assumption.
 */
static	int	cmplcol = 0;
#endif

/*
 * Initializes the name-completion logic
 */
void
kbd_init(void)
{
	testcol = -1;
}

/*
 * Erases the display that was shown in response to TESTC
 */
void
kbd_unquery(void)
{
	beginDisplay;
#if OPT_POPUPCHOICE
	if (cmplcol != ttcol && -cmplcol != ttcol)
		cmplcol = 0;
#endif
	if (testcol >= 0) {
		while (ttcol > testcol)
			kbd_erase();
		TTflush();
		testcol = -1;
	}
	endofDisplay;
}

/*
 * This is invoked to find the closest name to complete from the current buffer
 * contents.
 */
int
kbd_complete(
int	case_insensitive,
int	c,		/* TESTC, NAMEC or isreturn() */
char	*buf,
int	*pos,
const char *table,
SIZE_T	size_entry)
{
	register SIZE_T cpos = *pos;
	register const char *nbp; /* first ptr to entry in name binding table */
	int status = FALSE;
#if OPT_POPUPCHOICE
# if OPT_ENUM_MODES
	int gvalpopup_choices = global_g_val(GVAL_POPUP_CHOICES);
# else
	int gvalpopup_choices = global_g_val(GMDPOPUP_CHOICES);
# endif
#endif

	kbd_init();		/* nothing to erase */
	buf[cpos] = EOS;	/* terminate it for us */
	nbp = table;		/* scan for matches */

	while (THIS_NAME(nbp) != NULL) {
		if (StrNcmp(buf,  THIS_NAME(nbp), strlen(buf)) == 0) {
			testcol = ttcol;
			/* a possible match! exact? no more than one? */
#if OPT_POPUPCHOICE
			if (!clexec && c == NAMEC && cmplcol == -ttcol) {
				scroll_completions(case_insensitive, buf, cpos, nbp, size_entry);
				return FALSE;
			}
#endif
			if (c == TESTC) {
				show_partial(case_insensitive, buf, cpos, nbp, size_entry);
			}
			else if (Strcmp(buf,  THIS_NAME(nbp)) == 0 || /* exact? */
				NEXT_NAME(nbp) == NULL ||
				StrNcmp(buf, NEXT_NAME(nbp), strlen(buf)) != 0)
			{
				/* exact or only one like it.  print it */
				if (!clexec) {
#if 0 /* case insensitive reply correction doesn't work reliably yet */
					if (case_insensitive) {
						int spos = cpos;

						while (spos > 0 && buf[spos - 1] != SLASHC) {
							kbd_erase();
							spos--;
						}
						kbd_puts(THIS_NAME(nbp) + spos);
					}
					else
#endif
						kbd_puts(THIS_NAME(nbp) + cpos);
					TTflush();
					testcol = ttcol;
				}
				if (c != NAMEC)  /* put it back */
					unkeystroke(c);
				/* return complete name */
				(void)strncpy0(buf, THIS_NAME(nbp),
						(SIZE_T)(NLINE - 1));
				*pos = strlen(buf);
#if OPT_POPUPCHOICE
				if (gvalpopup_choices != POPUP_CHOICES_OFF
				 && !clexec && (c == NAMEC))
					status = FALSE;
				else
#endif
					status = TRUE;
			}
			else {
				/* try for a partial match against the list */
				*pos = fill_partial(case_insensitive, buf, cpos, nbp,
					skip_partial(case_insensitive, buf, cpos, nbp, size_entry),
					size_entry);
				testcol = ttcol;
			}
#if OPT_POPUPCHOICE
# if OPT_ENUM_MODES
			if (!clexec
			 && gvalpopup_choices != POPUP_CHOICES_OFF
			 && c == NAMEC
			 && *pos == cpos) {
				if (gvalpopup_choices == POPUP_CHOICES_IMMED
				 || cmplcol == ttcol) {
					show_completions(case_insensitive, buf, cpos, nbp, size_entry);
					cmplcol = -ttcol;
				}
				else
					cmplcol = ttcol;
			}
			else
				cmplcol = 0;
# else
			if (!clexec && gvalpopup_choices 
			 && c == NAMEC && *pos == cpos) {
				show_completions(case_insensitive, buf, cpos, nbp, size_entry);
				cmplcol = -ttcol;
			}
			else
				cmplcol = 0;
# endif
#endif
			return status;
		}
		nbp = NEXT_DATA(nbp);
	}

#if OPT_POPUPCHOICE
	cmplcol = 0;
#endif
	kbd_alarm();	/* no match */
	buf[*pos = cpos] = EOS;
	return FALSE;
}

/*
 * Test a buffer to see if it looks like a shift-command, which may have
 * repeated characters (but they must all be the same).
 */
static int
is_shift_cmd(
char	*buffer,
int	cpos)
{
	register int c = *buffer;
	if (isRepeatable(c)) {
		while (--cpos > 0)
			if (*(++buffer) != c)
				return FALSE;
		return TRUE;
	}
	return FALSE;
}

/*
 * The following mess causes the command to terminate if:
 *
 *	we've got the eolchar
 *		-or-
 *	we're in the first few chars and we're switching from punctuation
 *	(i.e., delimiters) to non-punctuation (i.e., characters that are part
 *	of command-names), or vice-versa.  oh yeah -- '-' isn't punctuation
 *	today, and '!' isn't either, in one direction, at any rate.
 *	All this allows things like:
 *		: e#
 *		: e!%
 *		: !ls
 *		: q!
 *		: up-line
 *	to work properly.
 *
 *	If we pass this "if" with c != NAMEC, then c is ungotten below,
 *	so it can be picked up by the commands argument getter later.
 */

#define ismostpunct(c) (ispunct(c) && (c) != '-')

static int
eol_command(
char *	buffer,
int	cpos,
int	c,
int	eolchar)
{
	/*
	 * Handle special case of repeated-character implying repeat-count
	 */
	if (is_shift_cmd(buffer, cpos) && (c == *buffer))
		return TRUE;

	/*
	 * Shell-commands aren't complete until the line is complete.
	 */
	if ((cpos > 0) && isShellOrPipe(buffer))
		return isreturn(c);

	return	(c == eolchar)
	  ||	(
		  cpos > 0 &&  cpos < 3
	      &&(
		  (!ismostpunct(c)
		&&  ismostpunct(buffer[cpos-1])
		  )
		|| ((c != '!' && ismostpunct(c))
		  && (buffer[cpos-1] == '!' || !ismostpunct(buffer[cpos-1]))
		  )
		)
	      );
}

/*
 * This procedure is invoked from 'kbd_string()' to setup the command-name
 * completion and query displays.
 */
static int
cmd_complete(
int	c,
char	*buf,
int	*pos)
{
	register int status;
#if OPT_HISTORY
	/*
	 * If the user scrolled back in 'edithistory()', the text may be a
	 * repeated-shift command, which won't match the command-table (e.g.,
	 * ">>>").
	 */
	if ((*pos > 1) && is_shift_cmd(buf, *pos)) {
		int	len = 1;
		char	tmp[NLINE];
		tmp[0] = *buf;
		tmp[1] = EOS;
		status = cmd_complete(c, tmp, &len);
	} else
#endif
	 if ((*pos > 0) && isShellOrPipe(buf)) {
#if COMPLETE_FILES
		status = shell_complete(c, buf, pos);
#else
		status = isreturn(c);
		if (c != NAMEC)
			unkeystroke(c);
#endif
	} else {
		status = kbd_complete(FALSE, c, buf, pos,
			(const char *)&nametbl[0], sizeof(nametbl[0]));
	}
	return status;
}

int
kbd_engl_stat(
const char *prompt,
char	*buffer)
{
	int	kbd_flags = KBD_EXPCMD|KBD_NULLOK|((NAMEC != ' ') ? 0 : KBD_MAYBEC);

	*buffer = EOS;
#if COMPLETE_FILES
	init_filec(FILECOMPLETION_BufName);
#endif
	return kbd_reply(
		prompt,		/* no-prompt => splice */
		buffer,		/* in/out buffer */
		NLINE,		/* sizeof(buffer) */
		eol_command,
		' ',		/* eolchar */
		kbd_flags,	/* allow blank-return */
		cmd_complete);
}

#if NO_LEAKS
void
bind_leaks(void)
{
#if OPT_REBIND
	while (KeyBindings != kbindtbl) {
		KBIND *kbp = KeyBindings;
		KeyBindings = kbp->k_link;
		free((char *)kbp);
	}
#endif
}
#endif	/* NO_LEAKS */

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.