summary refs log tree commit diff
diff options
context:
space:
mode:
authorSven Wischnowsky <wischnow@users.sourceforge.net>2000-04-10 08:06:38 +0000
committerSven Wischnowsky <wischnow@users.sourceforge.net>2000-04-10 08:06:38 +0000
commit0713dfcd1a1513d17d63eddaa8954f839ea5fefe (patch)
tree9552dcdfeffdd37bb76a0fcb01661ee6015091f6
parentf7a6aa096aa333f7274be219b92540d09dba1eb7 (diff)
downloadzsh-0713dfcd1a1513d17d63eddaa8954f839ea5fefe.tar.gz
zsh-0713dfcd1a1513d17d63eddaa8954f839ea5fefe.tar.xz
zsh-0713dfcd1a1513d17d63eddaa8954f839ea5fefe.zip
fix for bashautolist+automenu (10609)
-rw-r--r--ChangeLog5
-rw-r--r--Src/Zle/compcore.c5
-rw-r--r--Src/Zle/compresult.c4
-rw-r--r--Src/Zle/zle_tricky.c4609
4 files changed, 1450 insertions, 3173 deletions
diff --git a/ChangeLog b/ChangeLog
index bba84ea83..14b14af5a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2000-04-10  Sven Wischnowsky <wischnow@informatik.hu-berlin.de>
+
+	* 10609: Src/Zle/compcore.c, Src/Zle/compresult.c, Src/Zle/zle_tricky.c:
+ 	fix for bashautolist+automenu
+	
 2000-04-09  Bart Schaefer  <schaefer@zsh.org>
 
 	* 10606: Fix thinko in 10598.
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index 2a9ebbaa5..e40873764 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -776,8 +776,9 @@ callcompfunc(char *s, char *fn)
 		}
 	    }
 	}
-	startauto = (compinsert &&
-		     !strcmp(compinsert, "automenu-unambiguous"));
+	startauto = ((compinsert &&
+		      !strcmp(compinsert, "automenu-unambiguous")) ||
+		     (bashlistfirst && (!compinsert || !*compinsert)));
 	useexact = (compexact && !strcmp(compexact, "accept"));
 
 	if (!comptoend || !*comptoend)
diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c
index 0d93b8727..6870bbcd9 100644
--- a/Src/Zle/compresult.c
+++ b/Src/Zle/compresult.c
@@ -665,7 +665,9 @@ do_ambiguous(void)
 	 * 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 (uselist == 3 && la) {
+	if ((uselist == 3 ||
+	     (!uselist && isset(BASHAUTOLIST) && isset(LISTAMBIGUOUS))) &&
+	    la) {
 	    int fc = fromcomp;
 
 	    invalidatelist();
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 1aa1a008c..3b5e97b65 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -42,157 +42,108 @@
  * 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;
+/* The line before completion was tried. */
 
-/* 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).                                 */
+/**/
+mod_export char *origline;
+/**/
+mod_export int origcs, origll;
 
-static char **menucur;
+/* Words on the command line, for use in completion */
+ 
+/**/
+mod_export int clwsize, clwnum, clwpos;
+/**/
+mod_export char **clwords;
 
-/* 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.              */
+/* wb and we hold the beginning/end position of the word we are completing. */
 
-static int menupos, menulen, menuend, menuwe, menuinsc;
+/**/
+mod_export int wb, we;
 
-/* This is used as a flag from get_comp_string() that we are doing *
- * completion inside a brace expansion.                            */
+/* offs is the cursor position within the tokenized *
+ * current word after removing nulargs.             */
 
-static int complinbrace;
+/**/
+mod_export int offs;
 
-/* The list of matches.  fmatches contains the matches we first ignore *
- * because of fignore.                                                 */
+/* These control the type of completion that will be done.  They are       *
+ * affected by the choice of ZLE command and by relevant shell options.    *
+ * usemenu is set to 2 if we have to start automenu and 3 if we have to    *
+ * insert a match as if for menucompletion but without really starting it. */
 
-static LinkList matches, fmatches;
+/**/
+mod_export int usemenu, useglob;
 
-/* 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).    */
+/* != 0 if we are in the middle of a menu completion. May be == 2 to force *
+ * menu completion even if using different widgets.                        */
 
-static char **amatches;
+/**/
+mod_export int menucmp;
 
-/* The number of matches. */
+/* Lists of brace-infos before/after cursor (first and last for each). */
 
-static int nmatches;
+/**/
+mod_export Brinfo brbeg, lastbrbeg, brend, lastbrend;
 
-/* A list of user-defined explanations for the completions to be shown *
- * instead of amatches when listing completions.                       */
+/**/
+mod_export int nbrbeg, nbrend;
 
-static char **aylist;
+/**/
+mod_export char *lastprebr, *lastpostbr;
 
 /* !=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;
+/**/
+mod_export int validlist;
 
-/* This is either zero or equal to the special character the word we are *
- * trying to complete starts with (e.g. Tilde or Equals).                */
+/* Non-zero if we have to redisplay the list of matches. */
 
-static char ic;
+/**/
+mod_export int showagain = 0;
 
-/* These hold the minimum common prefix/suffix lengths (normal and for *
- * fignore ignored).                                                   */
+/* This holds the word we are completing in quoted from. */
 
-static int ab, ae, fab, fae;
+static char *qword;
 
-/* This variable says what we are currently adding to the list of matches. */
+/* This holds the word we are working on without braces removed. */
 
-static int addwhat;
+static char *origword;
 
-/* firstm hold the first match we found, shortest contains the shortest *
- * one (normal and for fignore ignored).                                */
+/* The quoted prefix/suffix and a flag saying if we want to add the
+ * closing quote. */
 
-static char *firstm, *shortest, *ffirstm, *fshortest;
+/**/
+mod_export char *qipre, *qisuf, *autoq;
 
-/* This holds the word we are completing in quoted from. */
+/* This contains the name of the function to call if this is for a new  *
+ * style completion. */
 
-static char *qword;
+/**/
+mod_export char *compfunc = NULL;
 
-/* This is the length of the shortest match we found (normal and for *
- * fignore ignored).                                                 */
+/* 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.                              *
+ *                                                                    *
+ * If both AUTOMENU and BASHAUTOLIST are set, then we get a listing   *
+ * on the second tab, a` la bash, and then automenu kicks in when     *
+ * lastambig == 2.                                                    */
 
-static int shortl, fshortl;
+/**/
+mod_export int lastambig, bashlistfirst;
 
-/* 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). */
+/* Arguments for and return value of completion widget. */
 
-static int amenu;
+/**/
+mod_export char **cfargs;
+/**/
+mod_export int cfret;
 
 /* Find out if we have to insert a tab (instead of trying to complete). */
 
@@ -208,208 +159,200 @@ usetab(void)
     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)
+/**/
+int
+completecall(char **args)
+{
+    cfargs = args;
+    cfret = 0;
+    compfunc = compwidget->u.comp.func;
+    if (compwidget->u.comp.fn(zlenoargs) && !cfret)
+	cfret = 1;
+    compfunc = NULL;
+
+    return cfret;
+}
 
 /**/
-void
-completeword(void)
+int
+completeword(char **args)
 {
-    usemenu = isset(MENUCOMPLETE);
+    usemenu = !!isset(MENUCOMPLETE);
     useglob = isset(GLOBCOMPLETE);
     if (c == '\t' && usetab())
-	selfinsert();
-    else
-	docomplete(COMP_COMPLETE);
+	return selfinsert(args);
+    else {
+	int ret;
+	if (lastambig == 1 && isset(BASHAUTOLIST) && !usemenu && !menucmp) {
+	    bashlistfirst = 1;
+	    ret = docomplete(COMP_LIST_COMPLETE);
+	    bashlistfirst = 0;
+	    lastambig = 2;
+	} else
+	    ret = docomplete(COMP_COMPLETE);
+	return ret;
+    }
 }
 
 /**/
-void
-menucomplete(void)
+mod_export int
+menucomplete(char **args)
 {
     usemenu = 1;
     useglob = isset(GLOBCOMPLETE);
     if (c == '\t' && usetab())
-	selfinsert();
+	return selfinsert(args);
     else
-	docomplete(COMP_COMPLETE);
+	return docomplete(COMP_COMPLETE);
 }
 
 /**/
-void
-listchoices(void)
+int
+listchoices(char **args)
 {
-    usemenu = isset(MENUCOMPLETE);
+    usemenu = !!isset(MENUCOMPLETE);
     useglob = isset(GLOBCOMPLETE);
-    docomplete(COMP_LIST_COMPLETE);
+    return docomplete(COMP_LIST_COMPLETE);
 }
 
 /**/
-void
-spellword(void)
+int
+spellword(char **args)
 {
     usemenu = useglob = 0;
-    docomplete(COMP_SPELL);
+    return docomplete(COMP_SPELL);
 }
 
 /**/
-void
-deletecharorlist(void)
+int
+deletecharorlist(char **args)
 {
-    char **mc = menucur;
-
-    usemenu = isset(MENUCOMPLETE);
+    usemenu = !!isset(MENUCOMPLETE);
     useglob = isset(GLOBCOMPLETE);
-    if (cs != ll)
-	deletechar();
-    else
-	docomplete(COMP_LIST_COMPLETE);
 
-    menucur = mc;
+    if (cs != ll) {
+	fixsuffix();
+	invalidatelist();
+	return deletechar(args);
+    }
+    return docomplete(COMP_LIST_COMPLETE);
 }
 
 /**/
-void
-expandword(void)
+int
+expandword(char **args)
 {
     usemenu = useglob = 0;
     if (c == '\t' && usetab())
-	selfinsert();
+	return selfinsert(args);
     else
-	docomplete(COMP_EXPAND);
+	return docomplete(COMP_EXPAND);
 }
 
 /**/
-void
-expandorcomplete(void)
+int
+expandorcomplete(char **args)
 {
-    usemenu = isset(MENUCOMPLETE);
+    usemenu = !!isset(MENUCOMPLETE);
     useglob = isset(GLOBCOMPLETE);
     if (c == '\t' && usetab())
-	selfinsert();
-    else
-	docomplete(COMP_EXPAND_COMPLETE);
+	return selfinsert(args);
+    else {
+	int ret;
+	if (lastambig == 1 && isset(BASHAUTOLIST) && !usemenu && !menucmp) {
+	    bashlistfirst = 1;
+	    ret = docomplete(COMP_LIST_COMPLETE);
+	    bashlistfirst = 0;
+	    lastambig = 2;
+	} else
+	    ret = docomplete(COMP_EXPAND_COMPLETE);
+	return ret;
+    }
 }
 
 /**/
-void
-menuexpandorcomplete(void)
+int
+menuexpandorcomplete(char **args)
 {
     usemenu = 1;
     useglob = isset(GLOBCOMPLETE);
     if (c == '\t' && usetab())
-	selfinsert();
+	return selfinsert(args);
     else
-	docomplete(COMP_EXPAND_COMPLETE);
+	return docomplete(COMP_EXPAND_COMPLETE);
 }
 
 /**/
-void
-listexpand(void)
+int
+listexpand(char **args)
 {
-    usemenu = isset(MENUCOMPLETE);
+    usemenu = !!isset(MENUCOMPLETE);
     useglob = isset(GLOBCOMPLETE);
-    docomplete(COMP_LIST_EXPAND);
+    return docomplete(COMP_LIST_EXPAND);
 }
 
 /**/
-void
-reversemenucomplete(void)
+mod_export int
+reversemenucomplete(char **args)
 {
-    if (!menucmp) {
-	menucomplete();
-	return;
-    }
-    HEAPALLOC {
-	if (menucur == amatches)
-	    menucur = amatches + nmatches - 1;
-	else
-	    menucur--;
-	metafy_line();
-	do_single(*menucur);
-	unmetafy_line();
-    } LASTALLOC;
-}
+    if (!menucmp)
+	return menucomplete(args);
 
-/* 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.  */
+    runhookdef(REVERSEMENUHOOK, NULL);
+    return 0;
+}
 
 /**/
