/*
 * $Id: zle_tricky.c,v 1.46 1995/05/30 06:21:41 coleman Exp coleman $
 *
 * zle_tricky.c - expansion and completion
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1995 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 its documentation 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.
 *
 */

#define ZLE
#include "zsh.h"

#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

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

extern char *glob_pre, *glob_suf;

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

static int we, wb, usemenu, useglob;

/* A pointer to the array of completions used for menu-completion and a
   pointer to the current one in this array (the one that was put in the
   command line last). */

static char **menuarr, **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 cann add suffizes, 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 Lklist 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;

/* 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 build
   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 prefizes/suffizes:
   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;


/* Skip over a balanced pair of parenthesis. */

static int
fwskipparens(char inpar, char outpar, char **s)
{
    int level;

    if (**s != inpar)
	return -1;

    for (level = 1; *++*s && level;)
	if (**s == inpar)
	   ++level;
	else if (**s == outpar)
	   --level;

   return level;
}

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

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

    if (!isfirstln)
	return 0;

    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
menucompleteword(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) {
	menucompleteword();	/* better than just feep'ing, pem */
	return;
    }
    if (menucur == menuarr)
	menucur = menuarr + arrlen(menuarr) - 1;
    else
	menucur--;
    complexpect = menuce;
    do_single(*menucur);
}

/*
 * 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)
	inststrlen(lpre, 1, -1);
    if (lsuf)
	inststrlen(lsuf, 0, -1);
    menupos = cs;
    menuend = cs + (lsuf ? strlen(lsuf) : 0);
    menulen = 0;
    menuwe = 1;
    menucompleteword();
}

/* the first two are flags saying if we are completing in the command
   position or in a redirection.  inwhat says what exactly we are in (its
   value is one of the IN_* things below). */

static int lincmd, linredir, inwhat;

/* Nothing special. */

#define IN_NOTHING 0

/* In command position. */

#define IN_CMD     1

/* In a mathematical environment. */

#define IN_MATH    2

/* In a condition. */

#define IN_COND    3

/* In a parameter assignment (e.g. `foo=bar'). */

#define IN_ENV     4

