/*
 * $Id: zle_tricky.c,v 3.1.0.10 1996/12/17 20:07:07 hzoli Exp $
 *
 * zle_tricky.c - expansion and completion
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1996 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zle.h"

/* The main part of ZLE maintains the line being edited as binary data, *
 * but here, where we interface with the lexer and other bits of zsh,   *
 * we need the line metafied.  The technique used is quite simple: on   *
 * entry to the expansion/completion system, we metafy the line in      *
 * place, adjusting ll and cs to match.  All completion and expansion   *
 * is done on the metafied line.  Immediately before returning, the     *
 * line is unmetafied again, changing ll and cs back.  (ll and cs might *
 * have changed during completion, so they can't be merely saved and    *
 * restored.)  The various indexes into the line that are used in this  *
 * file only are not translated: they remain indexes into the metafied  *
 * line.                                                                */

#ifdef HAVE_NIS_PLUS
# include <rpcsvc/nis.h>
#else
# ifdef HAVE_NIS
#  include	<rpc/types.h>
#  include	<rpc/rpc.h>
#  include	<rpcsvc/ypclnt.h>
#  include	<rpcsvc/yp_prot.h>

/* This is used when getting usernames from the NIS. */
typedef struct {
    int len;
    char *s;
}
dopestring;
# endif
#endif

#define inststr(X) inststrlen((X),1,-1)

/* Prefix and suffix for globbing, used as an optimisation. */

extern char *glob_pre, *glob_suf;

/* wb and we hold the beginning/end position of the word we are completing. */

static int wb, we;

/* offs is the cursor position within the tokenized *
 * current word after removing nulargs.             */

static int offs;

/* These control the type of completion that will be done.  They are    *
 * affected by the choice of ZLE command and by relevant shell options. */

static int usemenu, useglob;

/* A pointer to the current position in the menu-completion array (the one *
 * that was put in the command line last).                                 */

static char **menucur;

/* The point (in the command line) where the menu-completion strings are  *
 * inserted, the length of the string that was inserted last, the end     *
 * position of this string in the command line and two flags: menuwe is   *
 * non-zero if the cursor was at the end of the word (which means that we *
 * can add suffixes, e.g. a slash for directories), and menuce is used to *
 * save/restore the value of complexpect during consecutive menu          *
 * completions (complexpect is used when completing after `$' or `${' and *
 * says whether some characters should be treated specially, when the     *
 * option autoparamkeys is set).  menuinsc is nonzero if a trailing slash *
 * marking a directory is added.                                          */

static int menupos, menulen, menuend, menuwe, menuce, menuinsc;

/* The list of matches.  fmatches contains the matches we first ignore *
 * because of fignore.                                                 */

static LinkList matches, fmatches;

/* The list of matches turned into an array.  This is used to sort this *
 * list and when menu-completion is used (directly or via automenu).    */

static char **amatches;

/* The number of matches. */

static int nmatches;

/* !=0 if we have a valid completion list. */

static int validlist;

/* This flag is non-zero if we are completing a pattern (with globcomplete) */

static int ispattern;

/* Two patterns used when doing glob-completion.  The first one is built *
 * from the whole word we are completing and the second one from that    *
 * part of the word that was identified as a possible filename.          */

static Comp patcomp, filecomp;

/* We store the following prefixes/suffixes:                             *
 * lpre/lsuf -- what's on the line                                       *
 * rpre/rsuf -- same as lpre/lsuf, but expanded                          *
 *                                                                       *
 * ... and if we are completing files, too:                              *
 * ppre/psuf -- the path prefix/suffix                                   *
 * fpre/fsuf -- prefix/suffix of the pathname component the cursor is in *
 * prpre     -- ppre in expanded form usable for opendir                 *
 *                                                                       *
 * The integer variables hold the lengths of lpre, lsuf, rpre, rsuf,     *
 * fpre, and fsuf.  noreal is non-zero if we have rpre/rsuf.             */

static char *lpre, *lsuf;
static char *rpre, *rsuf;
static char *ppre, *psuf, *prpre;
static char *fpre, *fsuf;
static int lpl, lsl, rpl, rsl, fpl, fsl;
static int noreal;

/* This is used when completing after `$' and holds the whole prefix,   *
 * used in do_single() to check whether the word expands to a directory *
 * name (in that case and if autoparamslash is set, we add a `/').      *
 * qparampre is the same but quoted. The length of it is in qparprelen  */

static char *parampre = NULL, *qparampre = NULL;
static int qparprelen;

/* This is either zero or equal to the special character the word we are *
 * trying to complete starts with (e.g. Tilde or Equals).                */

static char ic;

/* These hold the minimum common prefix/suffix lengths (normal and for *
 * fignore ignored).                                                   */

static int ab, ae, fab, fae;

/* This variable says what we are currently adding to the list of matches. */
   
static int addwhat;

/* firstm hold the first match we found, shortest contains the shortest *
 * one (normal and for fignore ignored).                                */

static char *firstm, *shortest, *ffirstm, *fshortest;

/* This holds the word we are completing in quoted from. */

static char *qword;

/* This is the length of the shortest match we found (normal and for *
 * fignore ignored).                                                 */

static int shortl, fshortl;

/* This is non-zero if we are doing a menu-completion and this is not the *
 * first call (e.g. when automenu is set and menu-completion was entered  *
 * due to this). */

static int amenu;

/* This is used by expandorcompleteprefix to save the position of the *
 * inserted space (so that we can remove it later).                   */

static int remove_at = -1;

/* Find out if we have to insert a tab (instead of trying to complete). */

/**/
int
usetab(void)
{
    unsigned char *s = line + cs - 1;

    for (; s >= line && *s != '\n'; s--)
	if (*s != '\t' && *s != ' ')
	    return 0;
    return 1;
}

#define COMP_COMPLETE 0
#define COMP_LIST_COMPLETE 1
#define COMP_SPELL 2
#define COMP_EXPAND 3
#define COMP_EXPAND_COMPLETE 4
#define COMP_LIST_EXPAND 5
#define COMP_ISEXPAND(X) ((X) >= COMP_EXPAND)

/**/
void
completeword(void)
{
    usemenu = isset(MENUCOMPLETE);
    useglob = isset(GLOBCOMPLETE);
    if (c == '\t' && usetab())
	selfinsert();
    else
	docomplete(COMP_COMPLETE);
}

/**/
void
menucomplete(void)
{
    usemenu = 1;
    useglob = isset(GLOBCOMPLETE);
    if (c == '\t' && usetab())
	selfinsert();
    else
	docomplete(COMP_COMPLETE);
}

/**/
void
listchoices(void)
{
    usemenu = isset(MENUCOMPLETE);
    useglob = isset(GLOBCOMPLETE);
    docomplete(COMP_LIST_COMPLETE);
}

/**/
void
spellword(void)
{
    usemenu = useglob = 0;
    docomplete(COMP_SPELL);
}

/**/
void
deletecharorlist(void)
{
    char **mc = menucur;

    usemenu = isset(MENUCOMPLETE);
    useglob = isset(GLOBCOMPLETE);
    if (cs != ll)
	deletechar();
    else
	docomplete(COMP_LIST_COMPLETE);

    menucur = mc;
}

/**/
void
expandword(void)
{
    usemenu = useglob = 0;
    if (c == '\t' && usetab())
	selfinsert();
    else
	docomplete(COMP_EXPAND);
}

/**/
void
expandorcomplete(void)
{
    usemenu = isset(MENUCOMPLETE);
    useglob = isset(GLOBCOMPLETE);
    if (c == '\t' && usetab())
	selfinsert();
    else
	docomplete(COMP_EXPAND_COMPLETE);
}

/**/
void
menuexpandorcomplete(void)
{
    usemenu = 1;
    useglob = isset(GLOBCOMPLETE);
    if (c == '\t' && usetab())
	selfinsert();
    else
	docomplete(COMP_EXPAND_COMPLETE);
}

/**/
void
listexpand(void)
{
    usemenu = isset(MENUCOMPLETE);
    useglob = isset(GLOBCOMPLETE);
    docomplete(COMP_LIST_EXPAND);
}

/**/
void
reversemenucomplete(void)
{
    if (!menucmp) {
	menucomplete();
	return;
    }
    HEAPALLOC {
	if (menucur == amatches)
	    menucur = amatches + nmatches - 1;
	else
	    menucur--;
	complexpect = menuce;
	metafy_line();
	do_single(*menucur);
	unmetafy_line();
    } LASTALLOC;
}

/* Accepts the current completion and starts a new arg, *
 * with the next completions. This gives you a way to   *
 * accept several selections from the list of matches.  */

/**/
void
acceptandmenucomplete(void)
{
    if (!menucmp) {
	feep();
	return;
    }
    cs = menuend;
    inststrlen(" ", 1, 1);
    if (qparampre)
	inststrlen(qparampre, 1, qparprelen);
    if (lpre && !ispattern)
	inststrlen(lpre, 1, -1);
    if (lsuf && !ispattern)
	inststrlen(lsuf, 0, -1);
    menupos = cs;
    menuend = cs + (lsuf ? strlen(lsuf) : 0);
    menulen = 0;
    menuwe = 1;
    menucomplete();
}

/* These are flags saying if we are completing in the command *
 * position or in a redirection.                              */

static int lincmd, linredir;

/* Non-zero if the last completion done was ambiguous (used to find   *
 * out if AUTOMENU should start).  More precisely, it's nonzero after *
 * successfully doing any completion, unless the completion was       *
 * unambiguous and did not cause the display of a completion list.    *
 * From the other point of view, it's nonzero iff AUTOMENU (if set)   *
 * should kick in on another completion (provided the text isn't      *
 * changed in the meantime... AAAAAAAAAAUUUUGGGHH!).                  */

static int lastambig;

/* This describes some important things collected during the last *
 * completion.  Its value is zero or the inclusive OR of some of  *
 * the HAS_* things below.                                        */

static int haswhat;

/* We have a suffix to add (given with compctl -S). */

#define HAS_SUFFIX  1

/* We have filenames in the completion list. */

#define HAS_FILES   2

/* We have other things than files in the completion list.  If this is *
 * not set but HAS_FILES is, we probably put the file type characters  *
 * in the completion list (if listtypes is set) and we attempt to add  *
 * a slash to completed directories.                                   */

#define HAS_MISC    4

/* This is set if we have filenames in the completion list that were *
 * generated by a globcompletion pattern.                            */

#define HAS_PATHPAT 8


/* This holds the naem of the current command (used to find the right *
 * compctl).                                                          */

static char *cmdstr;


/* Check if the given string is the name of a parameter and if this *
 * parameter is one worth expanding.                                */

/**/
int
checkparams(char *p)
{
    int t0, n, l = strlen(p), e = 0;
    struct hashnode *hn;

    for (t0 = paramtab->hsize - 1, n = 0; n < 2 && t0 >= 0; t0--)
	for (hn = paramtab->nodes[t0]; n < 2 && hn; hn = hn->next)
	    if (pfxlen(p, hn->nam) == l) {
		n++;
		if (strlen(hn->nam) == l)
		    e = 1;
	    }
    return (n == 1) ? (getsparam(p) != NULL) :
	(!menucmp && e && isset(RECEXACT));
}

/* Check if the given string has wildcards.  The difficulty is that we *
 * have to treat things like job specifications (%...) and parameter   *
 * expressions correctly.                                              */

/**/
int
cmphaswilds(char *str)
{
    if ((*str == Inbrack || *str == Outbrack) && !str[1])
	return 0;

    /* If a leading % is immediately followed by ?, then don't *
     * treat that ? as a wildcard.  This is so you don't have  *
     * to escape job references such as %?foo.                 */
    if (str[0] == '%' && str[1] ==Quest)
	str += 2;

    for (; *str;) {
	if (*str == String || *str == Qstring) {
	    /* A parameter expression. */

	    if (*++str == Inbrace)
		skipparens(Inbrace, Outbrace, &str);
	    else if (*str == String || *str == Qstring)
		str++;
	    else {
		/* Skip all the things a parameter expression might start *
		 * with (before we come to the parameter name).           */
		for (; *str; str++)
		    if (*str != '^' && *str != Hat &&
			*str != '=' && *str != Equals &&
			*str != '~' && *str != Tilde)
			break;
		if (*str == '#' || *str == Pound)
		    str++;
		/* Star and Quest are parameter names here, not wildcards */
		if (*str == Star || *str == Quest)
		    str++;
	    }
	} else {
	    /* Not a parameter expression so we check for wildcards */
	    if (((*str == Pound || *str == Hat) && isset(EXTENDEDGLOB)) ||
		*str == Star || *str == Bar || *str == Quest ||
		!skipparens(Inbrack, Outbrack, &str) ||
		!skipparens(Inang,   Outang,   &str) ||
		(unset(IGNOREBRACES) &&
		 !skipparens(Inbrace, Outbrace, &str)) ||
		(*str == Inpar && str[1] == ':' &&
		 !skipparens(Inpar, Outpar, &str)))
		return 1;
	    if (*str)
		str++;
	}
    }
    return 0;
}

/* The main entry point for completion. */