-void
-acceptandmenucomplete(void)
+int
+acceptandmenucomplete(char **args)
 {
-    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();
+    if (!menucmp)
+	return 1;
+    runhookdef(ACCEPTCOMPHOOK, NULL);
+    return menucomplete(args);
 }
 
 /* These are flags saying if we are completing in the command *
- * position or in a redirection.                              */
-
-static int lincmd, linredir;
+ * position, in a redirection, or in a parameter expansion.   */
 
-/* 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;
+/**/
+mod_export int lincmd, linredir, linarr;
 
-/* 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.                                        */
+/* The string for the redirection operator. */
 
-static int haswhat;
+/**/
+mod_export char *rdstr;
 
-/* We have a suffix to add (given with compctl -S). */
+/* This holds the name of the current command (used to find the right *
+ * compctl).                                                          */
 
-#define HAS_SUFFIX  1
+/**/
+mod_export char *cmdstr;
 
-/* We have filenames in the completion list. */
+/* This hold the name of the variable we are working on. */
 
-#define HAS_FILES   2
+/**/
+mod_export char *varname;
 
-/* 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.                                   */
+/* != 0 if we are in a subscript */
 
-#define HAS_MISC    4
+/**/
+mod_export int insubscr;
 
-/* This is set if we have filenames in the completion list that were *
- * generated by a globcompletion pattern.                            */
+/* Parameter pointer for completing keys of an assoc array. */
 
-#define HAS_PATHPAT 8
+/**/
+mod_export Param keypm;
 
+/* 1 if we are completing in a quoted string (or inside `...`) */
 
-/* This holds the naem of the current command (used to find the right *
- * compctl).                                                          */
+/**/
+mod_export int instring, inbackt;
 
-static char *cmdstr;
+/* Convenience macro for calling bslashquote() (formerly quotename()). *
+ * This uses the instring variable above.                              */
 
+#define quotename(s, e) bslashquote(s, e, instring)
 
 /* Check if the given string is the name of a parameter and if this *
  * parameter is one worth expanding.                                */
@@ -429,7 +372,7 @@ checkparams(char *p)
 		    e = 1;
 	    }
     return (n == 1) ? (getsparam(p) != NULL) :
-	(!menucmp && e && isset(RECEXACT));
+	(!menucmp && e && (!hascompmod || isset(RECEXACT)));
 }
 
 /* Check if the given string has wildcards.  The difficulty is that we *
@@ -489,36 +432,112 @@ cmphaswilds(char *str)
     return 0;
 }
 
-/* The main entry point for completion. */
+/* Check if we have to complete a parameter name. */
 
 /**/