/* Non-zero if the last completion done was ambiguous (used to find out 
   if automenu should start). */

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)
		fwskipparens(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 || *str == Star ||
		*str == Bar || *str == Quest ||
		!fwskipparens(Inbrack, Outbrack, &str) ||
		!fwskipparens(Inang,   Outang,   &str) ||
		(!isset(IGNOREBRACES) &&
		 !fwskipparens(Inbrace, Outbrace, &str)) ||
		(*str == Inpar && str[1] == ':' &&
		 !fwskipparens(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;

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

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

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

    /* Expand history references before starting completion. */

    if (doexpandhist())
	return;

    /* 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. */

    if (!isfirstln && chline != NULL) {
	char *p, *q;

	ol = dupstring((char *)line);
	/* Make sure that chline is zero-terminated. */
	*hptr = '\0';
	/* Calculate the length of chline. */
	chl = strlen(chline);
	/* Now add 2 to this length for every bangchar in chline.  Below we
	   put two backslashes before each bangchar to be sure that the lexer
	   doesn't expand these history references (that would make the line
	   positions reported by the lexer worthless). */
	for (p = chline; *p; p++)
	    if (*p == (char)bangchar)
		chl+=2;
	/* Make sure that the line buffer is big enough. */
	sizeline(ll + chl + 1);
	/* Now copy chline (adding the backslashes if needed). */
	for (p = chline, q = (char *) line; *p; p++) {
	    if (*p == (char)bangchar) {
		*q++ = '\\';
		*q++ = '\\';
	    }
	    *q++ = *p;
	}
	/* Put a zero byte after it and append the original line contents. */
	*q = '\0';
	strcat((char *)line, ol);
	/* Finally remove quoted newlines and replace non-quoted newlines with
	   semicolons.  This second thing isn't really correct (e.g. if
	   the command separated by the newlines were actually connected with
	   a pipe, but the effect in the completion code is the same). */
	for (p = (char *)line; *p; p++)
	    if (*p == '\n')
		if (p > (char *) line && p[-1] == '\\')
		    p[-1] = *p = ' ';
		else
		    *p = ';';
	/* Now make sure that the cursor position variable and the
	   line length stored in ll are correct. */
	cs += chl;
	ll = strlen((char *)line);
    }
    else
	ol = NULL;
    inwhat = IN_NOTHING;
    qword = NULL;
    /* Get the word to complete. */
    noerrs = 1;
    s = get_comp_string();
    noerrs = ne;
    /* When completing between words, the contents of wb and we may be
       garbled. */
    if (we > wb && inblank(line[we - 1]))
	we--;
    /* 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 > wb)
	viinsbegin = wb;
    /* Restore the lexer state. */
    lexrestore();
    /* If we added chline to the line buffer, reset the original contents. */
    if (ol) {
	strcpy((char *) line, ol);
	ll = strlen((char *) line);
	cs -= chl;
	wb -= chl;
	we -= chl;
	if (wb < 0 || we < 0)
	    return;
    }
    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 (gethnode(q, cmdnamtab) || 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) &&
				    ISEXCMD(((Cmdnam) hn)->flags) &&
				    (fc = findcmd(hn->nam))) {
				    zsfree(fc);
				    n++;
				}
				if (n == 2)
				    break;
			    }

			if (n == 1)
			    lst = COMP_EXPAND;
		    }
	    } else /* if (*q != Tilde) Why was this test here? */
		do {
		    /* An equivalent check for words not starting with `~'.
		       First 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 (! fwskipparens(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 = &s;
	    char *q = s;

	    for (; *q; q++)
		if (INULL(*q))
		    *q = Nularg;
	    untokenize(s);
	    cs = wb;
	    foredel(we - wb);
	    /* call the real spell checker, ash@aaii.oz.zu */
	    spckword(x, NULL, NULL, !lincmd, 0);
	    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;

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

/* Do menu completion. */

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

/* 1 if x added to complete in a blank between words */
int addedx;

/* 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] || inblank(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;
    }
}

/* 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 than in the parser, and than...). */

/**/
char *
get_comp_string(void)
{
    int t0, tt0, i, j, k, l, cp, rd, inc = 0, opb = -1, sl, im = 0, ocs;
    char *s = NULL, *linptr, *tmp, *p, *tt = NULL, *q = NULL;

    /* This global flag is used to signal the lexer code if it should 
       expand aliases or not. */

    noaliases = isset(COMPLETEALIASES);

    instring = 0;

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

    for (i = j = k = l = 0, q = p = (char *)line; p < (char *)line + cs; p++)
	if (*p == '`' && !(k & 1))
	    i++, q = p;
	else if (*p == '\"' && !(k & 1))
	    j++;
	else if (*p == '\'' && !(j & 1))
	    k++;
	else if (*p == '(' && p[1] == '(')
	    l = 1 + k + j, p++;
	else if (*p == ')' && p[1] == ')') {
	    if (l == 1 + k + j)
		l = 0;
	    p++;
	}
	else if (*p == '\\' && p[1] && !((k & 1) && p[1] == '\''))
	    p++;
    if ((i & 1) || (j & 1) || (k & 1) || l) {
	/* Yes, we are in a string. */
	instring = (j & 1) ? 2 : (k & 1);
	/* im is non-zero if we are in a mathematical expression, set it if
	   we are inside a ((...)). */
	if (l == 1 || (l > 1 && ((j & 1) || (k & 1))))
	    im = 2;
	/* Now add the `x', see above. */
	addx(&tmp);
	if (!addedx) {
	    /* If no `x' was added, set the tmp variable anyway. */
	    tmp = (char *)line;
	    if (i & 1) {
		line = (unsigned char *)dupstring((char *)line);
		strcpy((char *)line, (char *)tmp);
	    } else
		line = (unsigned char *)dupstring((char *)line);
	}
	/* Now remove the quotes and the `(('s and `))'s.
	   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. */
	for (p = (char *)line; *p; p++)
	    if (*p == '"' || *p == '\'')
		*p = ' ';
	    else if ((*p == '(' && p[1] == '(') || (*p == ')' && p[1] == ')'))
		*p = p[1] = ' ', p++;
    } else
	/* We are not in a string, add the `x' */
	addx(&tmp);
    if (i & 1) {
	/* We are inside a `...`.  The lexer code treats this differently
	   than $(...) if the second backtick is missing (which is often 
	   the case when completing).  In fact with just one backtick this
	   completely fails.  But it works with `$(' (even if the closing
	   `)' is missing).  So we replace the backtick with `$(' and set
	   a flag (inc) so that we can adjust the positions we get from the
	   lexer. */
	sizeline(ll + 1);
	if (tmp)
	    q = (char *)line + (q - tmp);
	for (p = q + strlen(q); p > q; p--)
	    p[1] = *p;
	*q++ = '$';
	*q = '(';
	ll++;
	cs++;
	inc = 1;
    }
    linptr = (char *)line;
    q = NULL;
  start:
    /* Now set up the lexer and start it. */
    lincmd = incmdpos;
    linredir = inredir;
    cmdstr = NULL;
    zleparse = 1;
    clwpos = -1;
    lexsave();
    hungets(" ");		/* KLUDGE! */
    hungets(UTOSCP(linptr));
    strinbeg();
    pushheap();
    heapalloc();
    i = tt0 = cp = rd = 0;

    /* This loop and what comes after it is the real magic.
       I would give my right arm if someone could explain to me in less than
       100 lines what all this does exactly. */
    do {
	lincmd = incmdpos;
	linredir = inredir;
	/* Get the next token. */
	ctxtlex();
	/* As said above, im will be used to find out if we are in a
	   mathematical environment (or a condition). */
	if (tok == DINBRACK)
	    im |= 1;
	else if (tok == DOUTBRACK)
	    im &= ~1;
	else if (tok == DINPAR)
	    im |= 2;
	else if (tok == DOUTPAR)
	    im &= ~2;

	/* We reached the end. */
	if (tok == ENDINPUT)
	    break;
	if (tok == BAR || tok == AMPER || tok == BARAMP ||
	    tok == DBAR || tok == DAMPER)
	    /* 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;
	    else
		/* 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). */
	    cmdstr = dupstring(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 (!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) {
	    clwords = (char **)realloc(clwords,
				       (clwsize *= 2) * sizeof(char *));
	    memset((void *) (clwords + i), 0, (clwsize / 2) * sizeof(char *));
	}
	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. */
	while (sl && clwords[i][sl - 1] == ' ')
	    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;

    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; *s && *s != '='; s++, wb++);
	if (*s) {
	    s++;
	    wb++;
	    t0 = STRING;
	    s = ztrdup(s);
	    inwhat = IN_ENV;
	}
	lincmd = 1;
    }
    hflush();
    strinend();
    errflag = zleparse = 0;
    if (addedx)
	wb++;
    if (we > ll)
	we = ll;
    if (im & 2) {
	if (tokstr) {
	    *tokstr = ' ';
	    if (addedx)
		tokstr[cs - wb + 1] = '\0';
	    s = ztrdup(tokstr);
	}
	if (tmp)
	    line = (unsigned char *)tmp;
	goto check;
    }
    if (t0 == LEXERR && parbegin != -1) {
	/* We are somwhere inside parenthesis (e.g in `foo=(bar baz _ )'). */ 
	if (opb == parbegin) {
	    if (inc)
		cs--, ll--, inc = 0;
	    if (tmp)
		line = (unsigned char *)tmp;
	    feep();
	    noaliases = 0;
	    permalloc();
	    return NULL;
	}
	opb = parbegin;
	linptr += ll + 1 - parbegin;
	popheap();
	permalloc();
	lexrestore();
	if (inc)
	    cs--, ll--, inc = 0;
	goto start;
    }
    if (inc)
	cs--, ll--, inc = 0;
    tt = (char *)line;
    if (tmp)
	line = (unsigned char *)tmp;
    if (t0 != STRING) {
	if (tmp) {
	    tmp = NULL;
	    linptr = (char *)line;
	    popheap();
	    permalloc();
	    lexrestore();
	    goto start;
	}
	feep();
	noaliases = 0;
	permalloc();
	return NULL;
    } else if (clwords[clwpos] && clwords[clwpos][0]) {
	for (p = clwords[clwpos]; *p; p++)
	    if ((*p == String || *p == Equals ||
		 *p == Inang || *p == Outang) &&
		p[1] == Inpar)
		break;
	if (*p && cs > p - clwords[clwpos]) {
	    for (q = clwords[clwpos] + we - wb; q > p; q--)
		if (*q == Outpar)
		    break;
	    if (q > p && cs - wb <= q - clwords[clwpos]) {
		if (!tmp) {
		    tmp = (char *)line;
		    tt = dupstring((char *)line);
		}
		line = (unsigned char *)tt;
		line[wb + q - clwords[clwpos] + addedx] = '\0';
		linptr = (char *)line;
		ll = strlen((char *)line) - 1;
		popheap();
		permalloc();
		lexrestore();
		q = NULL;
		goto start;
	    }
	    q = NULL;
	}
    }
  check:

    if (q)
	wb--, we--;

    noaliases = 0;

    inwhat = (im & 1) ? IN_COND : (inwhat == IN_ENV ? IN_ENV : IN_NOTHING);
    im &= ~1;

    /* Check if we are in a mathematical expression (inside $[...]). */
    if (!im) {
	/* See if there is a `[' to the left. */
	for (tt = s + cs - wb; tt > s && *tt != Inbrack; tt--);
	if (*tt == Inbrack && tt > s) {
	    if (tt[-1] == String)
		im = 1;
	    else if ((p = tt - 1) > s) {
		/* We have a `[', so this may be a subscript expression. */
		if (*p == Quest || *p == Star ||
		    *p == String || *p == Qstring ||
		    *p == '?' || *p == '*' || *p == '$' ||
		    *p == '-' || *p == '!' || *p == '@')
		    p--;
		else
		    while (p > s && iident(*p))
			p--;
		if (p > s && (*p == '#' || *p == Pound || *p == '+'))
		    p--;
		if (p < tt - 1)
		    for (; p > s; p--)
			if (*p != '^' && *p != Hat &&
			    *p != '=' && *p != Equals &&
			    *p != '~' && *p != Tilde)
			    break;
		if (*p == String ||
		    (*p == Inbrace && p > s && p[-1] == String))
		    /* We finally found the `$', so it is a subscript. */
		    im = 1;
		else if (*p == Outpar) {
		    /* We found a ')', so it may be a `${(...)'. */
		    for (i = 1, p--; p >= s && i; p--)
			if (*p == Outpar)
			    i++;
			else if (*p == Inpar)
			    i--;
		    if (!i && p > s && *p == Inbrace && p[-1] == String)
			/* Yes, there is a `$'. */
			im = 1;
		}
	    }
	    if (im) {
		/* But if we are outside the `]'... */
		if (! (p = tt, fwskipparens(Inbrack, Outbrack, &p)) &&
		    cs - wb >= p - s)
		    im = 0;
	    }
	}
    }
    if (im) {
	/* 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. */
	inwhat = IN_MATH;
	for (p = s + cs - wb - 1; p >= s && (ialnum(*p) || *p == '_'); p--);
	if (++p > s) {
	    strcpy(s, p);
	    wb += p - s - ((im & 2) ? 1 : 0);
	}
	for (p = s; *p && (ialnum(*p) || *p == '_'); p++);
	*p = '\0';
	we = wb + p - s;
    }
    /* This variable will hold the current word in quoted form. */
    qword = ztrdup(s);
    /* While buildin the quoted form, we also clean up the command line. */
    for (p = s, tt = qword, i = wb; *p; p++, tt++, i++)
	if (INULL(*p)) {
	    if (p[1] || *p != Bnull) {
		if (*p == Bnull)
		    *tt = '\\';
		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--);
	}
    if (unset(NOBANGHIST)) {
	/* In qword bangchars (normally `!') are quoted if nobanghist is
	   unset. */
	q = tt = ncalloc(2 * strlen(qword) + 1);
	for (p = qword; *p; p++) {
	    if (*p == (char)bangchar)
		*q++ = '\\';
	    *q++ = *p;
	}
	*q = '\0';
	zsfree(qword);
	qword = ztrdup(tt);
    }
    ll = strlen((char *)line);
    permalloc();

    return (char *)s;
}

/* Expand the current word. */

/**/
void
doexpansion(char *s, int lst, int olst, int explincmd)
{
    Lklist vl = newlist();
    char *ss;
    int ng = opts[NULLGLOB];

    opts[NULLGLOB] = OPT_SET;
    lexsave();
    s = dupstring(s);
    pushheap();
    addnode(vl, s);
    prefork(vl, 0);
    if (errflag)
	goto end;
    postfork(vl, (lst == COMP_LIST_EXPAND) || (lst == COMP_EXPAND));
    if (errflag)
	goto end;
    if (empty(vl) || !*(char *)peekfirst(vl)) {
	if (!noerrs)
	    feep();
	goto end;
    }
    if (lst == COMP_LIST_EXPAND) {
	/* Only the list of exapansions was requested. */
	listlist(vl);
	goto end;
    } else if (peekfirst(vl) == (void *) s ||
	       (!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);
	else
	    feep();
	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 (full(vl)) {
	    spaceinline(1);
	    line[cs++] = ' ';
	}
#endif
	if (olst != COMP_EXPAND_COMPLETE || full(vl) ||
	    (cs && line[cs-1] != '/')) {
	    spaceinline(1);
	    line[cs++] = ' ';
	}
    }
  end:
    opts[NULLGLOB] = ng;
    popheap();
    lexrestore();
}

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

/**/
void
gotword(char *s)
{
    we = ll + 1 - inbufct;
    if (cs <= we) {
	wb = ll - wordbeg;
	zleparse = 0;
	/* major hack ahead */
	if (wb > 0 && line[wb] == bangchar && line[wb - 1] == '\\')
	    wb--;
    }
}

/* 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. */

/**/
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 && *te == u)
	    *pl = v - tt, sf |= 2;
	if (ispecial(*u) &&
	    (!instring || (!isset(NOBANGHIST) &&
			   *u == (char)bangchar) ||
	     (instring == 2 &&
	      (*u == '$' || *u == '`' || *u == '\"')) ||
	     (instring == 1 && *u == '\'')))
	    if (*u == '\n' || (instring == 1 && *u == '\'')) {
		*v++ = '\'';
		if (*u == '\'')
		    *v++ = '\\';
		*v++ = *u;
		*v++ = '\'';
		continue;
	    } else
		*v++ = '\\';
	*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 (te && (sf & 2))
	*te += tt - buf;

    if (e && *e == u)
	*e = v;
    if (te && *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 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;
    char sav = 0, *e = NULL, *tt, *te, *fc, **fm;
    Comp cp = patcomp;
    Param pm;
    Lklist l = matches;

    if (!addwhat)
	test = 1;
    /* addwhat: -5 is for files, -6 is for glob expansions, -8 is for
       executable files (e.g. command paths), -7 is for command names (from
       cmdnamtab) and -1 is for other file specifications (things with
       `~' of `=' at the beginning, ...). */
    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 && ISEXCMD(((Cmdnam) t)->flags) &&
		!(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 == -2 ||
	       (addwhat == -3 && !(((Cmdnam) t)->flags & DISABLED)) ||
	       (addwhat == -4 && (pm = (Param) t) &&
		(pmtype(pm) == PMFLAG_s) &&
		(tt = pm->gets.cfn(pm)) && *tt == '/') ||
	       (addwhat > 0 &&
		(((addwhat & CC_ARRAYS) && (((Param) t)->flags & PMFLAG_A)) ||
		 ((addwhat & CC_INTVARS) && (((Param) t)->flags & PMFLAG_i)) ||
		 ((addwhat & CC_ENVVARS) && (((Param) t)->flags & PMFLAG_x)) ||
		 ((addwhat & CC_SCALARS) &&
		  !(((Param) t)->flags & (PMFLAG_A | PMFLAG_i))) ||
		 ((addwhat & CC_READONLYS) &&
		  (((Param) t)->flags & PMFLAG_r)) ||
		 ((addwhat & CC_SPECIALS) &&
		  (((Param) t)->flags & PMFLAG_SPECIAL)) ||
		 ((addwhat & CC_PARAMS) && !(((Param) t)->flags & PMFLAG_x)) ||
		 ((addwhat & CC_FUNCS) && (((Cmdnam) t)->flags & SHFUNC)) ||
		 ((addwhat & CC_BUILTINS) && (((Cmdnam) t)->flags & BUILTIN) &&
		  !(((Cmdnam) t)->flags & EXCMD)) ||
		 ((addwhat & CC_DISCMDS) &&
		  (((Cmdnam) t)->flags & DISABLED)) ||
		 ((addwhat & CC_EXCMDS) && (((Cmdnam) t)->flags & EXCMD)) ||
		 ((addwhat & CC_ALREG) && (((Alias) t)->cmd) == 1) ||
		 ((addwhat & CC_ALGLOB) && !(((Alias) t)->cmd))))) {
	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 (test)
	    haswhat |= HAS_MISC;
    }
    if (!test)
	return;

    t = s += (ispattern ? 0 : pl);
    e += t - s;
    s = t;

    if (ispattern)
	e = NULL, sav = '\0';
    else {
	if ((sav = *e)) {
	    *e = '\0';
	    t = dupstring(t);
	}
    }

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

    if (!ispattern && *fm) {
	if ((test = pfxlen(*fm, s)) < ab)
	    *bp = test;
	if ((test = sfxlen(*fm, s)) < ae)
	    *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. */
    addnode(l, t);
    if (!*fm) {
	*bp = *ep = 10000;
	*fm = t;
	if (l == fmatches)
	    fshortl = 100000;
	else
	    shortl = 100000;
    }
    if (!ispattern && (sl = strlen(t)) < shortl)
	if (l == fmatches)
	    fshortl = sl, fshortest = t;
	else
	    shortl = sl, shortest = t;
    if (sav)
	*e = sav;
}

#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

/**/
void
maketildelist(void)
{
    int i;

#ifdef HAVE_NIS
    char domain[YPMAXDOMAIN];
    struct ypall_callback cb;
    dopestring data;
    FILE *pwf;
    char buf[BUFSIZ], *p;
    int skipping;

    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 ((*)()))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);*/
    }
    /* 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
# ifdef CACHE_USERNAMES
    addusernames();
# else
    setpwent();

    while ((tmppwd = getpwent()) != NULL && !errflag)
	addmatch(dupstring(tmppwd->pw_name), NULL);
    endpwent();

    if (addwhat != -1)
	return;
# endif
#endif

    for (i = 0; i < userdirct; i++)
	if (addwhat == -1 || namdirs[i].homedir)
	    addmatch(namdirs[i].name, NULL);
}

/* 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 *str, int cpatindex, char *cpat, int class)
{
    char *s, *t, *p;
    int d = 0;

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

    cpat = rembslash(cpat);

    str = ztrdup(str);
    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);
	    return (int)(t - str);
	}
    }
    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 = rb = 0;
    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) gethnode(cmd, compctltab)) &&
		     (compc = ret = ccp->cc)) ||
 		    ((s = dupstring(cmd)) && remlpaths(&s) &&
		     (ccp = (Compctlp) gethnode(s, compctltab)) &&
		     (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++;
	    incmd = 0;
	    ops = clwords[ra - 1];
	    clwords[ra - 1] = cmdstr = ret->subcmd;
	    clwords += ra - 1;
	} else {
	    cmdstr = clwords[ra];
	    incmd = !clwpos;
	    clwords += ra;
	    clwnum--;
	}
	*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. */