/**/
void
docomplete(int lst)
{
    char *s, *ol;
    int olst = lst, chl = 0, ne = noerrs, ocs;

    /* If we are doing a menu-completion... */

    if (menucmp && lst != COMP_LIST_EXPAND) {
	do_menucmp(lst);
	return;
    }
    
    /* Check if we have to start a menu-completion (via automenu). */

    if ((amenu = (isset(AUTOMENU) &&
		       (lastcmd & ZLE_MENUCMP) &&
		       lastambig)))
	usemenu = 1;

    /* Expand history references before starting completion.  If anything *
     * changed, do no more.                                               */

    if (doexpandhist())
	return;

    metafy_line();

    ocs = cs;
    if (!isfirstln && chline != NULL) {
	/* If we are completing in a multi-line buffer (which was not  *
	 * taken from the history), we have to prepend the stuff saved *
	 * in chline to the contents of line.                          */

	ol = dupstring((char *)line);
	/* Make sure that chline is zero-terminated. */
	*hptr = '\0';
	cs = 0;
	inststr(chline);
	chl = cs;
	cs += ocs;
    } else
	ol = NULL;
    inwhat = IN_NOTHING;
    qword = NULL;
    /* Get the word to complete. */
    noerrs = 1;
    s = get_comp_string();
    DPUTS(wb < 0 || cs < wb || cs > we,
	  "BUG: 0 <= wb <= cs <= we is not true!");
    noerrs = ne;
    /* For vi mode, reset the start-of-insertion pointer to the beginning *
     * of the word being completed, if it is currently later.  Vi itself  *
     * would never change the pointer in the middle of an insertion, but  *
     * then vi doesn't have completion.  More to the point, this is only  *
     * an emulation.                                                      */
    if (viinsbegin > ztrsub((char *) line + wb, (char *) line))
	viinsbegin = ztrsub((char *) line + wb, (char *) line);
    /* If we added chline to the line buffer, reset the original contents. */
    if (ol) {
	cs -= chl;
	wb -= chl;
	we -= chl;
	if (wb < 0) {
	    strcpy((char *) line, ol);
	    ll = strlen((char *) line);
	    cs = ocs;
	    unmetafy_line();
	    feep();
	    return;
	}
	ocs = cs;
	cs = 0;
	foredel(chl);
	cs = ocs;
    }
    freeheap();
    /* Save the lexer state, in case the completion code uses the lexer *
     * somewhere (e.g. when processing a compctl -s flag).              */
    lexsave();
    if (inwhat == IN_ENV)
	lincmd = 0;
    if (s) {
	if (lst == COMP_EXPAND_COMPLETE) {
	    /* Check if we have to do expansion or completion. */
	    char *q = s;

	    if (*q == Equals) {
		/* The word starts with `=', see if we can expand it. */
		q = s + 1;
		if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked))
		    if (isset(RECEXACT))
			lst = COMP_EXPAND;
		    else {
			int t0, n = 0;
			char *fc;
			struct hashnode *hn;

			for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--)
			    for (hn = cmdnamtab->nodes[t0]; hn;
				 hn = hn->next) {
				if (strpfx(q, hn->nam) && (fc = findcmd(hn->nam))) {
				    zsfree(fc);
				    n++;
				}
				if (n == 2)
				    break;
			    }

			if (n == 1)
			    lst = COMP_EXPAND;
		    }
	    }
	    if (lst == COMP_EXPAND_COMPLETE)
		do {
		    /* check if there is a parameter expresiion. */
		    for (; *q && *q != String; q++);
		    if (*q == String && q[1] != Inpar && q[1] != Inbrack) {
			if (*++q == Inbrace) {
			    if (! skipparens(Inbrace, Outbrace, &q) &&
				q == s + cs - wb)
				lst = COMP_EXPAND;
			} else {
			    char *t, sav, sav2;

			    /* Skip the things parameter expressions might *
			     * start with (the things before the parameter *
			     * name).                                      */
			    for (; *q; q++)
				if (*q != '^' && *q != Hat &&
				    *q != '=' && *q != Equals &&
				    *q != '~' && *q != Tilde)
				    break;
			    if ((*q == '#' || *q == Pound || *q == '+') &&
				q[1] != String)
				q++;

			    sav2 = *(t = q);
			    if (*q == Quest || *q == Star || *q == String ||
				*q == Qstring)
				*q = ztokens[*q - Pound], ++q;
			    else if (*q == '?' || *q == '*' || *q == '$' ||
				     *q == '-' || *q == '!' || *q == '@')
				q++;
			    else if (idigit(*q))
				do q++; while (idigit(*q));
			    else
				while (iident(*q))
				    q++;
			    sav = *q;
			    *q = '\0';
			    if (cs - wb == q - s &&
				(idigit(sav2) || checkparams(t)))
				lst = COMP_EXPAND;
			    *q = sav;
			    *t = sav2;
			}
			if (lst != COMP_EXPAND)
			    lst = COMP_COMPLETE;
		    } else
			break;
		} while (q < s + cs - wb);
	    if (lst == COMP_EXPAND_COMPLETE) {
		/* If it is still not clear if we should use expansion or   *
		 * completion and there is a `$' or a backtick in the word, *
		 * than do expansion.                                       */
		for (q = s; *q; q++)
		    if (*q == Tick || *q == Qtick ||
			*q == String || *q == Qstring)
			break;
		lst = *q ? COMP_EXPAND : COMP_COMPLETE;
	    }
	    /* And do expansion if there are wildcards and globcomplete is *
	     * not used.                                                   */
	    if (unset(GLOBCOMPLETE) && cmphaswilds(s))
		lst = COMP_EXPAND;
	}
	if (lincmd && (inwhat == IN_NOTHING))
	    inwhat = IN_CMD;

	if (lst == COMP_SPELL) {
	    char *x, *q;

	    for (q = s; *q; q++)
		if (INULL(*q))
		    *q = Nularg;
	    cs = wb;
	    foredel(we - wb);
	    HEAPALLOC {
		untokenize(x = dupstring(s));
		if (*s == Tilde || *s == Equals || *s == String)
		    *x = *s;
		spckword(&x, 0, lincmd, 0);
	    } LASTALLOC;
	    untokenize(x);
	    inststr(x);
	} else if (COMP_ISEXPAND(lst)) {
	    /* Do expansion. */
	    char *ol = (olst == COMP_EXPAND_COMPLETE) ?
		dupstring((char *)line) : (char *)line;
	    int ocs = cs, ne = noerrs;

	    noerrs = 1;
	    doexpansion(s, lst, olst, lincmd);
	    lastambig = 0;
	    noerrs = ne;

	    /* If expandorcomplete was invoked and the expansion didn't *
	     * change the command line, do completion.                  */
	    if (olst == COMP_EXPAND_COMPLETE &&
		!strcmp(ol, (char *)line)) {
		char *p;

		cs = ocs;
		errflag = 0;

		p = s;
		if (*p == Tilde || *p == Equals)
		    p++;
		for (; *p; p++)
		    if (itok(*p))
			if (*p != String && *p != Qstring)
			    *p = ztokens[*p - Pound];
			else if (p[1] == Inbrace)
			    p++, skipparens(Inbrace, Outbrace, &p);
		docompletion(s, lst, lincmd, 1);
	    }
	} else
	    /* Just do completion. */
	    docompletion(s, lst, lincmd, 0);
	zsfree(s);
    }
    /* Reset the lexer state, pop the heap. */
    lexrestore();
    popheap();
    zsfree(qword);
    menuce = complexpect;
    unmetafy_line();
}

/* Do completion, given that we are in the middle of a menu completion.  We *
 * don't need to generate a list of matches, because that's already been    *
 * done by previous commands.  We will either list the completions, or      *
 * insert the next completion.                                              */

/**/
void
do_menucmp(int lst)
{
    /* Just list the matches if the list was requested. */
    if (lst == COMP_LIST_COMPLETE) {
	showinglist = -2;
	return;
    }
    /* Otherwise go to the next match in the array... */
    HEAPALLOC {
	if (!*++menucur)
	    menucur = amatches;
	complexpect = menuce;
	/* ... and insert it into the command line. */
	metafy_line();
	do_single(*menucur);
	unmetafy_line();
    } LASTALLOC;
}

/* 1 if we are completing in a string */
int instring;

/* This function inserts an `x' in the command line at the cursor position. *
 *                                                                          *
 * Oh, you want to know why?  Well, if completion is tried somewhere on an  *
 * empty part of the command line, the lexer code would normally not be     *
 * able to give us the `word' we want to complete, since there is no word.  *
 * But we need to call the lexer to find out where we are (and for which    *
 * command we are completing and such things).  So we temporarily add a `x' *
 * (any character without special meaning would do the job) at the cursor   *
 * position, than the lexer gives us the word `x' and its beginning and end *
 * positions and we can remove the `x'.                                     */

/**/
void
addx(char **ptmp)
{
    if (!line[cs] || line[cs] == '\n' ||
	(iblank(line[cs]) && (!cs || line[cs-1] != '\\')) ||
	line[cs] == ')' || line[cs] == '`' ||
	(instring && (line[cs] == '"' || line[cs] == '\''))) {
	*ptmp = (char *)line;
	line = (unsigned char *)halloc(strlen((char *)line) + 3);
	memcpy(line, *ptmp, cs);
	line[cs] = 'x';
	strcpy((char *)line + cs + 1, (*ptmp) + cs);
	addedx = 1;
    } else {
	addedx = 0;
	*ptmp = NULL;
    }
}

/* Like dupstring, but add an extra space at the end of the string. */

/**/
char *
dupstrspace(const char *str)
{
    int len = strlen((char *)str);
    char *t = (char *)ncalloc(len + 2);
    strcpy(t, str);
    strcpy(t+len, " ");
    return t;
}

/* These functions metafy and unmetafy the ZLE buffer, as described at the *
 * top of this file.  Note that ll and cs are translated.  They *must* be  *
 * called in matching pairs, around all the expansion/completion code.     *
 * Currently, there are four pairs: in history expansion, in the main      *
 * completion function, and one in each of the middle-of-menu-completion   *
 * functions (there's one for each direction).                             */

/**/
void
metafy_line(void)
{
    int len = ll;
    char *s;

    for (s = (char *) line; s < (char *) line + ll;)
	if (imeta(*s++))
	    len++;
    sizeline(len);
    (void) metafy((char *) line, ll, META_NOALLOC);
    ll = len;
    cs = metalen((char *) line, cs);
}

/**/
void
unmetafy_line(void)
{
    cs = ztrsub((char *) line + cs, (char *) line);
    (void) unmetafy((char *) line, &ll);
}

/* Lasciate ogni speranza.                                                  *
 * This function is a nightmare.  It works, but I'm sure that nobody really *
 * understands why.  The problem is: to make it cleaner we would need       *
 * changes in the lexer code (and then in the parser, and then...).         */

/**/
char *
get_comp_string(void)
{
    int t0, tt0, i, j, k, cp, rd, sl, ocs;
    char *s = NULL, *linptr, *tmp, *p, *tt = NULL;

    /* This global flag is used to signal the lexer code if it should *
     * expand aliases or not.                                         */
    noaliases = isset(COMPLETEALIASES);

    /* Find out if we are somewhere in a `string', i.e. inside '...', *
     * "...", `...`, or ((...)).                                      */

    for (i = j = k = 0, p = (char *)line; p < (char *)line + cs; p++)
	if (*p == '`' && !(k & 1))
	    i++;
	else if (*p == '\"' && !(k & 1) && !(i & 1))
	    j++;
	else if (*p == '\'' && !(j & 1))
	    k++;
	else if (*p == '\\' && p[1] && !(k & 1))
	    p++;
    instring = (j & 1) ? 2 : (k & 1);
    addx(&tmp);
    if (instring) {
	/* Yes, we are in a string. */
	if (!tmp) {
	    tmp = (char *)line;
	    line = (unsigned char *) dupstring((char *) line);
	}
	/* Now remove the quotes.                                   *
	 * What??  Why that??  Well, we want to be able to complete *
	 * inside strings.  The lexer code gives us no help here,   *
	 * so we have to cheat.  We remove the quotes, the lexer    *
	 * will than treat the words in the strings normally and we *
	 * can complete them.                                       *
	 * This is completely the wrong thing to do, but it's       *
	 * occasionally useful, and we can't handle quotes properly *
	 * yet anyway.                                              */
	for (p = (char *)line; *p; p++)
	    if (*p == '"' || *p == '\'')
		*p = ' ';
    }
    linptr = (char *)line;
    pushheap();
    HEAPALLOC {
      start:
	inwhat = IN_NOTHING;
	/* Now set up the lexer and start it. */
	parbegin = parend = -1;
	lincmd = incmdpos;
	linredir = inredir;
	zsfree(cmdstr);
	cmdstr = NULL;
	zleparse = 1;
	clwpos = -1;
	lexsave();
	inpush(dupstrspace((char *) linptr), 0, NULL);
	strinbeg();
	stophist = 2;
	i = tt0 = cp = rd = 0;

	/* This loop is possibly the wrong way to do this.  It goes through *
	 * the previously massaged command line using the lexer.  It stores *
	 * each token in each command (commands being regarded, roughly, as *
	 * being separated by tokens | & &! |& || &&).  The loop stops when *
	 * the end of the command containing the cursor is reached.  It's a *
	 * simple way to do things, but suffers from an inability to        *
	 * distinguish actual command arguments from, for example,          *
	 * filenames in redirections.  (But note that code elsewhere checks *
	 * if we are completing *in* a redirection.)  The only way to fix   *
	 * this would be to pass the command line through the parser too,   *
	 * and get the arguments that way.  Maybe in 3.1...                 */
	do {
	    lincmd = incmdpos;
	    linredir = inredir;
	    /* Get the next token. */
	    ctxtlex();

	    /* We reached the end. */
	    if (tok == ENDINPUT)
		break;
	    if (tok == BAR    || tok == AMPER     ||
		tok == BARAMP || tok == AMPERBANG ||
		((tok == DBAR || tok == DAMPER) && !incond)) {
		/* This is one of the things that separate commands.  If we  *
		 * already have the things we need (e.g. the token strings), *
		 * leave the loop.                                           */
		if (tt)
		    break;
		/* Otherwise reset the variables we are collecting data in. */
		i = tt0 = cp = rd = 0;
	    }
	    if (lincmd && tok == STRING) {
		/* The lexer says, this token is in command position, so *
		 * store the token string (to find the right compctl).   */
		zsfree(cmdstr);
		cmdstr = ztrdup(tokstr);
		i = 0;
	    }
	    if (!zleparse && !tt0) {
		/* This is done when the lexer reached the word the cursor is on. */
		tt = tokstr ? dupstring(tokstr) : NULL;
		/* If we added a `x', remove it. */
		if (addedx && tt)
		    chuck(tt + cs - wb - 1);
		tt0 = tok;
		/* Store the number of this word. */
		clwpos = i;
		cp = lincmd;
		rd = linredir;
		if (inwhat == IN_NOTHING && incond)
		    inwhat = IN_COND;
	    }
	    if (!tokstr)
		continue;
	    /* We need to store the token strings of all words (for some of *
	     * the more complicated compctl -x things).  They are stored in *
	     * the clwords array.  Make this array big enough.              */
	    if (i + 1 == clwsize) {
		int n;
		clwords = (char **)realloc(clwords,
					   (clwsize *= 2) * sizeof(char *));
		for(n = clwsize; --n > i; )
		    clwords[n] = NULL;
	    }
	    zsfree(clwords[i]);
	    /* And store the current token string. */
	    clwords[i] = ztrdup(tokstr);
	    sl = strlen(tokstr);
	    /* Sometimes the lexer gives us token strings ending with *
	     * spaces we delete the spaces.                           */
	    while (sl && clwords[i][sl - 1] == ' ' &&
		   (sl < 2 || (clwords[i][sl - 2] != Bnull &&
			       clwords[i][sl - 2] != Meta)))
		clwords[i][--sl] = '\0';
	    /* If this is the word the cursor is in and we added a `x', *
	     * remove it.                                               */
	    if (clwpos == i++ && addedx)
		chuck(&clwords[i - 1][((cs - wb - 1) >= sl) ?
				     (sl - 1) : (cs - wb - 1)]);
	} while (tok != LEXERR && tok != ENDINPUT &&
		 (tok != SEPER || (zleparse && !tt0)));
	/* Calculate the number of words stored in the clwords array. */
	clwnum = (tt || !i) ? i : i - 1;
	zsfree(clwords[clwnum]);
	clwords[clwnum] = NULL;
	t0 = tt0;
	lincmd = cp;
	linredir = rd;
	strinend();
	inpop();
	errflag = zleparse = 0;
	if (addedx)
	    wb++;
	if (parbegin != -1) {
	    /* We are in command or process substitution */
	    if (parend >= 0 && !tmp)
		line = (unsigned char *) dupstring(tmp = (char *)line);
	    linptr = (char *) line + ll + addedx - parbegin + 1;
	    if (parend >= 0) {
		ll -= parend;
		line[ll + addedx] = '\0';
	    }
	    lexrestore();
	    goto start;
	}

	if (inwhat == IN_MATH)
	    s = NULL;
	else if (!t0 || t0 == ENDINPUT) {
	    /* There was no word (empty line). */
	    s = ztrdup("");
	    we = wb = cs;
	    clwpos = clwnum;
	    t0 = STRING;
	} else if (t0 == STRING) {
	    /* We found a simple string. */
	    s = ztrdup(clwords[clwpos]);
	} else if (t0 == ENVSTRING) {
	    /* The cursor was inside a parameter assignment. */
	    for (s = tt; iident(*s); s++);
	    if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb)
		s = NULL, inwhat = IN_MATH;
	    else if (*s == '=') {
		s++;
		wb += s - tt;
		t0 = STRING;
		s = ztrdup(s);
		inwhat = IN_ENV;
	    }
	    lincmd = 1;
	}
	if (we > ll)
	    we = ll;
	tt = (char *)line;
	if (tmp) {
	    line = (unsigned char *)tmp;
	    ll = strlen((char *)line);
	}
	if (t0 != STRING && inwhat != IN_MATH) {
	    if (tmp) {
		tmp = NULL;
		linptr = (char *)line;
		lexrestore();
		goto start;
	    }
	    feep();
	    noaliases = 0;
	    lexrestore();
	    LASTALLOC_RETURN NULL;
	}

	noaliases = 0;

	/* Check if we are in an array subscript.  We simply assume that  *
	 * we are in a subscript if we are in brackets.  Correct solution *
	 * is very difficult.  This is quite close, but gets things like  *
	 * foo[_ wrong (note no $).  If we are in a subscript, treat it   *
	 * as being in math.                                              */
	if (inwhat != IN_MATH) {
	    int i = 0;
	    for (tt = s; ++tt < s + cs - wb;)
		if (*tt == Inbrack)
		    i++;
		else if (i && *tt == Outbrack)
		    i--;
	    if (i)
		inwhat = IN_MATH;
	}
	if (inwhat == IN_MATH) {
	    /* In mathematical expression, we complete parameter names (even *
	     * if they don't have a `$' in front of them).  So we have to    *
	     * find that name.                                               */
	    for (we = cs; iident(line[we]); we++);
	    for (wb = cs; --wb >= 0 && iident(line[wb]););
	    wb++;
	    zsfree(s);
	    s = zalloc(we - wb + 1);
	    strncpy(s, (char *) line + wb, we - wb);
	    s[we - wb] = '\0';
	}
	/* This variable will hold the current word in quoted form. */
	qword = ztrdup(s);
	/* While building the quoted form, we also clean up the command line. */
	offs = cs - wb;
	for (p = s, tt = qword, i = wb; *p; p++, tt++, i++)
	    if (INULL(*p)) {
		if (i < cs)
		    offs--;
		if (p[1] || *p != Bnull) {
		    if (*p == Bnull) {
			*tt = '\\';
			if (cs == i + 1)
			    cs++, offs++;
		    } else {
			ocs = cs;
			cs = i;
			foredel(1);
			chuck(tt--);
			if ((cs = ocs) > i--)
			    cs--;
			we--;
		    }
		} else {
		    ocs = cs;
		    *tt = '\0';
		    cs = we;
		    backdel(1);
		    if (ocs == we)
			cs = we - 1;
		    else
			cs = ocs;
		    we--;
		}
		chuck(p--);
	    }
    } LASTALLOC;
    lexrestore();

    return (char *)s;
}