-static void
-docomplete(int lst)
+char *
+parambeg(char *s)
 {
-    char *s, *ol;
-    int olst = lst, chl = 0, ne = noerrs, ocs;
+    char *p;
+
+    /* Try to find a `$'. */
+    for (p = s + offs; p > s && *p != String && *p != Qstring; p--);
+    if (*p == String || *p == Qstring) {
+	/* Handle $$'s */
+	while (p > s && (p[-1] == String || p[-1] == Qstring))
+	    p--;
+	while ((p[1] == String || p[1] == Qstring) &&
+	       (p[2] == String || p[2] == Qstring))
+	    p += 2;
+    }
+    if ((*p == String || *p == Qstring) && 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, nest = 0;
 
-    /* If we are doing a menu-completion... */
+	if (*b == Inbrace) {
+	    char *tb = b;
+
+	    /* If this is a ${...}, see if we are before the '}'. */
+	    if (!skipparens(Inbrace, Outbrace, &tb))
+		return NULL;
+
+	    /* Ignore the possible (...) flags. */
+	    b++, br++;
+	    n = skipparens(Inpar, Outpar, &b);
+
+	    for (tb = p - 1; tb > s && *tb != Outbrace && *tb != Inbrace; tb--);
+	    if (tb > s && *tb == Inbrace && (tb[-1] == String || *tb == Qstring))
+		nest = 1;
+	}
+
+	/* 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++;
 
-    if (menucmp && lst != COMP_LIST_EXPAND) {
-	do_menucmp(lst);
-	return;
+	e = b;
+	if (br) {
+	    while (*e == Dnull)
+		e++;
+	}
+	/* 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))
+		e++;
+
+	/* Now make sure that the cursor is inside the name. */
+	if (offs <= e - s && offs >= b - s && n <= 0) {
+	    if (br) {
+		p = e;
+		while (*p == Dnull)
+		    p++;
+	    }
+	    /* It is. */
+	    return b;
+	}
     }
+    return NULL;
+}
 
-    /* Check if we have to start a menu-completion (via automenu). */
+/* The main entry point for completion. */
 
-    if ((amenu = (isset(AUTOMENU) && lastambig)))
-	usemenu = 1;
+/**/
+static int
+docomplete(int lst)
+{
+    char *s, *ol;
+    int olst = lst, chl = 0, ne = noerrs, ocs, ret = 0;
+
+    if (undoing)
+	setlastline();
+
+    if (runhookdef(BEFORECOMPLETEHOOK, (void *) &lst))
+	return 0;
 
     /* Expand history references before starting completion.  If anything *
      * changed, do no more.                                               */
 
     if (doexpandhist())
-	return;
+	return 0;
 
     metafy_line();
 
     ocs = cs;
+    origline = dupstring((char *) line);
+    origcs = cs;
+    origll = ll;
     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 *
@@ -535,6 +554,12 @@ docomplete(int lst)
 	ol = NULL;
     inwhat = IN_NOTHING;
     qword = NULL;
+    zsfree(qipre);
+    qipre = ztrdup("");
+    zsfree(qisuf);
+    qisuf = ztrdup("");
+    zsfree(autoq);
+    autoq = NULL;
     /* Get the word to complete. */
     noerrs = 1;
     s = get_comp_string();
@@ -558,8 +583,7 @@ docomplete(int lst)
 	    ll = strlen((char *) line);
 	    cs = ocs;
 	    unmetafy_line();
-	    feep();
-	    return;
+	    return 1;
 	}
 	ocs = cs;
 	cs = 0;
@@ -580,21 +604,18 @@ docomplete(int lst)
 	    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))
+		if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked)) {
+		    if (!hascompmod || 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);
+				if (strpfx(q, hn->nam) && findcmd(hn->nam, 0))
 				    n++;
-				}
 				if (n == 2)
 				    break;
 			    }
@@ -602,10 +623,11 @@ docomplete(int lst)
 			if (n == 1)
 			    lst = COMP_EXPAND;
 		    }
+		}
 	    }
 	    if (lst == COMP_EXPAND_COMPLETE)
 		do {
-		    /* check if there is a parameter expresiion. */
+		    /* Check if there is a parameter expression. */
 		    for (; *q && *q != String; q++);
 		    if (*q == String && q[1] != Inpar && q[1] != Inbrack) {
 			if (*++q == Inbrace) {
@@ -671,19 +693,20 @@ docomplete(int lst)
 	    inwhat = IN_CMD;
 
 	if (lst == COMP_SPELL) {
-	    char *x, *q;
+	    char *w = dupstring(origword), *x, *q, *ox;
 
-	    for (q = s; *q; q++)
+	    for (q = w; *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 = ox = dupstring(w));
+	    if (*w == Tilde || *w == Equals || *w == String)
+		*x = *w;
+	    spckword(&x, 0, lincmd, 0);
+	    ret = !strcmp(x, ox);
+
 	    untokenize(x);
 	    inststr(x);
 	} else if (COMP_ISEXPAND(lst)) {
@@ -693,7 +716,7 @@ docomplete(int lst)
 	    int ocs = cs, ne = noerrs;
 
 	    noerrs = 1;
-	    doexpansion(s, lst, olst, lincmd);
+	    ret = doexpansion(origword, lst, olst, lincmd);
 	    lastambig = 0;
 	    noerrs = ne;
 
@@ -701,62 +724,43 @@ docomplete(int lst)
 	     * 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);
-	    }
+		if (!compfunc) {
+		    char *p;
+
+		    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);
+			}
+		}
+		ret = docompletion(s, lst, lincmd);
+	    } else if (ret)
+		clearlist = 1;
 	} else
 	    /* Just do completion. */
-	    docompletion(s, lst, lincmd, 0);
+	    ret = docompletion(s, lst, lincmd);
 	zsfree(s);
-    }
+    } else
+	ret = 1;
     /* 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.                                              */
+    runhookdef(AFTERCOMPLETEHOOK, (void *) &lst);
 
-/**/
-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;
+    return ret;
 }
 
-/* 1 if we are completing in a string */
-static int instring;
-
 /* 1 if we are completing the prefix */
 static int comppref;
 