/**/
void
dumphtable(Hashtab ht, int what)
{
    int t0;
    struct hashnode *hn;

    addwhat = what;

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

}

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

/**/
char *
getreal(char *str)
{
    Lklist l = newlist();
    int ne = noerrs;

    noerrs = 1;
    addnode(l, dupstring(str));
    prefork(l, 0);
    if (!errflag) {
	postfork(l, 0);
	if (!errflag && full(l)) {
	    noerrs = ne;
	    return ztrdup(peekfirst(l));
	}
    }
    errflag = 0;
    noerrs = ne;

    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 dirent *de;
    struct stat buf;
    char *n, p[PATH_MAX], *q = NULL, *e;
    Lklist l = NULL;
    int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat;

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

    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 = newlist();
	/* 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 ((de = readdir(d)) && !errflag) {
	    n = de->d_name;
	    /* 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... */
			    addnode(l, p);
			    postfork(l, 1);
			    /* And see if that produced a filename. */
			    tt = full(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)
{
    Compctl cc = NULL;
    int offs, t, sf1, sf2, compadd, ooffs, isf = 1;
    char *p, *sd = NULL, sav, *tt, *s1, *s2, *os = NULL;
    int owe = we, owb = wb, ocs = cs, delit;
    unsigned char *ol = NULL;

  xorrec:

    /* Go to the end of the word if complete_in_word is not set. */
    if (unset(COMPLETEINWORD) && cs != we)
	cs = we;
    /* offs holds the distance between the cursor and the beginning of the
       word. */
    if ((offs = cs - wb) > (t = strlen(s)))
	offs = t;

    ispattern = haswhat = lastambig = 0;
    patcomp = filecomp = NULL;
    menucur = NULL;
    shortest = NULL;

    /* Free the old values of the prefizes and suffizes. */
    zsfree(rpre);
    zsfree(rsuf);
    zsfree(lpre);
    zsfree(lsuf);
    zsfree(ppre);
    zsfree(psuf);
    zsfree(prpre);
    zsfree(fpre);
    zsfree(fsuf);
    zsfree(parampre);
    zsfree(qparampre);

    /* And set them to zero. */
    rpre = rsuf = lpre = lsuf = ppre = psuf = prpre =
	fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL;

    /* If it is the first time we reach this point, prepare the heap
       allocation and os and ol which hold copies of the word and the command
       line, so that we can easily reset it later. */
    if (!cc) {
	heapalloc();
	pushheap();
	os = dupstring(s);
	ol = (unsigned char *)dupstring((char *)line);
    }
    matches = newlist();
    fmatches = newlist();

    /* 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... */
	feep();
	goto compend;
    }
    /* 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;
    }
    /* 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 = (*s == '~' ? Tilde : (*s == '=' ? Equals : '\0'));
    /* Re-adjust the offs variable after all the changes we might have done
       to s, cs, and wb. */
    if ((offs = cs - wb) > (t = strlen(s)))
	offs = t;

    /* 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 = fwskipparens(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. */
	    wb += qparprelen;
	    offs -= b - s;
	    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. */

    sav = s[offs];
    s[offs] = '\0';
    p = quotename(s, NULL, NULL, NULL);
    if (strcmp(p, s) && !strpfx(p, qword)) {
	int l1, l2;

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

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

    /* First check for ~... and =.... */

    if (ic) {
	/* It is one of these, so search backward to see if there is a slash.
	   If there is one, we have to do normal file completion. */
	for (p = lpre + strlen(lpre); 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 ? 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);

    lpl = strlen(lpre);
    lsl = strlen(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. */
	    if (isset(HASHLISTALL))
		fullhash();
	    dumphtable(cmdnamtab, -7);
	} else {
	    /* Normal file completion... */
	    if (ispattern & 1) {
		/* But with pattern matching. */
		Lklist l = newlist();
		Lknode n;
		int ng = opts[NULLGLOB];

		opts[NULLGLOB] = OPT_SET;

		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);
		addnode(l, p);
		postfork(l, 1);

		if (full(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[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] = OPT_UNSET;

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

			    /* 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 == Equals || *g == Tilde) {
				/* The pattern has a `~' or `=' at the
				   beginning, so we expand this and use the
				   result. */
				filesub(&g, 0);
				addnode(l, dupstring(g));
			    } else if (*g == '/')
				/* The pattern is a full path (starting with
				   '/'), so add it unchanged. */
				addnode(l, dupstring(g));
			    else {
				/* It's a simple patter, so append it to the
				   path we have on the command line. */
				strcpy(pa + o, g);
				addnode(l, dupstring(pa));
			    }
			    /* Do the globbing. */
			    postfork(l, 1);
			    /* Get the results. */
			    if (full(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 == Tilde || ic == Equals) {
	/* 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, too. */
	dumphtable(aliastab, -2);
	dumphtable(reswdtab, -2);
	if (isset(HASHLISTALL))
	    fullhash();
	dumphtable(cmdnamtab, -3);
	/* And parameter names if autocd and cdablevars are set. */
	if (isset(AUTOCD) && isset(CDABLEVARS))
	    dumphtable(paramtab, -4);
    }
    addwhat = -2;

    if (cc->mask & CC_NAMED) {
	/* Add named directories. */
	int t0;

	for (t0 = 0; t0 < userdirct; t0++)
	    addmatch(namdirs[t0].name, NULL);
    }
    if (cc->mask & CC_OPTIONS) {
	/* Add option names. */
	struct option *o;

	for (o = optns; o->name; o++)
	    addmatch(dupstring(o->name), NULL);
    }
    if (cc->mask & CC_VARS)
	/* And parameter names. */
	dumphtable(paramtab, -2);
    if (cc->mask & CC_BINDINGS) {
	/* And zle function names... */
	int t0;

	for (t0 = 0; t0 != ZLECMDCOUNT; t0++)
	    if (*zlecmds[t0].name)
		addmatch(dupstring(zlecmds[t0].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. */
	    Lklist args = newlist();

	    addnode(args, cc->func);

	    if (delit) {
		sav = os[ooffs];
		os[ooffs] = '\0';
		p = dupstring(os);
		untokenize(p);
		addnode(args, p);
		os[ooffs] = sav;
		p = dupstring(os + ooffs);
		untokenize(p);
		addnode(args, p);
	    } else {
		addnode(args, lpre);
		addnode(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. */
	Lklist foo = newlist();
	Lknode n;
	int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb;

	opts[NULLGLOB] = OPT_SET;

	/* Put the strin in the lexer buffer and call the lexer to get the
	   words we have to expand. */
	zleparse = 1;
	lexsave();
	hungets(cc->str);
	hungets("foo ");	/* KLUDGE! */
	strinbeg();
	noaliases = 1;
	do {
	    ctxtlex();
	    if (tok == ENDINPUT)
		break;
	    if (!first && tokstr && *tokstr)
		addnode(foo, ztrdup(tokstr));
	    first = 0;
	}
	while (tok != ENDINPUT);
	noaliases = 0;
	hflush();
	strinend();
	errflag = zleparse = 0;
	lexrestore();
	/*Fine, now do full expansion. */
	prefork(foo, 0);
	if (!errflag) {
	    postfork(foo, 1);
	    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;
	int i = curhist - 1, n = cc->hnum, l = lithist;

	/* 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;
	lithist = 0;

	/* Now search the history. */
	while (n-- && (h = qgetevent(i--))) {
	    while (*h) {
		for (e = h; *e && *e != HISTSPACE; e++);
		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;
		    h = e + 1;
		} else
		    h = e;
	    }
	}
	lithist = l;
    }
    if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS |
			 CC_READONLYS | CC_SPECIALS | CC_PARAMS)))
	/* Add various flavours of parameters. */
	dumphtable(paramtab, t);
    if ((t = cc->mask & (CC_FUNCS | CC_BUILTINS | CC_DISCMDS | CC_EXCMDS)))
	/* And of things from the cmdnamtab. */
	dumphtable(cmdnamtab, t);
    if ((t = cc->mask & (CC_ALREG | CC_ALGLOB)))
	/* And the two types of aliases. */
	dumphtable(aliastab, t);

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

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

    /* And quote the prefizes/suffize. */
    quotepresuf(&lpre);
    quotepresuf(&lsuf);
    quotepresuf(&fpre);
    quotepresuf(&fsuf);
    quotepresuf(&ppre);
    quotepresuf(&psuf);

    if (empty(matches)) {
	matches = fmatches;
	firstm = ffirstm;
	shortest = fshortest;
	ab = fab;
	ae = fae;
	shortl = fshortl;
    }

    if (empty(matches) || errflag) {
	/* We found no matches. */
 	if ((isf || cc->xor) && !parampre) {
	    /* 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;
	    s = dupstring(os);
	    strcpy((char *)line, (char *)ol);
	    goto xorrec;
	}
	/* Otherwise restore the command line and feeeeeeeeeeeeep(). */
	strcpy((char *)line, (char *)ol);
	feep();
    } else if (lst == COMP_LIST_COMPLETE)
	/* All this and the guy only wants to see the list, sigh. */
	listtlist(matches);
    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 (nextnode(firstnode(matches)))
	    /* There are more than one match. */
	    do_ambiguous();
	else
	    /* Only one match. */
	    do_single((char *)peekfirst(matches));
    }

    /* Print the explanation string if needed. */
    if (expl && (empty(matches) || nextnode(firstnode(matches)))) {
	int up;

	trashzle();

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

	up = printfmt(expl, countnodes(matches), 1);

	if (clearflag)
	    tcmultout(TCUP, TCMULTUP, up + nlnct);
	fflush(shout);
    }
    ll = strlen((char *)line);
    if (cs > ll)
	cs = ll;
  compend:
    if (ccmain != &cc_dummy)
	freecompctl(ccmain);
    popheap();
    permalloc();
}

/* 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, nonempty = 0, brk = 0;
	Lklist arrlist = newlist();

	ptr = dupstring(nam);
	s = ptr + 1;
	while (*++ptr) {
	    if (*ptr == '\\' && ptr[1])
		chuck(ptr), nonempty++;
	    else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') {
		if (*ptr == ')')
		    brk++;
		if (nonempty) {
		    *ptr = '\0';
		    count++;
		    if (*s == '\n')
			s++;
		    addnode(arrlist, s);
		}
		s = ptr + 1;
		nonempty = 0;
	    } else
		nonempty++;
	    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);
}

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

/**/
void
do_ambiguous(void)
{
    char **ap, **bp, **cp;
    int p, atend = (cs == we);
    Lknode nod;

    lastambig = 1;

    if (menuarr) {
	/* There is an array of matches lingering around, free it. */
	freearray(menuarr);
	menuarr = NULL;
	menucmp = 0;
    }
    /* Build an array for the matches. */
    ap = amatches = (char **)ncalloc(((nmatches = countnodes(matches)) + 1) *
				     sizeof(char *));

    /* And copy them into it. */
    for (nod = firstnode(matches); 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;

    if ((nmatches = arrlen(amatches)) == 1) {
	/* After deleteing the metches that occured more than once, there
	   is only one left... */
	lastambig = 0;
	do_single(amatches[0]);
	return;
    }

    /* If we have to do menu-completion, store the array of matches. */
    if ((p = (usemenu || ispattern)) || isset(AUTOMENU)) {
	permalloc();
	menuarr = arrdup(amatches);
	heapalloc();
    }
    /* If we have to insert the first match, call do_single(). */
    if (shortest && shortl == 0 && isset(RECEXACT) &&
	(usemenu == 0 || unset(AUTOMENU))) {
	do_single(shortest);
    } else {
	if (p)
	    /* Otherwise we have to do menu-completion... */
	    do_ambig_menu();
	else {
	    /* Or to insert the minimal common prefix and suffix. */
	    complexpect = 0;
	    if (ab)
		inststrlen(firstm, 1, ab);
	    if (ae && !atend)
		inststrlen(firstm + strlen(firstm) - ae, 0, ae);
	    if (isset(LISTAMBIGUOUS) && (ab || (ae && !atend))) {
		lastambig = 0;
		return;
	    }
	}
	if (unset(NOLISTBEEP))
	    feep();
	/* And finally list the matches if needed (and requested. */
	if (isset(AUTOLIST) && !amenu)
	    listmatches();
    }
}

/* This is a stat that ignores backslashes in the filename (the second
   parameter says if we have to do lstat() or stat(). */

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

    for (p = b; *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[PATH_MAX], *ss;
	    struct stat buf;

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

		noerrs = 1;

		if (parampre) {
		    int pl = strlen(parampre);
		    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) {
		    sprintf(p, "%c%s%s%s%s%s", ic,
			    ppre, fpre, str, fsuf, psuf);
		}
		else
		    strcpy(p, str);
		ss = dupstring(p);
		tokenize(ss);
		singsub(&ss);
		strcpy(p, ss);

		noerrs = ne;
	    } else
		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)
	    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(*menuarr);
    menucur = menuarr;
}

/* Return non-zero if s is a prefix of t. */

/**/
int
strpfx(char *s, char *t)
{
    while (*s && *s == *t)
	s++, t++;
    return !*s;
}

/* Return non-zero if s is a suffix of t. */

/**/
int
strsfx(char *s, char *t)
{
    int ls = strlen(s), lt = strlen(t);

    if (ls <= lt)
	return !strcmp(t + lt - ls, s);
    return 0;
}

/* 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[14];
    int l = 1, 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);
	    } else
		break;
	else {
	    cc++;
	    if (*p == '\n') {
		l += 1 + (cc / columns);
		cc = 0;
	    }
	    if (dopr)
		putc(*p, shout);
	}
    }
    if (dopr)
	putc('\n', shout);

    return l + (cc / columns);
}

/* List the matches. */

/**/
void
listmatches(void)
{
    int longest = 1, fct, fw = 0, colsz, t0, t1, ct, up, cl;
    int off, boff;
    int of = (isset(LISTTYPES) && !(haswhat & HAS_MISC));
    char **arr, **ap, sav;

    /* Calculate the lengths of the prefizes/suffizes 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;

    /* 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;

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

    arr = amatches;

    /* Calculate the column width, the number of columns and the number
       of lines. */
    for (ap = arr; *ap; ap++)
	if ((cl = strlen(*ap + off) - boff +
	     (ispattern ? 0 :
	      (!(haswhat & HAS_MISC) ? fpl + fsl : lpl + lsl))) > longest)
	    longest = cl;
    if (of)
	longest++;

    fct = (columns - 1) / (longest + 2);
    if (fct == 0)
	fct = 1;
    else
	fw = (columns - 1) / fct;
    colsz = (ct + fct - 1) / fct;

    up = colsz + nlnct - clearflag;

    /* Print the explanation string, if any. */
    if (expl)
	up += printfmt(expl, ct, 0);

    /* Maybe we have to ask if the user wants to see the list. */
    if ((listmax && ct > listmax) || (!listmax && up >= lines)) {
	setterm();
	fprintf(shout, "zsh: do you wish to see all %d possibilities? ", ct);
	fflush(shout);
	if (getzlequery() != 'y') {
	    if (clearflag) {
		putc('\r', shout);
		if (tccan(TCCLEAREOD))
		    tcout(TCCLEAREOD);
		tcmultout(TCUP, TCMULTUP, nlnct);
	    } else
		putc('\n', shout);
	    return;
	}
	if (clearflag) {
	    putc('\r', shout);
	    if (tccan(TCCLEAREOD))
		tcout(TCCLEAREOD);
	} else
	    putc('\n', shout);
	settyinfo(&shttyinfo);
    }
    /* After the query, the explanation string was deleted, print it
       again. */
    if (expl)
	printfmt(expl, ct, 1);

    /* 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 = ispattern ? strlen(*ap) :
		strlen(*ap + off) - boff + 1 + fpl + fsl;
		char pbuf[PATH_MAX], *pb;
		struct stat buf;

		/* Build the path name for the stat. */
		if (ispattern) {
		    sav = ap[0][t2 - boff];
		    ap[0][t2 - boff] = '\0';
		    fprintf(shout, "%s", *ap + off);
		    ap[0][t2 - boff] = sav;
		    pb = *ap;
		    t2 -= off + boff - 1;
		} else {
		    fprintf(shout, "%s%s%s", fpre, *ap, fsuf);
		    sprintf(pb = pbuf, "%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 < fw; t2++)
			putc(' ', shout);
	    }
	} else
	    while (*ap) {
		int t2 = ispattern ? strlen(*ap) :
		strlen(*ap + off) - boff;

		if (ispattern) {
		    sav = ap[0][t2 - boff];
		    ap[0][t2 - boff] = '\0';
		    fprintf(shout, "%s", *ap + off);
		    ap[0][t2 - boff] = sav;
		    t2 -= off + boff;
		} else if (!(haswhat & HAS_MISC)) {
		    fprintf(shout, "%s%s%s", fpre, *ap, fsuf);
		    t2 += fpl + fsl;
		} else {
		    fprintf(shout, "%s%s%s", lpre, *ap, lsuf);
		    t2 += lpl + lsl;
		}
		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);
	else
	    clearflag = 0, putc('\n', shout);

    expl = NULL;

    fflush(shout);
}

/* A list printing function, used for COMP_LIST_COMPLETE, it sorts the
   matches, makes them unique and then calls listmatches. */

/**/
void
listtlist(Lklist l)
{
    int n = countnodes(l), nm = nmatches;
    char **am = amatches, **ap, **bp, **cp;
    Lknode nod;

    nmatches = n;

    amatches = ap = (char **)ncalloc((n + 1) * sizeof(char *));

    for (nod = firstnode(l); nod; incnode(nod))
	*ap++ = (char *)getdata(nod);
    *ap = NULL;

    qsort((void *) amatches, nmatches, sizeof(char *),
	       (int (*) _((const void *, const void *)))strpcmp);

    for (ap = cp = amatches; *ap; ap++) {
	*cp++ = *ap;
	for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++);
	ap = bp;
    }
    *cp = NULL;

    listmatches();

    amatches = am;
    nmatches = nm;
}

/* This is used to print expansions. */

/**/
void
listlist(Lklist l)
{
    int hw = haswhat, ip = ispattern;
    char *lp = lpre, *ls = lsuf;

    haswhat = HAS_MISC;
    ispattern = 0;
    lpre = lsuf = "";

    listtlist(l);

    lpre = lp;
    lsuf = ls;
    ispattern = ip;
    haswhat = hw;
}

/* And this is used to print select lists.  Hm, this function should
   probably be move to loop.c. */

/**/
void
selectlist(Lklist l)
{
    int longest = 1, fct, fw = 0, colsz, t0, t1, ct;
    Lknode n;
    char **arr, **ap;

    trashzle();
    ct = countnodes(l);
    ap = arr = (char **)alloc((countnodes(l) + 1) * sizeof(char **));

    for (n = (Lknode) firstnode(l); n; incnode(n))
	*ap++ = (char *)getdata(n);
    *ap = NULL;
    for (ap = arr; *ap; ap++)
	if (strlen(*ap) > longest)
	    longest = strlen(*ap);
    t0 = ct;
    longest++;
    while (t0)
	t0 /= 10, longest++;
    /* to compensate for added ')' */
    fct = (columns - 1) / (longest + 3);
    if (fct == 0)
	fct = 1;
    else
	fw = (columns - 1) / fct;
    colsz = (ct + fct - 1) / fct;
    for (t1 = 0; t1 != colsz; t1++) {
	ap = arr + t1;
	do {
	    int t2 = strlen(*ap) + 2, t3;

	    fprintf(stderr, "%d) %s", t3 = ap - arr + 1, *ap);
	    while (t3)
		t2++, t3 /= 10;
	    for (; t2 < fw; t2++)
		fputc(' ', stderr);
	    for (t0 = colsz; t0 && *ap; t0--, ap++);
	}
	while (*ap);
	fputc('\n', stderr);
    }

 /* Below is a simple attempt at doing it the Korn Way..
       ap = arr;
       t0 = 0;
       do
       {
       t0++;
       fprintf(stderr,"%d) %s\n",t0,*ap);
       ap++;
       }
       while (*ap);*/
    fflush(stderr);
}

/* Expand the history references. */

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

    heapalloc();
    pushheap();
    ol = (unsigned char *)dupstring((char *)line);
    expanding = 1;
    excs = cs;
    exmax = ll + 1;
    exlast = ll + 42;   /* just make sure that it's bigger than ll */
    ll = cs = 0;
    zleparse = 1;
    lexsave();
    hungets(" ");		/* KLUDGE! */
    hungets(UTOSCP(line));
    strinbeg();
    pushheap();
    noaliases = 1;
    noerrs = 1;
    do {
	ctxtlex();
    }
    while (tok != ENDINPUT && zleparse);
    noerrs = ne;
    noaliases = 0;
    hflush();
    popheap();
    strinend();
    errflag = zleparse = 0;
    lexrestore();
    expanding = 0;
    popheap();
    permalloc();
    cs = (ce ? ll : excs);

    /* Compensate for the added space above. */
    if (ll && line[ll - 1] == ' ')
      ll--;

    if (cs > ll)
	cs = ll;

    line[ll] = '\0';

    if (!errflag && strcmp((char *)line, (char *)ol)) {
	/* 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 !!".  Why on earth doesn't Zsh expand history
	references like everything else, rather than doing the whole line? */
	if (viinsbegin > findbol())
	    viinsbegin = findbol();
	return 1;
    }

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

    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;

    zleparse = 1;
    lexsave();
    hungets(" ");		/* KLUDGE! */
    hungets(UTOSCP(line));
    strinbeg();
    pushheap();
    do {
	curlincmd = incmdpos;
	ctxtlex();
	if (tok == ENDINPUT)
	    break;
	if (tok == STRING && curlincmd) {
	    zsfree(s);
	    s = ztrdup(tokstr);
	    cmdwb = ll - wordbeg;
	    cmdwe = ll + 1 - inbufct;
	}
    }
    while (tok != ENDINPUT && zleparse);
    hflush();
    popheap();
    strinend();
    errflag = zleparse = 0;
    lexrestore();
    return s;
}

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

    s = getcurcmd();
    if (!s) {
	feep();
	return;
    }
    t = zlecmds[bindk].name;
    mult = 1;
    permalloc();
    pushline();
    lastalloc();
    sizeline(strlen(s) + strlen(t) + 1);
    strcpy((char *)line, t);
    strcat((char *)line, " ");
    cs = ll = strlen((char *)line);
    inststr(s);
    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;
    mult = 1;
    expandorcomplete();		/* complete. */
    mult = -1;

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