/* Expand the current word. */

/**/
void
doexpansion(char *s, int lst, int olst, int explincmd)
{
    LinkList vl;
    char *ss;

    DPUTS(useheap, "BUG: useheap in doexpansion()");
    HEAPALLOC {
	pushheap();
	vl = newlinklist();
	ss = dupstring(s);
	addlinknode(vl, ss);
	prefork(vl, 0);
	if (errflag)
	    goto end;
	if ((lst == COMP_LIST_EXPAND) || (lst == COMP_EXPAND)) {
	    int ng = opts[NULLGLOB];

	    opts[NULLGLOB] = 1;
	    globlist(vl);
	    opts[NULLGLOB] = ng;
	}
	if (errflag)
	    goto end;
	if (empty(vl) || !*(char *)peekfirst(vl)) {
	    if (!noerrs)
		feep();
	    goto end;
	}
	if (peekfirst(vl) == (void *) ss ||
		(olst == COMP_EXPAND_COMPLETE &&
		 !nextnode(firstnode(vl)) && *s == Tilde &&
		 (ss = dupstring(s), filesubstr(&ss, 0)) &&
		 !strcmp(ss, (char *)peekfirst(vl)))) {
	    /* If expansion didn't change the word, try completion if *
	     * expandorcomplete was called, otherwise, just beep.     */
	    if (lst == COMP_EXPAND_COMPLETE)
		docompletion(s, COMP_COMPLETE, explincmd, 0);
	    else
		feep();
	    goto end;
	}
	if (lst == COMP_LIST_EXPAND) {
	    /* Only the list of expansions was requested. */
	    listlist(vl);
	    goto end;
	}
	/* Remove the current word and put the expansions there. */
	cs = wb;
	foredel(we - wb);
	while ((ss = (char *)ugetnode(vl))) {
	    untokenize(ss);
	    ss = quotename(ss, NULL, NULL, NULL);
	    inststr(ss);
#if 0
	    if (nonempty(vl)) {
		spaceinline(1);
		line[cs++] = ' ';
	    }
#endif
	    if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) ||
		(cs && line[cs-1] != '/')) {
		spaceinline(1);
		line[cs++] = ' ';
	    }
	}
      end:
	popheap();
    } LASTALLOC;
}

/* This is called from the lexer to give us word positions. */

/**/
void
gotword(void)
{
    we = ll + 1 - inbufct;
    if (cs <= we) {
	wb = ll - wordbeg;
	zleparse = 0;
    }
}

/* Insert the given string into the command line.  If move is non-zero, *
 * the cursor position is changed and len is the length of the string   *
 * to insert (if it is -1, the length is calculated here).              */

/**/
void
inststrlen(char *str, int move, int len)
{
    if (!len)
	return;
    if (len == -1)
	len = strlen(str);
    spaceinline(len);
    strncpy((char *)(line + cs), str, len);
    if (remove_at >= cs)
        remove_at += len;
    if (move)
	cs += len;
}

/* Quote the string s and return the result.  If e is non-zero, it the    *
 * pointer it points to may point to aposition in s and in e the position *
 * of the corresponding character in the quoted string is returned.  Like *
 * e, te may point to a position in the string and pl is used to return   *
 * the position of the character pointed to by te in the quoted string.   *
 * The string is metafied and may contain tokens.                         */

/**/
char *
quotename(char *s, char **e, char *te, int *pl)
{
    char *tt, *v, *u, buf[PATH_MAX * 2];
    int sf = 0;

    tt = v = buf;
    u = s;
    for (; *u; u++) {
	if (e && *e == u)
	    *e = v, sf |= 1;
	if (te == u)
	    *pl = v - tt, sf |= 2;
	if (ispecial(*u) &&
	    (!instring || (isset(BANGHIST) &&
			   *u == (char)bangchar) ||
	     (instring == 2 &&
	      (*u == '$' || *u == '`' || *u == '\"')) ||
	     (instring == 1 && *u == '\'')))
	    if (*u == '\n' || (instring == 1 && *u == '\'')) {
		if (unset(RCQUOTES)) {
		    *v++ = '\'';
		    if (*u == '\'')
			*v++ = '\\';
		    *v++ = *u;
		    *v++ = '\'';
		} else if (*u == '\n')
		    *v++ = '"', *v++ = '\n', *v++ = '"';
		else
		    *v++ = '\'', *v++ = '\'';
		continue;
	    } else
		*v++ = '\\';
	if(*u == Meta)
	    *v++ = *u++;
	*v++ = *u;
    }
    *v = '\0';
    if (strcmp(buf, s))
	tt = dupstring(buf);
    else
	tt = s;
    v += tt - buf;
    if (e && (sf & 1))
	*e += tt - buf;

    if (e && *e == u)
	*e = v;
    if (te == u)
	*pl = v - tt;

    return tt;
}

/* This adds a match to the list of matches.  The string to add is given   *
 * in s, the type of match is given in the global variable addwhat and     *
 * the parameter t (if not NULL) is a pointer to a hash node node which    *
 * may be used to give other information to this function.                 *
 *                                                                         *
 * addwhat contains either one of the special values (negative, see below) *
 * or the inclusive OR of some of the CC_* flags used for compctls.        */

/**/
void
addmatch(char *s, char *t)
{
    int test = 0, sl = strlen(s), pl = rpl, cc = 0, *bp, *ep, *sp;
    char *e = NULL, *tt, *te, *fc, **fm;
    Comp cp = patcomp;
    HashNode hn;
    Param pm;
    LinkList l = matches;

/*
 * addwhat: -5 is for files,
 *          -6 is for glob expansions,
 *          -8 is for executable files (e.g. command paths),
 *          -9 is for parameters
 *          -7 is for command names (from cmdnamtab)
 *          -4 is for a cdable parameter
 *          -3 is for executable command names.
 *          -2 is for anything unquoted
 *          -1 is for other file specifications
 *          (things with `~' of `=' at the beginning, ...).
 */

    /* Just to make the code cleaner */
    hn = (HashNode) t;
    pm = (Param) t;

    if (!addwhat) {
	test = 1;
    } else if (addwhat == -1 || addwhat == -5 || addwhat == -6 ||
	       addwhat == CC_FILES || addwhat == -7 || addwhat == -8) {
	if (sl < fpl + fsl)
	    return;

	if ((addwhat == CC_FILES ||
	     addwhat == -5) && !*psuf && !*fsuf) {
	    /* If this is a filename, do the fignore check. */
	    char **pt = fignore;
	    int filell;

	    for (test = 1; test && *pt; pt++)
		if ((filell = strlen(*pt)) < sl
		    && !strcmp(*pt, s + sl - filell))
		    test = 0;

	    if (!test)
		l = fmatches;
	}
	pl = fpl;
	if (addwhat == -5 || addwhat == -8) {
	    test = 1;
	    cp = filecomp;
	    cc = cp || ispattern;
	    e = s + sl - fsl;
	} else {
	    if ((cp = filecomp)) {
		if ((test = domatch(s, filecomp, 0)))
		    cc = 1;
	    } else {
		e = s + sl - fsl;
		if ((test = !strncmp(s, fpre, fpl)))
		    test = !strcmp(e, fsuf);
		if (ispattern)
		    cc = 1;
	    }
	}
	if (test) {
	    fc = NULL;
	    if (addwhat == -7 && !(fc = findcmd(s)))
		return;
	    if (fc)
		zsfree(fc);
	    haswhat |= HAS_FILES;

	    if (addwhat == CC_FILES || addwhat == -6 ||
		addwhat == -5 || addwhat == -8) {
		te = s + pl;
		s = quotename(s, &e, te, &pl);
		sl = strlen(s);
	    } else if (!cc) {
		s = dupstring(t = s);
		e += s - t;
	    }
	    if (cc) {
		tt = (char *)halloc(strlen(ppre) + strlen(psuf) + sl + 1);
		strcpy(tt, ppre);
		strcat(tt, s);
		strcat(tt, psuf);
		untokenize(s = tt);
	    }
	}
    } else if (addwhat == CC_QUOTEFLAG || addwhat == -2  ||
	      (addwhat == -3 && !(hn->flags & DISABLED)) ||
	      (addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) &&
	       (tt = pm->gets.cfn(pm)) && *tt == '/')    ||
	      (addwhat == -9 && !(hn->flags & PM_UNSET)) ||
	      (addwhat > 0 &&
	       ((!(hn->flags & PM_UNSET) &&
		 (((addwhat & CC_ARRAYS)    &&  (hn->flags & PM_ARRAY))    ||
		  ((addwhat & CC_INTVARS)   &&  (hn->flags & PM_INTEGER))  ||
		  ((addwhat & CC_ENVVARS)   &&  (hn->flags & PM_EXPORTED)) ||
		  ((addwhat & CC_SCALARS)   &&  (hn->flags & PM_SCALAR))   ||
		  ((addwhat & CC_READONLYS) &&  (hn->flags & PM_READONLY)) ||
		  ((addwhat & CC_SPECIALS)  &&  (hn->flags & PM_SPECIAL))  ||
		  ((addwhat & CC_PARAMS)    && !(hn->flags & PM_EXPORTED)))) ||
		((( addwhat & CC_SHFUNCS)				  ||
		  ( addwhat & CC_BUILTINS)				  ||
		  ( addwhat & CC_EXTCMDS)				  ||
		  ( addwhat & CC_RESWDS)				  ||
		  ((addwhat & CC_ALREG)   && !(hn->flags & ALIAS_GLOBAL)) ||
		  ((addwhat & CC_ALGLOB)  &&  (hn->flags & ALIAS_GLOBAL))) &&
		 (((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) ||
		  ((addwhat & CC_EXCMDS)  && !(hn->flags & DISABLED))))))) {
	if (sl >= rpl + rsl) {
	    if (cp)
		test = domatch(s, patcomp, 0);
	    else {
		e = s + sl - rsl;
		if ((test = !strncmp(s, rpre, rpl)))
		    test = !strcmp(e, rsuf);
	    }
	}
	if (!test && sl < lpl + lsl)
	    return;
	if (!test && lpre && lsuf && sl >= lpl + lsl) {
	    e = s + sl - lsl;
	    if ((test = !strncmp(s, lpre, lpl)))
		test = !strcmp(e, lsuf);
	    pl = lpl;
	}
	if (addwhat == CC_QUOTEFLAG) {
	    te = s + pl;
	    s = quotename(s, &e, te, &pl);
	    sl = strlen(s);
	}
	if (test)
	    haswhat |= HAS_MISC;
    }
    if (!test)
	return;

    if (ispattern) {
	t = s;
    } else {
	t = s += pl;
	if (*e) {
	    sl = e - s;
	    t = s = dupstring(t);
	    s[sl] = '\0';
	}
    }

    if (l == fmatches) {
	bp = &fab;
	ep = &fae;
	sp = &fshortl;
	fm = &ffirstm;
    } else {
	bp = &ab;
	ep = &ae;
	sp = &shortl;
	fm = &firstm;
    }

    if (!ispattern && *fm) {
	if ((test = pfxlen(*fm, s)) < *bp)
	    *bp = test;
	if ((test = sfxlen(*fm, s)) < *ep)
	    *ep = test;
    }

    /* If we are doing a glob completion we store the whole string in *
     * the list. Otherwise only the part that fits between the prefix *
     * and the suffix is stored.                                      */
    addlinknode(l, t);
    if (!*fm) {
	*bp = *ep = 10000;
	*fm = t;
	*sp = 100000;
    }
    if (!ispattern && (sl = strlen(t)) < *sp) {
	*sp = sl;
	if (l == fmatches)
	    fshortest = t;
	else
	    shortest = t;
    }
}