@@ -790,7 +794,7 @@ addx(char **ptmp)
 	(instring && (line[cs] == '"' || line[cs] == '\'')) ||
 	(addspace = (comppref && !iblank(line[cs])))) {
 	*ptmp = (char *)line;
-	line = (unsigned char *)halloc(strlen((char *)line) + 3 + addspace);
+	line = (unsigned char *)zhalloc(strlen((char *)line) + 3 + addspace);
 	memcpy(line, *ptmp, cs);
 	line[cs] = 'x';
 	if (addspace)
@@ -806,11 +810,11 @@ addx(char **ptmp)
 /* Like dupstring, but add an extra space at the end of the string. */
 
 /**/
-static char *
+mod_export char *
 dupstrspace(const char *str)
 {
     int len = strlen((char *)str);
-    char *t = (char *)ncalloc(len + 2);
+    char *t = (char *) hcalloc(len + 2);
     strcpy(t, str);
     strcpy(t+len, " ");
     return t;
@@ -824,7 +828,7 @@ dupstrspace(const char *str)
  * functions (there's one for each direction).                             */
 
 /**/
-static void
+mod_export void
 metafy_line(void)
 {
     int len = ll;
@@ -840,13 +844,71 @@ metafy_line(void)
 }
 
 /**/
-static void
+mod_export void
 unmetafy_line(void)
 {
     cs = ztrsub((char *) line + cs, (char *) line);
     (void) unmetafy((char *) line, &ll);
 }
 
+/* Free a brinfo list. */
+
+/**/
+mod_export void
+freebrinfo(Brinfo p)
+{
+    Brinfo n;
+
+    while (p) {
+	n = p->next;
+	zsfree(p->str);
+	zfree(p, sizeof(*p));
+
+	p = n;
+    }
+}
+
+/* Duplicate a brinfo list. */
+
+/**/
+mod_export Brinfo
+dupbrinfo(Brinfo p, Brinfo *last, int heap)
+{
+    Brinfo ret = NULL, *q = &ret, n = NULL;
+
+    while (p) {
+	n = *q = (heap ? (Brinfo) zhalloc(sizeof(*n)) :
+		  (Brinfo) zalloc(sizeof(*n)));
+	q = &(n->next);
+
+	n->next = NULL;
+	n->str = (heap ? dupstring(p->str) : ztrdup(p->str));
+	n->pos = p->pos;
+	n->qpos = p->qpos;
+	n->curpos = p->curpos;
+
+	p = p->next;
+    }
+    if (last)
+	*last = n;
+
+    return ret;
+}
+
+/* This is a bit like has_token(), but ignores nulls. */
+
+static int
+has_real_token(const char *s)
+{
+    while (*s) {
+	if (itok(*s) && !INULL(*s))
+	    return 1;
+	s++;
+    }
+    return 0;
+}
+
+
 /* 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       *
@@ -856,16 +918,24 @@ unmetafy_line(void)
 static char *
 get_comp_string(void)
 {
-    int t0, tt0, i, j, k, cp, rd, sl, ocs;
+    int t0, tt0, i, j, k, cp, rd, sl, ocs, ins, oins, ia, parct, varq = 0;
     char *s = NULL, *linptr, *tmp, *p, *tt = NULL;
 
-    complinbrace = 0;
+    freebrinfo(brbeg);
+    freebrinfo(brend);
+    brbeg = lastbrbeg = brend = lastbrend = NULL;
+    nbrbeg = nbrend = 0;
+    zsfree(lastprebr);
+    zsfree(lastpostbr);
+    lastprebr = lastpostbr = NULL;
+
     /* This global flag is used to signal the lexer code if it should *
      * expand aliases or not.                                         */
     noaliases = isset(COMPLETEALIASES);
 
     /* Find out if we are somewhere in a `string', i.e. inside '...', *
-     * "...", `...`, or ((...)).                                      */
+     * "...", `...`, or ((...)). Nowadays this is only used to find   *
+     * out if we are inside `...`.                                    */
 
     for (i = j = k = 0, p = (char *)line; p < (char *)line + cs; p++)
 	if (*p == '`' && !(k & 1))
@@ -876,144 +946,185 @@ get_comp_string(void)
 	    k++;
 	else if (*p == '\\' && p[1] && !(k & 1))
 	    p++;
-    instring = (j & 1) ? 2 : (k & 1);
+    inbackt = (i & 1);
+    instring = 0;
     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;
-	    }
+
+ start:
+    inwhat = IN_NOTHING;
+    /* Now set up the lexer and start it. */
+    parbegin = parend = -1;
+    lincmd = incmdpos;
+    linredir = inredir;
+    zsfree(cmdstr);
+    cmdstr = NULL;
+    zsfree(varname);
+    varname = NULL;
+    insubscr = 0;
+    zleparse = 1;
+    clwpos = -1;
+    lexsave();
+    inpush(dupstrspace((char *) linptr), 0, NULL);
+    strinbeg(0);
+    i = tt0 = cp = rd = ins = oins = linarr = parct = ia = 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 && !ins && !incond) || (oins == 2 && i == 2) ||
+		  (ins == 3 && i == 1));
+	linredir = (inredir && !ins);
+	oins = ins;
+	/* Get the next token. */
+	if (linarr)
+	    incmdpos = 0;
+	ctxtlex();
+
+	if (tok == LEXERR) {
 	    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;
+		break;
+	    for (j = 0, p = tokstr; *p; p++)
+		if (*p == Snull || *p == Dnull)
+		    j++;
+	    if (j & 1) {
+		if (lincmd && strchr(tokstr, '=')) {
+		    varq = 1;
+		    tok = ENVSTRING;
+		} else
+		    tok = STRING;
 	    }
-	    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;
+	} else if (tok == ENVSTRING)
+	    varq = 0;
+	if (tok == ENVARRAY) {
+	    linarr = 1;
+	    zsfree(varname);
+	    varname = ztrdup(tokstr);
+	} else if (tok == INPAR)
+	    parct++;
+	else if (tok == OUTPAR) {
+	    if (parct)
+		parct--;
+	    else
+		linarr = 0;
+	}
+	if (inredir)
+	    rdstr = tokstrings[tok];
+	if (tok == DINPAR)
+	    tokstr = NULL;
+
+	/* We reached the end. */
+	if (tok == ENDINPUT)
+	    break;
+	if ((ins && (tok == DO || tok == SEPER)) ||
+	    (ins == 2 && i == 2) || (ins == 3 && i == 3) ||
+	    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 = ins = 0;
+	}
+	if (lincmd && (tok == STRING || tok == FOR || tok == FOREACH ||
+		       tok == SELECT || tok == REPEAT || tok == CASE)) {
+	    /* The lexer says, this token is in command position, so *
+	     * store the token string (to find the right compctl).   */
+	    ins = (tok == REPEAT ? 2 : (tok != STRING));
+	    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;
+	    ia = linarr;
+	    if (inwhat == IN_NOTHING && incond)
+		inwhat = IN_COND;
+	} else if (linredir)
+	    continue;
+	if (incond) {
+	    if (tok == DBAR)
+		tokstr = "||";
+	    else if (tok == DAMPER)
+		tokstr = "&&";
+	}
+	if (!tokstr)
+	    continue;
+	/* Hack to allow completion after `repeat n do'. */
+	if (oins == 2 && !i && !strcmp(tokstr, "do"))
+	    ins = 3;
+	/* 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;
+    if (ia) {
+	lincmd = linredir = 0;
+	inwhat = IN_ENV;
+    } else {
 	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;
+    }
+    strinend();
+    inpop();
+    errflag = zleparse = 0;
+    if (parbegin != -1) {
+	/* We are in command or process substitution if we are not in
+	 * a $((...)). */
+	if (parend >= 0 && !tmp)
+	    line = (unsigned char *) dupstring(tmp = (char *)line);
+	linptr = (char *) line + ll + addedx - parbegin + 1;
+	if ((linptr - (char *) line) < 3 || *linptr != '(' ||
+	    linptr[-1] != '(' || linptr[-2] != '$') {
 	    if (parend >= 0) {
 		ll -= parend;
 		line[ll + addedx] = '\0';
@@ -1021,2467 +1132,566 @@ get_comp_string(void)
 	    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) {
+	char sav;
+	/* The cursor was inside a parameter assignment. */
+
+	if (varq)
+	    tt = clwords[clwpos];
 
-	if (inwhat == IN_MATH)
+	for (s = tt; iident(*s); s++);
+	sav = *s;
+	*s = '\0';
+	zsfree(varname);
+	varname = ztrdup(tt);
+	*s = sav;
+	if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb) {
 	    s = NULL;
-	else if (!t0 || t0 == ENDINPUT) {
-	    /* There was no word (empty line). */
-	    s = ztrdup("");
-	    we = wb = cs;
-	    clwpos = clwnum;
+	    inwhat = IN_MATH;
+	    if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
+		(keypm->flags & PM_HASHED))
+		insubscr = 2;
+	    else
+		insubscr = 1;
+	} else if (*s == '=' && cs > wb + (s - tt)) {
+	    s++;
+	    wb += s - tt;
 	    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;
+	    s = ztrdup(s);
+	    inwhat = IN_ENV;
 	}
-	if (we > ll)
-	    we = ll;
-	tt = (char *)line;
+	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) {
-	    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;
+	    tmp = NULL;
+	    linptr = (char *)line;
 	    lexrestore();
-	    LASTALLOC_RETURN NULL;
+	    addedx = 0;
+	    goto start;
 	}
-
 	noaliases = 0;
