about summary refs log tree commit diff
path: root/Src/Zle/zle_tricky.c
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-04-15 18:05:35 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-04-15 18:05:35 +0000
commit32c2ebbaa5d7927f33ee0ecf98472a71cf902cf3 (patch)
tree212c29fe244d0222bab8efe3032e7fa965842945 /Src/Zle/zle_tricky.c
parentc175751b501a3a4cb40ad4787340a597ea769be4 (diff)
downloadzsh-32c2ebbaa5d7927f33ee0ecf98472a71cf902cf3.tar.gz
zsh-32c2ebbaa5d7927f33ee0ecf98472a71cf902cf3.tar.xz
zsh-32c2ebbaa5d7927f33ee0ecf98472a71cf902cf3.zip
zsh-3.1.5 zsh-3.1.5
Diffstat (limited to 'Src/Zle/zle_tricky.c')
-rw-r--r--Src/Zle/zle_tricky.c4015
1 files changed, 4015 insertions, 0 deletions
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
new file mode 100644
index 000000000..1aa1a008c
--- /dev/null
+++ b/Src/Zle/zle_tricky.c
@@ -0,0 +1,4015 @@
+/*
+ * zle_tricky.c - expansion and completion
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_tricky.pro"
+
+/* The main part of ZLE maintains the line being edited as binary data, *
+ * but here, where we interface with the lexer and other bits of zsh,   *
+ * we need the line metafied.  The technique used is quite simple: on   *
+ * entry to the expansion/completion system, we metafy the line in      *
+ * place, adjusting ll and cs to match.  All completion and expansion   *
+ * is done on the metafied line.  Immediately before returning, the     *
+ * line is unmetafied again, changing ll and cs back.  (ll and cs might *
+ * have changed during completion, so they can't be merely saved and    *
+ * restored.)  The various indexes into the line that are used in this  *
+ * file only are not translated: they remain indexes into the metafied  *
+ * line.                                                                */
+
+#ifdef HAVE_NIS_PLUS
+# include <rpcsvc/nis.h>
+#else
+# ifdef HAVE_NIS
+#  include	<rpc/types.h>
+#  include	<rpc/rpc.h>
+#  include	<rpcsvc/ypclnt.h>
+#  include	<rpcsvc/yp_prot.h>
+
+/* This is used when getting usernames from the NIS. */
+typedef struct {
+    int len;
+    char *s;
+}
+dopestring;
+# endif
+#endif
+
+#define inststr(X) inststrlen((X),1,-1)
+
+/* wb and we hold the beginning/end position of the word we are completing. */
+
+static int wb, we;
+
+/* offs is the cursor position within the tokenized *
+ * current word after removing nulargs.             */
+
+static int offs;
+
+/* These control the type of completion that will be done.  They are    *
+ * affected by the choice of ZLE command and by relevant shell options. */
+
+static int usemenu, useglob;
+
+/* != 0 if we are in the middle of a menu completion */
+
+static int menucmp;
+
+/* A pointer to the current position in the menu-completion array (the one *
+ * that was put in the command line last).                                 */
+
+static char **menucur;
+
+/* menupos is the point (in the command line) where the menu-completion   *
+ * strings are inserted.  menulen is the length of the string that was    *
+ * inserted last.  menuend is the end position of this string in the      *
+ * command line.  menuwe is non-zero if the cursor was at the end of the  *
+ * word (meaning that suffixes should go before the cursor).  menuinsc is *
+ * the length of any suffix that has been temporarily added.              */
+
+static int menupos, menulen, menuend, menuwe, menuinsc;
+
+/* This is used as a flag from get_comp_string() that we are doing *
+ * completion inside a brace expansion.                            */
+
+static int complinbrace;
+
+/* The list of matches.  fmatches contains the matches we first ignore *
+ * because of fignore.                                                 */
+
+static LinkList matches, fmatches;
+
+/* The list of matches turned into an array.  This is used to sort this *
+ * list and when menu-completion is used (directly or via automenu).    */
+
+static char **amatches;
+
+/* The number of matches. */
+
+static int nmatches;
+
+/* A list of user-defined explanations for the completions to be shown *
+ * instead of amatches when listing completions.                       */
+
+static char **aylist;
+
+/* !=0 if we have a valid completion list. */
+
+static int validlist;
+
+/* This flag is non-zero if we are completing a pattern (with globcomplete) */
+
+static int ispattern;
+
+/* Two patterns used when doing glob-completion.  The first one is built *
+ * from the whole word we are completing and the second one from that    *
+ * part of the word that was identified as a possible filename.          */
+
+static Comp patcomp, filecomp;
+
+/* We store the following prefixes/suffixes:                             *
+ * lpre/lsuf -- what's on the line                                       *
+ * rpre/rsuf -- same as lpre/lsuf, but expanded                          *
+ *                                                                       *
+ * ... and if we are completing files, too:                              *
+ * ppre/psuf -- the path prefix/suffix                                   *
+ * fpre/fsuf -- prefix/suffix of the pathname component the cursor is in *
+ * prpre     -- ppre in expanded form usable for opendir                 *
+ *                                                                       *
+ * The integer variables hold the lengths of lpre, lsuf, rpre, rsuf,     *
+ * fpre, and fsuf.  noreal is non-zero if we have rpre/rsuf.             */
+
+static char *lpre, *lsuf;
+static char *rpre, *rsuf;
+static char *ppre, *psuf, *prpre;
+static char *fpre, *fsuf;
+static int lpl, lsl, rpl, rsl, fpl, fsl;
+static int noreal;
+
+/* This is used when completing after `$' and holds the whole prefix,   *
+ * used in do_single() to check whether the word expands to a directory *
+ * name (in that case and if autoparamslash is set, we add a `/').      *
+ * qparampre is the same but quoted. The length of it is in qparprelen. *
+ * parambr is != 0 if the parameter name is in braces.                  */
+
+static char *parampre = NULL, *qparampre = NULL;
+static int qparprelen, parambr;
+
+/* 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;
+
+/* Find out if we have to insert a tab (instead of trying to complete). */
+
+/**/
+static int
+usetab(void)
+{
+    unsigned char *s = line + cs - 1;
+
+    for (; s >= line && *s != '\n'; s--)
+	if (*s != '\t' && *s != ' ')
+	    return 0;
+    return 1;
+}
+
+#define COMP_COMPLETE 0
+#define COMP_LIST_COMPLETE 1
+#define COMP_SPELL 2
+#define COMP_EXPAND 3
+#define COMP_EXPAND_COMPLETE 4
+#define COMP_LIST_EXPAND 5
+#define COMP_ISEXPAND(X) ((X) >= COMP_EXPAND)
+
+/**/
+void
+completeword(void)
+{
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_COMPLETE);
+}
+
+/**/
+void
+menucomplete(void)
+{
+    usemenu = 1;
+    useglob = isset(GLOBCOMPLETE);
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_COMPLETE);
+}
+
+/**/
+void
+listchoices(void)
+{
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    docomplete(COMP_LIST_COMPLETE);
+}
+
+/**/
+void
+spellword(void)
+{
+    usemenu = useglob = 0;
+    docomplete(COMP_SPELL);
+}
+
+/**/
+void
+deletecharorlist(void)
+{
+    char **mc = menucur;
+
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    if (cs != ll)
+	deletechar();
+    else
+	docomplete(COMP_LIST_COMPLETE);
+
+    menucur = mc;
+}
+
+/**/
+void
+expandword(void)
+{
+    usemenu = useglob = 0;
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_EXPAND);
+}
+
+/**/
+void
+expandorcomplete(void)
+{
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_EXPAND_COMPLETE);
+}
+
+/**/
+void
+menuexpandorcomplete(void)
+{
+    usemenu = 1;
+    useglob = isset(GLOBCOMPLETE);
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_EXPAND_COMPLETE);
+}
+
+/**/
+void
+listexpand(void)
+{
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    docomplete(COMP_LIST_EXPAND);
+}
+
+/**/
+void
+reversemenucomplete(void)
+{
+    if (!menucmp) {
+	menucomplete();
+	return;
+    }
+    HEAPALLOC {
+	if (menucur == amatches)
+	    menucur = amatches + nmatches - 1;
+	else
+	    menucur--;
+	metafy_line();
+	do_single(*menucur);
+	unmetafy_line();
+    } LASTALLOC;
+}
+
+/* Accepts the current completion and starts a new arg, *
+ * with the next completions. This gives you a way to   *
+ * accept several selections from the list of matches.  */
+
+/**/
+void
+acceptandmenucomplete(void)
+{
+    if (!menucmp) {
+	feep();
+	return;
+    }
+    cs = menuend + menuinsc;
+    inststrlen(" ", 1, 1);
+    if (qparampre)
+	inststrlen(qparampre, 1, qparprelen);
+    if (lpre && !ispattern)
+	inststrlen(lpre, 1, -1);
+    if (lsuf && !ispattern)
+	inststrlen(lsuf, 0, -1);
+    menupos = cs;
+    menuend = cs + (lsuf ? strlen(lsuf) : 0);
+    menulen = 0;
+    menuinsc = 0;
+    menuwe = 1;
+    menucomplete();
+}
+
+/* These are flags saying if we are completing in the command *
+ * position or in a redirection.                              */
+
+static int lincmd, linredir;
+
+/* Non-zero if the last completion done was ambiguous (used to find   *
+ * out if AUTOMENU should start).  More precisely, it's nonzero after *
+ * successfully doing any completion, unless the completion was       *
+ * unambiguous and did not cause the display of a completion list.    *
+ * From the other point of view, it's nonzero iff AUTOMENU (if set)   *
+ * should kick in on another completion.                              */
+
+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.                                */
+
+/**/
+static 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.                                              */
+
+/**/
+static int
+cmphaswilds(char *str)
+{
+    if ((*str == Inbrack || *str == Outbrack) && !str[1])
+	return 0;
+
+    /* If a leading % is immediately followed by ?, then don't *
+     * treat that ? as a wildcard.  This is so you don't have  *
+     * to escape job references such as %?foo.                 */
+    if (str[0] == '%' && str[1] ==Quest)
+	str += 2;
+
+    for (; *str;) {
+	if (*str == String || *str == Qstring) {
+	    /* A parameter expression. */
+
+	    if (*++str == Inbrace)
+		skipparens(Inbrace, Outbrace, &str);
+	    else if (*str == String || *str == Qstring)
+		str++;
+	    else {
+		/* Skip all the things a parameter expression might start *
+		 * with (before we come to the parameter name).           */
+		for (; *str; str++)
+		    if (*str != '^' && *str != Hat &&
+			*str != '=' && *str != Equals &&
+			*str != '~' && *str != Tilde)
+			break;
+		if (*str == '#' || *str == Pound)
+		    str++;
+		/* Star and Quest are parameter names here, not wildcards */
+		if (*str == Star || *str == Quest)
+		    str++;
+	    }
+	} else {
+	    /* Not a parameter expression so we check for wildcards */
+	    if (((*str == Pound || *str == Hat) && isset(EXTENDEDGLOB)) ||
+		*str == Star || *str == Bar || *str == Quest ||
+		!skipparens(Inbrack, Outbrack, &str) ||
+		!skipparens(Inang,   Outang,   &str) ||
+		(unset(IGNOREBRACES) &&
+		 !skipparens(Inbrace, Outbrace, &str)) ||
+		(*str == Inpar && str[1] == ':' &&
+		 !skipparens(Inpar, Outpar, &str)))
+		return 1;
+	    if (*str)
+		str++;
+	}
+    }
+    return 0;
+}
+
+/* The main entry point for completion. */
+
+/**/
+static void
+docomplete(int lst)
+{
+    char *s, *ol;
+    int olst = lst, chl = 0, ne = noerrs, ocs;
+
+    /* If we are doing a menu-completion... */
+
+    if (menucmp && lst != COMP_LIST_EXPAND) {
+	do_menucmp(lst);
+	return;
+    }
+
+    /* Check if we have to start a menu-completion (via automenu). */
+
+    if ((amenu = (isset(AUTOMENU) && lastambig)))
+	usemenu = 1;
+
+    /* Expand history references before starting completion.  If anything *
+     * changed, do no more.                                               */
+
+    if (doexpandhist())
+	return;
+
+    metafy_line();
+
+    ocs = cs;
+    if (!isfirstln && chline != NULL) {
+	/* If we are completing in a multi-line buffer (which was not  *
+	 * taken from the history), we have to prepend the stuff saved *
+	 * in chline to the contents of line.                          */
+
+	ol = dupstring((char *)line);
+	/* Make sure that chline is zero-terminated. */
+	*hptr = '\0';
+	cs = 0;
+	inststr(chline);
+	chl = cs;
+	cs += ocs;
+    } else
+	ol = NULL;
+    inwhat = IN_NOTHING;
+    qword = NULL;
+    /* Get the word to complete. */
+    noerrs = 1;
+    s = get_comp_string();
+    DPUTS(wb < 0 || cs < wb || cs > we,
+	  "BUG: 0 <= wb <= cs <= we is not true!");
+    noerrs = ne;
+    /* For vi mode, reset the start-of-insertion pointer to the beginning *
+     * of the word being completed, if it is currently later.  Vi itself  *
+     * would never change the pointer in the middle of an insertion, but  *
+     * then vi doesn't have completion.  More to the point, this is only  *
+     * an emulation.                                                      */
+    if (viinsbegin > ztrsub((char *) line + wb, (char *) line))
+	viinsbegin = ztrsub((char *) line + wb, (char *) line);
+    /* If we added chline to the line buffer, reset the original contents. */
+    if (ol) {
+	cs -= chl;
+	wb -= chl;
+	we -= chl;
+	if (wb < 0) {
+	    strcpy((char *) line, ol);
+	    ll = strlen((char *) line);
+	    cs = ocs;
+	    unmetafy_line();
+	    feep();
+	    return;
+	}
+	ocs = cs;
+	cs = 0;
+	foredel(chl);
+	cs = ocs;
+    }
+    freeheap();
+    /* Save the lexer state, in case the completion code uses the lexer *
+     * somewhere (e.g. when processing a compctl -s flag).              */
+    lexsave();
+    if (inwhat == IN_ENV)
+	lincmd = 0;
+    if (s) {
+	if (lst == COMP_EXPAND_COMPLETE) {
+	    /* Check if we have to do expansion or completion. */
+	    char *q = s;
+
+	    if (*q == Equals) {
+		/* The word starts with `=', see if we can expand it. */
+		q = s + 1;
+		if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked))
+		    if (isset(RECEXACT))
+			lst = COMP_EXPAND;
+		    else {
+			int t0, n = 0;
+			char *fc;
+			struct hashnode *hn;
+
+			for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--)
+			    for (hn = cmdnamtab->nodes[t0]; hn;
+				 hn = hn->next) {
+				if (strpfx(q, hn->nam) && (fc = findcmd(hn->nam))) {
+				    zsfree(fc);
+				    n++;
+				}
+				if (n == 2)
+				    break;
+			    }
+
+			if (n == 1)
+			    lst = COMP_EXPAND;
+		    }
+	    }
+	    if (lst == COMP_EXPAND_COMPLETE)
+		do {
+		    /* check if there is a parameter expresiion. */
+		    for (; *q && *q != String; q++);
+		    if (*q == String && q[1] != Inpar && q[1] != Inbrack) {
+			if (*++q == Inbrace) {
+			    if (! skipparens(Inbrace, Outbrace, &q) &&
+				q == s + cs - wb)
+				lst = COMP_EXPAND;
+			} else {
+			    char *t, sav, sav2;
+
+			    /* Skip the things parameter expressions might *
+			     * start with (the things before the parameter *
+			     * name).                                      */
+			    for (; *q; q++)
+				if (*q != '^' && *q != Hat &&
+				    *q != '=' && *q != Equals &&
+				    *q != '~' && *q != Tilde)
+				    break;
+			    if ((*q == '#' || *q == Pound || *q == '+') &&
+				q[1] != String)
+				q++;
+
+			    sav2 = *(t = q);
+			    if (*q == Quest || *q == Star || *q == String ||
+				*q == Qstring)
+				*q = ztokens[*q - Pound], ++q;
+			    else if (*q == '?' || *q == '*' || *q == '$' ||
+				     *q == '-' || *q == '!' || *q == '@')
+				q++;
+			    else if (idigit(*q))
+				do q++; while (idigit(*q));
+			    else
+				while (iident(*q))
+				    q++;
+			    sav = *q;
+			    *q = '\0';
+			    if (cs - wb == q - s &&
+				(idigit(sav2) || checkparams(t)))
+				lst = COMP_EXPAND;
+			    *q = sav;
+			    *t = sav2;
+			}
+			if (lst != COMP_EXPAND)
+			    lst = COMP_COMPLETE;
+		    } else
+			break;
+		} while (q < s + cs - wb);
+	    if (lst == COMP_EXPAND_COMPLETE) {
+		/* If it is still not clear if we should use expansion or   *
+		 * completion and there is a `$' or a backtick in the word, *
+		 * than do expansion.                                       */
+		for (q = s; *q; q++)
+		    if (*q == Tick || *q == Qtick ||
+			*q == String || *q == Qstring)
+			break;
+		lst = *q ? COMP_EXPAND : COMP_COMPLETE;
+	    }
+	    /* And do expansion if there are wildcards and globcomplete is *
+	     * not used.                                                   */
+	    if (unset(GLOBCOMPLETE) && cmphaswilds(s))
+		lst = COMP_EXPAND;
+	}
+	if (lincmd && (inwhat == IN_NOTHING))
+	    inwhat = IN_CMD;
+
+	if (lst == COMP_SPELL) {
+	    char *x, *q;
+
+	    for (q = s; *q; q++)
+		if (INULL(*q))
+		    *q = Nularg;
+	    cs = wb;
+	    foredel(we - wb);
+	    HEAPALLOC {
+		untokenize(x = dupstring(s));
+		if (*s == Tilde || *s == Equals || *s == String)
+		    *x = *s;
+		spckword(&x, 0, lincmd, 0);
+	    } LASTALLOC;
+	    untokenize(x);
+	    inststr(x);
+	} else if (COMP_ISEXPAND(lst)) {
+	    /* Do expansion. */
+	    char *ol = (olst == COMP_EXPAND_COMPLETE) ?
+		dupstring((char *)line) : (char *)line;
+	    int ocs = cs, ne = noerrs;
+
+	    noerrs = 1;
+	    doexpansion(s, lst, olst, lincmd);
+	    lastambig = 0;
+	    noerrs = ne;
+
+	    /* If expandorcomplete was invoked and the expansion didn't *
+	     * change the command line, do completion.                  */
+	    if (olst == COMP_EXPAND_COMPLETE &&
+		!strcmp(ol, (char *)line)) {
+		char *p;
+
+		cs = ocs;
+		errflag = 0;
+
+		p = s;
+		if (*p == Tilde || *p == Equals)
+		    p++;
+		for (; *p; p++)
+		    if (itok(*p))
+			if (*p != String && *p != Qstring)
+			    *p = ztokens[*p - Pound];
+			else if (p[1] == Inbrace)
+			    p++, skipparens(Inbrace, Outbrace, &p);
+		docompletion(s, lst, lincmd, 1);
+	    }
+	} else
+	    /* Just do completion. */
+	    docompletion(s, lst, lincmd, 0);
+	zsfree(s);
+    }
+    /* Reset the lexer state, pop the heap. */
+    lexrestore();
+    popheap();
+    zsfree(qword);
+    unmetafy_line();
+}
+
+/* Do completion, given that we are in the middle of a menu completion.  We *
+ * don't need to generate a list of matches, because that's already been    *
+ * done by previous commands.  We will either list the completions, or      *
+ * insert the next completion.                                              */
+
+/**/
+static void
+do_menucmp(int lst)
+{
+    /* Just list the matches if the list was requested. */
+    if (lst == COMP_LIST_COMPLETE) {
+	showinglist = -2;
+	return;
+    }
+    /* Otherwise go to the next match in the array... */
+    HEAPALLOC {
+	if (!*++menucur)
+	    menucur = amatches;
+	/* ... and insert it into the command line. */
+	metafy_line();
+	do_single(*menucur);
+	unmetafy_line();
+    } LASTALLOC;
+}
+
+/* 1 if we are completing in a string */
+static int instring;
+
+/* 1 if we are completing the prefix */
+static int comppref;
+
+/* 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'.                                     *
+ *									    *
+ * If we are just completing the prefix (comppref set), we also insert a    *
+ * space after the x to end the word.  We never need to remove the space:   *
+ * anywhere we are able to retrieve a word for completion it will be	    *
+ * discarded as whitespace.  It has the effect of making any suffix	    *
+ * referrable to as the next word on the command line when indexing	    *
+ * from a completion function.                                              */
+
+/**/
+static void
+addx(char **ptmp)
+{
+    int addspace = 0;
+
+    if (!line[cs] || line[cs] == '\n' ||
+	(iblank(line[cs]) && (!cs || line[cs-1] != '\\')) ||
+	line[cs] == ')' || line[cs] == '`' ||
+	(instring && (line[cs] == '"' || line[cs] == '\'')) ||
+	(addspace = (comppref && !iblank(line[cs])))) {
+	*ptmp = (char *)line;
+	line = (unsigned char *)halloc(strlen((char *)line) + 3 + addspace);
+	memcpy(line, *ptmp, cs);
+	line[cs] = 'x';
+	if (addspace)
+	    line[cs+1] = ' ';
+	strcpy((char *)line + cs + 1 + addspace, (*ptmp) + cs);
+	addedx = 1 + addspace;
+    } else {
+	addedx = 0;
+	*ptmp = NULL;
+    }
+}
+
+/* Like dupstring, but add an extra space at the end of the string. */
+
+/**/
+static char *
+dupstrspace(const char *str)
+{
+    int len = strlen((char *)str);
+    char *t = (char *)ncalloc(len + 2);
+    strcpy(t, str);
+    strcpy(t+len, " ");
+    return t;
+}
+
+/* These functions metafy and unmetafy the ZLE buffer, as described at the *
+ * top of this file.  Note that ll and cs are translated.  They *must* be  *
+ * called in matching pairs, around all the expansion/completion code.     *
+ * Currently, there are four pairs: in history expansion, in the main      *
+ * completion function, and one in each of the middle-of-menu-completion   *
+ * functions (there's one for each direction).                             */
+
+/**/
+static void
+metafy_line(void)
+{
+    int len = ll;
+    char *s;
+
+    for (s = (char *) line; s < (char *) line + ll;)
+	if (imeta(*s++))
+	    len++;
+    sizeline(len);
+    (void) metafy((char *) line, ll, META_NOALLOC);
+    ll = len;
+    cs = metalen((char *) line, cs);
+}
+
+/**/
+static void
+unmetafy_line(void)
+{
+    cs = ztrsub((char *) line + cs, (char *) line);
+    (void) unmetafy((char *) line, &ll);
+}
+
+/* Lasciate ogni speranza.                                                  *
+ * This function is a nightmare.  It works, but I'm sure that nobody really *
+ * understands why.  The problem is: to make it cleaner we would need       *
+ * changes in the lexer code (and then in the parser, and then...).         */
+
+/**/
+static char *
+get_comp_string(void)
+{
+    int t0, tt0, i, j, k, cp, rd, sl, ocs;
+    char *s = NULL, *linptr, *tmp, *p, *tt = NULL;
+
+    complinbrace = 0;
+    /* This global flag is used to signal the lexer code if it should *
+     * expand aliases or not.                                         */
+    noaliases = isset(COMPLETEALIASES);
+
+    /* Find out if we are somewhere in a `string', i.e. inside '...', *
+     * "...", `...`, or ((...)).                                      */
+
+    for (i = j = k = 0, p = (char *)line; p < (char *)line + cs; p++)
+	if (*p == '`' && !(k & 1))
+	    i++;
+	else if (*p == '\"' && !(k & 1) && !(i & 1))
+	    j++;
+	else if (*p == '\'' && !(j & 1))
+	    k++;
+	else if (*p == '\\' && p[1] && !(k & 1))
+	    p++;
+    instring = (j & 1) ? 2 : (k & 1);
+    addx(&tmp);
+    if (instring) {
+	/* Yes, we are in a string. */
+	if (!tmp) {
+	    tmp = (char *)line;
+	    line = (unsigned char *) dupstring((char *) line);
+	}
+	/* Now remove the quotes.                                   *
+	 * What??  Why that??  Well, we want to be able to complete *
+	 * inside strings.  The lexer code gives us no help here,   *
+	 * so we have to cheat.  We remove the quotes, the lexer    *
+	 * will than treat the words in the strings normally and we *
+	 * can complete them.                                       *
+	 * This is completely the wrong thing to do, but it's       *
+	 * occasionally useful, and we can't handle quotes properly *
+	 * yet anyway.                                              */
+	for (p = (char *)line; *p; p++)
+	    if (*p == '"' || *p == '\'')
+		*p = ' ';
+    }
+    linptr = (char *)line;
+    pushheap();
+    HEAPALLOC {
+      start:
+	inwhat = IN_NOTHING;
+	/* Now set up the lexer and start it. */
+	parbegin = parend = -1;
+	lincmd = incmdpos;
+	linredir = inredir;
+	zsfree(cmdstr);
+	cmdstr = NULL;
+	zleparse = 1;
+	clwpos = -1;
+	lexsave();
+	inpush(dupstrspace((char *) linptr), 0, NULL);
+	strinbeg();
+	stophist = 2;
+	i = tt0 = cp = rd = 0;
+
+	/* This loop is possibly the wrong way to do this.  It goes through *
+	 * the previously massaged command line using the lexer.  It stores *
+	 * each token in each command (commands being regarded, roughly, as *
+	 * being separated by tokens | & &! |& || &&).  The loop stops when *
+	 * the end of the command containing the cursor is reached.  It's a *
+	 * simple way to do things, but suffers from an inability to        *
+	 * distinguish actual command arguments from, for example,          *
+	 * filenames in redirections.  (But note that code elsewhere checks *
+	 * if we are completing *in* a redirection.)  The only way to fix   *
+	 * this would be to pass the command line through the parser too,   *
+	 * and get the arguments that way.  Maybe in 3.1...                 */
+	do {
+	    lincmd = incmdpos;
+	    linredir = inredir;
+	    /* Get the next token. */
+	    ctxtlex();
+	    if (tok == DINPAR)
+		tokstr = NULL;
+
+	    /* We reached the end. */
+	    if (tok == ENDINPUT)
+		break;
+	    if (tok == BAR    || tok == AMPER     ||
+		tok == BARAMP || tok == AMPERBANG ||
+		((tok == DBAR || tok == DAMPER) && !incond)) {
+		/* This is one of the things that separate commands.  If we  *
+		 * already have the things we need (e.g. the token strings), *
+		 * leave the loop.                                           */
+		if (tt)
+		    break;
+		/* Otherwise reset the variables we are collecting data in. */
+		i = tt0 = cp = rd = 0;
+	    }
+	    if (lincmd && tok == STRING) {
+		/* The lexer says, this token is in command position, so *
+		 * store the token string (to find the right compctl).   */
+		zsfree(cmdstr);
+		cmdstr = ztrdup(tokstr);
+		i = 0;
+	    }
+	    if (!zleparse && !tt0) {
+		/* This is done when the lexer reached the word the cursor is on. */
+		tt = tokstr ? dupstring(tokstr) : NULL;
+		/* If we added a `x', remove it. */
+		if (addedx && tt)
+		    chuck(tt + cs - wb);
+		tt0 = tok;
+		/* Store the number of this word. */
+		clwpos = i;
+		cp = lincmd;
+		rd = linredir;
+		if (inwhat == IN_NOTHING && incond)
+		    inwhat = IN_COND;
+	    }
+	    if (!tokstr)
+		continue;
+	    /* We need to store the token strings of all words (for some of *
+	     * the more complicated compctl -x things).  They are stored in *
+	     * the clwords array.  Make this array big enough.              */
+	    if (i + 1 == clwsize) {
+		int n;
+		clwords = (char **)realloc(clwords,
+					   (clwsize *= 2) * sizeof(char *));
+		for(n = clwsize; --n > i; )
+		    clwords[n] = NULL;
+	    }
+	    zsfree(clwords[i]);
+	    /* And store the current token string. */
+	    clwords[i] = ztrdup(tokstr);
+	    sl = strlen(tokstr);
+	    /* Sometimes the lexer gives us token strings ending with *
+	     * spaces we delete the spaces.                           */
+	    while (sl && clwords[i][sl - 1] == ' ' &&
+		   (sl < 2 || (clwords[i][sl - 2] != Bnull &&
+			       clwords[i][sl - 2] != Meta)))
+		clwords[i][--sl] = '\0';
+	    /* If this is the word the cursor is in and we added a `x', *
+	     * remove it.                                               */
+	    if (clwpos == i++ && addedx)
+		chuck(&clwords[i - 1][((cs - wb) >= sl) ?
+				     (sl - 1) : (cs - wb)]);
+	} while (tok != LEXERR && tok != ENDINPUT &&
+		 (tok != SEPER || (zleparse && !tt0)));
+	/* Calculate the number of words stored in the clwords array. */
+	clwnum = (tt || !i) ? i : i - 1;
+	zsfree(clwords[clwnum]);
+	clwords[clwnum] = NULL;
+	t0 = tt0;
+	lincmd = cp;
+	linredir = rd;
+	strinend();
+	inpop();
+	errflag = zleparse = 0;
+	if (parbegin != -1) {
+	    /* We are in command or process substitution */
+	    if (parend >= 0 && !tmp)
+		line = (unsigned char *) dupstring(tmp = (char *)line);
+	    linptr = (char *) line + ll + addedx - parbegin + 1;
+	    if (parend >= 0) {
+		ll -= parend;
+		line[ll + addedx] = '\0';
+	    }
+	    lexrestore();
+	    goto start;
+	}
+
+	if (inwhat == IN_MATH)
+	    s = NULL;
+	else if (!t0 || t0 == ENDINPUT) {
+	    /* There was no word (empty line). */
+	    s = ztrdup("");
+	    we = wb = cs;
+	    clwpos = clwnum;
+	    t0 = STRING;
+	} else if (t0 == STRING) {
+	    /* We found a simple string. */
+	    s = ztrdup(clwords[clwpos]);
+	} else if (t0 == ENVSTRING) {
+	    /* The cursor was inside a parameter assignment. */
+	    for (s = tt; iident(*s); s++);
+	    if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb)
+		s = NULL, inwhat = IN_MATH;
+	    else if (*s == '=') {
+		s++;
+		wb += s - tt;
+		t0 = STRING;
+		s = ztrdup(s);
+		inwhat = IN_ENV;
+	    }
+	    lincmd = 1;
+	}
+	if (we > ll)
+	    we = ll;
+	tt = (char *)line;
+	if (tmp) {
+	    line = (unsigned char *)tmp;
+	    ll = strlen((char *)line);
+	}
+	if (t0 != STRING && inwhat != IN_MATH) {
+	    if (tmp) {
+		tmp = NULL;
+		linptr = (char *)line;
+		lexrestore();
+		goto start;
+	    }
+	    feep();
+	    noaliases = 0;
+	    lexrestore();
+	    LASTALLOC_RETURN NULL;
+	}
+
+	noaliases = 0;
+
+	/* Check if we are in an array subscript.  We simply assume that  *
+	 * we are in a subscript if we are in brackets.  Correct solution *
+	 * is very difficult.  This is quite close, but gets things like  *
+	 * foo[_ wrong (note no $).  If we are in a subscript, treat it   *
+	 * as being in math.                                              */
+	if (inwhat != IN_MATH) {
+	    int i = 0;
+	    for (tt = s; ++tt < s + cs - wb;)
+		if (*tt == Inbrack)
+		    i++;
+		else if (i && *tt == Outbrack)
+		    i--;
+	    if (i)
+		inwhat = IN_MATH;
+	}
+	if (inwhat == IN_MATH) {
+	    /* In mathematical expression, we complete parameter names (even *
+	     * if they don't have a `$' in front of them).  So we have to    *
+	     * find that name.                                               */
+	    for (we = cs; iident(line[we]); we++);
+	    for (wb = cs; --wb >= 0 && iident(line[wb]););
+	    wb++;
+	    zsfree(s);
+	    s = zalloc(we - wb + 1);
+	    strncpy(s, (char *) line + wb, we - wb);
+	    s[we - wb] = '\0';
+	}
+	/* This variable will hold the current word in quoted form. */
+	qword = ztrdup(s);
+	/* While building the quoted form, we also clean up the command line. */
+	offs = cs - wb;
+	for (p = s, tt = qword, i = wb; *p; p++, tt++, i++)
+	    if (INULL(*p)) {
+		if (i < cs)
+		    offs--;
+		if (p[1] || *p != Bnull) {
+		    if (*p == Bnull) {
+			*tt = '\\';
+			if (cs == i + 1)
+			    cs++, offs++;
+		    } else {
+			ocs = cs;
+			cs = i;
+			foredel(1);
+			chuck(tt--);
+			if ((cs = ocs) > i--)
+			    cs--;
+			we--;
+		    }
+		} else {
+		    ocs = cs;
+		    *tt = '\0';
+		    cs = we;
+		    backdel(1);
+		    if (ocs == we)
+			cs = we - 1;
+		    else
+			cs = ocs;
+		    we--;
+		}
+		chuck(p--);
+	    }
+
+	if (!isset(IGNOREBRACES)) {
+	    /* Try and deal with foo{xxx etc.; only simple cases
+	     * (only one inbrace, completion after inbrace and before outbrace
+	     * if present).
+	     */
+	    int myoffs = isset(COMPLETEINWORD) ? offs : strlen(s);
+	    tt = NULL;
+	    /* First check the conditions mentioned above
+	     * and locate opening brace
+	     */
+	    for (i = 0, p = s; *p; p++, i++) {
+		/* careful, ${... is not a brace expansion...
+		 * in fact, if it's got a substitution in it's too
+		 * hard for us anyway.  sorry.
+		 */
+		if (*p == String || *p == Qstring) {
+		    tt = NULL;
+		    break;
+		} else if (*p == Inbrace) {
+		    if (tt) {
+			/* too many inbraces */
+			tt = NULL;
+			break;
+		    }
+		    tt = p;
+		} else if (*p == Outbrace && i < myoffs) {
+		    /* outbrace is before cursor pos, so nothing to complete */
+		    tt = NULL;
+		    break;
+		}
+	    }
+
+	    if (tt && tt < s + myoffs) {
+		/* Braces are go:  delete opening brace */
+		char *com = NULL;
+		chuck(tt);
+		offs--;
+		myoffs--;
+
+		/* Look for text up to comma before cursor and delete it */
+		for (i = tt - s, p = tt; *p && i < myoffs; p++, i++)
+		    if (*p == Comma)
+			com = p;
+		if (com) {
+		    i = com - tt + 1;
+		    while (i--)
+			chuck(tt), offs--, myoffs--;
+		}
+
+		/* Look for text between subsequent comma
+		 * and closing brace or end of string and delete it
+		 */
+		for (p = s + myoffs; *p && *p != Outbrace; p++)
+		    if (*p == Comma) {
+			while (*p && *p != Outbrace)
+			    chuck(p);
+			break;
+		    }
+		if (*p == Outbrace)
+		    chuck(p);
+		else {
+		    /* we are still waiting for an outbrace and maybe commas */
+		    complinbrace = 1;
+		}
+	    }
+	}
+
+    } LASTALLOC;
+    lexrestore();
+
+    return (char *)s;
+}
+
+/* Expand the current word. */
+
+/**/
+static void
+doexpansion(char *s, int lst, int olst, int explincmd)
+{
+    LinkList vl;
+    char *ss;
+
+    DPUTS(useheap, "BUG: useheap in doexpansion()");
+    HEAPALLOC {
+	pushheap();
+	vl = newlinklist();
+	ss = dupstring(s);
+	addlinknode(vl, ss);
+	prefork(vl, 0);
+	if (errflag)
+	    goto end;
+	if ((lst == COMP_LIST_EXPAND) || (lst == COMP_EXPAND)) {
+	    int ng = opts[NULLGLOB];
+
+	    opts[NULLGLOB] = 1;
+	    globlist(vl);
+	    opts[NULLGLOB] = ng;
+	}
+	if (errflag)
+	    goto end;
+	if (empty(vl) || !*(char *)peekfirst(vl)) {
+	    if (!noerrs)
+		feep();
+	    goto end;
+	}
+	if (peekfirst(vl) == (void *) ss ||
+		(olst == COMP_EXPAND_COMPLETE &&
+		 !nextnode(firstnode(vl)) && *s == Tilde &&
+		 (ss = dupstring(s), filesubstr(&ss, 0)) &&
+		 !strcmp(ss, (char *)peekfirst(vl)))) {
+	    /* If expansion didn't change the word, try completion if *
+	     * expandorcomplete was called, otherwise, just beep.     */
+	    if (lst == COMP_EXPAND_COMPLETE)
+		docompletion(s, COMP_COMPLETE, explincmd, 0);
+	    else
+		feep();
+	    goto end;
+	}
+	if (lst == COMP_LIST_EXPAND) {
+	    /* Only the list of expansions was requested. */
+	    listlist(vl);
+	    goto end;
+	}
+	/* Remove the current word and put the expansions there. */
+	cs = wb;
+	foredel(we - wb);
+	while ((ss = (char *)ugetnode(vl))) {
+	    untokenize(ss);
+	    ss = quotename(ss, NULL, NULL, NULL);
+	    inststr(ss);
+#if 0
+	    if (nonempty(vl)) {
+		spaceinline(1);
+		line[cs++] = ' ';
+	    }
+#endif
+	    if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) ||
+		(cs && line[cs-1] != '/')) {
+		spaceinline(1);
+		line[cs++] = ' ';
+	    }
+	}
+      end:
+	popheap();
+    } LASTALLOC;
+}
+
+/* This is called from the lexer to give us word positions. */
+
+/**/
+void
+gotword(void)
+{
+    we = ll + 1 - inbufct + (addedx == 2 ? 1 : 0);
+    if (cs <= we) {
+	wb = ll - wordbeg + addedx;
+	zleparse = 0;
+    }
+}
+
+/* Insert the given string into the command line.  If move is non-zero, *
+ * the cursor position is changed and len is the length of the string   *
+ * to insert (if it is -1, the length is calculated here).              */
+
+/**/
+static 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 (move)
+	cs += len;
+}
+
+/* Quote the string s and return the result.  If e is non-zero, it the    *
+ * pointer it points to may point to aposition in s and in e the position *
+ * of the corresponding character in the quoted string is returned.  Like *
+ * e, te may point to a position in the string and pl is used to return   *
+ * the position of the character pointed to by te in the quoted string.   *
+ * The string is metafied and may contain tokens.                         */
+
+/**/
+static char *
+quotename(const char *s, char **e, char *te, int *pl)
+{
+    const char *u, *tt;
+    char *v, buf[PATH_MAX * 2];
+    int sf = 0;
+
+    tt = v = buf;
+    u = s;
+    for (; *u; u++) {
+	if (e && *e == u)
+	    *e = v, sf |= 1;
+	if (te == u)
+	    *pl = v - tt, sf |= 2;
+	if (ispecial(*u) &&
+	    (!instring || (isset(BANGHIST) &&
+			   *u == (char)bangchar) ||
+	     (instring == 2 &&
+	      (*u == '$' || *u == '`' || *u == '\"')) ||
+	     (instring == 1 && *u == '\'')))
+	    if (*u == '\n' || (instring == 1 && *u == '\'')) {
+		if (unset(RCQUOTES)) {
+		    *v++ = '\'';
+		    if (*u == '\'')
+			*v++ = '\\';
+		    *v++ = *u;
+		    *v++ = '\'';
+		} else if (*u == '\n')
+		    *v++ = '"', *v++ = '\n', *v++ = '"';
+		else
+		    *v++ = '\'', *v++ = '\'';
+		continue;
+	    } else
+		*v++ = '\\';
+	if(*u == Meta)
+	    *v++ = *u++;
+	*v++ = *u;
+    }
+    *v = '\0';
+    if (strcmp(buf, s))
+	tt = dupstring(buf);
+    else
+	tt = s;
+    v += tt - buf;
+    if (e && (sf & 1))
+	*e += tt - buf;
+
+    if (e && *e == u)
+	*e = v;
+    if (te == u)
+	*pl = v - tt;
+
+    return (char *) tt;
+}
+
+/* This adds a match to the list of matches.  The string to add is given   *
+ * in s, the type of match is given in the global variable addwhat and     *
+ * the parameter t (if not NULL) is a pointer to a hash node node which    *
+ * may be used to give other information to this function.                 *
+ *                                                                         *
+ * addwhat contains either one of the special values (negative, see below) *
+ * or the inclusive OR of some of the CC_* flags used for compctls.        */
+
+/**/
+static void
+addmatch(char *s, char *t)
+{
+    int test = 0, sl = strlen(s), pl = rpl, cc = 0, *bp, *ep, *sp;
+    char *e = NULL, *tt, *te, *fc, **fm;
+    Comp cp = patcomp;
+    HashNode hn;
+    Param pm;
+    LinkList l = matches;
+
+/*
+ * addwhat: -5 is for files,
+ *          -6 is for glob expansions,
+ *          -8 is for executable files (e.g. command paths),
+ *          -9 is for parameters
+ *          -7 is for command names (from cmdnamtab)
+ *          -4 is for a cdable parameter
+ *          -3 is for executable command names.
+ *          -2 is for anything unquoted
+ *          -1 is for other file specifications
+ *          (things with `~' of `=' at the beginning, ...).
+ */
+
+    /* Just to make the code cleaner */
+    hn = (HashNode) t;
+    pm = (Param) t;
+
+    if (!addwhat) {
+	test = 1;
+    } else if (addwhat == -1 || addwhat == -5 || addwhat == -6 ||
+	       addwhat == CC_FILES || addwhat == -7 || addwhat == -8) {
+	if (sl < fpl + fsl)
+	    return;
+
+	if ((addwhat == CC_FILES ||
+	     addwhat == -5) && !*psuf && !*fsuf) {
+	    /* If this is a filename, do the fignore check. */
+	    char **pt = fignore;
+	    int filell;
+
+	    for (test = 1; test && *pt; pt++)
+		if ((filell = strlen(*pt)) < sl
+		    && !strcmp(*pt, s + sl - filell))
+		    test = 0;
+
+	    if (!test)
+		l = fmatches;
+	}
+	pl = fpl;
+	if (addwhat == -5 || addwhat == -8) {
+	    test = 1;
+	    cp = filecomp;
+	    cc = cp || ispattern;
+	    e = s + sl - fsl;
+	} else {
+	    if ((cp = filecomp)) {
+		if ((test = domatch(s, filecomp, 0)))
+		    cc = 1;
+	    } else {
+		e = s + sl - fsl;
+		if ((test = !strncmp(s, fpre, fpl)))
+		    test = !strcmp(e, fsuf);
+		if (ispattern)
+		    cc = 1;
+	    }
+	}
+	if (test) {
+	    fc = NULL;
+	    if (addwhat == -7 && !(fc = findcmd(s)))
+		return;
+	    if (fc)
+		zsfree(fc);
+	    haswhat |= HAS_FILES;
+
+	    if (addwhat == CC_FILES || addwhat == -6 ||
+		addwhat == -5 || addwhat == -8) {
+		te = s + pl;
+		s = quotename(s, &e, te, &pl);
+		sl = strlen(s);
+	    } else if (!cc) {
+		s = dupstring(t = s);
+		e += s - t;
+	    }
+	    if (cc) {
+		tt = (char *)halloc(strlen(ppre) + strlen(psuf) + sl + 1);
+		strcpy(tt, ppre);
+		strcat(tt, s);
+		strcat(tt, psuf);
+		untokenize(s = tt);
+	    }
+	}
+    } else if (addwhat == CC_QUOTEFLAG || addwhat == -2  ||
+	      (addwhat == -3 && !(hn->flags & DISABLED)) ||
+	      (addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) &&
+	       (tt = pm->gets.cfn(pm)) && *tt == '/')    ||
+	      (addwhat == -9 && !(hn->flags & PM_UNSET)) ||
+	      (addwhat > 0 &&
+	       ((!(hn->flags & PM_UNSET) &&
+		 (((addwhat & CC_ARRAYS)    &&  (hn->flags & PM_ARRAY))    ||
+		  ((addwhat & CC_INTVARS)   &&  (hn->flags & PM_INTEGER))  ||
+		  ((addwhat & CC_ENVVARS)   &&  (hn->flags & PM_EXPORTED)) ||
+		  ((addwhat & CC_SCALARS)   &&  (hn->flags & PM_SCALAR))   ||
+		  ((addwhat & CC_READONLYS) &&  (hn->flags & PM_READONLY)) ||
+		  ((addwhat & CC_SPECIALS)  &&  (hn->flags & PM_SPECIAL))  ||
+		  ((addwhat & CC_PARAMS)    && !(hn->flags & PM_EXPORTED)))) ||
+		((( addwhat & CC_SHFUNCS)				  ||
+		  ( addwhat & CC_BUILTINS)				  ||
+		  ( addwhat & CC_EXTCMDS)				  ||
+		  ( addwhat & CC_RESWDS)				  ||
+		  ((addwhat & CC_ALREG)   && !(hn->flags & ALIAS_GLOBAL)) ||
+		  ((addwhat & CC_ALGLOB)  &&  (hn->flags & ALIAS_GLOBAL))) &&
+		 (((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) ||
+		  ((addwhat & CC_EXCMDS)  && !(hn->flags & DISABLED)))) ||
+		((addwhat & CC_BINDINGS) && !(hn->flags & DISABLED))))) {
+	if (sl >= rpl + rsl) {
+	    if (cp)
+		test = domatch(s, patcomp, 0);
+	    else {
+		e = s + sl - rsl;
+		if ((test = !strncmp(s, rpre, rpl)))
+		    test = !strcmp(e, rsuf);
+	    }
+	}
+	if (!test && sl < lpl + lsl)
+	    return;
+	if (!test && lpre && lsuf && sl >= lpl + lsl) {
+	    e = s + sl - lsl;
+	    if ((test = !strncmp(s, lpre, lpl)))
+		test = !strcmp(e, lsuf);
+	    pl = lpl;
+	}
+	if (addwhat == CC_QUOTEFLAG) {
+	    te = s + pl;
+	    s = quotename(s, &e, te, &pl);
+	    sl = strlen(s);
+	}
+	if (test)
+	    haswhat |= HAS_MISC;
+    }
+    if (!test)
+	return;
+
+    if (ispattern) {
+	t = s;
+    } else {
+	t = s += pl;
+	if (*e)
+	    t = s = dupstrpfx(t, e - t);
+    }
+
+    if (l == fmatches) {
+	bp = &fab;
+	ep = &fae;
+	sp = &fshortl;
+	fm = &ffirstm;
+    } else {
+	bp = &ab;
+	ep = &ae;
+	sp = &shortl;
+	fm = &firstm;
+    }
+
+    if (!ispattern && *fm) {
+	if ((test = pfxlen(*fm, s)) < *bp)
+	    *bp = test;
+	if ((test = sfxlen(*fm, s)) < *ep)
+	    *ep = test;
+	if (*ep > *sp - *bp)
+	    *ep = *sp - *bp;
+    }
+
+    /* If we are doing a glob completion we store the whole string in *
+     * the list. Otherwise only the part that fits between the prefix *
+     * and the suffix is stored.                                      */
+    addlinknode(l, t);
+    if (!*fm) {
+	*bp = *ep = 10000;
+	*fm = t;
+	*sp = 100000;
+    }
+    if (!ispattern && (sl = strlen(t)) < *sp) {
+	*sp = sl;
+	if (l == fmatches)
+	    fshortest = t;
+	else
+	    shortest = t;
+    }
+}
+
+#ifdef HAVE_NIS_PLUS
+static int
+match_username(nis_name table, nis_object *object, void *userdata)
+{
+    if (errflag)
+	return 1;
+    else {
+	static char buf[40];
+	register entry_col *ec =
+	    object->zo_data.objdata_u.en_data.en_cols.en_cols_val;
+	register int l = minimum(ec->ec_value.ec_value_len, 39);
+
+	memcpy(buf, ec->ec_value.ec_value_val, l);
+	buf[l] = '\0';
+
+	addmatch(dupstring(buf), NULL);
+    }
+    return 0;
+}
+#else
+# ifdef HAVE_NIS
+static int
+match_username(int status, char *key, int keylen, char *val, int vallen, dopestring *data)
+{
+    if (errflag || status != YP_TRUE)
+	return 1;
+
+    if (vallen > keylen && val[keylen] == ':') {
+	val[keylen] = '\0';
+	addmatch(dupstring(val), NULL);
+    }
+    return 0;
+}
+# endif /* HAVE_NIS */
+#endif  /* HAVE_NIS_PLUS */
+
+/**/
+static void
+maketildelist(void)
+{
+#if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS)
+    FILE *pwf;
+    char buf[BUFSIZ], *p;
+    int skipping;
+
+# ifndef HAVE_NIS_PLUS
+    char domain[YPMAXDOMAIN];
+    struct ypall_callback cb;
+    dopestring data;
+
+    data.s = fpre;
+    data.len = fpl;
+    /* Get potential matches from NIS and cull those without local accounts */
+    if (getdomainname(domain, YPMAXDOMAIN) == 0) {
+	cb.foreach = (int (*)()) match_username;
+	cb.data = (char *)&data;
+	yp_all(domain, PASSWD_MAP, &cb);
+    }
+# else  /* HAVE_NIS_PLUS */
+       /* Maybe we should turn this string into a #define'd constant...? */
+
+    nis_list("passwd.org_dir", EXPAND_NAME|ALL_RESULTS|FOLLOW_LINKS|FOLLOW_PATH,
+	     match_username, 0);
+# endif
+    /* Don't forget the non-NIS matches from the flat passwd file */
+    if ((pwf = fopen(PASSWD_FILE, "r")) != NULL) {
+	skipping = 0;
+	while (fgets(buf, BUFSIZ, pwf) != NULL) {
+	    if (strchr(buf, '\n') != NULL) {
+		if (!skipping) {
+		    if ((p = strchr(buf, ':')) != NULL) {
+			*p = '\0';
+			addmatch(dupstring(buf), NULL);
+		    }
+		} else
+		    skipping = 0;
+	    } else
+		skipping = 1;
+	}
+	fclose(pwf);
+    }
+#else  /* no NIS or NIS_PLUS */
+    /* add all the usernames to the named directory table */
+    nameddirtab->filltable(nameddirtab);
+#endif
+
+    scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0,
+	    addhnmatch, 0);
+}
+
+/* Copy the given string and remove backslashes from the copy and return it. */
+
+/**/
+static 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. */
+
+/**/
+static int
+getcpat(char *wrd, int cpatindex, char *cpat, int class)
+{
+    char *str, *s, *t, *p;
+    int d = 0;
+
+    if (!wrd || !*wrd)
+	return -1;
+
+    cpat = rembslash(cpat);
+
+    str = ztrdup(wrd);
+    untokenize(str);
+    if (!cpatindex)
+	cpatindex++, d = 0;
+    else if ((d = (cpatindex < 0)))
+	cpatindex = -cpatindex;
+
+    for (s = d ? str + strlen(str) - 1 : str;
+	 d ? (s >= str) : *s;
+	 d ? s-- : s++) {
+	for (t = s, p = cpat; *t && *p; p++) {
+	    if (class) {
+		if (*p == *s && !--cpatindex) {
+		    zsfree(str);
+		    return (int)(s - str + 1);
+		}
+	    } else if (*t++ != *p)
+		break;
+	}
+	if (!class && !*p && !--cpatindex) {
+	    zsfree(str);
+	    t += wrd - str;
+	    for (d = 0; --t >= wrd;)
+		if (! INULL(*t))
+		    d++;
+	    return d;
+	}
+    }
+    zsfree(str);
+    return -1;
+}
+
+/* This holds a pointer to the compctl we are using. */
+
+static 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.                            */
+
+/**/
+static Compctl
+get_ccompctl(Compctl occ, int *compadd, int incmd)
+{
+    Compctl compc, ret;
+    Compctlp ccp;
+    int t, i, a, b, tt, ra, rb, j, isf = 1;
+    Compcond or, cc;
+    char *s, *ss, *sc, *cmd = dupstring(cmdstr);
+    Comp comp;
+
+   first_rec:
+    *compadd = 0;
+    ra = 0;
+    rb = clwnum - 1;
+    sc = NULL;
+
+    if (!(ret = compc = occ)) {
+      if (isf) {
+        isf = 0;
+        ret = &cc_first;
+      }
+      else if (inwhat == IN_ENV)
+        /* Default completion for parameter values. */
+        ret = &cc_default;
+      else if (inwhat == IN_MATH) {
+        /* Parameter names inside mathematical expression. */
+        cc_dummy.mask = CC_PARAMS;
+	    ret = &cc_dummy;
+	    cc_dummy.refc = 10000;
+	} else if (inwhat == IN_COND) {
+	    /* We try to be clever here: in conditions we complete option   *
+	     * names after a `-o', file names after `-nt', `-ot', and `-ef' *
+	     * and file names and parameter names elsewhere.                */
+	    s = clwpos ? clwords[clwpos - 1] : "";
+	    cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS :
+		((*s == '-' && s[1] && !s[2]) ||
+		 !strcmp("-nt", s) ||
+		 !strcmp("-ot", s) ||
+		 !strcmp("-ef", s)) ? CC_FILES :
+		(CC_FILES | CC_PARAMS);
+	    ret = &cc_dummy;
+	    cc_dummy.refc = 10000;
+	} else if (incmd)
+	    ret = &cc_compos;
+	/* And in redirections or if there is no command name (and we are *
+	 * not in command position) or if no special compctl was given    *
+	 * for the command: use default completion.  Note that we first   *
+	 * search the complete command name and than the trailing         *
+	 * pathname component.                                            */
+	else if (linredir ||
+ 		 !(cmd &&
+ 		   (((ccp = (Compctlp) compctltab->getnode(compctltab, cmd)) &&
+		     (compc = ret = ccp->cc)) ||
+ 		    ((s = dupstring(cmd)) && remlpaths(&s) &&
+		     (ccp = (Compctlp) compctltab->getnode(compctltab, s)) &&
+		     (compc = ret = ccp->cc)))))
+	    ret = &cc_default;
+
+	ccmain = compc = ret;
+	ccmain->refc++;
+    }
+    /* The compctl we found has extended completion patterns, check them. */
+    if (compc && compc->ext) {
+	compc = compc->ext;
+	/* This loops over the patterns separated by `--'. */
+	for (t = 0; compc && !t; compc = compc->next) {
+	    /* This loops over OR'ed patterns. */
+	    for (cc = compc->cond; cc && !t; cc = or) {
+		or = cc->or;
+		/* This loops over AND'ed patterns. */
+		for (t = 1; cc && t; cc = cc->and) {
+		    /* And this loops of [...] pairs. */
+		    for (t = i = 0; i < cc->n && !t; i++) {
+			s = NULL;
+			ra = 0;
+			rb = clwnum - 1;
+			switch (cc->type) {
+			case CCT_POS:
+			    tt = clwpos;
+			    goto cct_num;
+			case CCT_NUMWORDS:
+			    tt = clwnum;
+			  cct_num:
+			    if ((a = cc->u.r.a[i]) < 0)
+				a += clwnum;
+			    if ((b = cc->u.r.b[i]) < 0)
+				b += clwnum;
+			    if (cc->type == CCT_POS)
+				ra = a, rb = b;
+			    t = (tt >= a && tt <= b);
+			    break;
+			case CCT_CURSUF:
+			case CCT_CURPRE:
+			    s = ztrdup(clwpos < clwnum ? clwords[clwpos] : "");
+			    untokenize(s);
+			    sc = rembslash(cc->u.s.s[i]);
+			    a = strlen(sc);
+			    if (!strncmp(s, sc, a)) {
+				*compadd = (cc->type == CCT_CURSUF ? a : 0);
+				t = 1;
+			    }
+			    break;
+			case CCT_CURSUB:
+			case CCT_CURSUBC:
+			    if (clwpos < 0 || clwpos > clwnum)
+				t = 0;
+			    else {
+				a = getcpat(clwords[clwpos],
+					    cc->u.s.p[i],
+					    cc->u.s.s[i],
+					    cc->type == CCT_CURSUBC);
+				if (a != -1)
+				    *compadd = a, t = 1;
+			    }
+			    break;
+
+			case CCT_CURPAT:
+			case CCT_CURSTR:
+			    tt = clwpos;
+			    goto cct_str;
+			case CCT_WORDPAT:
+			case CCT_WORDSTR:
+			    tt = 0;
+			  cct_str:
+			    if ((a = tt + cc->u.s.p[i]) < 0)
+				a += clwnum;
+			    s = ztrdup((a < 0 || a >= clwnum) ? "" :
+				       clwords[a]);
+			    untokenize(s);
+
+			    if (cc->type == CCT_CURPAT ||
+				cc->type == CCT_WORDPAT) {
+				tokenize(ss = dupstring(cc->u.s.s[i]));
+				t = ((comp = parsereg(ss)) &&
+				     domatch(s, comp, 0));
+			    } else
+				t = (!strcmp(s, rembslash(cc->u.s.s[i])));
+			    break;
+			case CCT_RANGESTR:
+			case CCT_RANGEPAT:
+			    if (cc->type == CCT_RANGEPAT)
+				tokenize(sc = dupstring(cc->u.l.a[i]));
+			    for (j = clwpos; j; j--) {
+				untokenize(s = ztrdup(clwords[j]));
+				if (cc->type == CCT_RANGESTR)
+				    sc = rembslash(cc->u.l.a[i]);
+				if (cc->type == CCT_RANGESTR ?
+				    !strncmp(s, sc, strlen(sc)) :
+				    ((comp = parsereg(sc)) &&
+				     domatch(s, comp, 0))) {
+				    zsfree(s);
+				    ra = j + 1;
+				    t = 1;
+				    break;
+				}
+				zsfree(s);
+			    }
+			    if (t) {
+				if (cc->type == CCT_RANGEPAT)
+				    tokenize(sc = dupstring(cc->u.l.b[i]));
+				for (j++; j < clwnum; j++) {
+				    untokenize(s = ztrdup(clwords[j]));
+				    if (cc->type == CCT_RANGESTR)
+					sc = rembslash(cc->u.l.b[i]);
+				    if (cc->type == CCT_RANGESTR ?
+					!strncmp(s, sc, strlen(sc)) :
+					((comp = parsereg(sc)) &&
+					 domatch(s, comp, 0))) {
+					zsfree(s);
+					rb = j - 1;
+					t = clwpos <= rb;
+					break;
+				    }
+				    zsfree(s);
+				}
+			    }
+			    s = NULL;
+			}
+			zsfree(s);
+		    }
+		}
+	    }
+	    if (t)
+		break;
+	}
+	if (compc)
+	    /* We found a matching pattern, we may return it. */
+	    ret = compc;
+    }
+    if (ret->subcmd) {
+	/* The thing we want to return has a subcmd flag (-l). */
+	char **ow = clwords, *os = cmdstr, *ops = NULL;
+	int oldn = clwnum, oldp = clwpos;
+
+	/* So we restrict the words-array. */
+	if (ra >= clwnum)
+	    ra = clwnum - 1;
+	if (ra < 1)
+	    ra = 1;
+	if (rb >= clwnum)
+	    rb = clwnum - 1;
+	if (rb < 1)
+	    rb = 1;
+	clwnum = rb - ra + 1;
+	clwpos = clwpos - ra;
+
+	if (ret->subcmd[0]) {
+	    /* And probably put the command name given to the flag *
+	     * in the array.                                       */
+	    clwpos++;
+	    clwnum++;
+	    incmd = 0;
+	    ops = clwords[ra - 1];
+	    clwords[ra - 1] = cmdstr = ret->subcmd;
+	    clwords += ra - 1;
+	} else {
+	    cmdstr = clwords[ra];
+	    incmd = !clwpos;
+	    clwords += ra;
+	}
+	*compadd = 0;
+	if (ccmain != &cc_dummy)
+	    freecompctl(ccmain);
+	/* Then we call this function recursively. */
+
+	ret = get_ccompctl(NULL, compadd, incmd);
+	/* And restore the things we changed. */
+	clwords = ow;
+	cmdstr = os;
+	clwnum = oldn;
+	clwpos = oldp;
+	if (ops)
+	    clwords[ra - 1] = ops;
+    }
+    if (ret == &cc_first)
+      goto first_rec;
+    return ret;
+}
+
+/* Dump a hash table (without sorting).  For each element the addmatch  *
+ * function is called and at the beginning the addwhat variable is set. *
+ * This could be done using scanhashtable(), but this is easy and much  *
+ * more efficient.                                                      */
+
+/**/
+static void
+dumphashtable(HashTable ht, int what)
+{
+    HashNode hn;
+    int i;
+
+    addwhat = what;
+
+    for (i = 0; i < ht->hsize; i++)
+	for (hn = ht->nodes[i]; hn; hn = hn->next)
+	    addmatch(hn->nam, (char *) hn);
+
+}
+
+/* ScanFunc used by maketildelist() et al. */
+
+/**/
+static void
+addhnmatch(HashNode hn, int flags)
+{
+    addmatch(hn->nam, NULL);
+}
+
+/* Perform expansion on the given string and return the result. *
+ * During this errors are not reported.                         */
+
+/**/
+static char *
+getreal(char *str)
+{
+    LinkList l = newlinklist();
+    int ne = noerrs;
+
+    noerrs = 1;
+    addlinknode(l, dupstring(str));
+    prefork(l, 0);
+    noerrs = ne;
+    if (!errflag && nonempty(l))
+	return ztrdup(peekfirst(l));
+    errflag = 0;
+
+    return ztrdup(str);
+}
+
+/* This reads a directory and adds the files to the list of  *
+ * matches.  The parameters say which files should be added. */
+
+/**/
+static void
+gen_matches_files(int dirs, int execs, int all)
+{
+    DIR *d;
+    struct stat buf;
+    char *n, p[PATH_MAX], *q = NULL, *e;
+    LinkList l = NULL;
+    int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat;
+
+    addwhat = execs ? -8 : -5;
+    opts[NULLGLOB] = 1;
+
+    if (*psuf) {
+	/* If there is a path suffix, check if it doesn't have a `*' or *
+	 * `)' at the end (this is used to determine if we should use   *
+	 * globbing).                                                   */
+	q = psuf + strlen(psuf) - 1;
+	ns = !(*q == Star || *q == Outpar);
+	l = newlinklist();
+	/* And generate only directory names. */
+	dirs = 1;
+	all = execs = 0;
+    }
+    /* Open directory. */
+    if ((d = opendir((prpre && *prpre) ? prpre : "."))) {
+	/* If we search only special files, prepare a path buffer for stat. */
+	if (!all && prpre) {
+	    strcpy(p, prpre);
+	    q = p + strlen(prpre);
+	}
+	/* Fine, now read the directory. */
+	while ((n = zreaddir(d, 1)) && !errflag) {
+	    /* 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 && S_ISDIR(buf.st_mode)) ||
+		    (execs && S_ISREG(buf.st_mode) && (buf.st_mode&S_IXUGO))) {
+		    /* If we want all files or the file has the right type... */
+		    if (*psuf) {
+			/* We have to test for a path suffix. */
+			int o = strlen(p), tt;
+
+			/* Append it to the path buffer. */
+			strcpy(p + o, psuf);
+
+			/* Do we have to use globbing? */
+			if (ispattern || (ns && isset(GLOBCOMPLETE))) {
+			    /* Yes, so append a `*' if needed. */
+			    if (ns) {
+				int tl = strlen(p);
+
+				p[tl] = Star;
+				p[tl + 1] = '\0';
+			    }
+			    /* Do the globbing... */
+			    remnulargs(p);
+			    addlinknode(l, p);
+			    globlist(l);
+			    /* And see if that produced a filename. */
+			    tt = nonempty(l);
+			    while (ugetnode(l));
+			} else
+			    /* Otherwise just check, if we have access *
+			     * to the file.                            */
+			    tt = !access(p, F_OK);
+
+			p[o] = '\0';
+			if (tt)
+			    /* Ok, we can add the filename to the *
+			     * list of matches.                   */
+			    addmatch(dupstring(n), NULL);
+		    } else
+			/* We want all files, so just add the name *
+			 * to the matches.                         */
+			addmatch(dupstring(n), NULL);
+		}
+	    }
+	}
+	closedir(d);
+    }
+    opts[NULLGLOB] = ng;
+    addwhat = aw;
+}
+
+/* This holds the explanation string we have to print. */
+
+static char *expl;
+
+/* This holds the suffix to add (given with compctl -S). */
+
+static 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).      */
+
+static int remsuffix;
+
+/**/
+static void
+quotepresuf(char **ps)
+{
+    if (*ps) {
+	char *p = quotename(*ps, NULL, NULL, NULL);
+
+	if (p != *ps) {
+	    zsfree(*ps);
+	    *ps = ztrdup(p);
+	}
+    }
+}
+
+/**/
+static void
+docompletion(char *s, int lst, int incmd, int untokenized)
+{
+    static int delit, compadd;
+
+    fixsuffix();
+    HEAPALLOC {
+	pushheap();
+
+	/* Make sure we have the completion list and compctl. */
+	if(makecomplist(s, incmd, &delit, &compadd, untokenized)) {
+	    /* Error condition: feeeeeeeeeeeeep(). */
+	    feep();
+	    goto compend;
+	}
+
+	if (lst == COMP_LIST_COMPLETE)
+	    /* All this and the guy only wants to see the list, sigh. */
+	    showinglist = -2;
+	else {
+	    /* We have matches. */
+	    if (delit) {
+		/* If we have to delete the word from the command line, *
+		 * do it now.                                           */
+		wb -= compadd;
+		strcpy((char *)line + wb, (char *)line + we);
+		we = cs = wb;
+	    }
+	    if (nmatches > 1)
+		/* There are more than one match. */
+		do_ambiguous();
+	    else if (nmatches == 1) {
+		/* Only one match. */
+		do_single(amatches[0]);
+		invalidatelist();
+	    }
+	}
+
+	/* Print the explanation string if needed. */
+	if (!showinglist && expl && nmatches != 1) {
+	    int up;
+
+	    if (!nmatches)
+		feep();
+	    trashzle();
+
+	    clearflag = (isset(USEZLE) && !termflags &&
+			 (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
+			(unset(ALWAYSLASTPROMPT) && zmult != 1);
+
+	    up = printfmt(expl, nmatches, 1);
+
+	    if (clearflag)
+		tcmultout(TCUP, TCMULTUP, up + nlnct);
+	    else
+		putc('\n', shout);
+	    fflush(shout);
+	}
+      compend:
+	ll = strlen((char *)line);
+	if (cs > ll)
+	    cs = ll;
+	popheap();
+    } LASTALLOC;
+}
+
+/* Create the completion list.  This is called whenever some bit of  *
+ * completion code needs the list.  If the list is already available *
+ * (validlist!=0), this function doesn't do anything.  Along with    *
+ * the list is maintained the prefixes/suffixes etc.  When any of    *
+ * this becomes invalid -- e.g. if some text is changed on the       *
+ * command line -- invalidatelist() should be called, to set         *
+ * validlist to zero and free up the memory used.  This function     *
+ * returns non-zero on error.  delit and compadd return information  *
+ * about bits of the command line that need to be deleted.           */
+
+/**/
+static int
+makecomplist(char *s, int incmd, int *delit, int *compadd, int untokenized)
+{
+    Compctl cc = NULL;
+    int oloffs = offs, owe = we, owb = wb, ocs = cs, oll = ll, isf = 1;
+    int t, sf1, sf2, ooffs;
+    char *p, *sd = NULL, *tt, *s1, *s2, *os = NULL;
+    unsigned char *ol = NULL;
+
+    /* If we already have a list from a previous execution of this *
+     * function, skip the list building code.                      */
+    if (validlist)
+	return !nmatches;
+
+    os = dupstring(s);
+    ol = (unsigned char *)dupstring((char *)line);
+
+  xorrec:
+
+    DPUTS(ll != strlen((char *) line), "BUG: xorrec: ll != strlen(line)");
+
+    /* Go to the end of the word if complete_in_word is not set. */
+    if (unset(COMPLETEINWORD) && cs != we)
+	cs = we, offs = strlen(s);
+
+    ispattern = haswhat = lastambig = 0;
+    patcomp = filecomp = NULL;
+    menucur = NULL;
+    shortest = NULL;
+    fshortest = NULL;
+    rpre = rsuf = lpre = lsuf = ppre = psuf = prpre =
+	fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL;
+
+    /* Blank out the lists. */
+    matches = newlinklist();
+    fmatches = newlinklist();
+
+    /* If we don't have a compctl definition yet or we have a compctl *
+     * with extended completion, get it (or the next one, resp.).     */
+    if (!cc || cc->ext)
+	cc = get_ccompctl(cc, compadd, incmd);
+
+    /* *compadd is the number of characters we have to ignore at the *
+     * beginning of the word.                                        */
+    wb += *compadd;
+    s += *compadd;
+    if ((offs -= *compadd) < 0)
+	/* It's bigger than our word prefix, so we can't help here... */
+	return 1;
+
+    /* Insert the prefix (compctl -P), if any. */
+    if (cc->prefix) {
+	int pl = 0, sl = strlen(cc->prefix);
+
+	if (*s) {
+	    /* First find out how much of the prefix is already on the line. */
+	    sd = dupstring(s);
+	    untokenize(sd);
+	    pl = pfxlen(cc->prefix, sd);
+	    s += pl;
+	}
+	if (pl < sl) {
+	    int savecs = cs;
+
+	    /* Then insert the prefix. */
+	    cs = wb + pl;
+	    inststrlen(cc->prefix + pl, 0, sl - pl);
+	    cs = savecs + sl - pl;
+	}
+	/* And adjust the word beginning/end variables. */
+	wb += sl;
+	we += sl - pl;
+	offs -= pl;
+    }
+    /* Does this compctl have a suffix (compctl -S)? */
+    if ((ccsuffix = cc->suffix) && *ccsuffix) {
+	char *sdup = dupstring(ccsuffix);
+	int sl = strlen(sdup), suffixll;
+
+	/* Ignore trailing spaces. */
+	for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--);
+	p[1] = '\0';
+
+	if (!sd) {
+	    sd = dupstring(s);
+	    untokenize(sd);
+	}
+	/* If the suffix is already there, ignore it (and don't add *
+	 * it again).                                               */
+	if (*sd && (suffixll = strlen(sd)) >= sl &&
+	    offs <= suffixll - sl && !strcmp(sdup, sd + suffixll - sl)) {
+	    ccsuffix = NULL;
+	    haswhat |= HAS_SUFFIX;
+	    s[suffixll - sl] = '\0';
+	}
+    }
+    /* Do we have one of the special characters `~' and `=' at the beginning? */
+    if ((ic = *s) != Tilde && ic != Equals)
+	ic = 0;
+
+    /* Check if we have to complete a parameter name... */
+
+    /* Try to find a `$'. */
+    for (p = s + offs; p > s && *p != String; p--);
+    if (*p == String) {
+	/* Handle $$'s */
+	while (p > s && p[-1] == String)
+	    p--;
+	while (p[1] == String && p[2] == String)
+	    p += 2;
+    }
+    if (*p == String &&	p[1] != Inpar && p[1] != Inbrack) {
+	/* This is really a parameter expression (not $(...) or $[...]). */
+	char *b = p + 1, *e = b;
+	int n = 0, br = 1;
+
+	if (*b == Inbrace) {
+	    /* If this is a ${...}, ignore the possible (...) flags. */
+	    b++, br++;
+	    n = skipparens(Inpar, Outpar, &b);
+	}
+
+	/* Ignore the stuff before the parameter name. */
+	for (; *b; b++)
+	    if (*b != '^' && *b != Hat &&
+		*b != '=' && *b != Equals &&
+		*b != '~' && *b != Tilde)
+		break;
+	if (*b == '#' || *b == Pound || *b == '+')
+	    b++;
+
+	e = b;
+	/* Find the end of the name. */
+	if (*e == Quest || *e == Star || *e == String || *e == Qstring ||
+	    *e == '?'   || *e == '*'  || *e == '$'    ||
+	    *e == '-'   || *e == '!'  || *e == '@')
+	    e++;
+	else if (idigit(*e))
+	    while (idigit(*e))
+		e++;
+	else if (iident(*e))
+	    while (iident(*e) ||
+		   (useglob && (*e == Star || *e == Quest)))
+		e++;
+
+	/* Now make sure that the cursor is inside the name. */
+	if (offs <= e - s && offs >= b - s && n <= 0) {
+	    /* It is. */
+	    parambr = br - 1;
+	    /* Get the prefix (anything up to the character before the name). */
+	    *e = '\0';
+	    parampre = ztrduppfx(s, b - s);
+	    qparampre = ztrdup(quotename(parampre, NULL, NULL, NULL));
+	    untokenize(qparampre);
+	    qparprelen = strlen(qparampre);
+	    /* And adjust wb, we, and offs again. */
+	    offs -= b - s;
+	    wb = cs - offs;
+	    we = wb + e - b;
+	    s = b;
+	    /* And now make sure that we complete parameter names. */
+	    cc = ccmain = &cc_dummy;
+	    cc_dummy.refc = 10000;
+	    cc_dummy.mask = CC_PARAMS | CC_ENVVARS;
+	}
+    }
+    ooffs = offs;
+    /* If we have to ignore the word, do that. */
+    if (cc->mask & CC_DELETE) {
+	*delit = 1;
+	*s = '\0';
+	offs = 0;
+    } else
+	*delit = 0;
+
+    /* Compute line prefix/suffix. */
+
+    lpl = offs;
+    lpre = zalloc(lpl + 1);
+    memcpy(lpre, s, lpl);
+    lpre[lpl] = '\0';
+    p = quotename(lpre, NULL, NULL, NULL);
+    if (strcmp(p, lpre) && !strpfx(p, qword)) {
+	int l1, l2;
+
+	backdel(l1 = cs - wb);
+	untokenize(p);
+	inststrlen(p, 1, l2 = strlen(p));
+	we += l2 - l1;
+    }
+    lsuf = ztrdup(s + offs);
+    lsl = strlen(lsuf);
+    if (lsl && (p = quotename(lsuf, NULL, NULL, NULL)) &&
+	(strcmp(p, lsuf) && !strsfx(p, qword))) {
+	int l1, l2;
+
+	foredel(l1 = strlen(s + offs));
+	untokenize(p);
+	inststrlen(p, 0, l2 = strlen(p));
+	we += l2 - l1;
+    }
+
+    /* First check for ~.../... */
+    if (ic == Tilde) {
+	for (p = lpre + lpl; p > lpre; p--)
+	    if (*p == '/')
+		break;
+
+	if (*p == '/')
+	    ic = 0;
+    }
+    /* Compute real prefix/suffix. */
+
+    noreal = !*delit;
+    for (p = lpre; *p && *p != String && *p != Tick; p++);
+    tt = ic && !parampre ? lpre + 1 : lpre;
+    rpre = (*p || *lpre == Tilde || *lpre == Equals) ?
+	(noreal = 0, getreal(tt)) :
+	ztrdup(tt);
+
+    for (p = lsuf; *p && *p != String && *p != Tick; p++);
+    rsuf = *p ? (noreal = 0, getreal(lsuf)) : ztrdup(lsuf);
+
+    /* Check if word is a pattern. */
+
+    for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1;
+	 p >= rpre && (ispattern != 3 || !sf1);
+	 p--)
+	if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde)))
+	    ispattern |= sf1 ? 1 : 2;
+	else if (*p == '/') {
+	    sf1++;
+	    if (!s1)
+		s1 = p;
+	}
+    for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++)
+	if (itok(*p))
+	    t |= sf2 ? 4 : 2;
+	else if (*p == '/') {
+	    sf2++;
+	    if (!s2)
+		s2 = p;
+	}
+    ispattern = ispattern | t;
+
+    /* But if we were asked not to do glob completion, we never treat the *
+     * thing as a pattern.                                                */
+    if (!useglob)
+	ispattern = 0;
+
+    if (ispattern) {
+	/* The word should be treated as a pattern, so compute the matcher. */
+	p = (char *)ncalloc(rpl + rsl + 2);
+	strcpy(p, rpre);
+	if (rpl && p[rpl - 1] != Star) {
+	    p[rpl] = Star;
+	    strcpy(p + rpl + 1, rsuf);
+	} else
+	    strcpy(p + rpl, rsuf);
+	patcomp = parsereg(p);
+    }
+    if (!patcomp) {
+	untokenize(rpre);
+	untokenize(rsuf);
+
+	rpl = strlen(rpre);
+	rsl = strlen(rsuf);
+    }
+    untokenize(lpre);
+    untokenize(lsuf);
+
+    /* Handle completion of files specially (of course). */
+
+    if ((cc->mask & (CC_FILES | CC_DIRS | 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
+	    ppre = ztrduppfx(rpre, s1 - rpre + 1);
+	psuf = ztrdup(s2);
+
+	/* And get the file prefix. */
+	fpre = ztrdup(((s1 == s || s1 == rpre || ic) &&
+		       (*s != '/' || cs == wb)) ? s1 : s1 + 1);
+	/* And the suffix. */
+	fsuf = ztrduppfx(rsuf, s2 - rsuf);
+
+	if (useglob && (ispattern & 2)) {
+	    int t2;
+
+	    /* We have to use globbing, so compute the pattern from *
+	     * the file prefix and suffix with a `*' between them.  */
+	    p = (char *)ncalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2);
+	    strcpy(p, fpre);
+	    if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star)
+		p[t2++] = Star;
+	    strcpy(p + t2, fsuf);
+	    filecomp = parsereg(p);
+	}
+	if (!filecomp) {
+	    untokenize(fpre);
+	    untokenize(fsuf);
+
+	    fpl = strlen(fpre);
+	    fsl = strlen(fsuf);
+	}
+	addwhat = -1;
+
+	/* Completion after `~', maketildelist adds the usernames *
+	 * and named directories.                                 */
+	if (ic == Tilde)
+	    maketildelist();
+	else if (ic == Equals) {
+	    /* Completion after `=', get the command names from *
+	     * the cmdnamtab and aliases from aliastab.         */
+	    if (isset(HASHLISTALL))
+		cmdnamtab->filltable(cmdnamtab);
+	    dumphashtable(cmdnamtab, -7);
+	    dumphashtable(aliastab, -2);
+	} else {
+	    /* Normal file completion... */
+	    if (ispattern & 1) {
+		/* But with pattern matching. */
+		LinkList l = newlinklist();
+		LinkNode n;
+		int ng = opts[NULLGLOB];
+
+		opts[NULLGLOB] = 1;
+
+		addwhat = 0;
+		p = (char *)ncalloc(lpl + lsl + 3);
+		strcpy(p, lpre);
+		if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*')
+		    strcat(p, "*");
+		strcat(p, lsuf);
+		if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')')
+		    strcat(p, "*");
+
+		/* Do the globbing. */
+		tokenize(p);
+		remnulargs(p);
+		addlinknode(l, p);
+		globlist(l);
+
+		if (nonempty(l)) {
+		    /* And add the resulting words. */
+		    haswhat |= HAS_PATHPAT;
+		    for (n = firstnode(l); n; incnode(n))
+			addmatch(getdata(n), NULL);
+		}
+		opts[NULLGLOB] = ng;
+	    } else {
+		/* No pattern matching. */
+		addwhat = CC_FILES;
+		if (cc->withd) {
+		    prpre = tricat(cc->withd, "/", ppre);
+		} else
+		    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 || cc->withd)
+			    /* There is a path prefix, so add *
+			     * directories and executables.   */
+			    gen_matches_files(1, 1, 0);
+			else {
+			    /* No path prefix, so add the things *
+			     * reachable via the PATH variable.  */
+			    char **pc = path, *pp = prpre;
+
+			    for (; *pc; pc++)
+				if (!**pc || (pc[0][0] == '.' && !pc[0][1]))
+				    break;
+			    if (*pc) {
+				prpre = "./";
+				gen_matches_files(1, 1, 0);
+				prpre = pp;
+			    }
+			}
+		    } else if (cc->mask & CC_DIRS)
+			gen_matches_files(1, 0, 0);
+		    /* The compctl has a glob pattern (compctl -g). */
+		    if (cc->glob) {
+			int ns, pl = strlen(prpre), o;
+			char *g = dupstring(cc->glob), pa[PATH_MAX];
+			char *p2, *p3;
+			int ne = noerrs, md = opts[MARKDIRS];
+
+			/* These are used in the globbing code to make *
+			 * things a bit faster.                        */
+			glob_pre = fpre;
+			glob_suf = fsuf;
+
+			noerrs = 1;
+			addwhat = -6;
+			strcpy(pa, prpre);
+			o = strlen(pa);
+			opts[MARKDIRS] = 0;
+
+			/* The compctl -g string may contain more than *
+			 * one pattern, so we need a loop.             */
+			while (*g) {
+			    LinkList l = newlinklist();
+			    int ng;
+
+			    /* Find the blank terminating the pattern. */
+			    while (*g && inblank(*g))
+				g++;
+			    /* Oops, we already reached the end of the
+			       string. */
+			    if (!*g)
+				break;
+			    for (p = g + 1; *p && !inblank(*p); p++)
+				if (*p == '\\' && p[1])
+				    p++;
+			    /* Get the pattern string. */
+			    tokenize(g = dupstrpfx(g, p - g));
+			    if (*g == '=')
+				*g = Equals;
+			    if (*g == '~')
+				*g = Tilde;
+			    remnulargs(g);
+			    if ((*g == Equals || *g == Tilde) && !cc->withd) {
+				/* The pattern has a `~' or `=' at the  *
+				 * beginning, so we expand this and use *
+				 * the result.                          */
+				filesub(&g, 0);
+				addlinknode(l, dupstring(g));
+			    } else if (*g == '/' && !cc->withd)
+				/* The pattern is a full path (starting *
+				 * with '/'), so add it unchanged.      */
+				addlinknode(l, dupstring(g));
+			    else {
+				/* It's a simple pattern, so append it to *
+				 * the path we have on the command line.  */
+				strcpy(pa + o, g);
+				addlinknode(l, dupstring(pa));
+			    }
+			    /* Do the globbing. */
+			    ng = opts[NULLGLOB];
+			    opts[NULLGLOB] = 1;
+			    globlist(l);
+			    opts[NULLGLOB] = ng;
+			    /* Get the results. */
+			    if (nonempty(l) && peekfirst(l)) {
+				for (p2 = (char *)peekfirst(l); *p2; p2++)
+				    if (itok(*p2))
+					break;
+				if (!*p2) {
+				    if ((*g == Equals || *g == Tilde ||
+					*g == '/') || cc->withd) {
+					/* IF the pattern started with `~',  *
+					 * `=', or `/', add the result only, *
+					 * if it really matches what we have *
+					 * on the line.                      *
+					 * Do this if an initial directory   *
+					 * was specified, too.               */
+					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';
+			    g = p;
+			}
+			glob_pre = glob_suf = NULL;
+			noerrs = ne;
+			opts[MARKDIRS] = md;
+		    }
+		}
+	    }
+	}
+    }
+    /* Use tricat() instead of dyncat() to get zalloc()'d memory. */
+    if (ic) {
+	/* Now change the `~' and `=' tokens to the real characters so *
+	 * that things starting with these characters will be added.   */
+	char *orpre = rpre;
+
+	rpre = tricat("", (ic == Tilde) ? "~" : "=", rpre);
+	rpl++;
+	zsfree(orpre);
+    }
+    if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) {
+	/* If we have to complete commands, add alias names, *
+	 * shell functions and builtins too.                 */
+	dumphashtable(aliastab, -3);
+	dumphashtable(reswdtab, -3);
+	dumphashtable(shfunctab, -3);
+	dumphashtable(builtintab, -3);
+	if (isset(HASHLISTALL))
+	    cmdnamtab->filltable(cmdnamtab);
+	dumphashtable(cmdnamtab, -3);
+	/* And parameter names if autocd and cdablevars are set. */
+	if (isset(AUTOCD) && isset(CDABLEVARS))
+	    dumphashtable(paramtab, -4);
+    }
+    addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG;
+
+    if (cc->mask & CC_NAMED)
+	/* Add named directories. */
+	dumphashtable(nameddirtab, addwhat);
+    if (cc->mask & CC_OPTIONS)
+	/* Add option names. */
+	dumphashtable(optiontab, addwhat);
+    if (cc->mask & CC_VARS)
+	/* And parameter names. */
+	dumphashtable(paramtab, -9);
+    if (cc->mask & CC_BINDINGS)
+	/* And zle function names... */
+	dumphashtable(thingytab, CC_BINDINGS);
+    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)) != &dummy_list) {
+	    /* We have it, so build a argument list. */
+	    LinkList args = newlinklist();
+
+	    addlinknode(args, cc->func);
+
+	    if (*delit) {
+		p = dupstrpfx(os, ooffs);
+		untokenize(p);
+		addlinknode(args, p);
+		p = dupstring(os + ooffs);
+		untokenize(p);
+		addlinknode(args, p);
+	    } else {
+		addlinknode(args, lpre);
+		addlinknode(args, lsuf);
+	    }
+
+	    /* This flag allows us to use read -l and -c. */
+	    incompctlfunc = 1;
+	    /* Call the function. */
+	    doshfunc(list, args, 0, 1);
+	    incompctlfunc = 0;
+	    /* And get the result from the reply parameter. */
+	    if ((r = get_user_var("reply")))
+		while (*r)
+		    addmatch(*r++, NULL);
+	}
+	lastval = lv;
+    }
+    if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) {
+	/* Get job names. */
+	int i;
+	char *j, *jj;
+
+	for (i = 0; i < MAXJOB; i++)
+	    if (jobtab[i].stat & STAT_INUSE) {
+		int stopped = jobtab[i].stat & STAT_STOPPED;
+
+		j = jj = dupstring(jobtab[i].procs->text);
+		/* Find the first word. */
+		for (; *jj; jj++)
+		    if (*jj == ' ') {
+			*jj = '\0';
+			break;
+		    }
+		if ((cc->mask & CC_JOBS) ||
+		    (stopped && (cc->mask & CC_STOPPED)) ||
+		    (!stopped && (cc->mask & CC_RUNNING)))
+		    addmatch(j, NULL);
+	    }
+    }
+    if (cc->str) {
+	/* Get the stuff from a compctl -s. */
+	LinkList foo = newlinklist();
+	LinkNode n;
+	int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb;
+	char *tmpbuf;
+
+	opts[NULLGLOB] = 1;
+
+	/* Put the strin in the lexer buffer and call the lexer to *
+	 * get the words we have to expand.                        */
+	zleparse = 1;
+	lexsave();
+	tmpbuf = (char *)halloc(strlen(cc->str) + 5);
+	sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */
+	inpush(tmpbuf, 0, NULL);
+	strinbeg();
+	noaliases = 1;
+	do {
+	    ctxtlex();
+	    if (tok == ENDINPUT || tok == LEXERR)
+		break;
+	    if (!first && tokstr && *tokstr)
+		addlinknode(foo, ztrdup(tokstr));
+	    first = 0;
+	} while (tok != ENDINPUT && tok != LEXERR);
+	noaliases = 0;
+	strinend();
+	inpop();
+	errflag = zleparse = 0;
+	lexrestore();
+	/* Fine, now do full expansion. */
+	prefork(foo, 0);
+	if (!errflag) {
+	    globlist(foo);
+	    if (!errflag)
+		/* And add the resulting words as matches. */
+		for (n = firstnode(foo); n; incnode(n))
+		    addmatch((char *)n->dat, NULL);
+	}
+	opts[NULLGLOB] = ng;
+	we = oowe;
+	wb = oowb;
+    }
+    if (cc->hpat) {
+	/* We have a pattern to take things from the history. */
+	Comp compc = NULL;
+	char *e, *h, hpatsav;
+	Histent he;
+	int i = curhist - 1, n = cc->hnum;
+
+	/* Parse the pattern, if it isn't the null string. */
+	if (*(cc->hpat)) {
+	    char *thpat = dupstring(cc->hpat);
+
+	    tokenize(thpat);
+	    compc = parsereg(thpat);
+	}
+	/* n holds the number of history line we have to search. */
+	if (!n)
+	    n = -1;
+
+	/* Now search the history. */
+	while (n-- && (he = quietgethist(i--))) {
+	    int iwords;
+	    for (iwords = 0; iwords < he->nwords; iwords++) {
+		h = he->text + he->words[iwords*2];
+		e = he->text + he->words[iwords*2+1];
+		hpatsav = *e;
+		*e = '\0';
+		/* We now have a word from the history, ignore it *
+		 * if it begins with a quote or `$'.              */
+		if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' &&
+		    (!compc || domatch(h, compc, 0)))
+		    /* Otherwise add it if it was matched. */
+		    addmatch(dupstring(h), NULL);
+		if (hpatsav)
+		    *e = hpatsav;
+	    }
+	}
+    }
+    if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS |
+			 CC_READONLYS | CC_SPECIALS | CC_PARAMS)))
+	/* Add various flavours of parameters. */
+	dumphashtable(paramtab, t);
+    if ((t = cc->mask & CC_SHFUNCS))
+	/* Add shell functions. */
+	dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+    if ((t = cc->mask & CC_BUILTINS))
+	/* Add builtins. */
+	dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+    if ((t = cc->mask & CC_EXTCMDS))
+	/* Add external commands */
+	dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+    if ((t = cc->mask & CC_RESWDS))
+	/* Add reserved words */
+	dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+    if ((t = cc->mask & (CC_ALREG | CC_ALGLOB)))
+	/* Add the two types of aliases. */
+	dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+
+    /* If we have no matches, ignore fignore. */
+    if (empty(matches)) {
+	matches = fmatches;
+	firstm = ffirstm;
+	shortest = fshortest;
+	ab = fab;
+	ae = fae;
+	shortl = fshortl;
+    }
+
+    /* Make an array from the list of matches. */
+    makearray(matches);
+    PERMALLOC {
+	amatches = arrdup(amatches);
+	if (firstm)
+	    firstm = ztrdup(firstm);
+	/* And quote the prefixes/suffixes. */
+	if (hasspecial(s)) {
+	    zfree(lpre, lpl);
+	    zfree(lsuf, lsl);
+	    lpre = zalloc(lpl + 1);
+	    memcpy(lpre, s, lpl);
+	    lpre[lpl] = '\0';
+	    lsuf = ztrdup(s + offs);
+	    quotepresuf(&lpre);
+	    quotepresuf(&lsuf);
+	    untokenize(lpre);
+	    untokenize(lsuf);
+	}
+	quotepresuf(&fpre);
+	quotepresuf(&fsuf);
+	quotepresuf(&ppre);
+	quotepresuf(&psuf);
+    } LASTALLOC;
+
+    if (!errflag && cc->ylist) {
+	/* generate the user-defined display list: if anything fails, *
+	 * we silently allow the normal completion list to be used.   */
+	char **yaptr, *uv = NULL;
+	List list;
+
+	if (cc->ylist[0] == '$' || cc->ylist[0] == '(') {
+	    /* from variable */
+	    uv = cc->ylist + (cc->ylist[0] == '$');
+	} else if ((list = getshfunc(cc->ylist)) != &dummy_list) {
+	    /* from function:  pass completions as arg list */
+	    LinkList args = newlinklist();
+	    int addlen = strlen(rpre) + strlen(rsuf) + 1;
+
+	    addlinknode(args, cc->ylist);
+	    for (yaptr = amatches; *yaptr; yaptr++) {
+		/* can't use tricat(). rats. */
+		char *ptr = (char *)halloc(addlen + strlen(*yaptr));
+		sprintf(ptr, "%s%s%s", rpre, *yaptr, rsuf);
+		addlinknode(args, ptr);
+	    }
+
+	    /* No harm in allowing read -l and -c here, too */
+	    incompctlfunc = 1;
+	    doshfunc(list, args, 0, 1);
+	    incompctlfunc = 0;
+	    uv = "reply";
+	}
+	if (uv && (yaptr = get_user_var(uv))) {
+	    PERMALLOC {
+		aylist = arrdup(yaptr);
+	    } LASTALLOC;
+	}
+    }
+
+    /* Get the explanation string we will have to print:    *
+     * do this here in case a -y function alters the messge */
+    if ((expl = cc->explain)) {
+	if (cc->mask & CC_EXPANDEXPL && !parsestr(expl = dupstring(expl))) {
+	    singsub(&expl);
+	    untokenize(expl);
+	}
+	expl = ztrdup(expl);
+    }
+
+    remsuffix = (cc->mask & CC_REMOVE);
+    ccsuffix = cc->suffix;
+
+    validlist = 1;
+    if (nmatches && !errflag)
+	return 0;
+
+    if ((isf || cc->xor) && !parampre) {
+	/* We found no matches, but there is a xor'ed completion: *
+	 * fine, so go back and continue with that compctl.       */
+	errflag = 0;
+	cc = cc->xor;
+	isf = 0;
+	wb = owb;
+	we = owe;
+	cs = ocs;
+	ll = oll;
+	strcpy((char *)line, (char *)ol);
+	offs = oloffs;
+	s = dupstring(os);
+	free(amatches);
+	zsfree(rpre);
+	zsfree(rsuf);
+	zsfree(lpre);
+	zsfree(lsuf);
+	zsfree(ppre);
+	zsfree(psuf);
+	zsfree(fpre);
+	zsfree(fsuf);
+	zsfree(prpre);
+	zsfree(parampre);
+	zsfree(qparampre);
+	zsfree(firstm);
+	if (expl)
+	    zsfree(expl);
+	expl = NULL;
+	if (aylist)
+	    freearray(aylist);
+	aylist = NULL;
+	goto xorrec;
+    }
+
+    /* No matches and xor'ed completion: restore the command line if  *
+     * it was alredy quoted, which is the case when s is untokenized. */
+    if (untokenized)
+	strcpy((char *)line, (char *)ol);
+    return 1;
+}
+
+/* Invalidate the completion list. */
+
+/**/
+void
+invalidatelist(void)
+{
+    if(showinglist == -2)
+	listmatches();
+    if(validlist) {
+	freearray(amatches);
+	if (aylist)
+	    freearray(aylist);
+	aylist = NULL;
+	if (expl)
+	    zsfree(expl);
+	expl = 0;
+	zsfree(rpre);
+	zsfree(rsuf);
+	zsfree(lpre);
+	zsfree(lsuf);
+	zsfree(ppre);
+	zsfree(psuf);
+	zsfree(fpre);
+	zsfree(fsuf);
+	zsfree(prpre);
+	zsfree(parampre);
+	zsfree(qparampre);
+	zsfree(firstm);
+	if (ccmain != &cc_dummy)
+	    freecompctl(ccmain);
+    }
+    lastambig = menucmp = showinglist = validlist = 0;
+    menucur = NULL;
+}
+
+/* Get the words from a variable or a compctl -k list. */
+
+/**/
+static char **
+get_user_var(char *nam)
+{
+    if (!nam)
+	return NULL;
+    else if (*nam == '(') {
+	/* It's a (...) list, not a parameter name. */
+	char *ptr, *s, **uarr, **aptr;
+	int count = 0, notempty = 0, brk = 0;
+	LinkList arrlist = newlinklist();
+
+	ptr = dupstring(nam);
+	s = ptr + 1;
+	while (*++ptr) {
+	    if (*ptr == '\\' && ptr[1])
+		chuck(ptr), notempty = 1;
+	    else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') {
+		if (*ptr == ')')
+		    brk++;
+		if (notempty) {
+		    *ptr = '\0';
+		    count++;
+		    if (*s == '\n')
+			s++;
+		    addlinknode(arrlist, s);
+		}
+		s = ptr + 1;
+		notempty = 0;
+	    } else {
+		notempty = 1;
+		if(*ptr == Meta)
+		    ptr++;
+	    }
+	    if (brk)
+		break;
+	}
+	if (!brk || !count)
+	    return NULL;
+	*ptr = '\0';
+	aptr = uarr = (char **)ncalloc(sizeof(char *) * (count + 1));
+
+	while ((*aptr++ = (char *)ugetnode(arrlist)));
+	uarr[count] = NULL;
+	return uarr;
+    } else {
+	/* Otherwise it should be a parameter name. */
+	char **arr = NULL, *val;
+	if (!(arr = getaparam(nam)) && (val = getsparam(nam))) {
+	    arr = (char **)ncalloc(2*sizeof(char *));
+	    arr[0] = val;
+	    arr[1] = NULL;
+	}
+	return arr;
+    }
+
+}
+
+/* This is strcmp with ignoring backslashes. */
+
+/**/
+static int
+strbpcmp(const void *a, const void *b)
+{
+    char *aa = *((char **)a), *bb = *((char **)b);
+
+    while (*aa && *bb) {
+	if (*aa == '\\')
+	    aa++;
+	if (*bb == '\\')
+	    bb++;
+	if (*aa != *bb)
+	    return (int)(*aa - *bb);
+	if (*aa)
+	    aa++;
+	if (*bb)
+	    bb++;
+    }
+    return (int)(*aa - *bb);
+}
+
+/* Make an array from a linked list */
+
+/**/
+static void
+makearray(LinkList l)
+{
+    char **ap, **bp, **cp;
+    LinkNode nod;
+
+    /* Build an array for the matches. */
+    ap = amatches = (char **)ncalloc(((nmatches = countlinknodes(l)) + 1) *
+				     sizeof(char *));
+
+    /* And copy them into it. */
+    for (nod = firstnode(l); nod; incnode(nod))
+	*ap++ = (char *)getdata(nod);
+    *ap = NULL;
+
+    /* Now sort the array. */
+    qsort((void *) amatches, nmatches, sizeof(char *),
+	       (int (*) _((const void *, const void *)))strbpcmp);
+
+    /* And delete the ones that occur more than once. */
+    for (ap = cp = amatches; *ap; ap++) {
+	*cp++ = *ap;
+	for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++);
+	ap = bp;
+    }
+    *cp = NULL;
+    nmatches = arrlen(amatches);
+}
+
+/* Handle the case were we found more than one match. */
+
+/**/
+static void
+do_ambiguous(void)
+{
+    int p = (usemenu || ispattern), atend = (cs == we);
+    int inv = 0;
+
+    menucmp = 0;
+
+    /* If we have to insert the first match, call do_single().  This is *
+     * how REC_EXACT takes effect.  We effectively turn the ambiguous   *
+     * completion into an unambiguous one.                              */
+    if (shortest && shortl == 0 && isset(RECEXACT) &&
+	(usemenu == 0 || unset(AUTOMENU))) {
+	do_single(shortest);
+	invalidatelist();
+	return;
+    }
+    /* Setting lastambig here means that the completion is ambiguous and *
+     * AUTO_MENU might want to start a menu completion next time round,  *
+     * but this might be overridden below if we can complete an          *
+     * unambiguous prefix.                                               */
+    lastambig = 1;
+    if(p) {
+	/* p is set if we are in a position to start using menu completion *
+	 * due to one of the menu completion options, or due to the        *
+	 * menu-complete-word command, or due to using GLOB_COMPLETE which *
+	 * does menu-style completion regardless of the setting of the     *
+	 * normal menu completion options.                                 */
+	do_ambig_menu();
+    } else {
+	/* Sort-of general case: we have an ambiguous completion, and aren't *
+	 * starting menu completion or doing anything really weird.  We need *
+	 * to insert any unambiguous prefix and suffix, if possible.         */
+	if(ab)
+	    inststrlen(firstm, 1, ab);
+	if(ae && !atend)
+	    inststrlen(firstm + strlen(firstm) - ae, 0, ae);
+	if(ab || (ae && !atend))
+	    inv = 1;
+	/* If the LIST_AMBIGUOUS option (meaning roughly `show a list only *
+	 * if the completion is completely ambiguous') is set, and some    *
+	 * prefix was inserted, return now, bypassing the list-displaying  *
+	 * code.  On the way, invalidate the list and note that we don't   *
+	 * want to enter an AUTO_MENU imediately.                          */
+	if(isset(LISTAMBIGUOUS) && inv) {
+	    invalidatelist();
+	    lastambig = 0;
+	    return;
+	}
+    }
+    /* At this point, we might want a completion listing.  Show the listing *
+     * if it is needed.                                                     */
+    if (isset(LISTBEEP))
+	feep();
+    if (isset(AUTOLIST) && !amenu && !showinglist)
+	showinglist = -2;
+    if(inv)
+	invalidatelist();
+}
+
+/* This is a stat that ignores backslashes in the filename.  The `ls' *
+ * parameter says if we have to do lstat() or stat().  I think this   *
+ * should instead be done by use of a general function to expand a    *
+ * filename (stripping backslashes), combined with the actual         *
+ * (l)stat().                                                         */
+
+/**/
+static int
+ztat(char *nam, struct stat *buf, int ls)
+{
+    char b[PATH_MAX], *p;
+
+    for (p = b; p < b + sizeof(b) - 1 && *nam; nam++)
+	if (*nam == '\\' && nam[1])
+	    *p++ = *++nam;
+	else
+	    *p++ = *nam;
+    *p = '\0';
+
+    return ls ? lstat(b, buf) : stat(b, buf);
+}
+
+/* Insert a single match in the command line. */
+
+/**/
+static void
+do_single(char *str)
+{
+    int l;
+    int havesuff = 0;
+
+    fixsuffix();
+
+    if (!menucur) {
+	/* We are currently not in a menu-completion, *
+	 * so set the position variables.             */
+	if (ispattern) {
+	    cs = we;
+	    menupos = wb;
+	} else
+	    menupos = cs;
+	menuwe = (cs == we) || isset(ALWAYSTOEND);
+	menuend = we;
+    }
+    /* 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 + lsl;
+	    foredel(menuinsc);
+	}
+	l = menulen;
+    } else if (ispattern)
+	l = we - wb;
+    else
+	l = 0;
+
+    menuinsc = 0;
+    cs = menupos;
+    foredel(l);
+
+    /* And than we insert the new string. */
+    inststrlen(str, 1, menulen = strlen(str));
+    menuend = cs;
+
+    cs += lsl;
+
+    if (ccsuffix) {
+	/* There is a compctl -S suffix.  Add it. */
+	if (!(haswhat & HAS_SUFFIX) && *ccsuffix) {
+	    havesuff = 1;
+	    inststr(ccsuffix);
+	    menuinsc = ztrlen(ccsuffix);
+	    if (remsuffix && menuwe)
+		makesuffix(menuinsc);
+	}
+	havesuff = 1;
+    } else {
+	/* There is no user-specified suffix, *
+	 * so generate one automagically.     */
+	if(parampre && parambr) {
+	    /*{{*/
+	    /* Completing a parameter in braces.  Add a removable `}' suffix. */
+	    inststrlen("}", 1, 1);
+	    menuinsc++;
+	}
+	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. *
+	     * If it is, we append a slash.                                */
+	    char *p;
+	    struct stat buf;
+
+	    /* Build the path name. */
+	    if (ispattern || ic || parampre) {
+		int ne = noerrs;
+
+		noerrs = 1;
+
+		if (parampre) {
+		    int pl = strlen(parampre);
+		    p = (char *) ncalloc(pl + strlen(lpre) + strlen(str) +
+					 strlen(lsuf) + 1);
+		    sprintf(p, "%s%s%s%s", parampre, lpre, str, lsuf);
+		    if (pl && p[pl-1] == Inbrace)
+			strcpy(p+pl-1, p+pl);
+		}
+		else if (ic) {
+		    p = (char *) ncalloc(strlen(ppre) + strlen(fpre) + strlen(str) +
+					 strlen(fsuf) + strlen(psuf) + 2);
+		    sprintf(p, "%c%s%s%s%s%s", ic,
+			    ppre, fpre, str, fsuf, psuf);
+		}
+		else
+		    p = dupstring(str);
+		parsestr(p);
+		if (ic)
+		    *p = ic;
+		singsub(&p);
+
+		noerrs = ne;
+	    } else {
+		p = (char *) ncalloc((prpre ? strlen(prpre) : 0) + strlen(fpre) +
+				     strlen(str) + strlen(fsuf) + strlen(psuf) + 3);
+		sprintf(p, "%s%s%s%s%s",
+			(prpre && *prpre) ? prpre : "./", fpre, str,
+			fsuf, psuf);
+	    }
+	    /* And do the stat. */
+	    if (!ztat(p, &buf, 0) && S_ISDIR(buf.st_mode)) {
+		/* It is a directory, so add the slash. */
+		havesuff = 1;
+		inststrlen("/", 1, 1);
+		menuinsc++;
+		if(menuwe && isset(AUTOREMOVESLASH)) {
+		    makesuffix(1);
+		    suffixlen['/'] = 1;
+		}
+	    }
+	}
+    }
+    /* If completing in a brace expansion... */
+    if(complinbrace) {
+	if(havesuff) {
+	    /*{{*/
+	    /* If a suffix was added, and is removable, let *
+	     * `,' and `}' remove it.                       */
+	    if(isset(AUTOPARAMKEYS))
+		suffixlen[','] = suffixlen['}'] = suffixlen[256];
+	} else {
+	    /*{{*/
+	    /* Otherwise, add a `,' suffix, and let `}' remove it. */
+	    havesuff = 1;
+	    inststrlen(",", 1, 1);
+	    menuinsc++;
+	    if(menuwe && isset(AUTOPARAMKEYS))
+		suffixlen[','] = suffixlen['}'] = 1;
+	}
+    } else if(!menucmp && !havesuff) {
+	/* If we didn't add a suffix, add a space, unless we are *
+	 * doing menu completion.                                */
+	inststrlen(" ", 1, 1);
+	menuinsc++;
+	if(menuwe)
+	    makesuffix(1);
+    }
+    if(menuwe && parampre && isset(AUTOPARAMKEYS))
+	makeparamsuffix(parambr, menuinsc);
+
+    if (!menuwe)
+	cs = menuend;
+}
+
+/* This handles the beginning of menu-completion. */
+
+/**/
+static void
+do_ambig_menu(void)
+{
+    menucmp = 1;
+    menucur = NULL;
+    do_single(amatches[0]);
+    menucur = amatches;
+}
+
+/* Return the length of the common prefix of s and t. */
+
+/**/
+int
+pfxlen(char *s, char *t)
+{
+    int i = 0;
+
+    while (*s && *s == *t)
+	s++, t++, i++;
+    return i;
+}
+
+/* Return the length of the common suffix of s and t. */
+
+/**/
+static 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.       */
+
+/**/
+static int
+printfmt(char *fmt, int n, int dopr)
+{
+    char *p = fmt, nc[DIGBUFSIZE];
+    int l = 0, cc = 0;
+
+    for (; *p; p++) {
+	/* Handle the `%' stuff (%% == %, %n == <number of matches>). */
+	if (*p == '%') {
+	    if (*++p) {
+		switch (*p) {
+		case '%':
+		    if (dopr)
+			putc('%', shout);
+		    cc++;
+		    break;
+		case 'n':
+		    sprintf(nc, "%d", n);
+		    if (dopr)
+			fprintf(shout, nc);
+		    cc += strlen(nc);
+		    break;
+		}
+	    } else
+		break;
+	} else {
+	    cc++;
+	    if (*p == '\n') {
+		l += 1 + (cc / columns);
+		cc = 0;
+	    }
+	    if (dopr)
+		putc(*p, shout);
+	}
+    }
+
+    return l + (cc / columns);
+}
+
+/* List the matches.  Note that the list entries are metafied. */
+
+/**/
+void
+listmatches(void)
+{
+    int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0;
+    int off = 0, boff = 0, nboff = 0;
+    int of = (!aylist && isset(LISTTYPES) && !(haswhat & HAS_MISC));
+    char **arr, **ap, sav;
+    int nfpl, nfsl, nlpl, nlsl;
+    int listmax = getiparam("LISTMAX"), litnl = 0;
+    size_t (*strlenfn) _((char const *));
+
+#ifdef DEBUG
+    /* Sanity check */
+    if(!validlist) {
+	showmsg("BUG: listmatches called with bogus list");
+	return;
+    }
+#endif
+
+    /* Calculate lengths of prefixes/suffixes to be added */
+    nfpl = fpre ? niceztrlen(fpre) : 0;
+    nfsl = fsuf ? niceztrlen(fsuf) : 0;
+    nlpl = lpre ? niceztrlen(lpre) : 0;
+    nlsl = lsuf ? niceztrlen(lsuf) : 0;
+
+    /* Calculate the lengths of the prefixes/suffixes we have to ignore
+       during printing. */
+    if (ispattern && !aylist && !(haswhat & (HAS_MISC | HAS_PATHPAT))) {
+	if (ppre && *ppre)
+	    off = strlen(ppre);
+	if (psuf && *psuf) {
+	    boff = strlen(psuf);
+	    nboff = niceztrlen(psuf);
+	}
+    }
+
+    /* Set the cursor below the prompt. */
+    trashzle();
+    showinglist = 0;
+
+    clearflag = (isset(USEZLE) && !termflags &&
+		 (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
+	(unset(ALWAYSLASTPROMPT) && zmult != 1);
+
+    /* just to keep gcc happy */
+    fw = colsz = up = 0;
+    if (aylist) {
+	arr = aylist;
+	/* If no literal newlines, the remaining code should use strlen() */
+	strlenfn = (size_t (*) _((char const *)))strlen;
+
+	/* The hard bit here is that we are handling newlines literally.   *
+	 * In fact, we are in principle handling all characters literally, *
+	 * but it's quite enough work with just newlines.                  *
+	 * If there are such, we give up trying to print the list as       *
+	 * columns and print as rows, counting the extra newlines.         */
+	ct = 0;
+	for (ap = arr; *ap; ap++) {
+	    ct++;
+	    if (strchr(*ap, '\n'))
+		litnl++;
+	}
+	if (litnl) {
+	    colsz = ct;
+	    up = colsz + nlnct - clearflag;
+	    /* Count real newlines, as well as overflowing lines. */
+	    for (ap = arr; *ap; ap++) {
+		char *nlptr, *sptr = *ap;
+		while (sptr && *sptr) {
+		    up += (nlptr = strchr(sptr, '\n'))
+			? 1 + (nlptr-sptr)/columns
+			   : strlen(sptr)/columns;
+		    sptr = nlptr ? nlptr+1 : NULL;
+		}
+	    }
+	}
+    } else {
+	arr = amatches;
+	ct = nmatches;
+	strlenfn = niceztrlen;
+    }
+
+
+    if (!litnl) {
+	/* Calculate the column width, the number of columns and the
+	   number of lines. */
+	for (ap = arr; *ap; ap++)
+	    if ((cl = strlenfn(*ap + off) - nboff +
+		 ((ispattern || aylist) ? 0 :
+		  (!(haswhat & HAS_MISC) ?
+		   nfpl + nfsl : nlpl + nlsl))) > longest)
+		longest = cl;
+	if (of)
+	    longest++;
+
+	fw = longest + 2;
+	fct = (columns + 1) / fw;
+	if (fct == 0) {
+	    fct = 1;
+	    colsz = ct;
+	    up = colsz + nlnct - clearflag;
+	    for (ap = arr; *ap; ap++)
+		up += (strlenfn(*ap + off) - nboff + of +
+		       ((ispattern || aylist) ? 0 :
+			(!(haswhat & HAS_MISC) ?
+			 nfpl + nfsl : nlpl + nlsl))) / columns;
+	} else {
+	    colsz = (ct + fct - 1) / fct;
+	    up = colsz + nlnct - clearflag + (ct == 0);
+	}
+    }
+
+    /* Print the explanation string, if any. */
+    if (expl) {
+	xup = printfmt(expl, ct, 1) + 1;
+	putc('\n', shout);
+	up += xup;
+    }
+
+    /* Maybe we have to ask if the user wants to see the list. */
+    if ((listmax && ct > listmax) || (!listmax && up >= lines)) {
+	int qup;
+	setterm();
+	qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1);
+	fflush(shout);
+	if (getzlequery() != 'y') {
+	    if (clearflag) {
+		putc('\r', shout);
+		tcmultout(TCUP, TCMULTUP, qup);
+		if (tccan(TCCLEAREOD))
+		    tcout(TCCLEAREOD);
+		tcmultout(TCUP, TCMULTUP, nlnct + xup);
+	    } else
+		putc('\n', shout);
+	    return;
+	}
+	if (clearflag) {
+	    putc('\r', shout);
+	    tcmultout(TCUP, TCMULTUP, qup);
+	    if (tccan(TCCLEAREOD))
+		tcout(TCCLEAREOD);
+	} else
+	    putc('\n', shout);
+	settyinfo(&shttyinfo);
+    }
+
+    /* Now print the matches. */
+    for (t1 = 0; t1 != colsz; t1++) {
+	ap = arr + t1;
+	if (of) {
+	    /* We have to print the file types. */
+	    while (*ap) {
+		int t2;
+		char *pb;
+		struct stat buf;
+
+		/* Build the path name for the stat. */
+		if (ispattern) {
+		    int cut = strlen(*ap) - boff;
+
+		    sav = ap[0][cut];
+		    ap[0][cut] = '\0';
+		    nicezputs(*ap + off, shout);
+		    t2 = niceztrlen(*ap + off);
+		    ap[0][cut] = sav;
+		    pb = *ap;
+		} else {
+		    nicezputs(fpre, shout);
+		    nicezputs(*ap, shout);
+		    nicezputs(fsuf, shout);
+		    t2 = nfpl + niceztrlen(*ap) + nfsl;
+		    pb = (char *) halloc((prpre ? strlen(prpre) : 0) + 3 +
+					 strlen(fpre) + strlen(*ap) + strlen(fsuf));
+		    sprintf(pb, "%s%s%s%s",
+			    (prpre && *prpre) ? prpre : "./", fpre, *ap, fsuf);
+		}
+		if (ztat(pb, &buf, 1))
+		    putc(' ', shout);
+		else
+		    /* Print the file type character. */
+		    putc(file_type(buf.st_mode), shout);
+		for (t0 = colsz; t0 && *ap; t0--, ap++);
+		if (*ap)
+		    /* And add spaces to make the columns aligned. */
+		    for (++t2; t2 < fw; t2++)
+			putc(' ', shout);
+	    }
+	} else
+	    while (*ap) {
+		int t2;
+
+		if (aylist) {
+		    zputs(*ap, shout);
+		    t2 = strlen(*ap);
+		} else if (ispattern) {
+		    int cut = strlen(*ap) - boff;
+
+		    sav = ap[0][cut];
+		    ap[0][cut] = '\0';
+		    nicezputs(*ap + off, shout);
+		    t2 = niceztrlen(*ap + off);
+		    ap[0][cut] = sav;
+		} else if (!(haswhat & HAS_MISC)) {
+		    nicezputs(fpre, shout);
+		    nicezputs(*ap, shout);
+		    nicezputs(fsuf, shout);
+		    t2 = nfpl + niceztrlen(*ap) + nfsl;
+		} else {
+		    nicezputs(lpre, shout);
+		    nicezputs(*ap, shout);
+		    nicezputs(lsuf, shout);
+		    t2 = nlpl + niceztrlen(*ap) + nlsl;
+		}
+		for (t0 = colsz; t0 && *ap; t0--, ap++);
+		if (*ap)
+		    for (; t2 < fw; t2++)
+			putc(' ', shout);
+	    }
+	if (t1 != colsz - 1 || !clearflag)
+	    putc('\n', shout);
+    }
+    if (clearflag)
+	/* Move the cursor up to the prompt, if always_last_prompt *
+	 * is set and all that...                                  */
+	if (up < lines) {
+	    tcmultout(TCUP, TCMULTUP, up);
+	    showinglist = -1;
+	} else
+	    clearflag = 0, putc('\n', shout);
+}
+
+/* This is used to print expansions. */
+
+/**/
+void
+listlist(LinkList l)
+{
+    int hw = haswhat, ip = ispattern;
+    char *lp = lpre, *ls = lsuf;
+    int nm = nmatches, vl = validlist;
+    char **am = amatches, **ay = aylist;
+    char *ex = expl;
+
+    haswhat = HAS_MISC;
+    ispattern = 0;
+    validlist = 1;
+    lpre = lsuf = "";
+    aylist = NULL;
+    expl = NULL;
+
+    makearray(l);
+    listmatches();
+    showinglist = 0;
+
+    expl = ex;
+    amatches = am;
+    aylist = ay;
+    nmatches = nm;
+    validlist = vl;
+    lpre = lp;
+    lsuf = ls;
+    ispattern = ip;
+    haswhat = hw;
+}
+
+/* Expand the history references. */
+
+/**/
+int
+doexpandhist(void)
+{
+    unsigned char *ol;
+    int oll, ocs, ne = noerrs, err;
+
+    DPUTS(useheap, "BUG: useheap in doexpandhist()");
+    HEAPALLOC {
+	pushheap();
+	metafy_line();
+	oll = ll;
+	ocs = cs;
+	ol = (unsigned char *)dupstring((char *)line);
+	expanding = 1;
+	excs = cs;
+	ll = cs = 0;
+	lexsave();
+	/* We push ol as it will remain unchanged */
+	inpush((char *) ol, 0, NULL);
+	strinbeg();
+	noaliases = 1;
+	noerrs = 1;
+	exlast = inbufct;
+	do {
+	    ctxtlex();
+	} while (tok != ENDINPUT && tok != LEXERR);
+	stophist = 2;
+	while (!lexstop)
+	    hgetc();
+	/* We have to save errflags because it's reset in lexrestore. Since  *
+	 * noerrs was set to 1 errflag is true if there was a habort() which *
+	 * means that the expanded string is unusable.                       */
+	err = errflag;
+	noerrs = ne;
+	noaliases = 0;
+	strinend();
+	inpop();
+	zleparse = 0;
+	lexrestore();
+	expanding = 0;
+
+	if (!err) {
+	    cs = excs;
+	    if (strcmp((char *)line, (char *)ol)) {
+		unmetafy_line();
+		/* For vi mode -- reset the beginning-of-insertion pointer   *
+		 * to the beginning of the line.  This seems a little silly, *
+		 * if we are, for example, expanding "exec !!".              */
+		if (viinsbegin > findbol())
+		    viinsbegin = findbol();
+		popheap();
+		LASTALLOC_RETURN 1;
+	    }
+	}
+
+	strcpy((char *)line, (char *)ol);
+	ll = oll;
+	cs = ocs;
+	unmetafy_line();
+
+	popheap();
+    } LASTALLOC;
+    return 0;
+}
+
+/**/
+void
+magicspace(void)
+{
+    c = ' ';
+    selfinsert();
+    doexpandhist();
+}
+
+/**/
+void
+expandhistory(void)
+{
+    if (!doexpandhist())
+	feep();
+}
+
+static int cmdwb, cmdwe;
+
+/**/
+static char *
+getcurcmd(void)
+{
+    int curlincmd;
+    char *s = NULL;
+
+    DPUTS(useheap, "BUG: useheap in getcurcmd()");
+    HEAPALLOC {
+	zleparse = 2;
+	lexsave();
+	metafy_line();
+	inpush(dupstrspace((char *) line), 0, NULL);
+	unmetafy_line();
+	strinbeg();
+	pushheap();
+	do {
+	    curlincmd = incmdpos;
+	    ctxtlex();
+	    if (tok == ENDINPUT || tok == LEXERR)
+		break;
+	    if (tok == STRING && curlincmd) {
+		zsfree(s);
+		s = ztrdup(tokstr);
+		cmdwb = ll - wordbeg;
+		cmdwe = ll + 1 - inbufct;
+	    }
+	}
+	while (tok != ENDINPUT && tok != LEXERR && zleparse);
+	popheap();
+	strinend();
+	inpop();
+	errflag = zleparse = 0;
+	lexrestore();
+    } LASTALLOC;
+    return s;
+}
+
+/**/
+void
+processcmd(void)
+{
+    char *s;
+    int m = zmult;
+
+    s = getcurcmd();
+    if (!s) {
+	feep();
+	return;
+    }
+    zmult = 1;
+    pushline();
+    zmult = m;
+    inststr(bindk->nam);
+    inststr(" ");
+    untokenize(s);
+    HEAPALLOC {
+	inststr(quotename(s, NULL, NULL, NULL));
+    } LASTALLOC;
+    zsfree(s);
+    done = 1;
+}
+
+/**/
+void
+expandcmdpath(void)
+{
+    int oldcs = cs, na = noaliases;
+    char *s, *str;
+
+    noaliases = 1;
+    s = getcurcmd();
+    noaliases = na;
+    if (!s || cmdwb < 0 || cmdwe < cmdwb) {
+	feep();
+	return;
+    }
+    str = findcmd(s);
+    zsfree(s);
+    if (!str) {
+	feep();
+	return;
+    }
+    cs = cmdwb;
+    foredel(cmdwe - cmdwb);
+    spaceinline(strlen(str));
+    strncpy((char *)line + cs, str, strlen(str));
+    cs = oldcs;
+    if (cs >= cmdwe - 1)
+	cs += cmdwe - cmdwb + strlen(str);
+    if (cs > ll)
+	cs = ll;
+    zsfree(str);
+}
+
+/* Extra function added by AR Iano-Fletcher. */
+/* This is a expand/complete in the vein of wash. */
+
+/**/
+void
+expandorcompleteprefix(void)
+{
+    comppref = 1;
+    expandorcomplete();
+    comppref = 0;
+}