#ifdef HAVE_NIS_PLUS
static int
match_username(nis_name table, nis_object *object, void *userdata)
{
    if (errflag)
	return 1;
    else {
	static char buf[40];
	register entry_col *ec =
	    object->zo_data.objdata_u.en_data.en_cols.en_cols_val;
	register int l = minimum(ec->ec_value.ec_value_len, 39);

	memcpy(buf, ec->ec_value.ec_value_val, l);
	buf[l] = '\0';

	addmatch(dupstring(buf), NULL);
    }
    return 0;
}
#else
# ifdef HAVE_NIS
static int
match_username(int status, char *key, int keylen, char *val, int vallen, dopestring *data)
{
    if (errflag || status != YP_TRUE)
	return 1;

    if (vallen > keylen && val[keylen] == ':') {
	val[keylen] = '\0';
	addmatch(dupstring(val), NULL);
    }
    return 0;
}
# endif /* HAVE_NIS */
#endif  /* HAVE_NIS_PLUS */

/**/
void
maketildelist(void)
{
#if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS)
    FILE *pwf;
    char buf[BUFSIZ], *p;
    int skipping;

# ifndef HAVE_NIS_PLUS
    char domain[YPMAXDOMAIN];
    struct ypall_callback cb;
    dopestring data;

    data.s = fpre;
    data.len = fpl;
    /* Get potential matches from NIS and cull those without local accounts */
    if (getdomainname(domain, YPMAXDOMAIN) == 0) {
	cb.foreach = (int ((*) _((void)))) match_username;
	cb.data = (char *)&data;
	yp_all(domain, PASSWD_MAP, &cb);
/*	for (n = firstnode(matches); n; incnode(n))
	    if (getpwnam(getdata(n)) == NULL)
		uremnode(matches, n);*/
    }
# else  /* HAVE_NIS_PLUS */
       /* Maybe we should turn this string into a #define'd constant...? */

    nis_list("passwd.org_dir", EXPAND_NAME|ALL_RESULTS|FOLLOW_LINKS|FOLLOW_PATH,
	     match_username, 0);
# endif
    /* Don't forget the non-NIS matches from the flat passwd file */
    if ((pwf = fopen(PASSWD_FILE, "r")) != NULL) {
	skipping = 0;
	while (fgets(buf, BUFSIZ, pwf) != NULL) {
	    if (strchr(buf, '\n') != NULL) {
		if (!skipping) {
		    if ((p = strchr(buf, ':')) != NULL) {
			*p = '\0';
			addmatch(dupstring(buf), NULL);
		    }
		} else
		    skipping = 0;
	    } else
		skipping = 1;
	}
	fclose(pwf);
    }
#else  /* no NIS or NIS_PLUS */
    /* add all the usernames to the named directory table */
    nameddirtab->filltable(nameddirtab);
#endif

    scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0,
	    addhnmatch, 0);
}

/* Copy the given string and remove backslashes from the copy and return it. */

/**/
char *
rembslash(char *s)
{
    char *t = s = dupstring(s);

    while (*s)
	if (*s == '\\') {
	    chuck(s);
	    if (*s)
		s++;
	} else
	    s++;

    return t;
}

/* This does the check for compctl -x `n' and `N' patterns. */

/**/
int
getcpat(char *wrd, int cpatindex, char *cpat, int class)
{
    char *str, *s, *t, *p;
    int d = 0;

    if (!wrd || !*wrd)
	return -1;

    cpat = rembslash(cpat);

    str = ztrdup(wrd);
    untokenize(str);
    if (!cpatindex)
	cpatindex++, d = 0;
    else if ((d = (cpatindex < 0)))
	cpatindex = -cpatindex;

    for (s = d ? str + strlen(str) - 1 : str;
	 d ? (s >= str) : *s;
	 d ? s-- : s++) {
	for (t = s, p = cpat; *t && *p; p++) {
	    if (class) {
		if (*p == *s && !--cpatindex) {
		    zsfree(str);
		    return (int)(s - str + 1);
		}
	    } else if (*t++ != *p)
		break;
	}
	if (!class && !*p && !--cpatindex) {
	    zsfree(str);
	    t += wrd - str;
	    for (d = 0; --t >= wrd;)
		if (! INULL(*t))
		    d++;
	    return d;
	}
    }
    zsfree(str);
    return -1;
}

/* This holds a pointer to the compctl we are using. */

Compctl ccmain;


/* Find the compctl to use and return it.  The first argument gives a *
 * compctl to start searching with (if it is zero, the hash table is  *
 * searched).  compadd is used to return a number of characters that  *
 * should be ignored at the beginning of the word and incmd is        *
 * non-zero if we are in command position.                            */

/**/
Compctl
get_ccompctl(Compctl occ, int *compadd, int incmd)
{
    Compctl compc, ret;
    Compctlp ccp;
    int t, i, a, b, tt, ra, rb, j, isf = 1;
    Compcond or, cc;
    char *s, *ss, *sc, *cmd = dupstring(cmdstr);
    Comp comp;

   first_rec:
    *compadd = 0;
    ra = 0;
    rb = clwnum - 1;
    sc = NULL;

    if (!(ret = compc = occ)) {
      if (isf) {
        isf = 0;
        ret = &cc_first;
      }
      else if (inwhat == IN_ENV)
        /* Default completion for parameter values. */
        ret = &cc_default;
      else if (inwhat == IN_MATH) {
        /* Parameter names inside mathematical expression. */
        cc_dummy.mask = CC_PARAMS;
	    ret = &cc_dummy;
	    cc_dummy.refc = 10000;
	} else if (inwhat == IN_COND) {
	    /* We try to be clever here: in conditions we complete option   *
	     * names after a `-o', file names after `-nt', `-ot', and `-ef' *
	     * and file names and parameter names elsewhere.                */
	    s = clwpos ? clwords[clwpos - 1] : "";
	    cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS :
		((*s == '-' && s[1] && !s[2]) ||
		 !strcmp("-nt", s) ||
		 !strcmp("-ot", s) ||
		 !strcmp("-ef", s)) ? CC_FILES :
		(CC_FILES | CC_PARAMS);
	    ret = &cc_dummy;
	    cc_dummy.refc = 10000;
	} else if (incmd)
	    ret = &cc_compos;
	/* And in redirections or if there is no command name (and we are *
	 * not in command position) or if no special compctl was given    *
	 * for the command: use default completion.  Note that we first   *
	 * search the complete command name and than the trailing         *
	 * pathname component.                                            */
	else if (linredir ||
 		 !(cmd &&
 		   (((ccp = (Compctlp) compctltab->getnode(compctltab, cmd)) &&
		     (compc = ret = ccp->cc)) ||
 		    ((s = dupstring(cmd)) && remlpaths(&s) &&
		     (ccp = (Compctlp) compctltab->getnode(compctltab, s)) &&
		     (compc = ret = ccp->cc)))))
	    ret = &cc_default;

	ccmain = compc = ret;
	ccmain->refc++;
    }
    /* The compctl we found has extended completion patterns, check them. */
    if (compc && compc->ext) {
	compc = compc->ext;
	/* This loops over the patterns separated by `--'. */
	for (t = 0; compc && !t; compc = compc->next) {
	    /* This loops over OR'ed patterns. */
	    for (cc = compc->cond; cc && !t; cc = or) {
		or = cc->or;
		/* This loops over AND'ed patterns. */
		for (t = 1; cc && t; cc = cc->and) {
		    /* And this loops of [...] pairs. */
		    for (t = i = 0; i < cc->n && !t; i++) {
			s = NULL;
			ra = 0;
			rb = clwnum - 1;
			switch (cc->type) {
			case CCT_POS:
			    tt = clwpos;
			    goto cct_num;
			case CCT_NUMWORDS:
			    tt = clwnum;
			  cct_num:
			    if ((a = cc->u.r.a[i]) < 0)
				a += clwnum;
			    if ((b = cc->u.r.b[i]) < 0)
				b += clwnum;
			    if (cc->type == CCT_POS)
				ra = a, rb = b;
			    t = (tt >= a && tt <= b);
			    break;
			case CCT_CURSUF:
			case CCT_CURPRE:
			    s = ztrdup(clwpos < clwnum ? clwords[clwpos] : "");
			    untokenize(s);
			    sc = rembslash(cc->u.s.s[i]);
			    a = strlen(sc);
			    if (!strncmp(s, sc, a)) {
				*compadd = (cc->type == CCT_CURSUF ? a : 0);
				t = 1;
			    }
			    break;
			case CCT_CURSUB:
			case CCT_CURSUBC:
			    if (clwpos < 0 || clwpos > clwnum)
				t = 0;
			    else {
				a = getcpat(clwords[clwpos],
					    cc->u.s.p[i],
					    cc->u.s.s[i],
					    cc->type == CCT_CURSUBC);
				if (a != -1)
				    *compadd = a, t = 1;
			    }
			    break;

			case CCT_CURPAT:
			case CCT_CURSTR:
			    tt = clwpos;
			    goto cct_str;
			case CCT_WORDPAT:
			case CCT_WORDSTR:
			    tt = 0;
			  cct_str:
			    if ((a = tt + cc->u.s.p[i]) < 0)
				a += clwnum;
			    s = ztrdup((a < 0 || a >= clwnum) ? "" :
				       clwords[a]);
			    untokenize(s);

			    if (cc->type == CCT_CURPAT ||
				cc->type == CCT_WORDPAT) {
				tokenize(ss = dupstring(cc->u.s.s[i]));
				t = ((comp = parsereg(ss)) &&
				     domatch(s, comp, 0));
			    } else
				t = (!strcmp(s, rembslash(cc->u.s.s[i])));
			    break;
			case CCT_RANGESTR:
			case CCT_RANGEPAT:
			    if (cc->type == CCT_RANGEPAT)
				tokenize(sc = dupstring(cc->u.l.a[i]));
			    for (j = clwpos; j; j--) {
				untokenize(s = ztrdup(clwords[j]));
				if (cc->type == CCT_RANGESTR)
				    sc = rembslash(cc->u.l.a[i]);
				if (cc->type == CCT_RANGESTR ?
				    !strncmp(s, sc, strlen(sc)) :
				    ((comp = parsereg(sc)) &&
				     domatch(s, comp, 0))) {
				    zsfree(s);
				    ra = j + 1;
				    t = 1;
				    break;
				}
				zsfree(s);
			    }
			    if (t) {
				if (cc->type == CCT_RANGEPAT)
				    tokenize(sc = dupstring(cc->u.l.b[i]));
				for (j++; j < clwnum; j++) {
				    untokenize(s = ztrdup(clwords[j]));
				    if (cc->type == CCT_RANGESTR)
					sc = rembslash(cc->u.l.b[i]);
				    if (cc->type == CCT_RANGESTR ?
					!strncmp(s, sc, strlen(sc)) :
					((comp = parsereg(sc)) &&
					 domatch(s, comp, 0))) {
					zsfree(s);
					rb = j - 1;
					t = clwpos <= rb;
					break;
				    }
				    zsfree(s);
				}
			    }
			    s = NULL;
			}
			zsfree(s);
		    }
		}
	    }
	    if (t)
		break;
	}
	if (compc)
	    /* We found a matching pattern, we may return it. */
	    ret = compc;
    }
    if (ret->subcmd) {
	/* The thing we want to return has a subcmd flag (-l). */
	char **ow = clwords, *os = cmdstr, *ops = NULL;
	int oldn = clwnum, oldp = clwpos;

	/* So we restrict the words-array. */
	if (ra >= clwnum)
	    ra = clwnum - 1;
	if (ra < 1)
	    ra = 1;
	if (rb >= clwnum)
	    rb = clwnum - 1;
	if (rb < 1)
	    rb = 1;
	clwnum = rb - ra + 1;
	clwpos = clwpos - ra;

	if (ret->subcmd[0]) {
	    /* And probably put the command name given to the flag *
	     * in the array.                                       */
	    clwpos++;
	    clwnum++;
	    incmd = 0;
	    ops = clwords[ra - 1];
	    clwords[ra - 1] = cmdstr = ret->subcmd;
	    clwords += ra - 1;
	} else {
	    cmdstr = clwords[ra];
	    incmd = !clwpos;
	    clwords += ra;
	}
	*compadd = 0;
	if (ccmain != &cc_dummy)
	    freecompctl(ccmain);
	/* Then we call this function recursively. */

	ret = get_ccompctl(NULL, compadd, incmd);
	/* And restore the things we changed. */
	clwords = ow;
	cmdstr = os;
	clwnum = oldn;
	clwpos = oldp;
	if (ops)
	    clwords[ra - 1] = ops;
    }
    if (ret == &cc_first)
      goto first_rec;
    return ret;
}

/* Dump a hash table (without sorting).  For each element the addmatch  *
 * function is called and at the beginning the addwhat variable is set. *
 * This could be done using scanhashtable(), but this is easy and much  *
 * more efficient.                                                      */