+	lexrestore();
+	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;
+    /* 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;
+	char *nnb = (iident(*s) ? s : s + 1), *nb = NULL, *ne = NULL;
+	
+	for (tt = s; ++tt < s + cs - wb;)
+	    if (*tt == Inbrack) {
+		i++;
+		nb = nnb;
+		ne = tt;
+	    } else if (i && *tt == Outbrack)
+		i--;
+	    else if (!iident(*tt))
+		nnb = tt + 1;
+	if (i) {
+	    inwhat = IN_MATH;
+	    insubscr = 1;
+	    if (nb < ne) {
+		char sav = *ne;
+		*ne = '\0';
+		zsfree(varname);
+		varname = ztrdup(nb);
+		*ne = sav;
+		if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
+		    (keypm->flags & PM_HASHED))
+		    insubscr = 2;
+	    }
 	}
-	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.                                               */
+    }
+    if (inwhat == IN_MATH) {
+	if (compfunc || insubscr == 2) {
+	    int lev;
+	    char *p;
+
+	    for (wb = cs - 1, lev = 0; wb > 0; wb--)
+		if (line[wb] == ']' || line[wb] == ')')
+		    lev++;
+		else if (line[wb] == '[') {
+		    if (!lev--)
+			break;
+		} else if (line[wb] == '(') {
+		    if (!lev && line[wb - 1] == '(')
+			break;
+		    if (lev)
+			lev--;
+		}
+	    p = (char *) line + wb;
+	    wb++;
+	    if (wb && (*p == '[' || *p == '(') &&
+		!skipparens(*p, (*p == '[' ? ']' : ')'), &p)) {
+		we = (p - (char *) line) - 1;
+		if (insubscr == 2)
+		    insubscr = 3;
+	    }
+	} else {
+	    /* 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--;
-		    }
+	zsfree(s);
+	s = zalloc(we - wb + 1);
+	strncpy(s, (char *) line + wb, we - wb);
+	s[we - wb] = '\0';
+	if (wb > 2 && line[wb - 1] == '[' && iident(line[wb - 2])) {
+	    int i = wb - 3;
+	    unsigned char sav = line[wb - 1];
+
+	    while (i >= 0 && iident(line[i]))
+		i--;
+
+	    line[wb - 1] = '\0';
+	    zsfree(varname);
+	    varname = ztrdup((char *) line + i + 1);
+	    line[wb - 1] = sav;
+	    if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
+		(keypm->flags & PM_HASHED)) {
+		if (insubscr != 3)
+		    insubscr = 2;
+	    } else
+		insubscr = 1;
+	}
+    }
+    /* This variable will hold the current word in quoted form. */
+    qword = ztrdup(s);
+    offs = cs - wb;
+    if ((p = parambeg(s))) {
+	for (p = s; *p; p++)
+	    if (*p == Dnull)
+		*p = '"';
+	    else if (*p == Snull)
+		*p = '\'';
+    }
+    if ((*s == Snull || *s == Dnull) && !has_real_token(s + 1)) {
+	char *q = (*s == Snull ? "'" : "\""), *n = tricat(qipre, q, "");
+	int sl = strlen(s);
+
+	instring = (*s == Snull ? 1 : 2);
+	zsfree(qipre);
+	qipre = n;
+	if (sl > 1 && s[sl - 1] == *s) {
+	    n = tricat(q, qisuf, "");
+	    zsfree(qisuf);
+	    qisuf = n;
+	}
+	autoq = ztrdup(q);
+    }
+    /* While building the quoted form, we also clean up the command line. */
+    for (p = s, tt = qword, i = wb; *p; p++, tt++, i++)
+	if (INULL(*p)) {
+	    if (i < cs)
+		offs--;
+	    if (p[1] || *p != Bnull) {
+		if (*p == Bnull) {
+		    *tt = '\\';
+		    if (cs == i + 1)
+			cs++, offs++;
 		} else {
 		    ocs = cs;
-		    *tt = '\0';
-		    cs = we;
-		    backdel(1);
-		    if (ocs == we)
-			cs = we - 1;
-		    else
-			cs = ocs;
+		    cs = i;
+		    foredel(1);
+		    chuck(tt--);
+		    if ((cs = ocs) > i--)
+			cs--;
 		    we--;
 		}
-		chuck(p--);
+	    } 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
+    zsfree(origword);
+    origword = ztrdup(s);
+
+    if (!isset(IGNOREBRACES)) {
+	/* Try and deal with foo{xxx etc. */
+	char *curs = s + (isset(COMPLETEINWORD) ? offs : strlen(s));
+	char *predup = dupstring(s), *dp = predup;
+	char *bbeg = NULL, *bend = NULL, *dbeg = NULL;
+	char *lastp = NULL, *firsts = NULL;
+	int cant = 0, begi = 0, boffs = offs, hascom = 0;
+
+	for (i = 0, p = s; *p; p++, dp++, i++) {
+	    /* careful, ${... is not a brace expansion...
+	     * we try to get braces after a parameter expansion right,
+	     * but this may fail sometimes. sorry.
 	     */
-	    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 */
+	    if (*p == String || *p == Qstring) {
+		if (p[1] == Inbrace || p[1] == Inpar || p[1] == Inbrack) {
+		    char *tp = p + 1;
+
+		    if (skipparens(*tp, (*tp == Inbrace ? Outbrace :
+					 (*tp == Inpar ? Outpar : Outbrack)),
+				   &tp)) {
 			tt = NULL;
 			break;
 		    }
-		    tt = p;
-		} else if (*p == Outbrace && i < myoffs) {
-		    /* outbrace is before cursor pos, so nothing to complete */
-		    tt = NULL;
+		    i += tp - p;
+		    dp += tp - p;
+		    p = tp;
+		} else {
+		    char *tp = p + 1;
+
+		    for (; *tp == '^' || *tp == Hat ||
+			     *tp == '=' || *tp == Equals ||
+			     *tp == '~' || *tp == Tilde ||
+			     *tp == '#' || *tp == Pound || *tp == '+';
+			 tp++);
+		    if (*tp == Quest || *tp == Star || *tp == String ||
+			*tp == Qstring || *tp == '?' || *tp == '*' ||
+			*tp == '$' || *tp == '-' || *tp == '!' ||
+			*tp == '@')
+			p++, i++;
+		    else {
+			if (idigit(*tp))
+			    while (idigit(*tp))
+				tp++;
+			else if (iident(*tp))
+			    while (iident(*tp))
+				tp++;
+			else {
+			    tt = NULL;
+			    break;
+			}
+			if (*tp == Inbrace) {
+			    cant = 1;
+			    break;
+			}
+			tp--;
+			i += tp - p;
+			dp += tp - p;
+			p = tp;
+		    }
+		}
+	    } else if (p < curs) {
+		if (*p == Outbrace) {
+		    cant = 1;
 		    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--;
+		if (*p == Inbrace) {
+		    if (bbeg) {
+			Brinfo new;
+			int len = bend - bbeg;
+
+			new = (Brinfo) zalloc(sizeof(*new));
+			nbrbeg++;
+
+			new->next = NULL;
+			if (lastbrbeg)
+			    lastbrbeg->next = new;
+			else
+			    brbeg = new;
+			lastbrbeg = new;
+
+			new->next = NULL;
+			new->str = ztrduppfx(bbeg, len);
+			untokenize(new->str);
+			new->pos = begi;
+			*dbeg = '\0';
+			new->qpos = strlen(quotename(predup, NULL));
+			*dbeg = '{';
+			i -= len;
+			boffs -= len;
+			strcpy(dbeg, dbeg + len);
+			dp -= len;
+		    }
+		    bbeg = lastp = p;
+		    dbeg = dp;
+		    bend = p + 1;
+		    begi = i;
+		} else if (*p == Comma && bbeg) {
+		    bend = p + 1;
+		    hascom = 1;
 		}
-
-		/* 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;
+	    } else {
+		if (*p == Inbrace) {
+		    cant = 1;
+		    break;
+		}
+		if (p == curs) {
+		    if (bbeg) {
+			Brinfo new;
+			int len = bend - bbeg;
+
+			new = (Brinfo) zalloc(sizeof(*new));
+			nbrbeg++;
+
+			new->next = NULL;
+			if (lastbrbeg)
+			    lastbrbeg->next = new;
+			else
+			    brbeg = new;
+			lastbrbeg = new;
+
+			new->str = ztrduppfx(bbeg, len);
+			untokenize(new->str);
+			new->pos = begi;
+			*dbeg = '\0';
+			new->qpos = strlen(quotename(predup, NULL));
+			*dbeg = '{';
+			i -= len;
+			boffs -= len;
+			strcpy(dbeg, dbeg + len);
+			dp -= len;
 		    }
-		if (*p == Outbrace)
-		    chuck(p);
-		else {
-		    /* we are still waiting for an outbrace and maybe commas */
-		    complinbrace = 1;
+		    bbeg = NULL;
+		}
+		if (*p == Comma) {
+		    if (!bbeg)
+			bbeg = p;
+		    hascom = 1;
+		} else if (*p == Outbrace) {
+		    Brinfo new;
+		    int len;
+
+		    if (!bbeg)
+			bbeg = p;
+		    len = p + 1 - bbeg;
+		    if (!firsts)
+			firsts = p + 1;
+
+		    new = (Brinfo) zalloc(sizeof(*new));
+		    nbrend++;
+
+		    if (!lastbrend)
+			lastbrend = new;
+
+		    new->next = brend;
+		    brend = new;
+
+		    new->str = ztrduppfx(bbeg, len);
+		    untokenize(new->str);
+		    new->pos = dp - predup - len + 1;
+		    new->qpos = len;
+		    bbeg = NULL;
 		}
 	    }
 	}
+	if (cant) {
+	    freebrinfo(brbeg);
+	    freebrinfo(brend);
+	    brbeg = lastbrbeg = brend = lastbrend = NULL;
+	    nbrbeg = nbrend = 0;
+	} else {
+	    if (p == curs && bbeg) {
+		Brinfo new;
+		int len = bend - bbeg;
 
-    } LASTALLOC;
-    lexrestore();
-
-    return (char *)s;
-}
-
-/* Expand the current word. */
+		new = (Brinfo) zalloc(sizeof(*new));
+		nbrbeg++;
 
-/**/
-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++] = ' ';
+		new->next = NULL;
+		if (lastbrbeg)
+		    lastbrbeg->next = new;
+		else
+		    brbeg = new;
+		lastbrbeg = new;
+
+		new->str = ztrduppfx(bbeg, len);
+		untokenize(new->str);
+		new->pos = begi;
+		*dbeg = '\0';
+		new->qpos = strlen(quotename(predup, NULL));
+		*dbeg = '{';
+		boffs -= len;
+		strcpy(dbeg, dbeg + len);
 	    }
-#endif
-	    if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) ||
-		(cs && line[cs-1] != '/')) {
-		spaceinline(1);
-		line[cs++] = ' ';
+	    if (brend) {
+		Brinfo bp, prev = NULL;
+		int p, l;
+
+		for (bp = brend; bp; bp = bp->next) {
+		    bp->prev = prev;
+		    prev = bp;
+		    p = bp->pos;
+		    l = bp->qpos;
+		    bp->pos = strlen(predup + p + l);
+		    bp->qpos = strlen(quotename(predup + p + l, NULL));
+		    strcpy(predup + p, predup + p + l);
+		}
 	    }
-	}
-      end:
-	popheap();
-    } LASTALLOC;
-}
+	    if (hascom) {
+		if (lastp) {
+		    char sav = *lastp;
 
-/* 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;
+		    *lastp = '\0';
+		    untokenize(lastprebr = ztrdup(s));
+		    *lastp = sav;
+		}
+		if ((lastpostbr = ztrdup(firsts)))
+		    untokenize(lastpostbr);
+	    }
+	    zsfree(s);
+	    s = ztrdup(predup);
+	    offs = boffs;
+	}
     }
+    lexrestore();
+
+    return (char *)s;
 }
 
 /* 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).              */
+ * to insert (if it is -1, the length is calculated here).              *
+ * The last argument says if we should quote the string.                */
 
 /**/
-static void
+mod_export int
 inststrlen(char *str, int move, int len)
 {
-    if (!len)
-	return;
+    if (!len || !str)
+	return 0;
     if (len == -1)
 	len = strlen(str);
     spaceinline(len);
     strncpy((char *)(line + cs), str, len);
     if (move)
 	cs += len;
+    return 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.           */
+/* Expand the current word. */
 
 /**/
 static int
-makecomplist(char *s, int incmd, int *delit, int *compadd, int untokenized)
+doexpansion(char *s, int lst, int olst, int explincmd)
 {
-    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;
+    int ret = 1;
+    LinkList vl;
+    char *ss;
 
-		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;
+    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;
-
-	/* 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);
-	}
+	globlist(vl, 1);
 	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);
+    if (errflag)
+	goto end;
+    if (empty(vl) || !*(char *)peekfirst(vl))
+	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);
+	goto end;
+    }
+    if (lst == COMP_LIST_EXPAND) {
+	/* Only the list of expansions was requested. */
+	ret = listlist(vl);
+	showinglist = 0;
+	goto end;
+    }
+    /* Remove the current word and put the expansions there. */
+    cs = wb;
+    foredel(we - wb);
+    while ((ss = (char *)ugetnode(vl))) {
+	ret = 0;
+	ss = quotename(ss, NULL);
+	untokenize(ss);
+	inststr(ss);
+#if 0
+	if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) ||
+	    (cs && line[cs-1] != '/')) {
+#endif
+	if (nonempty(vl)) {
+	    spaceinline(1);
+	    line[cs++] = ' ';
 	}
-	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;
     }
+    end:
+    popheap();
 
-    /* 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;
+    return ret;
 }
 
-/* Invalidate the completion list. */
+/* This is called from the lexer to give us word positions. */
 
 /**/
 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)
+gotword(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;
-	}
+    we = ll + 1 - inbufct + (addedx == 2 ? 1 : 0);
+    if (cs <= we) {
+	wb = ll - wordbeg + addedx;
+	zleparse = 0;
     }
-    /* 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)
+docompletion(char *s, int lst, int incmd)
 {
-    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;
-}
+    struct compldat dat;
 
-/* This handles the beginning of menu-completion. */
+    dat.s = s;
+    dat.lst = lst;
+    dat.incmd = incmd;
 
-/**/
-static void
-do_ambig_menu(void)
-{
-    menucmp = 1;
-    menucur = NULL;
-    do_single(amatches[0]);
-    menucur = amatches;
+    return runhookdef(COMPLETEHOOK, (void *) &dat);
 }
 
 /* Return the length of the common prefix of s and t. */
 
 /**/
-int
+mod_export int
 pfxlen(char *s, char *t)
 {
     int i = 0;
@@ -3493,7 +1703,7 @@ pfxlen(char *s, char *t)
 
 /* Return the length of the common suffix of s and t. */
 
-/**/
+#if 0
 static int
 sfxlen(char *s, char *t)
 {
@@ -3508,21 +1718,68 @@ sfxlen(char *s, char *t)
     } else
 	return 0;
 }
+#endif
+
+/* This is strcmp with ignoring backslashes. */
+
+/**/
+mod_export int
+strbpcmp(char **aa, char **bb)
+{
+    char *a = *aa, *b = *bb;
+
+    while (*a && *b) {
+	if (*a == '\\')
+	    a++;
+	if (*b == '\\')
+	    b++;
+	if (*a != *b)
+	    break;
+	if (*a)
+	    a++;
+	if (*b)
+	    b++;
+    }
+    if (isset(NUMERICGLOBSORT) && (idigit(*a) || idigit(*b))) {
+	for (; a > *aa && idigit(a[-1]); a--, b--);
+	if (idigit(*a) && idigit(*b)) {
+	    while (*a == '0')
+		a++;
+	    while (*b == '0')
+		b++;
+	    for (; idigit(*a) && *a == *b; a++, b++);
+	    if (idigit(*a) || idigit(*b)) {
+		int cmp = (int) STOUC(*a) - (int) STOUC(*b);
+
+		while (idigit(*a) && idigit(*b))
+		    a++, b++;
+		if (idigit(*a) && !idigit(*b))
+		    return 1;
+		if (idigit(*b) && !idigit(*a))
+		    return -1;
+
+		return cmp;
+	    }
+	}
+    }
+    return (int)(*a - *b);
+}
 
-/* This is used to print the explanation string. *
+/* This is used to print the strings (e.g. explanations). *
  * It returns the number of lines printed.       */
 
 /**/
-static int
-printfmt(char *fmt, int n, int dopr)
+mod_export int
+printfmt(char *fmt, int n, int dopr, int doesc)
 {
     char *p = fmt, nc[DIGBUFSIZE];
-    int l = 0, cc = 0;
+    int l = 0, cc = 0, b = 0, s = 0, u = 0, m;
 
     for (; *p; p++) {
 	/* Handle the `%' stuff (%% == %, %n == <number of matches>). */
-	if (*p == '%') {
+	if (doesc && *p == '%') {
 	    if (*++p) {
+		m = 0;
 		switch (*p) {
 		case '%':
 		    if (dopr)
@@ -3535,12 +1792,69 @@ printfmt(char *fmt, int n, int dopr)
 			fprintf(shout, nc);
 		    cc += strlen(nc);
 		    break;
+		case 'B':
+		    b = 1;
+		    if (dopr)
+			tcout(TCBOLDFACEBEG);
+		    break;
+		case 'b':
+		    b = 0; m = 1;
+		    if (dopr)
+			tcout(TCALLATTRSOFF);
+		    break;
+		case 'S':
+		    s = 1;
+		    if (dopr)
+			tcout(TCSTANDOUTBEG);
+		    break;
+		case 's':
+		    s = 0; m = 1;
+		    if (dopr)
+			tcout(TCSTANDOUTEND);
+		    break;
+		case 'U':
+		    u = 1;
+		    if (dopr)
+			tcout(TCUNDERLINEBEG);
+		    break;
+		case 'u':
+		    u = 0; m = 1;
+		    if (dopr)
+			tcout(TCUNDERLINEEND);
+		    break;
+		case '{':
+		    for (p++; *p && (*p != '%' || p[1] != '}'); p++)
+			if (dopr)
+			    putc(*p, shout);
+		    if (*p)
+			p++;
+		    else
+			p--;
+		    break;
+		}
+		if (dopr && m) {
+		    if (b)
+			tcout(TCBOLDFACEBEG);
+		    if (s)
+			tcout(TCSTANDOUTBEG);
+		    if (u)
+			tcout(TCUNDERLINEBEG);
 		}
 	    } else
 		break;
 	} else {
 	    cc++;
 	    if (*p == '\n') {
+		if (dopr) {
+		    if (tccan(TCCLEAREOL))
+			tcout(TCCLEAREOL);
+		    else {
+			int s = columns - 1 - (cc % columns);
+
+			while (s-- > 0)
+			    putc(' ', shout);
+		    }
+		}
 		l += 1 + (cc / columns);
 		cc = 0;
 	    }
@@ -3548,137 +1862,136 @@ printfmt(char *fmt, int n, int dopr)
 		putc(*p, shout);
 	}
     }
+    if (dopr) {
+	if (tccan(TCCLEAREOL))
+	    tcout(TCCLEAREOL);
+	else {
+	    int s = columns - 1 - (cc % columns);
 
+	    while (s-- > 0)
+		putc(' ', shout);
+	}
+    }
     return l + (cc / columns);
 }
 
-/* List the matches.  Note that the list entries are metafied. */
+/* This is used to print expansions. */
 
 /**/
-void
-listmatches(void)
+int
+listlist(LinkList l)
 {
-    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;
+    int num = countlinknodes(l);
+    VARARR(char *, data, (num + 1));
+    LinkNode node;
+    char **p;
+    VARARR(int, lens, num);
+    VARARR(int, widths, columns);
+    int longest = 0, shortest = columns, totl = 0;
+    int len, ncols, nlines, tolast, col, i, max, pack = 0, *lenp;
+
+    for (node = firstnode(l), p = data; node; incnode(node), p++)
+	*p = (char *) getdata(node);
+    *p = NULL;
+
+    qsort((void *) data, num, sizeof(char *),
+	  (int (*) _((const void *, const void *))) strbpcmp);
+
+    for (p = data, lenp = lens; *p; p++, lenp++) {
+	len = *lenp = niceztrlen(*p) + 2;
+	if (len > longest)
+	    longest = len;
+	if (len < shortest)
+	    shortest = len;
+	totl += len;
+    }
+    if ((ncols = ((columns + 2) / longest))) {
+	int tlines = 0, tline, tcols = 0, maxlen, nth, width;
+
+	nlines = (num + ncols - 1) / ncols;
+
+	if (isset(LISTPACKED)) {
+	    if (isset(LISTROWSFIRST)) {
+		int count, tcol, first, maxlines = 0, llines;
+
+		for (tcols = columns / shortest; tcols > ncols;
+		     tcols--) {
+		    for (nth = first = maxlen = width = maxlines =
+			     llines = tcol = 0,
+			     count = num;
+			 count > 0; count--) {
+			if (!(nth % tcols))
+			    llines++;
+			if (lens[nth] > maxlen)
+			    maxlen = lens[nth];
+			nth += tcols;
+			tlines++;
+			if (nth >= num) {
+			    if ((width += maxlen) >= columns)
+				break;
+			    widths[tcol++] = maxlen;
+			    maxlen = 0;
+			    nth = ++first;
+			    if (llines > maxlines)
+				maxlines = llines;
+			    llines = 0;
+			}
+		    }
+		    if (nth < num) {
+			widths[tcol++] = maxlen;
+			width += maxlen;
+		    }
+		    if (!count && width < columns)
+			break;
+		}
+		if (tcols > ncols)
+		    tlines = maxlines;
+	    } else {
+		for (tlines = ((totl + columns) / columns);
+		     tlines < nlines; tlines++) {
+		    for (p = data, nth = tline = width =
+			     maxlen = tcols = 0;
+			 *p; nth++, p++) {
+			if (lens[nth] > maxlen)
+			    maxlen = lens[nth];
+			if (++tline == tlines) {
+			    if ((width += maxlen) >= columns)
+				break;
+			    widths[tcols++] = maxlen;
+			    maxlen = tline = 0;
+			}
+		    }
+		    if (tline) {
+			widths[tcols++] = maxlen;
+			width += maxlen;
+		    }
+		    if (nth == num && width < columns)
+			break;
 		}
 	    }
+	    if ((pack = (tlines < nlines))) {
+		nlines = tlines;
+		ncols = tcols;
+	    }
 	}
     } 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);
-	}
+	nlines = 0;
+	for (p = data; *p; p++)
+	    nlines += 1 + (strlen(*p) / columns);
     }
