about summary refs log tree commit diff
path: root/Src/Zle/compresult.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Zle/compresult.c')
-rw-r--r--Src/Zle/compresult.c1940
1 files changed, 1940 insertions, 0 deletions
diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c
new file mode 100644
index 000000000..fe997b12b
--- /dev/null
+++ b/Src/Zle/compresult.c
@@ -0,0 +1,1940 @@
+/*
+ * compresult.c - the complete module, completion result handling
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1999 Sven Wischnowsky
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Sven Wischnowsky or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Sven Wischnowsky and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Sven Wischnowsky and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "complete.mdh"
+#define GLOBAL_PROTOTYPES
+#include "zle_tricky.pro"
+#undef GLOBAL_PROTOTYPES
+#include "compresult.pro"
+
+/* Convenience macro for calling bslashquote() (formerly quotename()). *
+ * This uses the instring variable above.                              */
+
+#define quotename(s, e) bslashquote(s, e, instring)
+
+#define inststr(X) inststrlen((X),1,-1)
+
+/* This cuts the cline list before the stuff that isn't worth
+ * inserting in the line. */
+
+/**/
+static Cline
+cut_cline(Cline l)
+{
+    Cline q, p, e = NULL, maxp = NULL;
+    int sum = 0, max = 0, tmp, ls = 0;
+
+    /* If no match was added with matching, we don't really know
+     * which parts of the unambiguous string are worth keeping,
+     * so for now we keep everything (in the hope that this
+     * produces a string containing at least everything that was 
+     * originally on the line). */
+
+    if (!hasmatched) {
+	cline_setlens(l, 0);
+	return l;
+    }
+    e = l = cp_cline(l, 0);
+
+    /* First, search the last struct for which we have something on
+     * the line. Anything before that is kept. */
+
+    for (q = NULL, p = l; p; p = p->next) {
+	if (p->orig || p->olen || !(p->flags & CLF_NEW))
+	    e = p->next;
+	if (!p->suffix && (p->wlen || p->llen || p->prefix))
+	    q = p;
+    }
+    if (!e && q && !q->orig && !q->olen && (q->flags & CLF_MISS) &&
+	!(q->flags & CLF_MATCHED) && (q->word ? q->wlen : q->llen) < 3) {
+	q->word = q->line = NULL;
+	q->wlen = q->llen = 0;
+    }
+    /* Then keep all structs without missing characters. */
+
+    while (e && !(e->flags & CLF_MISS))
+	e = e->next;
+
+    if (e) {
+	/* Then we see if there is another struct with missing
+	 * characters. If not, we keep the whole list. */
+
+	for (p = e->next; p && !(p->flags & CLF_MISS); p = p->next);
+
+	if (p) {
+	    for (p = e; p; p = p->next) {
+		if (!(p->flags & CLF_MISS))
+		    sum += p->max;
+		else {
+		    tmp = cline_sublen(p);
+		    if (tmp > 2 && tmp > ((p->max + p->min) >> 1))
+			sum += tmp - (p->max - tmp);
+		    else if (tmp < p->min)
+			sum -= (((p->max + p->min) >> 1) - tmp) << (tmp < 2);
+		}
+		if (sum > max) {
+		    max = sum;
+		    maxp = p;
+		}
+	    }
+	    if (max)
+		e = maxp;
+	    else {
+		int len = 0;
+
+		cline_setlens(l, 0);
+		ls = 1;
+
+		for (p = e; p; p = p->next)
+		    len += p->max;
+
+		if (len > ((minmlen << 1) / 3))
+		    return l;
+	    }
+	    e->line = e->word = NULL;
+	    e->llen = e->wlen = e->olen = 0;
+	    e->next = NULL;
+	}
+    }
+    if (!ls)
+	cline_setlens(l, 0);
+
+    return l;
+}
+
+/* This builds the unambiguous string. If ins is non-zero, it is
+ * immediatly inserted in the line. Otherwise csp is used to return
+ * the relative cursor position in the string returned. */
+
+/**/
+static char *
+cline_str(Cline l, int ins, int *csp)
+{
+    Cline s;
+    int ocs = cs, ncs, pcs, scs, pm, pmax, pmm, sm, smax, smm, d, dm, mid;
+    int i, j, li = 0, cbr;
+    Brinfo brp, brs;
+
+    l = cut_cline(l);
+
+    pmm = smm = dm = 0;
+    pm = pmax = sm = smax = d = mid = cbr = -1;
+
+    /* Get the information about the brace beginning and end we have
+     * to re-insert. */
+    if (ins) {
+	Brinfo bp;
+	int olen = we - wb;
+
+	if ((brp = brbeg)) {
+	    for (bp = brbeg; bp; bp = bp->next) {
+		bp->curpos = (hasunqu ? bp->pos : bp->qpos);
+		olen -= strlen(bp->str);
+	    }
+	}
+	if ((brs = lastbrend)) {
+	    for (bp = brend; bp; bp = bp->next)
+		olen -= strlen(bp->str);
+
+	    for (bp = brend; bp; bp = bp->next)
+		bp->curpos = olen - (hasunqu ? bp->pos : bp->qpos);
+	}
+	while (brp && !brp->curpos) {
+	    inststrlen(brp->str, 1, -1);
+	    brp = brp->next;
+	}
+	while (brs && !brs->curpos) {
+	    if (cbr < 0)
+		cbr = cs;
+	    inststrlen(brs->str, 1, -1);
+	    brs = brs->prev;
+	}
+    }
+    /* Walk through the top-level cline list. */
+    while (l) {
+	/* Insert the original string if no prefix. */
+	if (l->olen && !(l->flags & CLF_SUF) && !l->prefix) {
+	    pcs = cs + l->olen;
+	    inststrlen(l->orig, 1, l->olen);
+	} else {
+	    /* Otherwise insert the prefix. */
+	    for (s = l->prefix; s; s = s->next) {
+		pcs = cs + s->llen;
+		if (s->flags & CLF_LINE)
+		    inststrlen(s->line, 1, s->llen);
+		else
+		    inststrlen(s->word, 1, s->wlen);
+		scs = cs;
+
+		if ((s->flags & CLF_DIFF) && (!dm || (s->flags & CLF_MATCHED))) {
+		    d = cs; dm = s->flags & CLF_MATCHED;
+		}
+		li += s->llen;
+	    }
+	}
+	if (ins) {
+	    int ocs, bl;
+
+	    while (brp && li >= brp->curpos) {
+		ocs = cs;
+		bl = strlen(brp->str);
+		cs = pcs - (li - brp->curpos);
+		inststrlen(brp->str, 1, bl);
+		cs = ocs + bl;
+		pcs += bl;
+		scs += bl;
+		brp = brp->next;
+	    }
+	}
+	/* Remember the position if this is the first prefix with
+	 * missing characters. */
+	if ((l->flags & CLF_MISS) && !(l->flags & CLF_SUF) &&
+	    ((pmax < (l->min - l->max) && (!pmm || (l->flags & CLF_MATCHED))) ||
+	     ((l->flags & CLF_MATCHED) && !pmm))) {
+	    pm = cs; pmax = l->min - l->max; pmm = l->flags & CLF_MATCHED;
+	}
+	if (ins) {
+	    int ocs, bl;
+
+	    while (brs && li >= brs->curpos) {
+		ocs = cs;
+		bl = strlen(brs->str);
+		cs = scs - (li - brs->curpos);
+		if (cbr < 0)
+		    cbr = cs;
+		inststrlen(brs->str, 1, bl);
+		cs = ocs + bl;
+		pcs += bl;
+		brs = brs->prev;
+	    }
+	}
+	pcs = cs;
+	/* Insert the anchor. */
+	if (l->flags & CLF_LINE)
+	    inststrlen(l->line, 1, l->llen);
+	else
+	    inststrlen(l->word, 1, l->wlen);
+	scs = cs;
+	if (ins) {
+	    int ocs, bl;
+
+	    li += l->llen;
+
+	    while (brp && li >= brp->curpos) {
+		ocs = cs;
+		bl = strlen(brp->str);
+		cs = pcs + l->llen - (li - brp->curpos);
+		inststrlen(brp->str, 1, bl);
+		cs = ocs + bl;
+		pcs += bl;
+		scs += bl;
+		brp = brp->next;
+	    }
+	}
+	/* Remember the cursor position for suffixes and mids. */
+	if (l->flags & CLF_MISS) {
+	    if (l->flags & CLF_MID)
+		mid = cs;
+	    else if ((l->flags & CLF_SUF) && 
+		     ((smax < (l->min - l->max) &&
+		       (!smm || (l->flags & CLF_MATCHED))) ||
+		      ((l->flags & CLF_MATCHED) && !smm))) {
+		sm = cs; smax = l->min - l->max; smm = l->flags & CLF_MATCHED;
+	    }
+	}
+	if (ins) {
+	    int ocs, bl;
+
+	    while (brs && li >= brs->curpos) {
+		ocs = cs;
+		bl = strlen(brs->str);
+		cs = scs - (li - brs->curpos);
+		if (cbr < 0)
+		    cbr = cs;
+		inststrlen(brs->str, 1, bl);
+		cs = ocs + bl;
+		pcs += bl;
+		brs = brs->prev;
+	    }
+	}
+	/* And now insert the suffix or the original string. */
+	if (l->olen && (l->flags & CLF_SUF) && !l->suffix) {
+	    pcs = cs;
+	    inststrlen(l->orig, 1, l->olen);
+	    if (ins) {
+		int ocs, bl;
+
+		li += l->olen;
+
+		while (brp && li >= brp->curpos) {
+		    ocs = cs;
+		    bl = strlen(brp->str);
+		    cs = pcs + l->olen - (li - brp->curpos);
+		    inststrlen(brp->str, 1, bl);
+		    cs = ocs + bl;
+		    pcs += bl;
+		    brp = brp->next;
+		}
+		while (brs && li >= brs->curpos) {
+		    ocs = cs;
+		    bl = strlen(brs->str);
+		    cs = pcs + l->olen - (li - brs->curpos);
+		    if (cbr < 0)
+			cbr = cs;
+		    inststrlen(brs->str, 1, bl);
+		    cs = ocs + bl;
+		    pcs += bl;
+		    brs = brs->prev;
+		}
+	    }
+	} else {
+	    Cline js = NULL;
+
+	    for (j = -1, i = 0, s = l->suffix; s; s = s->next) {
+		if (j < 0 && (s->flags & CLF_DIFF))
+		    j = i, js = s;
+		pcs = cs;
+		if (s->flags & CLF_LINE) {
+		    inststrlen(s->line, 0, s->llen);
+		    i += s->llen; scs = cs + s->llen;
+		} else {
+		    inststrlen(s->word, 0, s->wlen);
+		    i += s->wlen; scs = cs + s->wlen;
+		}
+		if (ins) {
+		    int ocs, bl;
+
+		    li += s->llen;
+
+		    while (brp && li >= brp->curpos) {
+			ocs = cs;
+			bl = strlen(brp->str);
+			cs = pcs + (li - brp->curpos);
+			inststrlen(brp->str, 1, bl);
+			cs = ocs + bl;
+			pcs += bl;
+			scs += bl;
+			brp = brp->next;
+		    }
+		    while (brs && li >= brs->curpos) {
+			ocs = cs;
+			bl = strlen(brs->str);
+			cs = scs - (li - brs->curpos);
+			if (cbr < 0)
+			    cbr = cs;
+			inststrlen(brs->str, 1, bl);
+			cs = ocs + bl;
+			pcs += bl;
+			brs = brs->prev;
+		    }
+		}
+	    }
+	    cs += i;
+	    if (j >= 0 && (!dm || (js->flags & CLF_MATCHED))) {
+		d = cs - j; dm = js->flags & CLF_MATCHED;
+	    }
+	}
+	l = l->next;
+    }
+    if (ins) {
+	int ocs = cs;
+
+	for (; brp; brp = brp->next)
+	    inststrlen(brp->str, 1, -1);
+	for (; brs; brs = brs->prev) {
+	    if (cbr < 0)
+		cbr = cs;
+	    inststrlen(brs->str, 1, -1);
+	}
+	if (mid >= ocs)
+	    mid += cs - ocs;
+	if (pm >= ocs)
+	    pm += cs - ocs;
+	if (sm >= ocs)
+	    sm += cs - ocs;
+	if (d >= ocs)
+	    d += cs - ocs;
+    }
+    /* This calculates the new cursor position. If we had a mid cline
+     * with missing characters, we take this, otherwise if we have a
+     * prefix with missing characters, we take that, the same for a
+     * suffix, and finally a place where the matches differ. */
+    ncs = (cbr >= 0 ? cbr :
+	   (mid >= 0 ? mid :
+	    (pm >= 0 ? pm : (sm >= 0 ? sm : (d >= 0 ? d : cs)))));
+
+    if (!ins) {
+	/* We always inserted the string in the line. If that was not
+	 * requested, we copy it and remove from the line. */
+	char *r = zalloc((i = cs - ocs) + 1);
+
+	memcpy(r, (char *) (line + ocs), i);
+	r[i] = '\0';
+	cs = ocs;
+	foredel(i);
+
+	*csp = ncs - ocs;
+
+	return r;
+    }
+    lastend = cs;
+    cs = ncs;
+
+    return NULL;
+}
+
+/* This is a utility function using the function above to allow access
+ * to the unambiguous string and cursor position via compstate. */
+
+/**/
+char *
+unambig_data(int *cp)
+{
+    static char *scache = NULL;
+    static int ccache;
+
+    if (mnum && ainfo) {
+	if (mnum != unambig_mnum) {
+	    zsfree(scache);
+	    scache = cline_str((ainfo->count ? ainfo->line : fainfo->line),
+			       0, &ccache);
+	}
+    } else if (mnum != unambig_mnum || !ainfo || !scache) {
+	zsfree(scache);
+	scache = ztrdup("");
+	ccache = 0;
+    }
+    unambig_mnum = mnum;
+    if (cp)
+	*cp = ccache + 1;
+
+    return scache;
+}
+
+/* Insert the given match. This returns the number of characters inserted.
+ * scs is used to return the position where a automatically created suffix
+ * has to be inserted. */
+
+/**/
+static int
+instmatch(Cmatch m, int *scs)
+{
+    int l, r = 0, ocs, a = cs, brb = 0, bradd, *brpos;
+    Brinfo bp;
+
+    zsfree(lastprebr);
+    zsfree(lastpostbr);
+    lastprebr = lastpostbr = NULL;
+
+    /* Ignored prefix. */
+    if (m->ipre) {
+	char *p = m->ipre + (menuacc ? m->qipl : 0);
+
+	inststrlen(p, 1, (l = strlen(p)));
+	r += l;
+    }
+    /* -P prefix. */
+    if (m->pre) {
+	inststrlen(m->pre, 1, (l = strlen(m->pre)));
+	r += l;
+    }
+    /* Path prefix. */
+    if (m->ppre) {
+	inststrlen(m->ppre, 1, (l = strlen(m->ppre)));
+	r += l;
+    }
+    /* The string itself. */
+    inststrlen(m->str, 1, (l = strlen(m->str)));
+    r += l;
+    ocs = cs;
+    /* Re-insert the brace beginnings, if any. */
+    if (brbeg) {
+	int pcs = cs;
+
+	l = 0;
+	for (bp = brbeg, brpos = m->brpl,
+		 bradd = (m->pre ? strlen(m->pre) : 0);
+	     bp; bp = bp->next, brpos++) {
+	    cs = a + *brpos + bradd;
+	    pcs = cs;
+	    l = strlen(bp->str);
+	    bradd += l;
+	    brpcs = cs;
+	    inststrlen(bp->str, 1, l);
+	    r += l;
+	    ocs += l;
+	}
+	lastprebr = (char *) zalloc(pcs - a + 1);
+	memcpy(lastprebr, (char *) line + a, pcs - a);
+	lastprebr[pcs - a] = '\0';
+	cs = ocs;
+    }
+    /* Path suffix. */
+    if (m->psuf) {
+	inststrlen(m->psuf, 1, (l = strlen(m->psuf)));
+	r += l;
+    }
+    /* Re-insert the brace end. */
+    if (brend) {
+	a = cs;
+	for (bp = brend, brpos = m->brsl, bradd = 0; bp; bp = bp->next, brpos++) {
+	    cs = a - *brpos;
+	    ocs = brscs = cs;
+	    l = strlen(bp->str);
+	    bradd += l;
+	    inststrlen(bp->str, 1, l);
+	    brb = cs;
+	    r += l;
+	}
+	cs = a + bradd;
+	if (scs)
+	    *scs = ocs;
+    } else {
+	brscs = -1;
+
+	if (scs)
+	    *scs = cs;
+    }
+    /* -S suffix */
+    if (m->suf) {
+	inststrlen(m->suf, 1, (l = strlen(m->suf)));
+	r += l;
+    }
+    /* ignored suffix */
+    if (m->isuf) {
+	inststrlen(m->isuf, 1, (l = strlen(m->isuf)));
+	r += l;
+    }
+    if (brend) {
+	lastpostbr = (char *) zalloc(cs - brb + 1);
+	memcpy(lastpostbr, (char *) line + brb, cs - brb);
+	lastpostbr[cs - brb] = '\0';
+    }
+    lastend = cs;
+    cs = ocs;
+
+    return r;
+}
+
+/* Check if the match has the given prefix/suffix before/after the
+ * braces. */
+
+/**/
+int
+hasbrpsfx(Cmatch m, char *pre, char *suf)
+{
+    char *op = lastprebr, *os = lastpostbr;
+    VARARR(char, oline, ll);
+    int oll = ll, ocs = cs, ole = lastend, opcs = brpcs, oscs = brscs, ret;
+
+    memcpy(oline, line, ll);
+
+    lastprebr = lastpostbr = NULL;
+
+    instmatch(m, NULL);
+
+    cs = 0;
+    foredel(ll);
+    spaceinline(oll);
+    memcpy(line, oline, oll);
+    cs = ocs;
+    lastend = ole;
+    brpcs = opcs;
+    brscs = oscs;
+
+    ret = (((!pre && !lastprebr) ||
+	    (pre && lastprebr && !strcmp(pre, lastprebr))) &&
+	   ((!suf && !lastpostbr) ||
+	    (suf && lastpostbr && !strcmp(suf, lastpostbr))));
+
+    zsfree(lastprebr);
+    zsfree(lastpostbr);
+    lastprebr = op;
+    lastpostbr = os;
+
+    return ret;
+}
+
+/* Handle the case were we found more than one match. */
+
+/**/
+int
+do_ambiguous(void)
+{
+    int ret = 0;
+
+    menucmp = menuacc = 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 (ainfo && ainfo->exact == 1 && useexact && !(fromcomp & FC_LINE)) {
+	minfo.cur = NULL;
+	do_single(ainfo->exactm);
+	invalidatelist();
+	return ret;
+    }
+    /* 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 (usemenu || (haspattern && comppatinsert &&
+		    !strcmp(comppatinsert, "menu"))) {
+	/* 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 if (ainfo) {
+	int atend = (cs == we), la, eq, tcs;
+
+	minfo.cur = NULL;
+	minfo.asked = 0;
+
+	fixsuffix();
+
+	/* First remove the old string from the line. */
+	cs = wb;
+	foredel(we - wb);
+
+	/* Now get the unambiguous string and insert it into the line. */
+	cline_str(ainfo->line, 1, NULL);
+	if (eparq) {
+	    tcs = cs;
+	    cs = lastend;
+	    for (eq = eparq; eq; eq--)
+		inststrlen("\"", 0, 1);
+	    cs = tcs;
+	}
+	/* la is non-zero if listambiguous may be used. Copying and
+	 * comparing the line looks like BFI but it is the easiest
+	 * solution. Really. */
+	la = (ll != origll || strncmp(origline, (char *) line, ll));
+
+	/* If REC_EXACT and AUTO_MENU are set and what we inserted is an  *
+	 * exact match, we want menu completion the next time round       *
+	 * so we set fromcomp, to ensure that the word on the line is not *
+	 * taken as an exact match. Also we remember if we just moved the *
+	 * cursor into the word.                                          */
+	fromcomp = ((isset(AUTOMENU) ? FC_LINE : 0) |
+		    ((atend && cs != lastend) ? FC_INWORD : 0));
+
+	/* Probably move the cursor to the end. */
+	if (movetoend == 3)
+	    cs = lastend;
+
+	/* 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 (uselist == 3 && la) {
+	    int fc = fromcomp;
+
+	    invalidatelist();
+	    fromcomp = fc;
+	    lastambig = 0;
+	    clearlist = 1;
+	    return ret;
+	}
+    } else
+	return ret;
+
+    /* At this point, we might want a completion listing.  Show the listing *
+     * if it is needed.                                                     */
+    if (isset(LISTBEEP))
+	ret = 1;
+
+    if (uselist && (usemenu != 2 || (!listshown && !oldlist)) &&
+	((!showinglist && (!listshown || !oldlist)) ||
+	 (usemenu == 3 && !oldlist)) &&
+	(smatches >= 2 || (compforcelist && *compforcelist)))
+	showinglist = -2;
+
+    return ret;
+}
+
+/* This is a stat that ignores backslashes in the filename.  The `ls' *
+ * parameter says if we have to do lstat() or stat().  I think this   *
+ * should instead be done by use of a general function to expand a    *
+ * filename (stripping backslashes), combined with the actual         *
+ * (l)stat().                                                         */
+
+/**/
+int
+ztat(char *nam, struct stat *buf, int ls)
+{
+    char b[PATH_MAX], *p;
+
+    for (p = b; p < b + sizeof(b) - 1 && *nam; nam++)
+	if (*nam == '\\' && nam[1])
+	    *p++ = *++nam;
+	else
+	    *p++ = *nam;
+    *p = '\0';
+
+    return ls ? lstat(b, buf) : stat(b, buf);
+}
+
+/* Insert a single match in the command line. */
+
+/**/
+void
+do_single(Cmatch m)
+{
+    int l, sr = 0, scs;
+    int havesuff = 0;
+    int partest = (m->ripre || ((m->flags & CMF_ISPAR) && parpre));
+    char *str = m->str, *ppre = m->ppre, *psuf = m->psuf, *prpre = m->prpre;
+
+    if (!prpre) prpre = "";
+    if (!ppre) ppre = "";
+    if (!psuf) psuf = "";
+
+    fixsuffix();
+
+    if (!minfo.cur) {
+	/* We are currently not in a menu-completion, *
+	 * so set the position variables.             */
+	minfo.pos = wb;
+	minfo.we = (movetoend >= 2 || (movetoend == 1 && !menucmp));
+	minfo.end = 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 (minfo.cur)
+	l = minfo.len + minfo.insc;
+    else
+	l = we - wb;
+
+    minfo.insc = 0;
+    cs = minfo.pos;
+    foredel(l);
+
+    /* And then we insert the new string. */
+    minfo.len = instmatch(m, &scs);
+    minfo.end = cs;
+    cs = minfo.pos + minfo.len;
+
+    if (m->suf) {
+	havesuff = 1;
+	minfo.insc = ztrlen(m->suf);
+	minfo.len -= minfo.insc;
+	if (minfo.we) {
+	    minfo.end += minfo.insc;
+	    if (m->flags & CMF_REMOVE) {
+		makesuffixstr(m->remf, m->rems, minfo.insc);
+		if (minfo.insc == 1)
+		    suffixlen[STOUC(m->suf[0])] = 1;
+	    }
+	}
+    } else {
+	/* There is no user-specified suffix, *
+	 * so generate one automagically.     */
+	cs = scs;
+	if (partest && (m->flags & CMF_PARBR)) {
+	    int pq;
+
+	    /*{{*/
+	    /* Completing a parameter in braces.  Add a removable `}' suffix. */
+	    cs += eparq;
+	    for (pq = parq; pq; pq--)
+		inststrlen("\"", 1, 1);
+	    minfo.insc += parq;
+	    inststrlen("}", 1, 1);
+	    minfo.insc++;
+	    if (minfo.we)
+		minfo.end += minfo.insc;
+	    if (m->flags & CMF_PARNEST)
+		havesuff = 1;
+	}
+	if (((m->flags & CMF_FILE) || (partest && isset(AUTOPARAMSLASH))) &&
+	    cs > 0 && line[cs - 1] != '/') {
+	    /* If we have a filename 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.                                */
+	    struct stat buf;
+	    char *p;
+	    int t = 0;
+
+	    if (m->ipre && m->ipre[0] == '~' && !m->ipre[1])
+		t = 1;
+	    else {
+		/* Build the path name. */
+		if (partest && !*psuf && !(m->flags & CMF_PARNEST)) {
+		    int ne = noerrs;
+
+		    p = (char *) zhalloc(strlen((m->flags & CMF_ISPAR) ?
+						parpre : m->ripre) +
+					 strlen(str) + 2);
+		    sprintf(p, "%s%s%c",
+			    ((m->flags & CMF_ISPAR) ? parpre : m->ripre), str,
+			    ((m->flags & CMF_PARBR) ? Outbrace : '\0'));
+		    noerrs = 1;
+		    parsestr(p);
+		    singsub(&p);
+		    errflag = 0;
+		    noerrs = ne;
+		} else {
+		    p = (char *) zhalloc(strlen(prpre) + strlen(str) +
+				 strlen(psuf) + 3);
+		    sprintf(p, "%s%s%s", ((prpre && *prpre) ?
+					  prpre : "./"), str, psuf);
+		}
+		/* And do the stat. */
+		t = (!(sr = ztat(p, &buf, 0)) && S_ISDIR(buf.st_mode));
+	    }
+	    if (t) {
+		/* It is a directory, so add the slash. */
+		havesuff = 1;
+		inststrlen("/", 1, 1);
+		minfo.insc++;
+		if (minfo.we)
+		    minfo.end++;
+		if (!menucmp || minfo.we) {
+		    if (m->remf || m->rems)
+			makesuffixstr(m->remf, m->rems, 1);
+		    else if (isset(AUTOREMOVESLASH)) {
+			makesuffix(1);
+			suffixlen['/'] = 1;
+		    }
+		}
+	    }
+	}
+	if (!minfo.insc)
+	    cs = minfo.pos + minfo.len - m->qisl;
+    }
+    /* If completing in a brace expansion... */
+    if (brbeg) {
+	if (havesuff) {
+	    /*{{*/
+	    /* If a suffix was added, and is removable, let *
+	     * `,' and `}' remove it.                       */
+	    if (isset(AUTOPARAMKEYS))
+		suffixlen[','] = suffixlen['}'] = suffixlen[256];
+	} else if (!menucmp) {
+	    /*{{*/
+	    /* Otherwise, add a `,' suffix, and let `}' remove it. */
+	    cs = scs;
+	    havesuff = 1;
+	    inststrlen(",", 1, 1);
+	    minfo.insc++;
+	    makesuffix(1);
+	    if ((!menucmp || minfo.we) && isset(AUTOPARAMKEYS))
+		suffixlen[','] = suffixlen['}'] = 1;
+	}
+    } else if (!havesuff && (!(m->flags & CMF_FILE) || !sr)) {
+	/* If we didn't add a suffix, add a space, unless we are *
+	 * doing menu completion or we are completing files and  *
+	 * the string doesn't name an existing file.             */
+	if (m->autoq && (!m->isuf || m->isuf[0] != m->autoq)) {
+	    inststrlen(&(m->autoq), 1, 1);
+	    minfo.insc++;
+	}
+	if (!menucmp && (usemenu != 3 || insspace)) {
+	    inststrlen(" ", 1, 1);
+	    minfo.insc++;
+	    if (minfo.we)
+		makesuffix(1);
+	}
+    }
+    if (minfo.we && partest && isset(AUTOPARAMKEYS))
+	makeparamsuffix(((m->flags & CMF_PARBR) ? 1 : 0), minfo.insc - parq);
+
+    if ((menucmp && !minfo.we) || !movetoend) {
+	cs = minfo.end;
+	if (cs + m->qisl == lastend)
+	    cs += minfo.insc;
+    }
+    {
+	Cmatch *om = minfo.cur;
+	struct chdata dat;
+
+	dat.matches = amatches;
+	dat.num = nmatches;
+	dat.cur = m;
+
+	if (menucmp)
+	    minfo.cur = &m;
+	runhookdef(INSERTMATCHHOOK, (void *) &dat);
+	minfo.cur = om;
+    }
+}
+
+/* Do completion, given that we are in the middle of a menu completion.  We *
+ * don't need to generate a list of matches, because that's already been    *
+ * done by previous commands.  We will either list the completions, or      *
+ * insert the next completion.                                              */
+
+/**/
+void
+do_menucmp(int lst)
+{
+    /* Just list the matches if the list was requested. */
+    if (lst == COMP_LIST_COMPLETE) {
+	showinglist = -2;
+	return;
+    }
+    /* Otherwise go to the next match in the array... */
+    HEAPALLOC {
+	do {
+	    if (!*++(minfo.cur)) {
+		do {
+		    if (!(minfo.group = (minfo.group)->next))
+			minfo.group = amatches;
+		} while (!(minfo.group)->mcount);
+		minfo.cur = minfo.group->matches;
+	    }
+	} while (menuacc &&
+		 !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr));
+	/* ... and insert it into the command line. */
+	metafy_line();
+	do_single(*(minfo.cur));
+	unmetafy_line();
+    } LASTALLOC;
+}
+
+/**/
+int
+reverse_menu(Hookdef dummy, void *dummy2)
+{
+    HEAPALLOC {
+	do {
+	    if (minfo.cur == (minfo.group)->matches) {
+		do {
+		    if (!(minfo.group = (minfo.group)->prev))
+			minfo.group = lmatches;
+		} while (!(minfo.group)->mcount);
+		minfo.cur = (minfo.group)->matches + (minfo.group)->mcount - 1;
+	    } else
+		minfo.cur--;
+	} while (menuacc &&
+		 !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr));
+	metafy_line();
+	do_single(*(minfo.cur));
+	unmetafy_line();
+    } LASTALLOC;
+
+    return 0;
+}
+
+/* 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.  */
+
+/**/
+int
+accept_last(void)
+{
+    if (!menuacc) {
+	zsfree(minfo.prebr);
+	minfo.prebr = ztrdup(lastprebr);
+	zsfree(minfo.postbr);
+	minfo.postbr = ztrdup(lastpostbr);
+
+	if (listshown && (lastprebr || lastpostbr)) {
+	    Cmgroup g;
+	    Cmatch *m;
+
+	    for (g = amatches, m = NULL; g && (!m || !*m); g = g->next)
+		for (m = g->matches; *m; m++)
+		    if (!hasbrpsfx(*m, minfo.prebr, minfo.postbr)) {
+			showinglist = -2;
+			break;
+		    }
+	}
+    }
+    menuacc++;
+
+    if (brbeg) {
+	int l;
+
+	iremovesuffix(',', 1);
+
+	l = (brscs >= 0 ? brscs : cs) - brpcs;
+
+	zsfree(lastbrbeg->str);
+	lastbrbeg->str = (char *) zalloc(l + 2);
+	memcpy(lastbrbeg->str, line + brpcs, l);
+	lastbrbeg->str[l] = ',';
+	lastbrbeg->str[l + 1] = '\0';
+    } else {
+	int l;
+
+	cs = minfo.pos + minfo.len + minfo.insc;
+	iremovesuffix(' ', 1);
+	l = cs;
+	cs = minfo.pos + minfo.len + minfo.insc - (*(minfo.cur))->qisl;
+	if (cs < l)
+	    foredel(l - cs);
+	else if (cs > ll)
+	    cs = ll;
+	inststrlen(" ", 1, 1);
+	if (parpre)
+	    inststr(parpre);
+	minfo.insc = minfo.len = 0;
+	minfo.pos = cs;
+	minfo.we = 1;
+    }
+    return 0;
+}
+
+/* This maps the value in v into the range [0,m-1], decrementing v
+ * if it is non-negative and making negative values count backwards. */
+
+/**/
+static int
+comp_mod(int v, int m)
+{
+    if (v >= 0)
+	v--;
+    if (v >= 0)
+	return v % m;
+    else {
+	while (v < 0)
+	    v += m;
+	return v;
+    }
+}
+
+/* This handles the beginning of menu-completion. */
+
+/**/
+static void
+do_ambig_menu(void)
+{
+    Cmatch *mc;
+
+    if (usemenu != 3) {
+	menucmp = 1;
+	menuacc = 0;
+	minfo.cur = NULL;
+    } else {
+	if (oldlist) {
+	    if (oldins && minfo.cur)
+		acceptlast();
+	} else
+	    minfo.cur = NULL;
+    }
+    if (insgroup) {
+	insgnum = comp_mod(insgnum, lastpermgnum);
+	for (minfo.group = amatches;
+	     minfo.group && (minfo.group)->num != insgnum + 1;
+	     minfo.group = (minfo.group)->next);
+	if (!minfo.group || !(minfo.group)->mcount) {
+	    minfo.cur = NULL;
+	    minfo.asked = 0;
+	    return;
+	}
+	insmnum = comp_mod(insmnum, (minfo.group)->mcount);
+    } else {
+	insmnum = comp_mod(insmnum, lastpermmnum);
+	for (minfo.group = amatches;
+	     minfo.group && (minfo.group)->mcount <= insmnum;
+	     minfo.group = (minfo.group)->next)
+	    insmnum -= (minfo.group)->mcount;
+	if (!minfo.group) {
+	    minfo.cur = NULL;
+	    minfo.asked = 0;
+	    return;
+	}
+    }
+    mc = (minfo.group)->matches + insmnum;
+    do_single(*mc);
+    minfo.cur = mc;
+}
+
+/* Return the real number of matches. */
+
+/**/
+zlong
+num_matches(int normal)
+{
+    int alt;
+
+    PERMALLOC {
+	alt = permmatches(0);
+    } LASTALLOC;
+
+    if (normal)
+	return (alt ? 0 : nmatches);
+    else
+	return (alt ? nmatches : 0);
+}
+
+/* Return the number of screen lines needed for the list. */
+
+/**/
+zlong
+list_lines(void)
+{
+    Cmgroup oam;
+
+    PERMALLOC {
+	permmatches(0);
+    } LASTALLOC;
+
+    oam = amatches;
+    amatches = pmatches;
+    listdat.valid = 0;
+    calclist();
+    listdat.valid = 0;
+    amatches = oam;
+
+    return listdat.nlines;
+}
+
+/**/
+void
+comp_list(char *v)
+{
+    zsfree(complist);
+    complist = ztrdup(v);
+
+    onlyexpl = (v && strstr(v, "expl"));
+}
+
+/* This is used to print the explanation string. *
+ * It returns the number of lines printed.       */
+
+/**/
+int
+printfmt(char *fmt, int n, int dopr, int doesc)
+{
+    char *p = fmt, nc[DIGBUFSIZE];
+    int l = 0, cc = 0, b = 0, s = 0, u = 0, m;
+
+    for (; *p; p++) {
+	/* Handle the `%' stuff (%% == %, %n == <number of matches>). */
+	if (doesc && *p == '%') {
+	    if (*++p) {
+		m = 0;
+		switch (*p) {
+		case '%':
+		    if (dopr)
+			putc('%', shout);
+		    cc++;
+		    break;
+		case 'n':
+		    sprintf(nc, "%d", n);
+		    if (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++, cc++)
+			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;
+	    }
+	    if (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);
+}
+
+/* This skips over matches that are not to be listed. */
+
+/**/
+Cmatch *
+skipnolist(Cmatch *p)
+{
+    while (*p && (((*p)->flags & (CMF_NOLIST | CMF_HIDE)) ||
+		  ((*p)->disp && ((*p)->flags & (CMF_DISPLINE | CMF_HIDE)))))
+	p++;
+
+    return p;
+}
+
+/**/
+void
+calclist(void)
+{
+    Cmgroup g;
+    Cmatch *p, m;
+    Cexpl *e;
+    int hidden = 0, nlist = 0, nlines = 0, add = 2 + isset(LISTTYPES);
+    int max = 0, i;
+    VARARR(int, mlens, nmatches + 1);
+
+    if (listdat.valid && onlyexpl == listdat.onlyexpl &&
+	menuacc == listdat.menuacc &&
+	lines == listdat.lines && columns == listdat.columns)
+	return;
+
+    for (g = amatches; g; g = g->next) {
+	char **pp = g->ylist;
+	int nl = 0, l, glong = 1, gshort = columns, ndisp = 0, totl = 0;
+
+	if (!onlyexpl && pp) {
+	    /* We have an ylist, lets see, if it contains newlines. */
+	    hidden = 1;
+	    while (!nl && *pp)
+		nl = !!strchr(*pp++, '\n');
+
+	    pp = g->ylist;
+	    if (nl || !pp[1]) {
+		/* Yup, there are newlines, count lines. */
+		char *nlptr, *sptr;
+
+		g->flags |= CGF_LINES;
+		hidden = 1;
+		while ((sptr = *pp)) {
+		    while (sptr && *sptr) {
+			nlines += (nlptr = strchr(sptr, '\n'))
+			    ? 1 + (nlptr-sptr)/columns
+			    : strlen(sptr)/columns;
+			sptr = nlptr ? nlptr+1 : NULL;
+		    }
+		    nlines++;
+		    pp++;
+		}
+		nlines--;
+	    } else {
+		while (*pp) {
+		    l = strlen(*pp);
+		    ndisp++;
+		    if (l > glong)
+			glong = l;
+		    if (l < gshort)
+			gshort = l;
+		    totl += l;
+		    nlist++;
+		    pp++;
+		}
+	    }
+	} else if (!onlyexpl) {
+	    for (p = g->matches; (m = *p); p++) {
+		if (menuacc && !hasbrpsfx(m, minfo.prebr, minfo.postbr)) {
+		    m->flags |= CMF_HIDE;
+		    continue;
+		}
+		m->flags &= ~CMF_HIDE;
+
+		if (m->disp) {
+		    if (m->flags & CMF_DISPLINE) {
+			nlines += 1 + printfmt(m->disp, 0, 0, 0);
+			g->flags |= CGF_HASDL;
+		    } else {
+			l = niceztrlen(m->disp);
+			ndisp++;
+			if (l > glong)
+			    glong = l;
+			if (l < gshort)
+			    gshort = l;
+			totl += l;
+			mlens[m->gnum] = l;
+		    }
+		    nlist++;
+		} else if (!(m->flags & CMF_NOLIST)) {
+		    l = niceztrlen(m->str);
+		    ndisp++;
+		    if (l > glong)
+			glong = l;
+		    if (l < gshort)
+			gshort = l;
+		    totl += l;
+		    mlens[m->gnum] = l;
+		    nlist++;
+		} else
+		    hidden = 1;
+	    }
+	}
+	if ((e = g->expls)) {
+	    while (*e) {
+		if ((*e)->count)
+		    nlines += 1 + printfmt((*e)->str, (*e)->count, 0, 1);
+		e++;
+	    }
+	}
+	g->totl = totl + (ndisp * add);
+	g->dcount = ndisp;
+	g->width = glong + add;
+	g->shortest = gshort + add;
+	if ((g->cols = columns / g->width) > g->dcount)
+	    g->cols = g->dcount;
+	if (g->cols) {
+	    i = g->cols * g->width - add;
+	    if (i > max)
+		max = i;
+	}
+    }
+    if (!onlyexpl) {
+	for (g = amatches; g; g = g->next) {
+	    char **pp;
+	    int glines = 0;
+
+	    zfree(g->widths, 0);
+	    g->widths = NULL;
+
+	    if ((pp = g->ylist)) {
+		if (!(g->flags & CGF_LINES)) {
+		    if (g->cols) {
+			glines += (arrlen(pp) + g->cols - 1) / g->cols;
+			if (g->cols > 1)
+			    g->width += (max - (g->width * g->cols - add)) / g->cols;
+		    } else {
+			g->cols = 1;
+			g->width = 1;
+			
+			while (*pp)
+			    glines += 1 + (strlen(*pp++) / columns);
+		    }
+		}
+	    } else {
+		if (g->cols) {
+		    glines += (g->dcount + g->cols - 1) / g->cols;
+		    if (g->cols > 1)
+			g->width += (max - (g->width * g->cols - add)) / g->cols;
+		} else if (!(g->flags & CGF_LINES)) {
+		    g->cols = 1;
+		    g->width = 0;
+		    
+		    for (p = g->matches; (m = *p); p++)
+			if (!(m->flags & CMF_HIDE)) {
+			    if (m->disp) {
+				if (!(m->flags & CMF_DISPLINE))
+				    glines += 1 + (mlens[m->gnum] / columns);
+			    } else if (!(m->flags & CMF_NOLIST))
+				glines += 1 + ((1 + mlens[m->gnum]) / columns);
+			}
+		}
+	    }
+	    g->lins = glines;
+	    nlines += glines;
+	}
+    }
+    if (!onlyexpl && isset(LISTPACKED)) {
+	char **pp;
+	int *ws, tlines, tline, tcols, maxlen, nth, width;
+
+	for (g = amatches; g; g = g->next) {
+	    ws = g->widths = (int *) zalloc(columns * sizeof(int));
+	    memset(ws, 0, columns * sizeof(int));
+	    tlines = g->lins;
+	    tcols = g->cols;
+	    width = 0;
+
+	    if ((pp = g->ylist)) {
+		if (!(g->flags & CGF_LINES)) {
+		    int yl = arrlen(pp), i;
+		    VARARR(int, ylens, yl);
+
+		    for (i = 0; *pp; i++, pp++)
+			ylens[i] = strlen(*pp) + add;
+
+		    if (isset(LISTROWSFIRST)) {
+			int count, tcol, first, maxlines = 0, llines;
+
+			for (tcols = columns / g->shortest; tcols > g->cols;
+			     tcols--) {
+			    for (nth = first = maxlen = width = maxlines =
+				     llines = tcol = 0,
+				     count = g->dcount;
+				 count > 0; count--) {
+				if (ylens[nth] > maxlen)
+				    maxlen = ylens[nth];
+				nth += tcols;
+				tlines++;
+				if (nth >= g->dcount) {
+				    if ((width += maxlen) >= columns)
+					break;
+				    ws[tcol++] = maxlen;
+				    maxlen = 0;
+				    nth = ++first;
+				    if (llines > maxlines)
+					maxlines = llines;
+				    llines = 0;
+				}
+			    }
+			    if (nth < yl) {
+				ws[tcol++] = maxlen;
+				width += maxlen;
+			    }
+			    if (!count && width < columns)
+				break;
+			}
+			if (tcols > g->cols)
+			    tlines = maxlines;
+		    } else {
+			for (tlines = ((g->totl + columns) / columns);
+			     tlines < g->lins; tlines++) {
+			    for (pp = g->ylist, nth = tline = width =
+				     maxlen = tcols = 0;
+				 *pp; nth++, pp++) {
+				if (ylens[nth] > maxlen)
+				    maxlen = ylens[nth];
+				if (++tline == tlines) {
+				    if ((width += maxlen) >= columns)
+					break;
+				    ws[tcols++] = maxlen;
+				    maxlen = tline = 0;
+				}
+			    }
+			    if (tline) {
+				ws[tcols++] = maxlen;
+				width += maxlen;
+			    }
+			    if (nth == yl && width < columns)
+				break;
+			}
+		    }
+		}
+	    } else if (g->width) {
+		if (isset(LISTROWSFIRST)) {
+		    int addlen, count, tcol, maxlines = 0, llines, i;
+		    Cmatch *first;
+
+		    for (tcols = columns / g->shortest; tcols > g->cols;
+			 tcols--) {
+			p = first = skipnolist(g->matches);
+			for (maxlen = width = maxlines = llines = tcol = 0,
+				 count = g->dcount;
+			     count > 0; count--) {
+			    m = *p;
+			    addlen = mlens[m->gnum] + add;
+			    if (addlen > maxlen)
+				maxlen = addlen;
+			    for (i = tcols; i && *p; i--)
+				p = skipnolist(p + 1);
+
+			    llines++;
+			    if (!*p) {
+				if (llines > maxlines)
+				    maxlines = llines;
+				llines = 0;
+
+				if ((width += maxlen) >= columns)
+				    break;
+				ws[tcol++] = maxlen;
+				maxlen = 0;
+
+				p = first = skipnolist(first + 1);
+			    }
+			}
+			if (tlines) {
+			    ws[tcol++] = maxlen;
+			    width += maxlen;
+			}
+			if (!count && width < columns)
+			    break;
+		    }
+		    if (tcols > g->cols)
+			tlines = maxlines;
+		} else {
+		    int addlen;
+
+		    for (tlines = ((g->totl + columns) / columns);
+			 tlines < g->lins; tlines++) {
+			for (p = g->matches, nth = tline = width =
+				 maxlen = tcols = 0;
+			     (m = *p); p++, nth++) {
+			    if (!(m->flags &
+				  (m->disp ? (CMF_DISPLINE | CMF_HIDE) :
+				   (CMF_NOLIST | CMF_HIDE)))) {
+				addlen = mlens[m->gnum] + add;
+				if (addlen > maxlen)
+				    maxlen = addlen;
+				if (++tline == tlines) {
+				    if ((width += maxlen) >= columns)
+					break;
+				    ws[tcols++] = maxlen;
+				    maxlen = tline = 0;
+				}
+			    }
+			}
+			if (tline) {
+			    ws[tcols++] = maxlen;
+			    width += maxlen;
+			}
+			if (nth == g->dcount && width < columns)
+			    break;
+		    }
+		}
+	    }
+	    if (tlines == g->lins) {
+		zfree(ws, columns * sizeof(int));
+		g->widths = NULL;
+	    } else {
+		nlines += tlines - g->lins;
+		g->lins = tlines;
+		g->cols = tcols;
+		g->totl = width;
+		width -= add;
+		if (width > max)
+		    max = width;
+	    }
+	}
+	for (g = amatches; g; g = g->next) {
+	    if (g->widths) {
+		int *p, a = (max - g->totl + add) / g->cols;
+
+		for (i = g->cols, p = g->widths; i; i--, p++)
+		    *p += a;
+	    } else if (g->width && g->cols > 1)
+		g->width += (max - (g->width * g->cols - add)) / g->cols;
+	}
+    }
+    listdat.valid = 1;
+    listdat.hidden = hidden;
+    listdat.nlist = nlist;
+    listdat.nlines = nlines;
+    listdat.menuacc = menuacc;
+    listdat.onlyexpl = onlyexpl;
+    listdat.columns = columns;
+    listdat.lines = lines;
+}
+
+/**/
+int asklist(void)
+{
+    /* Set the cursor below the prompt. */
+    trashzle();
+    showinglist = listshown = 0;
+
+    clearflag = (isset(USEZLE) && !termflags &&
+		 complastprompt && *complastprompt);
+
+    /* Maybe we have to ask if the user wants to see the list. */
+    if ((!minfo.cur || !minfo.asked) &&
+	((complistmax && listdat.nlist > complistmax) ||
+	 (!complistmax && listdat.nlines >= lines))) {
+	int qup;
+	zsetterm();
+	qup = printfmt("zsh: do you wish to see all %n possibilities? ",
+		       listdat.nlist, 1, 1);
+	fflush(shout);
+	if (getzlequery() != 'y') {
+	    if (clearflag) {
+		putc('\r', shout);
+		tcmultout(TCUP, TCMULTUP, qup);
+		if (tccan(TCCLEAREOD))
+		    tcout(TCCLEAREOD);
+		tcmultout(TCUP, TCMULTUP, nlnct);
+	    } else
+		putc('\n', shout);
+	    if (minfo.cur)
+		minfo.asked = 2;
+	    return 1;
+	}
+	if (clearflag) {
+	    putc('\r', shout);
+	    tcmultout(TCUP, TCMULTUP, qup);
+	    if (tccan(TCCLEAREOD))
+		tcout(TCCLEAREOD);
+	} else
+	    putc('\n', shout);
+	settyinfo(&shttyinfo);
+	if (minfo.cur)
+	    minfo.asked = 1;
+    }
+    return 0;
+}
+
+/**/
+int
+printlist(int over, CLPrintFunc printm)
+{
+    Cmgroup g;
+    Cmatch *p, m;
+    Cexpl *e;
+    int pnl = 0, cl = (over ? listdat.nlines : -1);
+    int mc = 0, ml = 0, printed = 0;
+
+    if (cl < 2) {
+	cl = -1;
+	if (tccan(TCCLEAREOD))
+	    tcout(TCCLEAREOD);
+    }
+    g = amatches;
+    while (g) {
+	char **pp = g->ylist;
+
+	if ((e = g->expls)) {
+	    int l;
+
+	    while (*e) {
+		if ((*e)->count) {
+		    if (pnl) {
+			putc('\n', shout);
+			pnl = 0;
+			ml++;
+			if (cl >= 0 && --cl <= 1) {
+			    cl = -1;
+			    if (tccan(TCCLEAREOD))
+				tcout(TCCLEAREOD);
+			}
+		    }
+		    l = printfmt((*e)->str, (*e)->count, 1, 1);
+		    ml += l;
+		    if (cl >= 0 && (cl -= l) <= 1) {
+			cl = -1;
+			if (tccan(TCCLEAREOD))
+			    tcout(TCCLEAREOD);
+		    }
+		    pnl = 1;
+		}
+		e++;
+	    }
+	}
+	if (!listdat.onlyexpl && pp && *pp) {
+	    if (pnl) {
+		putc('\n', shout);
+		pnl = 0;
+		ml++;
+		if (cl >= 0 && --cl <= 1) {
+		    cl = -1;
+		    if (tccan(TCCLEAREOD))
+			tcout(TCCLEAREOD);
+		}
+	    }
+	    if (g->flags & CGF_LINES) {
+		while (*pp) {
+		    zputs(*pp, shout);
+		    if (*++pp)
+			putc('\n', shout);
+		}
+	    } else {
+		int n = g->lcount, nl, nc, i, a;
+		char **pq;
+
+		nl = nc = g->lins;
+
+		while (n && nl--) {
+		    i = g->cols;
+		    mc = 0;
+		    pq = pp;
+		    while (n && i--) {
+			if (pq - g->ylist >= g->lcount)
+			    break;
+			zputs(*pq, shout);
+			if (i) {
+			    a = (g->widths ? g->widths[mc] : g->width) -
+				strlen(*pq);
+			    while (a--)
+				putc(' ', shout);
+			}
+			pq += (isset(LISTROWSFIRST) ? 1 : nc);
+			mc++;
+			n--;
+		    }
+		    if (n) {
+			putc('\n', shout);
+			ml++;
+			if (cl >= 0 && --cl <= 1) {
+			    cl = -1;
+			    if (tccan(TCCLEAREOD))
+				tcout(TCCLEAREOD);
+			}
+		    }
+		    pp += (isset(LISTROWSFIRST) ? g->cols : 1);
+		}
+	    }
+	} else if (!listdat.onlyexpl && g->lcount) {
+	    int n = g->dcount, nl, nc, i, j, wid;
+	    Cmatch *q;
+
+	    nl = nc = g->lins;
+
+	    if (g->flags & CGF_HASDL) {
+		for (p = g->matches; (m = *p); p++)
+		    if (m->disp && (m->flags & CMF_DISPLINE)) {
+			if (pnl) {
+			    putc('\n', shout);
+			    pnl = 0;
+			    ml++;
+			    if (cl >= 0 && --cl <= 1) {
+				cl = -1;
+				if (tccan(TCCLEAREOD))
+				    tcout(TCCLEAREOD);
+			    }
+			}
+			printed++;
+			printm(g, p, 0, ml, 1, 0, NULL, NULL);
+			pnl = 1;
+		    }
+	    }
+	    if (n && pnl) {
+		putc('\n', shout);
+		pnl = 0;
+		ml++;
+		if (cl >= 0 && --cl <= 1) {
+		    cl = -1;
+		    if (tccan(TCCLEAREOD))
+			tcout(TCCLEAREOD);
+		}
+	    }
+	    for (p = skipnolist(g->matches); n && nl--;) {
+		i = g->cols;
+		mc = 0;
+		q = p;
+		while (n && i--) {
+		    wid = (g->widths ? g->widths[mc] : g->width);
+		    if (!(m = *q)) {
+			printm(g, NULL, mc, ml, (!i), wid, NULL, NULL);
+			break;
+		    }
+		    if (!m->disp && (m->flags & CMF_FILE)) {
+			struct stat buf;
+			char *pb;
+
+			pb = (char *) zhalloc((m->prpre ? strlen(m->prpre) : 0) +
+					     3 + strlen(m->str));
+			sprintf(pb, "%s%s", (m->prpre ? m->prpre : "./"),
+				m->str);
+
+			if (ztat(pb, &buf, 1))
+			    printm(g, q, mc, ml, (!i), wid, NULL, NULL);
+			else
+			    printm(g, q, mc, ml, (!i), wid, pb, &buf);
+		    } else
+			printm(g, q, mc, ml, (!i), wid, NULL, NULL);
+
+		    printed++;
+
+		    if (--n)
+			for (j = (isset(LISTROWSFIRST) ? 1 : nc); j && *q; j--)
+			    q = skipnolist(q + 1);
+		    mc++;
+		}
+		while (i-- > 0)
+		    printm(g, NULL, mc++, ml, (!i),
+			   (g->widths ? g->widths[mc] : g->width), NULL, NULL);
+
+		if (n) {
+		    putc('\n', shout);
+		    ml++;
+		    if (cl >= 0 && --cl <= 1) {
+			cl = -1;
+			if (tccan(TCCLEAREOD))
+			    tcout(TCCLEAREOD);
+		    }
+		    if (nl)
+			for (j = (isset(LISTROWSFIRST) ? g->cols : 1); j && *p; j--)
+			    p = skipnolist(p + 1);
+		}
+	    }
+	}
+	if (g->lcount)
+	    pnl = 1;
+	g = g->next;
+    }
+    if (clearflag) {
+	/* Move the cursor up to the prompt, if always_last_prompt *
+	 * is set and all that...                                  */
+	if ((ml = listdat.nlines + nlnct - 1) < lines) {
+	    tcmultout(TCUP, TCMULTUP, ml);
+	    showinglist = -1;
+	} else
+	    clearflag = 0, putc('\n', shout);
+    } else
+	putc('\n', shout);
+    listshown = (clearflag ? 1 : -1);
+
+    return printed;
+}
+
+/**/
+static void
+iprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
+	char *path, struct stat *buf)
+{
+    Cmatch m;
+    int len = 0;
+
+    if (!mp)
+	return;
+
+    m = *mp;
+    if (m->disp) {
+	if (m->flags & CMF_DISPLINE) {
+	    printfmt(m->disp, 0, 1, 0);
+	    return;
+	}
+	nicezputs(m->disp, shout);
+	len = niceztrlen(m->disp);
+    } else {
+	nicezputs(m->str, shout);
+	len = niceztrlen(m->str);
+
+	if (isset(LISTTYPES)) {
+	    if (buf)
+		putc(file_type(buf->st_mode), shout);
+	    len++;
+	}
+    }
+    if (!lastc) {
+	len = width - len;
+
+	while (len-- > 0)
+	    putc(' ', shout);
+    }
+}
+
+/**/
+int
+ilistmatches(Hookdef dummy, Chdata dat)
+{
+    calclist();
+
+    if (!listdat.nlines) {
+	showinglist = listshown = 0;
+	return 1;
+    }
+    if (asklist())
+	return 0;
+
+    printlist(0, iprintm);
+
+    return 0;
+}
+
+/* List the matches.  Note that the list entries are metafied. */
+
+/**/
+int
+list_matches(Hookdef dummy, void *dummy2)
+{
+    struct chdata dat;
+
+#ifdef DEBUG
+    /* Sanity check */
+    if (!validlist) {
+	showmsg("BUG: listmatches called with bogus list");
+	return 1;
+    }
+#endif
+
+    dat.matches = amatches;
+    dat.num = nmatches;
+    dat.cur = NULL;
+    return runhookdef(COMPLISTMATCHESHOOK, (void *) &dat);
+}
+
+/* Invalidate the completion list. */
+
+/**/
+int
+invalidate_list(void)
+{
+    if (showinglist == -2)
+	listmatches();
+    if (validlist) {
+	freematches(lastmatches);
+	lastmatches = NULL;
+	hasoldlist = 0;
+    }
+    lastambig = menucmp = menuacc = validlist = showinglist = fromcomp = 0;
+    listdat.valid = 0;
+    if (listshown < 0)
+	listshown = 0;
+    minfo.cur = NULL;
+    minfo.asked = 0;
+    zsfree(minfo.prebr);
+    zsfree(minfo.postbr);
+    minfo.postbr = minfo.prebr = NULL;
+    compwidget = NULL;
+
+    return 0;
+}