about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-06-27 05:33:25 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-06-27 05:33:25 +0000
commitb346c4796b3abdadd31cb0999d2f099ce9aa79af (patch)
tree748857cc950cb1bbe04bec42dd1b561b5d960afd
parentadefa2757ae03fd6af155345e386c4cde457b6b1 (diff)
downloadzsh-b346c4796b3abdadd31cb0999d2f099ce9aa79af.tar.gz
zsh-b346c4796b3abdadd31cb0999d2f099ce9aa79af.tar.xz
zsh-b346c4796b3abdadd31cb0999d2f099ce9aa79af.zip
Initial revision
-rw-r--r--Completion/User/_chown15
-rw-r--r--Completion/User/_groups6
-rw-r--r--Doc/Zsh/mod_complist.yo119
-rw-r--r--Src/Zle/complist.c918
-rw-r--r--Src/Zle/complist.mdd3
5 files changed, 1061 insertions, 0 deletions
diff --git a/Completion/User/_chown b/Completion/User/_chown
new file mode 100644
index 000000000..1ec76b39e
--- /dev/null
+++ b/Completion/User/_chown
@@ -0,0 +1,15 @@
+#compdef chown chgrp
+
+if [[ CURRENT -eq 2 || CURRENT -eq 3 && $words[CURRENT-1] = -* ]]; then
+  if [[ $words[1] = chgrp ]] || compset -P '*[:.]'; then
+    _groups
+  else
+    if [[ $OSTYPE = (solaris*|hpux*) ]]; then
+      compgen -u -S ':' -q
+    else
+      compgen -u -S '.' -q
+    fi
+  fi
+else
+  _files
+fi
diff --git a/Completion/User/_groups b/Completion/User/_groups
new file mode 100644
index 000000000..975189174
--- /dev/null
+++ b/Completion/User/_groups
@@ -0,0 +1,6 @@
+#compdef newgrp
+
+: ${(A)groups:=${${(s: :)$(</etc/group)}%%:*}}
+# : ${(A)groups:=${${(s: :)$(ypcat group.byname)}%%:*}} # If you use NIS
+
+compadd $groups
diff --git a/Doc/Zsh/mod_complist.yo b/Doc/Zsh/mod_complist.yo
new file mode 100644
index 000000000..460d4a609
--- /dev/null
+++ b/Doc/Zsh/mod_complist.yo
@@ -0,0 +1,119 @@
+texinode(The complist Module)(The deltochar Module)(The compctl Module)(Zsh Modules)
+sect(The complist Module)
+cindex(completion, listing)
+The tt(complist) module offers two extensions to completion listings:
+the ability to highlight matches in such a list and a different
+style of menu-completion.
+
+subsect(Parameters)
+For both extensions one of the parameters tt(ZLS_COLORS) or tt(ZLS_COLOURS)
+must be set, even if the value is empty (which uses all the default values
+given below). These describe how matches are highlighted. The format of the
+value of these parameters is the same as used by the GNU version of the
+tt(ls) command: a colon-separated list of specifications of the form
+`var(name)=var(value)'. The var(name) may be one of the following strings,
+most of which specify file-types for which the var(value) will be used. The
+strings and their default values are:
+
+startitem()
+item(tt(no 0))(
+for normal text (not the string displayed for a match)
+)
+item(tt(fi 0))(
+for regular files
+)
+item(tt(di 32))(
+for directories
+)
+item(tt(ln 36))(
+for symbolic links
+)
+item(tt(pi 31))(
+for named pipes (FIFOs)
+)
+item(tt(so 33))(
+for sockets
+)
+item(tt(bd 44;37))(
+for block devices
+)
+item(tt(cd 44;37))(
+for character devices
+)
+item(tt(ex 35))(
+for executable files
+)
+item(tt(mi) var(none))(
+for names not naming a file (default is the value defined for tt(fi))
+)
+item(tt(lc \e[))(
+for the left code (see below)
+)
+item(tt(rc m))(
+for the right code
+)
+item(tt(ec) var(none))(
+for the end code
+)
+enditem()
+
+Apart from these strings, the var(name) may also be an asterisk
+(`tt(*)') followed by any string. The var(value) given for such a
+string will be used for all files whose name ends with the string.
+
+When printing a match, the code prints the value of tt(lc), the value
+for the file-type or the last matching specification with a `tt(*)',
+the value of tt(rc), the string to display for the match itself, and
+then the value of tt(ec) if that is defined or the values of tt(lc),
+tt(no), and tt(rc) if tt(ec) is not defined.
+
+The default values are ISO 6429 (ANSI) compliant and can be used on
+vt100 compatible terminals such as tt(xterm)s. On monochrome terminals
+the default values will have no visual effect.
+
+Whenever one of the parameters tt(ZLS_COLORS) or tt(ZLS_COLOURS) is set 
+and the tt(complist) module is loaded or linked into the shell,
+completion lists will be colored.
+
+subsect(Menu selection)
+The tt(complist) module also offers a different style of selecting
+matches from a list called menu-selection. It can be invoked directly by
+the widget tt(menu-select) defined by the module.  Alternatively,
+the parameter tt(SELECTMIN) can be set to an integer giving the minimum
+number of matches which must be present before menu selection is
+automatically turned on.  This second method requires that menu completion
+be started, either directly from a widget such as tt(menu-complete), or due
+to one of the options tt(MENU_COMPLETE) or tt(AUTO_MENU) being set.  If
+tt(SELECTMIN) is set, but is 0, 1 or empty, menu selection will always be
+started during menu completion if the completion is ambiguous.
+
+After menu-selection is started, the matches will be listed. The
+matches to insert into the command line can be selected from this
+list. In the list one match is highlighted using the value for tt(ma)
+from the tt(ZLS_COLORS) or tt(ZLS_COLOURS) parameter. The default
+value for this it `tt(7)' which forces the selected match to be
+highlighted using standout mode on a vt100 compatible terminal.
+
+Selecting matches is done by moving the mark around using the zle movement
+functions. The zle functions tt(send-break) and tt(accept-line) can be used
+to leave menu-selection, leaving the match currently inserted into the line
+in place. The functions tt(accept-and-hold) and
+tt(accept-and-menu-complete) can be used to accept the match currently
+inserted and continue inserting matches after that. Matches inserted this
+way can be removed by invoking the tt(undo) function. Keys bound to one of
+the completion functions will cycle to the next (or, in case of
+tt(reverse-menu-complete), the previous) match, and the tt(redisplay) and
+tt(clear-screen) functions work as usual without leaving
+menu-selection. Any other zle function leaves menu-selection and executes
+that function.
+
+During this selection the widget uses the keymap tt(menuselect). Any
+key that is not defined in this keymap or that is bound to
+tt(undefined-key) is looked up in the keymap currently selected. This
+is used to ensure that the most important keys used during selection
+have sensible default (namely the cursor keys, return, and TAB). However,
+keys in the the tt(menuselect) keymap can be modified directly using the
+tt(bindkey) builtin command (see
+ifzman(zmanref(zshmodules))\
+ifnzman(noderef(The zle Module))\
+).
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
new file mode 100644
index 000000000..63bc4c6e3
--- /dev/null
+++ b/Src/Zle/complist.c
@@ -0,0 +1,918 @@
+/*
+ * complist.c - completion listing enhancements
+ *
+ * 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 "complist.mdh"
+#include "complist.pro"
+
+
+/* We use the parameters ZLS_COLORS and ZLS_COLOURS in the same way as
+ * the color ls does. It's just that we don't support the `or' file
+ * type. */
+
+
+static Widget w_menuselect;
+static Keymap mskeymap;
+
+/* Indixes into the terminal string arrays. */
+
+#define COL_NO  0
+#define COL_FI  1
+#define COL_DI  2
+#define COL_LN  3
+#define COL_PI  4
+#define COL_SO  5
+#define COL_BD  6
+#define COL_CD  7
+#define COL_EX  8
+#define COL_MI  9
+#define COL_LC 10
+#define COL_RC 11
+#define COL_EC 12
+#define COL_MA 13
+
+#define NUM_COLS 14
+
+/* Names of the terminal strings. */
+
+static char *colnames[] = {
+    "no", "fi", "di", "ln", "pi", "so", "bd", "cd", "ex", "mi",
+    "lc", "rc", "ec", "ma", NULL
+};
+
+/* Default values. */
+
+static char *defcols[] = {
+    "0", "0", "1;34", "1;36", "33", "1;35", "1;33", "1;33", "1;32", NULL,
+    "\033[", "m", NULL, "7"
+};
+
+/* This describes a terminal string for a filename extension. */
+
+typedef struct extcol *Extcol;
+
+struct extcol {
+    char *ext;			/* the extension */
+    char *col;			/* the terminal color string */
+    Extcol next;		/* the next one in the list */
+};
+
+/* This holds all terminal strings. */
+
+typedef struct listcols *Listcols;
+
+struct listcols {
+    char *cols[NUM_COLS];	/* strings for file types */
+    Extcol exts;		/* strings for extensions */
+};
+
+/* This parses the value of a definition (the part after the `=').
+ * The return value is a pointer to the character after it. */
+
+static char *
+getcolval(char *s)
+{
+    char *p;
+
+    for (p = s; *s && *s != ':'; p++, s++) {
+	if (*s == '\\' && s[1]) {
+	    switch (*++s) {
+	    case 'a': *p = '\007'; break;
+	    case 'n': *p = '\n'; break;
+	    case 'b': *p = '\b'; break;
+	    case 't': *p = '\t'; break;
+	    case 'v': *p = '\v'; break;
+	    case 'f': *p = '\f'; break;
+	    case 'r': *p = '\r'; break;
+	    case 'e': *p = '\033'; break;
+	    case '_': *p = ' '; break;
+	    case '?': *p = '\177'; break;
+	    default:
+		if (*s >= '0' && *s <= '7') {
+		    int i = STOUC(*s);
+
+		    if (*++s >= '0' && *s <= '7') {
+			i = (i * 8) + STOUC(*s);
+			if (*++s >= '0' && *s <= '7')
+			    i = (i * 8) + STOUC(*s);
+		    }
+		    *p = (char) i;
+		} else
+		    *p = *s;
+	    }
+	} else if (*s == '^') {
+	    if ((s[1] >= '@' && s[1] <= '_') ||
+		(s[1] >= 'a' && s[1] <= 'z'))
+		*p = (char) (STOUC(*s) & ~0x60);
+	    else if (s[1] == '?')
+		*p = '\177';
+	    else {
+		*p++ = *s;
+		*p = s[1];
+	    }
+	    s++;
+	} else
+	    *p = *s;
+    }
+    if (p != s)
+	*p = '\0';
+    return s;
+}
+
+/* This parses one definition. Return value is a pointer to the
+ * character after it. */
+
+static char *
+getcoldef(Listcols c, char *s)
+{
+    if (*s == '*') {
+	Extcol ec;
+	char *n, *p;
+
+	/* This is for an extension. */
+
+	n = ++s;
+	while (*s && *s != '=')
+	    s++;
+	if (!*s )
+	    return s;
+	*s++ = '\0';
+	p = getcolval(s);
+	if (*n) {
+	    ec = (Extcol) zhalloc(sizeof(*ec));
+	    ec->ext = n;
+	    ec->col = s;
+	    ec->next = c->exts;
+	    c->exts = ec;
+	}
+	if (*p)
+	    *p++ = '\0';
+	return p;
+    } else {
+	char *n = s, *p, **nn;
+	int i;
+
+	/* This is for a file type. */
+
+	while (*s && *s != '=')
+	    s++;
+	if (!*s)
+	    return s;
+	*s++ = '\0';
+	for (i = 0, nn = colnames; *nn; i++, nn++)
+	    if (!strcmp(n ,*nn))
+		break;
+	p = getcolval(s);
+	if (*nn)
+	    c->cols[i] = s;
+	if (*p)
+	    *p++ = '\0';
+	return p;
+    }
+}
+
+/* This initializes the given terminal color structure. */
+
+static int
+getcols(Listcols c)
+{
+    char *s;
+    int i;
+
+    if (!(s = getsparam("ZLS_COLORS")) &&
+	!(s = getsparam("ZLS_COLOURS"))) {
+	if (!c)
+	    return 1;
+	for (i = 0; i < NUM_COLS; i++)
+	    c->cols[i] = "";
+	
+	c->exts = NULL;
+	return 1;
+    }
+    if (!c)
+	return 0;
+    /* We have one of the parameters, use it. */
+    memset(c, 0, sizeof(*c));
+    s = dupstring(s);
+    while (*s)
+	s = getcoldef(c, s);
+
+    /* Use default values for those that aren't set explicitly. */
+    for (i = 0; i < NUM_COLS; i++)
+	if (!c->cols[i])
+	    c->cols[i] = defcols[i];
+    /* Default for missing files. */
+    if (!c->cols[COL_MI])
+	c->cols[COL_MI] = c->cols[COL_FI];
+
+    if (!c->cols[COL_EC]) {
+	char *e = (char *) zhalloc(strlen(c->cols[COL_LC]) +
+				   strlen(c->cols[COL_NO]) +
+				   strlen(c->cols[COL_RC]) + 1);
+
+	/* If no `ec' was given, we is `<lc><no><rc>' as the default. */
+	strcpy(e, c->cols[COL_LC]);
+	strcat(e, c->cols[COL_NO]);
+	strcat(e, c->cols[COL_RC]);
+	c->cols[COL_EC] = e;
+    }
+    return 0;
+}
+
+/* Get the terminal color string for the file with the given name and
+ * file modes. */
+
+static char *
+getcolstr(Listcols c, char *n, mode_t m)
+{
+    Extcol e;
+
+    for (e = c->exts; e; e = e->next)
+	if (strsfx(e->ext, n))
+	    return e->col;
+
+    if (S_ISDIR(m))
+	return c->cols[COL_DI];
+    else if (S_ISLNK(m))
+	return c->cols[COL_LN];
+    else if (S_ISFIFO(m))
+	return c->cols[COL_PI];
+    else if (S_ISSOCK(m))
+	return c->cols[COL_SO];
+    else if (S_ISBLK(m))
+	return c->cols[COL_BD];
+    else if (S_ISCHR(m))
+	return c->cols[COL_CD];
+    else if (S_ISREG(m) && (m & S_IXUGO))
+	return c->cols[COL_EX];
+
+    return c->cols[COL_FI];
+}
+
+/* Information about the list shown. */
+
+static int noselect, mselect, inselect, mcol, mline, mcols, mlines;
+static Cmatch *mmatch, **mtab;
+static Cmgroup mgroup, *mgtab;
+
+/* List the matches. Most of this is just taken from ilistmatches(),
+ * of course. */
+
+static int
+complistmatches(Hookdef dummy, Chdata dat)
+{
+    Cmgroup amatches = dat->matches, g;
+    Cmatch *p, m;
+    Cexpl *e;
+    int nlines = 0, ncols, nlist = 0, longest = 1, pnl = 0, opl = 0;
+    int of = isset(LISTTYPES);
+    int mc, ml = 0, cc, hasm = 0;
+    struct listcols col;
+
+    if (minfo.asked == 2) {
+	showinglist = 0;
+	return (noselect = 1);
+    }
+    getcols(&col);
+
+    /* Set the cursor below the prompt. */
+    if (inselect)
+	clearflag = 0;
+    trashzle();
+    showinglist = listshown = 0;
+
+    clearflag = (isset(USEZLE) && !termflags &&
+		 complastprompt && *complastprompt);
+
+    for (g = amatches; g; g = g->next) {
+	char **pp = g->ylist;
+	int nl = 0, l;
+
+	if (pp) {
+	    /* We have an ylist, lets see, if it contains newlines. */
+	    while (!nl && *pp)
+		nl = !!strchr(*pp++, '\n');
+
+	    pp = g->ylist;
+	    if (nl) {
+		/* Yup, there are newlines, count lines. */
+		char *nlptr, *sptr;
+
+		g->flags |= CGF_LINES;
+		noselect = 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) {
+		    if ((l = strlen(*pp)) > longest)
+			longest = l;
+		    nlist++;
+		    pp++;
+		}
+	    }
+	} else {
+	    for (p = g->matches; (m = *p); p++) {
+		if (!(m->flags & CMF_NOLIST)) {
+		    if ((l = niceztrlen(m->str)) > longest)
+			longest = l;
+		    nlist++;
+		} else
+		    noselect = 1;
+	    }
+	}
+	if ((e = g->expls)) {
+	    while (*e) {
+		if ((*e)->count)
+		    nlines += 1 + printfmt((*e)->str, (*e)->count, 0);
+		e++;
+	    }
+	}
+    }
+    longest += 2 + of;
+    if ((ncols = (columns + 1) / longest)) {
+	for (g = amatches; g; g = g->next)
+	    nlines += (g->lcount + ncols - 1) / ncols;
+    } else {
+	ncols = 1;
+	opl = 1;
+	for (g = amatches; g; g = g->next) {
+	    char **pp = g->ylist;
+
+	    if (pp) {
+		if (!(g->flags & CGF_LINES)) {
+		    while (*pp) {
+			nlines += 1 + (strlen(*pp) / columns);
+			pp++;
+		    }
+		}
+	    } else
+		for (p = g->matches; (m = *p); p++)
+		    if (!(m->flags & CMF_NOLIST))
+			nlines += 1 + ((1 + niceztrlen(m->str)) / columns);
+	}
+    }
+
+    /* Maybe we have to ask if the user wants to see the list. */
+    if ((!minfo.cur || !minfo.asked) &&
+	((complistmax && nlist > complistmax) ||
+	 (!complistmax && nlines >= lines))) {
+	int qup;
+	zsetterm();
+	qup = printfmt("zsh: do you wish to see all %n possibilities? ", nlist, 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);
+	    noselect = 1;
+	    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;
+    }
+    if (mselect >= 0) {
+	int i;
+
+	i = ncols * nlines;
+	free(mtab);
+	mtab = (Cmatch **) zalloc(i * sizeof(Cmatch **));
+	memset(mtab, 0, i * sizeof(Cmatch **));
+	free(mgtab);
+	mgtab = (Cmgroup *) zalloc(i * sizeof(Cmgroup));
+	memset(mgtab, 0, i * sizeof(Cmgroup));
+	mcols = ncols;
+	mlines = nlines;
+    }
+    /* Now print the matches. */
+    g = amatches;
+    while (g) {
+	char **pp = g->ylist;
+
+	if ((e = g->expls)) {
+	    while (*e) {
+		if ((*e)->count) {
+		    if (pnl) {
+			putc('\n', shout);
+			pnl = 0;
+			ml++;
+		    }
+		    ml += printfmt((*e)->str, (*e)->count, 1);
+		    pnl = 1;
+		}
+		e++;
+	    }
+	}
+	if (pp && *pp) {
+	    if (pnl) {
+		putc('\n', shout);
+		pnl = 0;
+		ml++;
+	    }
+	    if (g->flags & CGF_LINES) {
+		while (*pp) {
+		    zputs(*pp, shout);
+		    if (*++pp)
+			putc('\n', shout);
+		}
+	    } else {
+		int n = g->lcount, nl = (n + ncols - 1) / ncols, nc = nl, i, a;
+		char **pq;
+
+		while (n && nl--) {
+		    i = ncols;
+		    mc = 0;
+		    pq = pp;
+		    while (n && i--) {
+			if (pq - g->ylist >= g->lcount)
+			    break;
+			zputs(*pq, shout);
+			if (i) {
+			    a = longest - strlen(*pq);
+			    while (a--)
+				putc(' ', shout);
+			}
+			pq += nc;
+			n--;
+		    }
+		    if (n) {
+			putc('\n', shout);
+			ml++;
+		    }
+		    pp++;
+		}
+	    }
+	} else if (g->lcount) {
+	    int n = g->lcount, nl = (n + ncols - 1) / ncols, nc = nl, i, j, a = 0;
+	    int zt;
+	    Cmatch *q;
+
+	    if (n && pnl) {
+		putc('\n', shout);
+		pnl = 0;
+		ml++;
+	    }
+	    for (p = skipnolist(g->matches); n && nl--;) {
+		i = ncols;
+		mc = 0;
+		q = p;
+		while (n && i--) {
+		    fputs(col.cols[COL_LC], shout);
+		    if (!(m = *q)) {
+			fputs(col.cols[COL_MI], shout);
+			fputs(col.cols[COL_RC], shout);
+			a = longest - 2;
+			while (a--)
+			    putc(' ', shout);
+			fputs(col.cols[COL_EC], shout);
+			break;
+		    }
+		    hasm = 1;
+		    if (mselect >= 0) {
+			mtab[mc + (ncols * ml)] = q;
+			mgtab[mc + (ncols * ml)] = g;
+		    }
+		    if (m->gnum == mselect) {
+			mcol = mc;
+			mline = ml;
+			mmatch = q;
+			mgroup = g;
+			cc = COL_MA;
+		    } else
+			cc = -1;
+		    if (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);
+
+			zt = ztat(pb, &buf, 1);
+			if (cc >= 0)
+			    fputs(col.cols[cc], shout);
+			else if (zt)
+			    fputs(col.cols[COL_NO], shout);
+			else
+			    fputs(getcolstr(&col, pb, buf.st_mode), shout);
+			fputs(col.cols[COL_RC], shout);
+			nicezputs(m->str, shout);
+			if (zt)
+			    putc(' ', shout);
+			else
+			    putc(file_type(buf.st_mode), shout);
+		    } else {
+			fputs(col.cols[cc >= 0 ? cc : COL_NO], shout);
+			fputs(col.cols[COL_RC], shout);
+			nicezputs(m->str, shout);
+			if (of)
+			    putc(' ', shout);
+		    }
+		    a = longest - niceztrlen(m->str) - 2 - of;
+		    while (a--)
+			putc(' ', shout);
+		    fputs(col.cols[COL_EC], shout);
+		    if (i) {
+			fputs(col.cols[COL_LC], shout);
+			fputs(col.cols[COL_NO], shout);
+			fputs(col.cols[COL_RC], shout);
+			fputs("  ", shout);
+			fputs(col.cols[COL_EC], shout);
+		    }
+		    if (--n)
+			for (j = nc; j && *q; j--)
+			    q = skipnolist(q + 1);
+		    mc++;
+		}
+		if (i > 0) {
+		    fputs(col.cols[COL_LC], shout);
+		    fputs(col.cols[COL_MI], shout);
+		    fputs(col.cols[COL_RC], shout);
+		    a = longest - 2;
+		    while (a--)
+			putc(' ', shout);
+		    fputs(col.cols[COL_EC], shout);
+		}
+		if (n) {
+		    putc('\n', shout);
+		    ml++;
+		    if (n && nl)
+			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 ((nlines += nlnct - 1) < lines) {
+	    tcmultout(TCUP, TCMULTUP, nlines);
+	    showinglist = -1;
+	    listshown = 1;
+	} else
+	    clearflag = 0, putc('\n', shout);
+    } else
+	putc('\n', shout);
+    if (!hasm || nlines >= lines)
+	noselect = 1;
+    return noselect;
+}
+
+typedef struct menustack *Menustack;
+
+struct menustack {
+    Menustack prev;
+    char *line;
+    int cs;
+    struct menuinfo info;
+};
+
+static int
+domenuselect(Hookdef dummy, Chdata dat)
+{
+    Cmatch **p;
+    Cmgroup *pg;
+    Thingy cmd;
+    Menustack u = NULL;
+    int i = 0;
+    char *s;
+
+    if (getcols(NULL) || (dummy && (!(s = getsparam("SELECTMIN")) ||
+				    (dat && dat->num < atoi(s)))))
+	return 1;
+
+    selectlocalmap(mskeymap);
+    noselect = 0;
+    mselect = (*(minfo.cur))->gnum;
+    for (;;) {
+	showinglist = -2;
+	zrefresh();
+	inselect = 1;
+	if (noselect)
+	    break;
+	if (!i) {
+	    i = mcols * mlines;
+	    while (i--)
+		if (mtab[i])
+		    break;
+	    if (!i)
+		break;
+	    i = 1;
+	}
+	p = mtab + mcol + (mline * mcols);
+	pg = mgtab + mcol + (mline * mcols);
+	minfo.cur = *p;
+	minfo.group = *pg;
+
+    getk:
+
+	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak) ||
+	    cmd == Th(z_acceptline))
+	    break;
+	else if (cmd == Th(z_acceptandhold) ||
+		 cmd == Th(z_acceptandmenucomplete)) {
+	    Menustack s = (Menustack) zhalloc(sizeof(*s));
+
+	    s->prev = u;
+	    u = s;
+	    s->line = dupstring((char *) line);
+	    s->cs = cs;
+	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
+	    acceptlast();
+	    do_menucmp(0);
+	    mselect = (*(minfo.cur))->gnum;
+	    continue;
+	} else if (cmd == Th(z_undo)) {
+	    int l;
+
+	    if (!u)
+		goto getk;
+
+	    cs = 0;
+	    foredel(ll);
+	    spaceinline(l = strlen(u->line));
+	    strncpy((char *) line, u->line, l);
+	    cs = u->cs;
+	    memcpy(&minfo, &(u->info), sizeof(struct menuinfo));
+	    p = &(minfo.cur);
+	    u = u->prev;
+	} else if (cmd == Th(z_redisplay)) {
+	    redisplay(zlenoargs);
+	    continue;
+	} else if (cmd == Th(z_clearscreen)) {
+	    clearscreen(zlenoargs);
+	    continue;
+	} else if (cmd == Th(z_downhistory) ||
+		   cmd == Th(z_downlineorhistory) ||
+		   cmd == Th(z_downlineorsearch) ||
+		   cmd == Th(z_vidownlineorhistory)) {
+	    do {
+		if (mline == mlines - 1) {
+		    p -= mline * mcols;
+		    mline = 0;
+		} else {
+		    mline++;
+		    p += mcols;
+		}
+	    } while (!*p);
+	} else if (cmd == Th(z_uphistory) ||
+		   cmd == Th(z_uplineorhistory) ||
+		   cmd == Th(z_uplineorsearch) ||
+		   cmd == Th(z_viuplineorhistory)) {
+	    do {
+		if (!mline) {
+		    mline = mlines - 1;
+		    p += mline * mcols;
+		} else {
+		    mline--;
+		    p -= mcols;
+		}
+	    } while (!*p);
+	} else if (cmd == Th(z_forwardchar) || cmd == Th(z_viforwardchar)) {
+	    do {
+		if (mcol == mcols - 1) {
+		    p -= mcol;
+		    mcol = 0;
+		} else {
+		    mcol++;
+		    p++;
+		}
+	    } while (!*p);
+	} else if (cmd == Th(z_backwardchar) || cmd == Th(z_vibackwardchar)) {
+	    do {
+		if (!mcol) {
+		    mcol = mcols - 1;
+		    p += mcol;
+		} else {
+		    mcol--;
+		    p--;
+		}
+	    } while (!*p);
+	} else if (cmd == Th(z_beginningofbufferorhistory) ||
+		   cmd == Th(z_beginningofline) ||
+		   cmd == Th(z_beginningoflinehist) ||
+		   cmd == Th(z_vibeginningofline)) {
+	    p -= mcol;
+	    mcol = 0;
+	    while (!*p) {
+		mcol++;
+		p++;
+	    }
+	} else if (cmd == Th(z_endofbufferorhistory) ||
+		   cmd == Th(z_endofline) ||
+		   cmd == Th(z_endoflinehist) ||
+		   cmd == Th(z_viendofline)) {
+	    p += mcols - mcol - 1;
+	    mcol = mcols - 1;
+	    while (!*p) {
+		mcol--;
+		p--;
+	    }
+	} else if (cmd == Th(z_forwardword) ||
+		   cmd == Th(z_emacsforwardword) ||
+		   cmd == Th(z_viforwardword) ||
+		   cmd == Th(z_viforwardwordend)) {
+	    Cmgroup g = *pg;
+	    int ol = mline;
+
+	    do {
+		if (mline == mlines - 1) {
+		    p -= mline * mcols;
+		    pg -= mline * mcols;
+		    mline = 0;
+		} else {
+		    mline++;
+		    p += mcols;
+		    pg += mcols;
+		}
+	    } while (ol != mline && (*pg == g || !*pg));
+	} else if (cmd == Th(z_backwardword) ||
+		   cmd == Th(z_emacsbackwardword) ||
+		   cmd == Th(z_vibackwardword)) {
+	    Cmgroup g = *pg;
+	    int ol = mline;
+
+	    do {
+		if (!mline) {
+		    mline = mlines - 1;
+		    p += mline * mcols;
+		    pg += mline * mcols;
+		} else {
+		    mline--;
+		    p -= mcols;
+		    pg -= mcols;
+		}
+	    } while (ol != mline && (*pg == g || !*pg));
+	} else if (cmd == Th(z_completeword) ||
+		   cmd == Th(z_expandorcomplete) ||
+		   cmd == Th(z_expandorcompleteprefix) ||
+		   cmd == Th(z_menucomplete) ||
+		   cmd == Th(z_menuexpandorcomplete) ||
+		   !strcmp(cmd->nam, "menu-select") ||
+		   !strcmp(cmd->nam, "complete-word") ||
+		   !strcmp(cmd->nam, "expand-or-complete") ||
+		   !strcmp(cmd->nam, "expand-or-complete-prefix") ||
+		   !strcmp(cmd->nam, "menu-complete") ||
+		   !strcmp(cmd->nam, "menu-expand-or-complete")) {
+	    do_menucmp(0);
+	    mselect = (*(minfo.cur))->gnum;
+	    continue;
+	} else if (cmd == Th(z_reversemenucomplete) ||
+		   !strcmp(cmd->nam, "reverse-menu-complete")) {
+	    reversemenucomplete(zlenoargs);
+	    mselect = (*(minfo.cur))->gnum;
+	    continue;
+	} else {
+	    ungetkeycmd();
+	    break;
+	}
+	do_single(**p);
+	mselect = (**p)->gnum;
+    }
+    selectlocalmap(NULL);
+    mselect = -1;
+    inselect = 0;
+    if (!noselect) {
+	showinglist = -2;
+	zrefresh();
+    }
+    return noselect;
+}
+
+/* The widget function. */
+
+static int
+menuselect(char **args)
+{
+    int d = 0;
+
+    if (!minfo.cur) {
+	menucomplete(args);
+	if ((minfo.cur && minfo.asked == 2) || getsparam("ZLS_SELECT"))
+	    return 0;
+	d = 1;
+    }
+    if (minfo.cur && (minfo.asked == 2 || domenuselect(NULL, NULL)) && !d)
+	menucomplete(args);
+
+    return 0;
+}
+
+/**/
+int
+setup_complist(Module m)
+{
+    return 0;
+}
+
+/**/
+int
+boot_complist(Module m)
+{
+    mtab = NULL;
+    mgtab = NULL;
+    mselect = -1;
+    inselect = 0;
+
+    w_menuselect = addzlefunction("menu-select", menuselect,
+                                    ZLE_MENUCMP|ZLE_KEEPSUFFIX|ZLE_ISCOMP);
+    if (!w_menuselect) {
+	zwarnnam(m->nam, "name clash when adding ZLE function `menu-select'",
+		 NULL, 0);
+	return -1;
+    }
+    addhookfunc("list_matches", (Hookfn) complistmatches);
+    addhookfunc("menu_start", (Hookfn) domenuselect);
+    mskeymap = newkeymap(NULL, "menuselect");
+    linkkeymap(mskeymap, "menuselect", 1);
+    bindkey(mskeymap, "\t", refthingy(t_completeword), NULL);
+    bindkey(mskeymap, "\n", refthingy(t_acceptline), NULL);
+    bindkey(mskeymap, "\r", refthingy(t_acceptline), NULL);
+    bindkey(mskeymap, "\33[A",  refthingy(t_uplineorhistory), NULL);
+    bindkey(mskeymap, "\33[B",  refthingy(t_downlineorhistory), NULL);
+    bindkey(mskeymap, "\33[C",  refthingy(t_forwardchar), NULL);
+    bindkey(mskeymap, "\33[D",  refthingy(t_backwardchar), NULL);
+    bindkey(mskeymap, "\33OA",  refthingy(t_uplineorhistory), NULL);
+    bindkey(mskeymap, "\33OB",  refthingy(t_downlineorhistory), NULL);
+    bindkey(mskeymap, "\33OC",  refthingy(t_forwardchar), NULL);
+    bindkey(mskeymap, "\33OD",  refthingy(t_backwardchar), NULL);
+    return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_complist(Module m)
+{
+    free(mtab);
+    free(mgtab);
+
+    deletezlefunction(w_menuselect);
+    deletehookfunc("list_matches", (Hookfn) complistmatches);
+    deletehookfunc("menu_start", (Hookfn) domenuselect);
+    unlinkkeymap("menuselect", 1);
+    return 0;
+}
+
+/**/
+int
+finish_complist(Module m)
+{
+    return 0;
+}
+
+#endif
diff --git a/Src/Zle/complist.mdd b/Src/Zle/complist.mdd
new file mode 100644
index 000000000..8ea60b0a8
--- /dev/null
+++ b/Src/Zle/complist.mdd
@@ -0,0 +1,3 @@
+moddeps="comp1 zle"
+
+objects="complist.o"