/**/
void
dumphashtable(HashTable ht, int what)
{
    HashNode hn;
    int i;

    addwhat = what;

    for (i = 0; i < ht->hsize; i++)
	for (hn = ht->nodes[i]; hn; hn = hn->next)
	    addmatch(hn->nam, (char *) hn);

}

/* ScanFunc used by maketildelist() et al. */

/**/
void
addhnmatch(HashNode hn, int flags)
{
    addmatch(hn->nam, NULL);
}

/* Perform expansion on the given string and return the result. *
 * During this errors are not reported.                         */

/**/
char *
getreal(char *str)
{
    LinkList l = newlinklist();
    int ne = noerrs;

    noerrs = 1;
    addlinknode(l, dupstring(str));
    prefork(l, 0);
    noerrs = ne;
    if (!errflag && nonempty(l))
	return ztrdup(peekfirst(l));
    errflag = 0;

    return ztrdup(str);
}

/* This reads a directory and adds the files to the list of  *
 * matches.  The parameters say which files should be added. */

/**/
void
gen_matches_files(int dirs, int execs, int all)
{
    DIR *d;
    struct stat buf;
    char *n, p[PATH_MAX], *q = NULL, *e;
    LinkList l = NULL;
    int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat;

    addwhat = execs ? -8 : -5;
    opts[NULLGLOB] = 1;

    if (*psuf) {
	/* If there is a path suffix, check if it doesn't have a `*' or *
	 * `)' at the end (this is used to determine if we should use   *
	 * globbing).                                                   */
	q = psuf + strlen(psuf) - 1;
	ns = !(*q == Star || *q == Outpar);
	l = newlinklist();
	/* And generate only directory names. */
	dirs = 1;
	all = execs = 0;
    }
    /* Open directory. */
    if ((d = opendir((prpre && *prpre) ? prpre : "."))) {
	/* If we search only special files, prepare a path buffer for stat. */
	if (!all && prpre) {
	    strcpy(p, prpre);
	    q = p + strlen(prpre);
	}
	/* Fine, now read the directory. */
	while ((n = zreaddir(d)) && !errflag) {
	    /* Ignore `.' and `..'. */
	    if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
		continue;
	    /* Ignore files beginning with `.' unless the thing we found on *
	     * the command line also starts with a dot or GLOBDOTS is set.  */
	    if (*n != '.' || *fpre == '.' || isset(GLOBDOTS)) {
		if (filecomp)
		    /* If we have a pattern for the filename check, use it. */
		    test = domatch(n, filecomp, 0);
		else {
		    /* Otherwise use the prefix and suffix strings directly. */
		    e = n + strlen(n) - fsl;
		    if ((test = !strncmp(n, fpre, fpl)))
			test = !strcmp(e, fsuf);
		}
		/* Filename didn't match? */
		if (!test)
		    continue;
		if (!all) {
		    /* We still have to check the file type, so prepare *
		     * the path buffer by appending the filename.       */
		    strcpy(q, n);
		    /* And do the stat. */
		    if (stat(p, &buf) < 0)
			continue;
		}
		if (all ||
		    (dirs && (buf.st_mode & S_IFMT) == S_IFDIR) ||
		    (execs && ((buf.st_mode & (S_IFMT | S_IEXEC))
		      == (S_IFREG | S_IEXEC)))) {
		    /* If we want all files or the file has the right type... */
		    if (*psuf) {
			/* We have to test for a path suffix. */
			int o = strlen(p), tt;

			/* Append it to the path buffer. */
			strcpy(p + o, psuf);

			/* Do we have to use globbing? */
			if (ispattern || (ns && isset(GLOBCOMPLETE))) {
			    /* Yes, so append a `*' if needed. */
			    if (ns) {
				int tl = strlen(p);

				p[tl] = Star;
				p[tl + 1] = '\0';
			    }
			    /* Do the globbing... */
			    remnulargs(p);
			    addlinknode(l, p);
			    globlist(l);
			    /* And see if that produced a filename. */
			    tt = nonempty(l);
			    while (ugetnode(l));
			} else
			    /* Otherwise just check, if we have access *
			     * to the file.                            */
			    tt = !access(p, F_OK);

			p[o] = '\0';
			if (tt)
			    /* Ok, we can add the filename to the *
			     * list of matches.                   */
			    addmatch(dupstring(n), NULL);
		    } else
			/* We want all files, so just add the name *
			 * to the matches.                         */
			addmatch(dupstring(n), NULL);
		}
	    }
	}
	closedir(d);
    }
    opts[NULLGLOB] = ng;
    addwhat = aw;
}

/* This holds the explanation string we have to print. */

char *expl;

/* This holds the suffix to add (given with compctl -S). */

char *ccsuffix;

/* This s non-zero if the compctl -q flag was given (the suffix should *
 * be removed when a space or something like that is typed next).      */

int remsuffix;

/**/
void
quotepresuf(char **ps)
{
    if (*ps) {
	char *p = quotename(*ps, NULL, NULL, NULL);

	if (p != *ps) {
	    zsfree(*ps);
	    *ps = ztrdup(p);
	}
    }
}

/* This is non-zero if the cursor was moved up after showing a list *
 * of completions (with alwayslastprompt).                          */

int clearflag;