+    /* Set the cursor below the prompt. */
+    trashzle();
 
-    /* Print the explanation string, if any. */
-    if (expl) {
-	xup = printfmt(expl, ct, 1) + 1;
-	putc('\n', shout);
-	up += xup;
-    }
+    tolast = ((zmult == 1) == !!isset(ALWAYSLASTPROMPT));
+    clearflag = (isset(USEZLE) && !termflags && tolast);
 
-    /* Maybe we have to ask if the user wants to see the list. */
-    if ((listmax && ct > listmax) || (!listmax && up >= lines)) {
+    max = getiparam("LISTMAX");
+    if ((max && num > max) || (!max && nlines > lines)) {
 	int qup;
-	setterm();
-	qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1);
+
+	zsetterm();
+	qup = printfmt("zsh: do you wish to see all %n possibilities? ",
+		       num, 1, 1);
 	fflush(shout);
 	if (getzlequery() != 'y') {
 	    if (clearflag) {
@@ -3686,10 +1999,10 @@ listmatches(void)
 		tcmultout(TCUP, TCMULTUP, qup);
 		if (tccan(TCCLEAREOD))
 		    tcout(TCCLEAREOD);
-		tcmultout(TCUP, TCMULTUP, nlnct + xup);
+		tcmultout(TCUP, TCMULTUP, nlnct);
 	    } else
 		putc('\n', shout);
-	    return;
+	    return 1;
 	}
 	if (clearflag) {
 	    putc('\r', shout);
@@ -3700,124 +2013,61 @@ listmatches(void)
 	    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;
+    lastlistlen = (clearflag ? nlines : 0);
+
+    if (ncols) {
+	if (isset(LISTROWSFIRST)) {
+	    for (col = 1, p = data, lenp = lens; *p;
+		 p++, lenp++, col++) {
+		nicezputs(*p, shout);
+		if (col == ncols) {
+		    col = 0;
+		    if (p[1])
+			putc('\n', shout);
 		} 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 ((i = (pack ? widths[col - 1] : longest) - *lenp + 2) > 0)
+			while (i--)
+			    putc(' ', shout);
 		}
-		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;
+	} else {
+	    char **f;
+	    int *fl, line;
+
+	    for (f = data, fl = lens, line = 0; line < nlines;
+		 f++, fl++, line++) {
+		for (col = 1, p = f, lenp = fl; *p; col++) {
+		    nicezputs(*p, shout);
+		    if (col == ncols)
+			break;
+		    if ((i = (pack ? widths[col - 1] : longest) - *lenp + 2) > 0)
+			while (i--)
+			    putc(' ', shout);
+		    for (i = nlines; i && *p; i--, p++, lenp++);
 		}
-		for (t0 = colsz; t0 && *ap; t0--, ap++);
-		if (*ap)
-		    for (; t2 < fw; t2++)
-			putc(' ', shout);
+		if (line + 1 < nlines)
+		    putc('\n', shout);
 	    }
-	if (t1 != colsz - 1 || !clearflag)
+	}
+    } else {
+	for (p = data; *p; p++) {
+	    nicezputs(*p, shout);
 	    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);
+    if (clearflag) {
+	if ((nlines += nlnct - 1) < lines) {
+	    tcmultout(TCUP, TCMULTUP, nlines);
 	    showinglist = -1;
 	} else
 	    clearflag = 0, putc('\n', shout);
-}
+    } else
+	putc('\n', shout);
 
-/* This is used to print expansions. */
+    if (listshown)
+	showagain = 1;
 
-/**/
-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;
+    return !num;
 }
 
 /* Expand the history references. */
@@ -3829,80 +2079,80 @@ 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;
-	    }
+    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(1);
+    noaliases = 1;
+    noerrs = 1;
+    exlast = inbufct;
+    do {
+	ctxtlex();
+    } while (tok != ENDINPUT && tok != LEXERR);
+    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();
+	    return 1;
 	}
+    }
 
-	strcpy((char *)line, (char *)ol);
-	ll = oll;
-	cs = ocs;
-	unmetafy_line();
+    strcpy((char *)line, (char *)ol);
+    ll = oll;
+    cs = ocs;
+    unmetafy_line();
+
+    popheap();
 
-	popheap();
-    } LASTALLOC;
     return 0;
 }
 
 /**/
-void
-magicspace(void)
+int
+magicspace(char **args)
 {
+    int ret;
     c = ' ';
-    selfinsert();
-    doexpandhist();
+    if (!(ret = selfinsert(args)))
+	doexpandhist();
+    return ret;
 }
 
 /**/
-void
-expandhistory(void)
+int
+expandhistory(char **args)
 {
     if (!doexpandhist())
-	feep();
+	return 1;
+    return 0;
 }
 
 static int cmdwb, cmdwe;
@@ -3914,65 +2164,62 @@ 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;
-	    }
+    zleparse = 2;
+    lexsave();
+    metafy_line();
+    inpush(dupstrspace((char *) line), 0, NULL);
+    unmetafy_line();
+    strinbeg(1);
+    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;
+    }
+    while (tok != ENDINPUT && tok != LEXERR && zleparse);
+    popheap();
+    strinend();
+    inpop();
+    errflag = zleparse = 0;
+    lexrestore();
+
     return s;
 }
 
 /**/