/**/
void
docompletion(char *s, int lst, int incmd, int untokenized)
{
    static int delit, compadd;

    HEAPALLOC {
	pushheap();

	/* Make sure we have the completion list and compctl. */
	if(makecomplist(s, incmd, &delit, &compadd, untokenized)) {
	    /* Error condition: feeeeeeeeeeeeep(). */
	    feep();
	    goto compend;
	}

	if (lst == COMP_LIST_COMPLETE)
	    /* All this and the guy only wants to see the list, sigh. */
	    showinglist = -2;
	else {
	    /* We have matches. */
	    if (delit) {
		/* If we have to delete the word from the command line, *
		 * do it now.                                           */
		wb -= compadd;
		strcpy((char *)line + wb, (char *)line + we);
		we = cs = wb;
	    }
	    if (nmatches > 1)
		/* There are more than one match. */
		do_ambiguous();
	    else if (nmatches == 1) {
		/* Only one match. */
		do_single(amatches[0]);
		invalidatelist();
	    }
	}

	/* Print the explanation string if needed. */
	if (!showinglist && expl && !nmatches) {
	    int up;

	    feep();
	    trashzle();

	    clearflag = (isset(USEZLE) && termok &&
			 (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
			(unset(ALWAYSLASTPROMPT) && zmult != 1);

	    up = printfmt(expl, nmatches, 1);

	    if (clearflag)
		tcmultout(TCUP, TCMULTUP, up + nlnct);
	    else
		putc('\n', shout);
	    fflush(shout);
	}
      compend:
	ll = strlen((char *)line);
	if (cs > ll)
	    cs = ll;
	popheap();
    } LASTALLOC;
}

/* Create the completion list.  This is called whenever some bit of  *
 * completion code needs the list.  If the list is already available *
 * (validlist!=0), this function doesn't do anything.  Along with    *
 * the list is maintained the prefixes/suffixes etc.  When any of    *
 * this becomes invalid -- e.g. if some text is changed on the       *
 * command line -- invalidatelist() should be called, to set         *
 * validlist to zero and free up the memory used.  This function     *
 * returns non-zero on error.  delit and compadd return information  *
 * about bits of the command line that need to be deleted.           */

/**/
int
makecomplist(char *s, int incmd, int *delit, int *compadd, int untokenized)
{
    Compctl cc = NULL;
    int oloffs = offs, owe = we, owb = wb, ocs = cs, isf = 1;
    int t, sf1, sf2, ooffs;
    char *p, *sd = NULL, sav, *tt, *s1, *s2, *os = NULL;
    unsigned char *ol = NULL;

    /* If we already have a list from a previous execution of this *
     * function, skip the list building code.                      */
    if (validlist)
	return !nmatches;

    os = dupstring(s);
    ol = (unsigned char *)dupstring((char *)line);

  xorrec:

    /* Go to the end of the word if complete_in_word is not set. */
    if (unset(COMPLETEINWORD) && cs != we)
	cs = we, offs = strlen(s);

    ispattern = haswhat = lastambig = 0;
    patcomp = filecomp = NULL;
    menucur = NULL;
    shortest = NULL;
    fshortest = NULL;
    rpre = rsuf = lpre = lsuf = ppre = psuf = prpre =
	fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL;

    /* Blank out the lists. */
    matches = newlinklist();
    fmatches = newlinklist();

    /* If we don't have a compctl definition yet or we have a compctl *
     * with extended completion, get it (or the next one, resp.).     */
    if (!cc || cc->ext)
	cc = get_ccompctl(cc, compadd, incmd);

    /* *compadd is the number of characters we have to ignore at the *
     * beginning of the word.                                        */
    wb += *compadd;
    s += *compadd;
    if ((offs -= *compadd) < 0)
	/* It's bigger than our word prefix, so we can't help here... */
	return 1;

    /* Insert the prefix (compctl -P), if any. */
    if (cc->prefix) {
	int pl = 0, sl = strlen(cc->prefix);

	if (*s) {
	    /* First find out how much of the prefix is already on the line. */
	    sd = dupstring(s);
	    untokenize(sd);
	    pl = pfxlen(cc->prefix, sd);
	    s += pl;
	}
	if (pl < sl) {
	    int savecs = cs;

	    /* Then insert the prefix. */
	    cs = wb + pl;
	    inststrlen(cc->prefix + pl, 0, sl - pl);
	    cs = savecs + sl - pl;
	}
	/* And adjust the word beginning/end variables. */
	wb += sl;
	we += sl - pl;
	offs -= pl;
    }
    /* Does this compctl have a suffix (compctl -S)? */
    if ((ccsuffix = cc->suffix) && *ccsuffix) {
	char *sdup = dupstring(ccsuffix);
	int sl = strlen(sdup), suffixll;

	/* Ignore trailing spaces. */
	for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--);
	p[1] = '\0';

	if (!sd) {
	    sd = dupstring(s);
	    untokenize(sd);
	}
	/* If the suffix is already there, ignore it (and don't add *
	 * it again).                                               */
	if (*sd && (suffixll = strlen(sd)) >= sl &&
	    !strcmp(sdup, sd + suffixll - sl)) {
	    ccsuffix = NULL;
	    haswhat |= HAS_SUFFIX;
	    s[suffixll - sl] = '\0';
	}
    }
    /* Do we have one of the special characters `~' and `=' at the beginning? */
    if ((ic = *s) != Tilde && ic != Equals)
	ic = 0;

    /* Check if we have to complete a parameter name... */

    complexpect = menuce = 0;

    /* Try to find a `$'. */
    for (p = s + offs; p > s && *p != String; p--);
    if (*p == String) {
	/* Handle $$'s */
	while (p > s && p[-1] == String)
	    p--;
	while (p[1] == String && p[2] == String)
	    p += 2;
    }
    if (*p == String &&	p[1] != Inpar && p[1] != Inbrack) {
	/* This is really a parameter expression (not $(...) or $[...]). */
	char *b = p + 1, *e = b;
	int n = 0, br = 1;

	if (*b == Inbrace) {
	    /* If this is a ${...}, ignore the possible (...) flags. */
	    b++, br++;
	    n = skipparens(Inpar, Outpar, &b);
	}

	/* Ignore the stuff before the parameter name. */
	for (; *b; b++)
	    if (*b != '^' && *b != Hat &&
		*b != '=' && *b != Equals &&
		*b != '~' && *b != Tilde)
		break;
	if (*b == '#' || *b == Pound || *b == '+')
	    b++;

	e = b;
	/* Find the end of the name. */
	if (*e == Quest || *e == Star || *e == String || *e == Qstring ||
	    *e == '?'   || *e == '*'  || *e == '$'    ||
	    *e == '-'   || *e == '!'  || *e == '@')
	    e++;
	else if (idigit(*e))
	    while (idigit(*e))
		e++;
	else if (iident(*e))
	    while (iident(*e) ||
		   (useglob && (*e == Star || *e == Quest)))
		e++;

	/* Now make sure that the cursor is inside the name. */
	if (offs <= e - s && offs >= b - s && n <= 0) {
	    /* It is, set complexpect. */
	    if (cs == we || ! *e || *e == ' ')
		complexpect = br;
	    /* Get the prefix (anything up to the character before the name). */
	    *e = '\0';
	    sav = *b;
	    *b = '\0';
	    parampre = ztrdup(s);
	    qparampre = ztrdup(quotename(s, NULL, NULL, NULL));
	    untokenize(qparampre);
	    qparprelen = strlen(qparampre);
	    *b = sav;
	    /* And adjust wb, we, and offs again. */
	    offs -= b - s;
	    wb = cs - offs;
	    we = wb + e - b;
	    s = b;
	    /* And now make sure that we complete parameter names. */
	    cc = ccmain = &cc_dummy;
	    cc_dummy.refc = 10000;
	    cc_dummy.mask = CC_PARAMS | CC_ENVVARS;
	} else
	    complexpect = 0;
    }
    ooffs = offs;
    /* If we have to ignore the word, do that. */
    if (cc->mask & CC_DELETE) {
	*delit = 1;
	*s = '\0';
	offs = 0;
    } else
	*delit = 0;

    /* Compute line prefix/suffix. */

    lpl = offs;
    lpre = zalloc(lpl + 1);
    memcpy(lpre, s, lpl);
    lpre[lpl] = '\0';
    p = quotename(lpre, NULL, NULL, NULL);
    if (strcmp(p, lpre) && !strpfx(p, qword)) {
	int l1, l2;

	backdel(l1 = cs - wb);
	untokenize(p);
	inststrlen(p, 1, l2 = strlen(p));
	we += l2 - l1;
    }
    lsuf = ztrdup(s + offs);
    lsl = strlen(lsuf);
    if (lsl && (p = quotename(lsuf, NULL, NULL, NULL)) &&
	(strcmp(p, lsuf) && !strsfx(p, qword))) {
	int l1, l2;

	foredel(l1 = strlen(s + offs));
	untokenize(p);
	inststrlen(p, 0, l2 = strlen(p));
	we += l2 - l1;
    }

    /* First check for ~.../... */
    if (ic == Tilde) {
	for (p = lpre + lpl; p > lpre; p--)
	    if (*p == '/')
		break;

	if (*p == '/')
	    ic = 0;
    }
    /* Compute real prefix/suffix. */

    noreal = !*delit;
    for (p = lpre; *p && *p != String && *p != Tick; p++);
    tt = ic && !parampre ? lpre + 1 : lpre;
    rpre = (*p || *lpre == Tilde || *lpre == Equals) ?
	(noreal = 0, getreal(tt)) :
	ztrdup(tt);

    for (p = lsuf; *p && *p != String && *p != Tick; p++);
    rsuf = *p ? (noreal = 0, getreal(lsuf)) : ztrdup(lsuf);

    /* Check if word is a pattern. */

    for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1;
	 p >= rpre && (ispattern != 3 || !sf1);
	 p--)
	if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde)))
	    ispattern |= sf1 ? 1 : 2;
	else if (*p == '/') {
	    sf1++;
	    if (!s1)
		s1 = p;
	}
    for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++)
	if (itok(*p))
	    t |= sf2 ? 4 : 2;
	else if (*p == '/') {
	    sf2++;
	    if (!s2)
		s2 = p;
	}
    ispattern = ispattern | t;

    /* But if we were asked not to do glob completion, we never treat the *
     * thing as a pattern.                                                */
    if (!useglob)
	ispattern = 0;

    if (ispattern) {
	/* The word should be treated as a pattern, so compute the matcher. */
	p = (char *)ncalloc(rpl + rsl + 2);
	strcpy(p, rpre);
	if (rpl && p[rpl - 1] != Star) {
	    p[rpl] = Star;
	    strcpy(p + rpl + 1, rsuf);
	} else
	    strcpy(p + rpl, rsuf);
	patcomp = parsereg(p);
    }
    if (!patcomp) {
	untokenize(rpre);
	untokenize(rsuf);

	rpl = strlen(rpre);
	rsl = strlen(rsuf);
    }
    untokenize(lpre);
    untokenize(lsuf);

    /* Handle completion of files specially (of course). */

    if ((cc->mask & (CC_FILES | CC_COMMPATH)) || cc->glob) {
	/* s1 and s2 point to the last/first slash in the prefix/suffix. */
	if (!s1)
	    s1 = rpre;
	if (!s2)
	    s2 = rsuf + rsl;

	/* Compute the path prefix/suffix. */
	if (*s1 != '/')
	    ppre = ztrdup("");
	else {
	    if ((sav = *s1 ? s1[1] : '\0'))
		s1[1] = '\0';
	    ppre = ztrdup(rpre);
	    if (sav)
		s1[1] = sav;
	}
	psuf = ztrdup(s2);

	/* And get the file prefix. */
	fpre = ztrdup(((s1 == s || s1 == rpre || ic) &&
		       (*s != '/' || cs == wb)) ? s1 : s1 + 1);
	/* And the suffix. */
	sav = *s2;
	*s2 = '\0';
	fsuf = ztrdup(rsuf);
	*s2 = sav;

	if (useglob && (ispattern & 2)) {
	    int t2;

	    /* We have to use globbing, so compute the pattern from *
	     * the file prefix and suffix with a `*' between them.  */
	    p = (char *)ncalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2);
	    strcpy(p, fpre);
	    if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star)
		p[t2++] = Star;
	    strcpy(p + t2, fsuf);
	    filecomp = parsereg(p);
	}
	if (!filecomp) {
	    untokenize(fpre);
	    untokenize(fsuf);

	    fpl = strlen(fpre);
	    fsl = strlen(fsuf);
	}
	addwhat = -1;

	/* Completion after `~', maketildelist adds the usernames *
	 * and named directories.                                 */
	if (ic == Tilde)
	    maketildelist();
	else if (ic == Equals) {
	    /* Completion after `=', get the command names from *
	     * the cmdnamtab and aliases from aliastab.         */
	    if (isset(HASHLISTALL))
		cmdnamtab->filltable(cmdnamtab);
	    dumphashtable(cmdnamtab, -7);
	    dumphashtable(aliastab, -2);
	} else {
	    /* Normal file completion... */
	    if (ispattern & 1) {
		/* But with pattern matching. */
		LinkList l = newlinklist();
		LinkNode n;
		int ng = opts[NULLGLOB];

		opts[NULLGLOB] = 1;

		addwhat = 0;
		p = (char *)ncalloc(lpl + lsl + 3);
		strcpy(p, lpre);
		if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*')
		    strcat(p, "*");
		strcat(p, lsuf);
		if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')')
		    strcat(p, "*");

		/* Do the globbing. */
		tokenize(p);
		remnulargs(p);
		addlinknode(l, p);
		globlist(l);

		if (nonempty(l)) {
		    /* And add the resulting words. */
		    haswhat |= HAS_PATHPAT;
		    for (n = firstnode(l); n; incnode(n))
			addmatch(getdata(n), NULL);
		}
		opts[NULLGLOB] = ng;
	    } else {
		/* No pattern matching. */
		addwhat = CC_FILES;
		prpre = ztrdup(ppre);

		if (sf2)
		    /* We are in the path, so add only directories. */
		    gen_matches_files(1, 0, 0);
		else {
		    if (cc->mask & CC_FILES)
			/* Add all files. */
			gen_matches_files(0, 0, 1);
		    else if (cc->mask & CC_COMMPATH) {
			/* Completion of command paths. */
			if (sf1)
			    /* There is a path prefix, so add *
			     * directories and executables.   */
			    gen_matches_files(1, 1, 0);
			else {
			    /* No path prefix, so add the things *
			     * reachable via the PATH variable.  */
			    char **pc = path, *pp = prpre;

			    for (; *pc; pc++)
				if (!**pc || (pc[0][0] == '.' && !pc[0][1]))
				    break;
			    if (*pc) {
				prpre = "./";
				gen_matches_files(1, 1, 0);
				prpre = pp;
			    }
			}
		    }
		    /* The compctl has a glob pattern (compctl -g). */
		    if (cc->glob) {
			int ns, pl = strlen(prpre), o;
			char *g = dupstring(cc->glob), pa[PATH_MAX];
			char *p2, *p3;
			int ne = noerrs, md = opts[MARKDIRS];

			/* These are used in the globbing code to make *
			 * things a bit faster.                        */
			glob_pre = fpre;
			glob_suf = fsuf;

			noerrs = 1;
			addwhat = -6;
			strcpy(pa, prpre);
			o = strlen(pa);
			opts[MARKDIRS] = 0;

			/* The compctl -g string may contain more than *
			 * one pattern, so we need a loop.             */
			while (*g) {
			    LinkList l = newlinklist();
			    int ng;

			    /* Find the blank terminating the pattern. */
			    while (*g && inblank(*g))
				g++;
			    /* Oops, we already reached the end of the
			       string. */
			    if (!*g)
				break;
			    for (p = g + 1; *p && !inblank(*p); p++)
				if (*p == '\\' && p[1])
				    p++;
			    sav = *p;
			    *p = '\0';
			    /* Get the pattern string. */
			    tokenize(g = dupstring(g));
			    if (*g == '=')
				*g = Equals;
			    if (*g == '~')
				*g = Tilde;
			    remnulargs(g);
			    if (*g == Equals || *g == Tilde) {
				/* The pattern has a `~' or `=' at the  *
				 * beginning, so we expand this and use *
				 * the result.                          */
				filesub(&g, 0);
				addlinknode(l, dupstring(g));
			    } else if (*g == '/')
				/* The pattern is a full path (starting *
				 * with '/'), so add it unchanged.      */
				addlinknode(l, dupstring(g));
			    else {
				/* It's a simple pattern, so append it to *
				 * the path we have on the command line.  */
				strcpy(pa + o, g);
				addlinknode(l, dupstring(pa));
			    }
			    /* Do the globbing. */
			    ng = opts[NULLGLOB];
			    opts[NULLGLOB] = 1;
			    globlist(l);
			    opts[NULLGLOB] = ng;
			    /* Get the results. */
			    if (nonempty(l) && peekfirst(l)) {
				for (p2 = (char *)peekfirst(l); *p2; p2++)
				    if (itok(*p2))
					break;
				if (!*p2) {
				    if (*g == Equals || *g == Tilde ||
					*g == '/') {
					/* IF the pattern started with `~',  *
					 * `=', or `/', add the result only, *
					 * if it realy matches what we have  *
					 * on the line.                      */
					while ((p2 = (char *)ugetnode(l)))
					    if (strpfx(prpre, p2))
						addmatch(p2 + pl, NULL);
				    } else {
					/* Otherwise ignore the path we *
					 * prepended to the pattern.    */
					while ((p2 = p3 =
						(char *)ugetnode(l))) {
					    for (ns = sf1; *p3 && ns; p3++)
						if (*p3 == '/')
						    ns--;

					    addmatch(p3, NULL);
					}
				    }
				}
			    }
			    pa[o] = '\0';
			    *p = sav;
			    g = p;
			}
			glob_pre = glob_suf = NULL;
			noerrs = ne;
			opts[MARKDIRS] = md;
		    }
		}
	    }
	}
    }
    /* Use tricat() instead of dyncat() to get zalloc()'d memory. */
    if (ic) {
	/* Now change the `~' and `=' tokens to the real characters so *
	 * that things starting with these characters will be added.   */
	char *orpre = rpre;

	rpre = tricat("", (ic == Tilde) ? "~" : "=", rpre);
	rpl++;
	zsfree(orpre);
    }
    if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) {
	/* If we have to complete commands, add alias names, *
	 * shell functions and builtins too.                 */
	dumphashtable(aliastab, -3);
	dumphashtable(reswdtab, -3);
	dumphashtable(shfunctab, -3);
	dumphashtable(builtintab, -3);
	if (isset(HASHLISTALL))
	    cmdnamtab->filltable(cmdnamtab);
	dumphashtable(cmdnamtab, -3);
	/* And parameter names if autocd and cdablevars are set. */
	if (isset(AUTOCD) && isset(CDABLEVARS))
	    dumphashtable(paramtab, -4);
    }
    addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG;

    if (cc->mask & CC_NAMED)
	/* Add named directories. */
	scanhashtable(nameddirtab, 0, 0, 0, addhnmatch, 0);
    if (cc->mask & CC_OPTIONS) {
	/* Add option names. */
	struct option *o;

	for (o = optns + OPT_SIZE; (--o)->name; )
	    addmatch(dupstring(o->name), NULL);
    }
    if (cc->mask & CC_VARS)
	/* And parameter names. */
	dumphashtable(paramtab, -9);
    if (cc->mask & CC_BINDINGS) {
	/* And zle function names... */
	struct zlecmd *zc;

	for (zc = zlecmds; zc < zlecmds+zlecmdtot; zc++)
	    if (zc->name && *zc->name && !(zc->flags & ZLE_DELETED))
		addmatch(dupstring(zc->name), NULL);
	for (zc = zlecmdadd; zc < zlecmdadd+(zlecmdtot-ZLECMDCOUNT); zc++)
	    if (*zc->name && !(zc->flags & ZLE_DELETED))
		addmatch(dupstring(zc->name), NULL);
    }
    if (cc->keyvar) {
	/* This adds things given to the compctl -k flag *
	 * (from a parameter or a list of words).        */
	char **usr = get_user_var(cc->keyvar);

	if (usr)
	    while (*usr)
		addmatch(*usr++, NULL);
    }
    if (cc->mask & CC_USERS)
	/* Add user names. */
	maketildelist();
    if (cc->func) {
	/* This handles the compctl -K flag. */
	List list;
	char **r;
	int lv = lastval;

	/* Get the function. */
	if ((list = getshfunc(cc->func))) {
	    /* We have it, so build a argument list. */
	    LinkList args = newlinklist();

	    addlinknode(args, cc->func);

	    if (*delit) {
		sav = os[ooffs];
		os[ooffs] = '\0';
		p = dupstring(os);
		untokenize(p);
		addlinknode(args, p);
		os[ooffs] = sav;
		p = dupstring(os + ooffs);
		untokenize(p);
		addlinknode(args, p);
	    } else {
		addlinknode(args, lpre);
		addlinknode(args, lsuf);
	    }

	    /* This flag allows us to use read -l and -c. */
	    inzlefunc = 1;
	    /* Call the function. */
	    doshfunc(list, args, 0, 1);
	    inzlefunc = 0;
	    /* And get the result from the reply parameter. */
	    if ((r = get_user_var("reply")))
		while (*r)
		    addmatch(*r++, NULL);
	}
	lastval = lv;
    }
    if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) {
	/* Get job names. */
	int i;
	char *j, *jj;

	for (i = 0; i < MAXJOB; i++)
	    if (jobtab[i].stat & STAT_INUSE) {
		int stopped = jobtab[i].stat & STAT_STOPPED;

		j = jj = dupstring(jobtab[i].procs->text);
		/* Find the first word. */
		for (; *jj; jj++)
		    if (*jj == ' ') {
			*jj = '\0';
			break;
		    }
		if ((cc->mask & CC_JOBS) ||
		    (stopped && (cc->mask & CC_STOPPED)) ||
		    (!stopped && (cc->mask & CC_RUNNING)))
		    addmatch(j, NULL);
	    }
    }
    if (cc->str) {
	/* Get the stuff from a compctl -s. */
	LinkList foo = newlinklist();
	LinkNode n;
	int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb;
	char *tmpbuf;

	opts[NULLGLOB] = 1;

	/* Put the strin in the lexer buffer and call the lexer to *
	 * get the words we have to expand.                        */
	zleparse = 1;
	lexsave();
	tmpbuf = (char *)halloc(strlen(cc->str) + 5);
	sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */
	inpush(tmpbuf, 0, NULL);
	strinbeg();
	noaliases = 1;
	do {
	    ctxtlex();
	    if (tok == ENDINPUT || tok == LEXERR)
		break;
	    if (!first && tokstr && *tokstr)
		addlinknode(foo, ztrdup(tokstr));
	    first = 0;
	} while (tok != ENDINPUT && tok != LEXERR);
	noaliases = 0;
	strinend();
	inpop();
	errflag = zleparse = 0;
	lexrestore();
	/* Fine, now do full expansion. */
	prefork(foo, 0);
	if (!errflag) {
	    globlist(foo);
	    if (!errflag)
		/* And add the resulting words as matches. */
		for (n = firstnode(foo); n; incnode(n))
		    addmatch((char *)n->dat, NULL);
	}
	opts[NULLGLOB] = ng;
	we = oowe;
	wb = oowb;
    }
    if (cc->hpat) {
	/* We have a pattern to take things from the history. */
	Comp compc = NULL;
	char *e, *h, hpatsav;
	Histent he;
	int i = curhist - 1, n = cc->hnum;

	/* Parse the pattern, if it isn't the null string. */
	if (*(cc->hpat)) {
	    char *thpat = dupstring(cc->hpat);

	    tokenize(thpat);
	    compc = parsereg(thpat);
	}
	/* n holds the number of history line we have to search. */
	if (!n)
	    n = -1;

	/* Now search the history. */
	while (n-- && (he = quietgethist(i--))) {
	    int iwords;
	    for (iwords = 0; iwords < he->nwords; iwords++) {
		h = he->text + he->words[iwords*2];
		e = he->text + he->words[iwords*2+1];
		hpatsav = *e;
		*e = '\0';
		/* We now have a word from the history, ignore it *
		 * if it begins with a quote or `$'.              */
		if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' &&
		    (!compc || domatch(h, compc, 0)))
		    /* Otherwise add it if it was matched. */
		    addmatch(dupstring(h), NULL);
		if (hpatsav)
		    *e = hpatsav;
	    }
	}
    }
    if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS |
			 CC_READONLYS | CC_SPECIALS | CC_PARAMS)))
	/* Add various flavours of parameters. */
	dumphashtable(paramtab, t);
    if ((t = cc->mask & CC_SHFUNCS))
	/* Add shell functions. */
	dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
    if ((t = cc->mask & CC_BUILTINS))
	/* Add builtins. */
	dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
    if ((t = cc->mask & CC_EXTCMDS))
	/* Add external commands */
	dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
    if ((t = cc->mask & CC_RESWDS))
	/* Add reserved words */
	dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
    if ((t = cc->mask & (CC_ALREG | CC_ALGLOB)))
	/* Add the two types of aliases. */
	dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));

    /* If we have no matches, ignore fignore. */
    if (empty(matches)) {
	matches = fmatches;
	firstm = ffirstm;
	shortest = fshortest;
	ab = fab;
	ae = fae;
	shortl = fshortl;
    }

    /* Make an array from the list of matches. */
    makearray(matches);
    PERMALLOC {
	amatches = arrdup(amatches);
	if (firstm)
	    firstm = ztrdup(firstm);
	/* And quote the prefixes/suffixes. */
	if (hasspecial(s)) {
	    zfree(lpre, lpl);
	    zfree(lsuf, lsl);
	    lpre = zalloc(lpl + 1);
	    memcpy(lpre, s, lpl);
	    lpre[lpl] = '\0';
	    lsuf = ztrdup(s + offs);
	    quotepresuf(&lpre);
	    quotepresuf(&lsuf);
	    untokenize(lpre);
	    untokenize(lsuf);
	}
	quotepresuf(&fpre);
	quotepresuf(&fsuf);
	quotepresuf(&ppre);
	quotepresuf(&psuf);
    } LASTALLOC;

    /* Get the explanation string we will have to print. */
    expl = cc->explain;

    remsuffix = (cc->mask & CC_REMOVE);
    ccsuffix = cc->suffix;

    validlist = 1;
    if ((nmatches || expl) && !errflag)
	return 0;

    if ((isf || cc->xor) && !parampre) {
	/* We found no matches, but there is a xor'ed completion: *
	 * fine, so go back and continue with that compctl.       */
	errflag = 0;
	cc = cc->xor;
	isf = 0;
	wb = owb;
	we = owe;
	cs = ocs;
	strcpy((char *)line, (char *)ol);
	offs = oloffs;
	s = dupstring(os);
	free(amatches);
	zsfree(rpre);
	zsfree(rsuf);
	zsfree(lpre);
	zsfree(lsuf);
	zsfree(ppre);
	zsfree(psuf);
	zsfree(fpre);
	zsfree(fsuf);
	zsfree(prpre);
	zsfree(parampre);
	zsfree(qparampre);
	zsfree(firstm);
	goto xorrec;
    }

    /* No matches and xor'ed completion: restore the command line if  *
     * it was alredy quoted, which is the case when s is untokenized. */
    if (untokenized)
	strcpy((char *)line, (char *)ol);
    return 1;
}

/* Invalidate the completion list. */

/**/
void
invalidatelist(void)
{
    if(showinglist == -2)
	listmatches();
    if(validlist) {
	freearray(amatches);
	zsfree(rpre);
	zsfree(rsuf);
	zsfree(lpre);
	zsfree(lsuf);
	zsfree(ppre);
	zsfree(psuf);
	zsfree(fpre);
	zsfree(fsuf);
	zsfree(prpre);
	zsfree(parampre);
	zsfree(qparampre);
	zsfree(firstm);
	if (ccmain != &cc_dummy)
	    freecompctl(ccmain);
    }
    menucmp = showinglist = validlist = 0;
}

/* Get the words from a variable or a compctl -k list. */

/**/
char **
get_user_var(char *nam)
{
    if (!nam)
	return NULL;
    else if (*nam == '(') {
	/* It's a (...) list, not a parameter name. */
	char *ptr, *s, **uarr, **aptr;
	int count = 0, notempty = 0, brk = 0;
	LinkList arrlist = newlinklist();

	ptr = dupstring(nam);
	s = ptr + 1;
	while (*++ptr) {
	    if (*ptr == '\\' && ptr[1])
		chuck(ptr), notempty = 1;
	    else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') {
		if (*ptr == ')')
		    brk++;
		if (notempty) {
		    *ptr = '\0';
		    count++;
		    if (*s == '\n')
			s++;
		    addlinknode(arrlist, s);
		}
		s = ptr + 1;
		notempty = 0;
	    } else {
		notempty = 1;
		if(*ptr == Meta)
		    ptr++;
	    }
	    if (brk)
		break;
	}
	if (!brk || !count)
	    return NULL;
	*ptr = '\0';
	aptr = uarr = (char **)ncalloc(sizeof(char *) * (count + 1));

	while ((*aptr++ = (char *)ugetnode(arrlist)));
	uarr[count] = NULL;
	return uarr;
    } else
	/* Otherwise it should be a parameter name. */
	return getaparam(nam);
}

/* This is strcmp with ignoring backslashes. */

/**/
int
strbpcmp(const void *a, const void *b)
{
    char *aa = *((char **)a), *bb = *((char **)b);

    while (*aa && *bb) {
	if (*aa == '\\')
	    aa++;
	if (*bb == '\\')
	    bb++;
	if (*aa != *bb)
	    return (int)(*aa - *bb);
	if (*aa)
	    aa++;
	if (*bb)
	    bb++;
    }
    return (int)(*aa - *bb);
}

/* Make an array from a linked list */

/**/
void
makearray(LinkList l)
{
    char **ap, **bp, **cp;
    LinkNode nod;

    /* Build an array for the matches. */
    ap = amatches = (char **)ncalloc(((nmatches = countlinknodes(l)) + 1) *
				     sizeof(char *));

    /* And copy them into it. */
    for (nod = firstnode(l); nod; incnode(nod))
	*ap++ = (char *)getdata(nod);
    *ap = NULL;

    /* Now sort the array. */
    qsort((void *) amatches, nmatches, sizeof(char *),
	       (int (*) _((const void *, const void *)))strbpcmp);

    /* And delete the ones that occur more than once. */
    for (ap = cp = amatches; *ap; ap++) {
	*cp++ = *ap;
	for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++);
	ap = bp;
    }
    *cp = NULL;
    nmatches = arrlen(amatches);
}

/* Handle the case were we found more than one match. */

/**/
void
do_ambiguous(void)
{
    int p = (usemenu || ispattern), atend = (cs == we);
    int inv = 0;

    menucmp = 0;

    /* If we have to insert the first match, call do_single().  This is *
     * how REC_EXACT takes effect.  We effectively turn the ambiguous   *
     * completion into an unambiguous one.                              */
    if (shortest && shortl == 0 && isset(RECEXACT) &&
	(usemenu == 0 || unset(AUTOMENU))) {
	do_single(shortest);
	invalidatelist();
	return;
    }
    /* Setting lastambig here means that the completion is ambiguous and *
     * AUTO_MENU might want to start a menu completion next time round,  *
     * but this might be overridden below if we can complete an          *
     * unambiguous prefix.                                               */
    lastambig = 1;
    if(p) {
	/* p is set if we are in a position to start using menu completion *
	 * due to one of the menu completion options, or due to the        *
	 * menu-complete-word command, or due to using GLOB_COMPLETE which *
	 * does menu-style completion regardless of the setting of the     *
	 * normal menu completion options.                                 */
	do_ambig_menu();
    } else {
	/* Sort-of general case: we have an ambiguous completion, and aren't *
	 * starting menu completion or doing anything really weird.  We need *
	 * to insert any unambiguous prefix and suffix, if possible.         */
	complexpect = 0;
	if(ab)
	    inststrlen(firstm, 1, ab);
	if(ae && !atend)
	    inststrlen(firstm + strlen(firstm) - ae, 0, ae);
	if(ab || (ae && !atend))
	    inv = 1;
	/* If the LIST_AMBIGUOUS option (meaning roughly `show a list only *
	 * if the completion is completely ambiguous') is set, and some    *
	 * prefix was inserted, return now, bypassing the list-displaying  *
	 * code.  On the way, invalidate the list and note that we don't   *
	 * want to enter an AUTO_MENU imediately.                          */
	if(isset(LISTAMBIGUOUS) && inv) {
	    invalidatelist();
	    lastambig = 0;
	    return;
	}
    }
    /* At this point, we might want a completion listing.  Show the listing *
     * if it is needed.                                                     */
    if (isset(LISTBEEP))
	feep();
    if (isset(AUTOLIST) && !amenu && !showinglist)
	showinglist = -2;
    if(inv)
	invalidatelist();
}

/* This is a stat that ignores backslashes in the filename.  The `ls' *
 * parameter says if we have to do lstat() or stat().  I think this   *
 * should instead be done by use of a general function to expand a    *
 * filename (stripping backslashes), combined with the actual         *
 * (l)stat().                                                         */

/**/
int
ztat(char *nam, struct stat *buf, int ls)
{
    char b[PATH_MAX], *p;

    for (p = b; p < b + sizeof(b) - 1 && *nam; nam++)
	if (*nam == '\\' && nam[1])
	    *p++ = *++nam;
	else
	    *p++ = *nam;
    *p = '\0';

    return ls ? lstat(b, buf) : stat(b, buf);
}

/* Insert a single match in the command line. */

/**/
void
do_single(char *str)
{
    int ccs, l, insc = 0, inscs = 0;
    char singlec = ' ';

    if (!ccsuffix || !(haswhat & HAS_SUFFIX) || !remsuffix)
	addedsuffix = 0;

    if (!menucur) {
	/* We are currently not in a menu-completion, *
	 * so set the position variables.             */
	menuinsc = 0;
	if (ispattern) {
	    cs = we;
	    menupos = wb;
	} else
	    menupos = cs;
	menuwe = (cs == we);
	if (ccsuffix && !(haswhat & HAS_SUFFIX)) {
	    /* Add a compctl -S suffix if we have one. */
	    if (*ccsuffix) {
		ccs = cs;
		cs = we;
		inststrlen(ccsuffix, menuwe, -1);
		menuend = cs;
		cs = ccs;
		if (remsuffix)
		    /* addedsuffix is used by the key input handling  *
		     * code to find out if it has to delete a suffix. */
		    addedsuffix = strlen(ccsuffix);
	    } else
		menuend = we;

	    haswhat |= HAS_SUFFIX;
	} else
	    menuend = we;
    }
    ccs = cs;
    /* If we are already in a menu-completion or if we have done a *
     * glob completion, we have to delete some of the stuff on the *
     * command line.                                               */
    if (menucur) {
	if (menuinsc) {
	    cs = menuend;
	    foredel(1);
	}
	l = menulen;
    } else if (ispattern)
	l = we - wb;
    else
	l = 0;
    cs = menupos;
    menuinsc = 0;

    if (l) {
	foredel(l);
	if (menuwe)
	    ccs -= l;
	menuend -= l;
    }
    /* And than we insert the new string. */
    inststrlen(str, 1, menulen = strlen(str));

    /* And move the cursor and adjust the menuend variable. */
    if (menuwe)
	cs = ccs + menulen;
    menuend += menulen;

    if (!(haswhat & HAS_SUFFIX)) {
	/* There is no suffix, so we may add one. */
	if (!(haswhat & HAS_MISC) || (parampre && isset(AUTOPARAMSLASH))) {
	    /* If we have only filenames or we completed a parameter name  *
	     * and auto_param_slash is set, lets see if it is a directory. */
	    char *p;
	    struct stat buf;

	    /* Build the path name. */
	    if (ispattern || ic || parampre) {
		int ne = noerrs;

		noerrs = 1;

		if (parampre) {
		    int pl = strlen(parampre);
		    p = (char *) ncalloc(pl + strlen(lpre) + strlen(str) +
					 strlen(lsuf) + 1);
		    sprintf(p, "%s%s%s%s", parampre, lpre, str, lsuf);
		    if (pl && p[pl-1] == Inbrace)
			strcpy(p+pl-1, p+pl);
		}
		else if (ic) {
		    p = (char *) ncalloc(strlen(ppre) + strlen(fpre) + strlen(str) +
					 strlen(fsuf) + strlen(psuf) + 2);
		    sprintf(p, "%c%s%s%s%s%s", ic,
			    ppre, fpre, str, fsuf, psuf);
		}
		else
		    p = dupstring(str);
		parsestr(p);
		if (ic)
		    *p = ic;
		singsub(&p);

		noerrs = ne;
	    } else {
		p = (char *) ncalloc((prpre ? strlen(prpre) : 0) + strlen(fpre) +
				     strlen(str) + strlen(fsuf) + strlen(psuf) + 3);
		sprintf(p, "%s%s%s%s%s",
			(prpre && *prpre) ? prpre : "./", fpre, str,
			fsuf, psuf);
	    }
	    /* And do the stat. */
	    if (!ztat(p, &buf, 0) && (buf.st_mode & S_IFMT) == S_IFDIR) {
		/* It is a directory, so prepare to add *
		 * the slash and set addedsuffix.       */
		singlec = '/';
		if (menuwe || isset(ALWAYSTOEND))
		    addedsuffix = isset(AUTOREMOVESLASH) ? 1 : 0;
	    }
	}
	if (menuend > ll)
	    menuend = ll;
	if (menuend && ((((char)line[menuend - 1]) != singlec) ||
	    (menuend > 1 && singlec == ' ' &&
	      (line[menuend - 2] == '\\' || line[menuend - 2] == STOUC(Meta)))))
	    if (parampre && singlec == '/' && ((char)line[menuend]) == '/')
		addedsuffix = 0;
	    /* Now insert the slash or space if there is none already. */
	    else {
		ccs = cs;
		cs = menuend;
		inststrlen((char *)&singlec, 1, 1);
		insc = 1;
		if (singlec != ' ')
		    menuinsc = 1;
		inscs = cs;
		if (!menuwe)
		    cs = ccs;
	    }
    }
    /* Move to the end of the word if requested. */
    if (isset(ALWAYSTOEND) || menuwe)
	cs = menuend + (!(haswhat & HAS_SUFFIX) && insc);
    if (menucmp && singlec == ' ' && !(haswhat & HAS_SUFFIX)) {
	/* Get rid of the added space if we are doing menucompletion. */
	if (insc) {
	    ccs = cs;
	    cs = inscs;
	    backdel(1);
	    if (ccs != inscs)
	      cs = ccs;
	} else
	    cs--;
    }
}