-void
-processcmd(void)
+int
+processcmd(char **args)
 {
     char *s;
     int m = zmult;
 
     s = getcurcmd();
-    if (!s) {
-	feep();
-	return;
-    }
+    if (!s)
+	return 1;
     zmult = 1;
-    pushline();
+    pushline(zlenoargs);
     zmult = m;
     inststr(bindk->nam);
     inststr(" ");
     untokenize(s);
-    HEAPALLOC {
-	inststr(quotename(s, NULL, NULL, NULL));
-    } LASTALLOC;
+
+    inststr(quotename(s, NULL));
+
     zsfree(s);
     done = 1;
+    return 0;
 }
 
 /**/
-void
-expandcmdpath(void)
+int
+expandcmdpath(char **args)
 {
     int oldcs = cs, na = noaliases;
     char *s, *str;
@@ -3980,16 +2227,12 @@ expandcmdpath(void)
     noaliases = 1;
     s = getcurcmd();
     noaliases = na;
-    if (!s || cmdwb < 0 || cmdwe < cmdwb) {
-	feep();
-	return;
-    }
-    str = findcmd(s);
+    if (!s || cmdwb < 0 || cmdwe < cmdwb)
+	return 1;
+    str = findcmd(s, 1);
     zsfree(s);
-    if (!str) {
-	feep();
-	return;
-    }
+    if (!str)
+	return 1;
     cs = cmdwb;
     foredel(cmdwe - cmdwb);
     spaceinline(strlen(str));
@@ -3999,17 +2242,43 @@ expandcmdpath(void)
 	cs += cmdwe - cmdwb + strlen(str);
     if (cs > ll)
 	cs = ll;
-    zsfree(str);
+    return 0;
 }
 
 /* Extra function added by AR Iano-Fletcher. */
 /* This is a expand/complete in the vein of wash. */
 
 /**/
-void
-expandorcompleteprefix(void)
+int
+expandorcompleteprefix(char **args)
 {
+    int ret;
+
     comppref = 1;
-    expandorcomplete();
+    ret = expandorcomplete(args);
     comppref = 0;
+    return ret;
+}
+
+/**/
+int
+endoflist(char **args)
+{
+    if (lastlistlen > 0) {
+	int i;
+
+	clearflag = 0;
+	trashzle();
+
+	for (i = lastlistlen; i > 0; i--)
+	    putc('\n', shout);
+
+	showinglist = lastlistlen = 0;
+
+	if (sfcontext)
+	    zrefresh();
+
+	return 0;
+    }
+    return 1;
 }