/* This handles the beginning of menu-completion. */

/**/
void
do_ambig_menu(void)
{
    menucmp = 1;
    menucur = NULL;
    do_single(amatches[0]);
    menucur = amatches;
}

/* Return the length of the common prefix of s and t. */

/**/
int
pfxlen(char *s, char *t)
{
    int i = 0;

    while (*s && *s == *t)
	s++, t++, i++;
    return i;
}

/* Return the length of the common suffix of s and t. */

/**/
int
sfxlen(char *s, char *t)
{
    if (*s && *t) {
	int i = 0;
	char *s2 = s + strlen(s) - 1, *t2 = t + strlen(t) - 1;

	while (s2 >= s && t2 >= t && *s2 == *t2)
	    s2--, t2--, i++;

	return i;
    } else
	return 0;
}

/* This is used to print the explanation string. *
 * It returns the number of lines printed.       */

/**/
int
printfmt(char *fmt, int n, int dopr)
{
    char *p = fmt, nc[DIGBUFSIZE];
    int l = 0, cc = 0;

    for (; *p; p++) {
	/* Handle the `%' stuff (%% == %, %n == <number of matches>). */
	if (*p == '%') {
	    if (*++p) {
		switch (*p) {
		case '%':
		    if (dopr)
			putc('%', shout);
		    cc++;
		    break;
		case 'n':
		    sprintf(nc, "%d", n);
		    if (dopr)
			fprintf(shout, nc);
		    cc += strlen(nc);
		    break;
		}
	    } else
		break;
	} else {
	    cc++;
	    if (*p == '\n') {
		l += 1 + (cc / columns);
		cc = 0;
	    }
	    if (dopr)
		putc(*p, shout);
	}
    }

    return l + (cc / columns);
}

/* List the matches.  Note that the list entries are metafied. */

/**/
void
listmatches(void)
{
    int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0;
    int off, boff, nboff;
    int of = (isset(LISTTYPES) && !(haswhat & HAS_MISC));
    char **arr, **ap, sav;
    int nfpl, nfsl, nlpl, nlsl;
    int listmax = getiparam("LISTMAX");

#ifdef DEBUG
    /* Sanity check */
    if(!validlist) {
	trashzle();
	fputs("BUG: listmatches called with bogus list\n", shout);
	showinglist = 0;
	return;
    }
#endif

    /* Calculate lengths of prefixes/suffixes to be added */
    nfpl = fpre ? niceztrlen(fpre) : 0;
    nfsl = fsuf ? niceztrlen(fsuf) : 0;
    nlpl = lpre ? niceztrlen(lpre) : 0;
    nlsl = lsuf ? niceztrlen(lsuf) : 0;

    /* Calculate the lengths of the prefixes/suffixes we have to ignore
       during printing. */
    off = ispattern && ppre && *ppre &&
	!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? strlen(ppre) : 0;
    boff = ispattern && psuf && *psuf &&
	!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? strlen(psuf) : 0;
    nboff = ispattern && psuf && *psuf &&
	!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? niceztrlen(psuf) : 0;

    /* When called from expandorcompleteprefix, we probably have to
       remove a space now. */
    if (remove_at >= 0) {
	int ocs = cs;

	cs = remove_at;
	deletechar();
	remove_at = -1;
	cs = ocs;
    }

    /* Set the cursor below the prompt. */
    trashzle();
    ct = nmatches;
    showinglist = 0;

    clearflag = (isset(USEZLE) && termok &&
		 (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
	(unset(ALWAYSLASTPROMPT) && zmult != 1);

    arr = amatches;

    /* Calculate the column width, the number of columns and the number
       of lines. */
    for (ap = arr; *ap; ap++)
	if ((cl = niceztrlen(*ap + off) - nboff +
	     (ispattern ? 0 :
	      (!(haswhat & HAS_MISC) ? nfpl + nfsl : nlpl + nlsl))) > longest)
	    longest = cl;
    if (of)
	longest++;

    fw = longest + 2;
    fct = (columns + 1) / fw;
    if (fct == 0) {
	fct = 1;
	colsz = ct;
	up = colsz + nlnct - clearflag;
	for (ap = arr; *ap; ap++)
	    up += (niceztrlen(*ap + off) - nboff + of +
		(ispattern ? 0 :
		(!(haswhat & HAS_MISC) ? nfpl + nfsl : nlpl + nlsl))) / columns;
    } else {
	colsz = (ct + fct - 1) / fct;
	up = colsz + nlnct - clearflag + (ct == 0);
    }

    /* Print the explanation string, if any. */
    if (expl) {
	xup = printfmt(expl, ct, 1) + 1;
	putc('\n', shout);
	up += xup;
    }

    /* Maybe we have to ask if the user wants to see the list. */
    if ((listmax && ct > listmax) || (!listmax && up >= lines)) {
	int qup;
	setterm();
	qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1);
	fflush(shout);
	if (getzlequery() != 'y') {
	    if (clearflag) {
		putc('\r', shout);
		tcmultout(TCUP, TCMULTUP, qup);
		if (tccan(TCCLEAREOD))
		    tcout(TCCLEAREOD);
		tcmultout(TCUP, TCMULTUP, nlnct + xup);
	    } else
		putc('\n', shout);
	    return;
	}
	if (clearflag) {
	    putc('\r', shout);
	    tcmultout(TCUP, TCMULTUP, qup);
	    if (tccan(TCCLEAREOD))
		tcout(TCCLEAREOD);
	} else
	    putc('\n', shout);
	settyinfo(&shttyinfo);
    }

    /* Now print the matches. */
    for (t1 = 0; t1 != colsz; t1++) {
	ap = arr + t1;
	if (of) {
	    /* We have to print the file types. */
	    while (*ap) {
		int t2;
		char *pb;
		struct stat buf;

		/* Build the path name for the stat. */
		if (ispattern) {
		    int cut = strlen(*ap) - boff;

		    sav = ap[0][cut];
		    ap[0][cut] = '\0';
		    nicezputs(*ap + off, shout);
		    t2 = niceztrlen(*ap + off);
		    ap[0][cut] = sav;
		    pb = *ap;
		} else {
		    nicezputs(fpre, shout);
		    nicezputs(*ap, shout);
		    nicezputs(fsuf, shout);
		    t2 = nfpl + niceztrlen(*ap) + nfsl;
		    pb = (char *) ncalloc((prpre ? strlen(prpre) : 0) + 3 +
					  strlen(fpre) + strlen(*ap) + strlen(fsuf));
		    sprintf(pb, "%s%s%s%s",
			    (prpre && *prpre) ? prpre : "./", fpre, *ap, fsuf);
		}
		if (ztat(pb, &buf, 1))
		    putc(' ', shout);
		else
		    /* Print the file type character. */
		    putc(file_type(buf.st_mode), shout);
		for (t0 = colsz; t0 && *ap; t0--, ap++);
		if (*ap)
		    /* And add spaces to make the columns aligned. */
		    for (++t2; t2 < fw; t2++)
			putc(' ', shout);
	    }
	} else
	    while (*ap) {
		int t2;

		if (ispattern) {
		    int cut = strlen(*ap) - boff;

		    sav = ap[0][cut];
		    ap[0][cut] = '\0';
		    nicezputs(*ap + off, shout);
		    t2 = niceztrlen(*ap + off);
		    ap[0][cut] = sav;
		} else if (!(haswhat & HAS_MISC)) {
		    nicezputs(fpre, shout);
		    nicezputs(*ap, shout);
		    nicezputs(fsuf, shout);
		    t2 = nfpl + niceztrlen(*ap) + nfsl;
		} else {
		    nicezputs(lpre, shout);
		    nicezputs(*ap, shout);
		    nicezputs(lsuf, shout);
		    t2 = nlpl + niceztrlen(*ap) + nlsl;
		}
		for (t0 = colsz; t0 && *ap; t0--, ap++);
		if (*ap)
		    for (; t2 < fw; t2++)
			putc(' ', shout);
	    }
	if (t1 != colsz - 1 || !clearflag)
	    putc('\n', shout);
    }
    if (clearflag)
	/* Move the cursor up to the prompt, if always_last_prompt *
	 * is set and all that...                                  */
	if (up < lines) {
	    tcmultout(TCUP, TCMULTUP, up);
	    showinglist = -1;
	} else
	    clearflag = 0, putc('\n', shout);
}

/* This is used to print expansions. */

/**/
void
listlist(LinkList l)
{
    int hw = haswhat, ip = ispattern;
    char *lp = lpre, *ls = lsuf;
    int nm = nmatches, vl = validlist;
    char **am = amatches;
    char *ex = expl;

    haswhat = HAS_MISC;
    ispattern = 0;
    validlist = 1;
    lpre = lsuf = "";
    expl = NULL;

    makearray(l);
    listmatches();
    showinglist = 0;

    expl = ex;
    amatches = am;
    nmatches = nm;
    validlist = vl;
    lpre = lp;
    lsuf = ls;
    ispattern = ip;
    haswhat = hw;
}

/* Expand the history references. */

/**/
int
doexpandhist(void)
{
    unsigned char *ol;
    int oll, ocs, ne = noerrs, err;

    DPUTS(useheap, "BUG: useheap in doexpandhist()");
    HEAPALLOC {
	pushheap();
	metafy_line();
	oll = ll;
	ocs = cs;
	ol = (unsigned char *)dupstring((char *)line);
	expanding = 1;
	excs = cs;
	ll = cs = 0;
	lexsave();
	/* We push ol as it will remain unchanged */
	inpush((char *) ol, 0, NULL);
	strinbeg();
	noaliases = 1;
	noerrs = 1;
	exlast = inbufct;
	do {
	    ctxtlex();
	} while (tok != ENDINPUT && tok != LEXERR);
	stophist = 2;
	while (!lexstop)
	    hgetc();
	/* We have to save errflags because it's reset in lexrestore. Since  *
	 * noerrs was set to 1 errflag is true if there was a habort() which *
	 * means that the expanded string is unusable.                       */
	err = errflag;
	noerrs = ne;
	noaliases = 0;
	strinend();
	inpop();
	zleparse = 0;
	lexrestore();
	expanding = 0;

	if (!err) {
	    cs = excs;
	    if (strcmp((char *)line, (char *)ol)) {
		unmetafy_line();
		/* For vi mode -- reset the beginning-of-insertion pointer   *
		 * to the beginning of the line.  This seems a little silly, *
		 * if we are, for example, expanding "exec !!".              */
		if (viinsbegin > findbol())
		    viinsbegin = findbol();
		popheap();
		LASTALLOC_RETURN 1;
	    }
	}

	strcpy((char *)line, (char *)ol);
	ll = oll;
	cs = ocs;
	unmetafy_line();

	popheap();
    } LASTALLOC;
    return 0;
}

/**/
void
magicspace(void)
{
    c = ' ';
    selfinsert();
    doexpandhist();
}

/**/
void
expandhistory(void)
{
    if (!doexpandhist())
	feep();
}

static int cmdwb, cmdwe;

/**/
char *
getcurcmd(void)
{
    int curlincmd;
    char *s = NULL;

    DPUTS(useheap, "BUG: useheap in getcurcmd()");
    HEAPALLOC {
	zleparse = 2;
	lexsave();
	metafy_line();
	inpush(dupstrspace((char *) line), 0, NULL);
	unmetafy_line();
	strinbeg();
	pushheap();
	do {
	    curlincmd = incmdpos;
	    ctxtlex();
	    if (tok == ENDINPUT || tok == LEXERR)
		break;
	    if (tok == STRING && curlincmd) {
		zsfree(s);
		s = ztrdup(tokstr);
		cmdwb = ll - wordbeg;
		cmdwe = ll + 1 - inbufct;
	    }
	}
	while (tok != ENDINPUT && tok != LEXERR && zleparse);
	popheap();
	strinend();
	inpop();
	errflag = zleparse = 0;
	lexrestore();
    } LASTALLOC;
    return s;
}

/**/
void
processcmd(void)
{
    char *s, *t;

    s = getcurcmd();
    if (!s) {
	feep();
	return;
    }
    t = ZLEGETCMD(bindk)->name;
    zmult = 1;
    pushline();
    inststr(t);
    inststr(" ");
    untokenize(s);
    HEAPALLOC {
	inststr(quotename(s, NULL, NULL, NULL));
    } LASTALLOC;
    zsfree(s);
    done = 1;
}

/**/
void
expandcmdpath(void)
{
    int oldcs = cs, na = noaliases;
    char *s, *str;

    noaliases = 1;
    s = getcurcmd();
    noaliases = na;
    if (!s || cmdwb < 0 || cmdwe < cmdwb) {
	feep();
	return;
    }
    str = findcmd(s);
    zsfree(s);
    if (!str) {
	feep();
	return;
    }
    cs = cmdwb;
    foredel(cmdwe - cmdwb);
    spaceinline(strlen(str));
    strncpy((char *)line + cs, str, strlen(str));
    cs = oldcs;
    if (cs >= cmdwe - 1)
	cs += cmdwe - cmdwb + strlen(str);
    if (cs > ll)
	cs = ll;
    zsfree(str);
}

/* Extra function added by AR Iano-Fletcher. */
/* This is a expand/complete in the vein of wash. */

/**/
void
expandorcompleteprefix(void)
{
    /* global c is the current character typed. */
    int csafe = c;

    /* insert a space and backspace. */
    c = ' ';
    selfinsert();		/* insert the extra character */
    forwardchar();		/* move towards beginning */
    
    remove_at = cs;

    /* do the expansion/completion. */
    c = csafe;
    zmult = 1;
    expandorcomplete();		/* complete. */
    zmult = -1;

    /* remove the inserted space. */
    if (remove_at >= 0) {
	backwardchar();		/* move towards ends */
	deletechar();		/* delete the added space. */
    }
    remove_at = -1;
}
