about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--Doc/Zsh/mod_complist.yo283
-rw-r--r--Src/Zle/compcore.c3
-rw-r--r--Src/Zle/complete.c11
-rw-r--r--Src/Zle/complist.c1809
-rw-r--r--Src/Zle/compresult.c19
-rw-r--r--Src/Zle/zle_tricky.c2
-rw-r--r--Src/Zle/zle_utils.c33
-rw-r--r--Src/params.c2
9 files changed, 1777 insertions, 390 deletions
diff --git a/ChangeLog b/ChangeLog
index 1db9472ea..ba96db147 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,11 @@
 
 2000-04-17  Sven Wischnowsky <wischnow@informatik.hu-berlin.de>
 
+	* 10790: Doc/Zsh/mod_complist.yo, Src/params.c, Src/Zle/compcore.c,
+ 	Src/Zle/complete.c, Src/Zle/complist.c, Src/Zle/compresult.c,
+ 	Src/Zle/zle_tricky.c, Src/Zle/zle_utils.c: scrolling in completion
+ 	lists and menu-selection, version1
+	
 	* 10788: Src/text.c: display newlines as spaces in job-texts
 	
 	* 10782: Src/Zle/computil.c: fix for exclusion lists for -+o
diff --git a/Doc/Zsh/mod_complist.yo b/Doc/Zsh/mod_complist.yo
index 460d4a609..920df5fc1 100644
--- a/Doc/Zsh/mod_complist.yo
+++ b/Doc/Zsh/mod_complist.yo
@@ -1,23 +1,34 @@
-texinode(The complist Module)(The deltochar Module)(The compctl Module)(Zsh Modules)
-sect(The complist Module)
+COMMENT(!MOD!zsh/complist
+Completion listing extensions.
+!MOD!)
 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
+cindex(completion, coloured listings)
+cindex(completion, scroll listings)
+The tt(zsh/complist) module offers three extensions to completion listings:
+the ability to highlight matches in such a list, the ability to
+scroll through long lists and a different style of menu-completion.
+
+subsect(Colored completion listings)
+Whenever one of the parameters tt(ZLS_COLORS) or tt(ZLS_COLOURS) is set 
+and the tt(zsh/complist) module is loaded or linked into the shell,
+completion lists will be colored.  Note, however, that tt(complist) will
+not automatically be loaded if it is not linked in:  on systems with
+dynamic loading, `tt(zmodload zsh/complist)' is required.
+
+vindex(ZLS_COLORS)
+vindex(ZLS_COLOURS)
+The parameters tt(ZLS_COLORS) and tt(ZLS_COLOURS) describe how matches
+are highlighted. To turn on highlighting an empty value suffices, in
+which case all the default values given below will be used. 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
+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)
+for normal text (i.e. when displaying something other than a matched file)
 )
 item(tt(fi 0))(
 for regular files
@@ -44,7 +55,7 @@ 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))
+for non-existent file (default is the value defined for tt(fi))
 )
 item(tt(lc \e[))(
 for the left code (see below)
@@ -52,6 +63,13 @@ for the left code (see below)
 item(tt(rc m))(
 for the right code
 )
+item(tt(tc) var(0))(
+for the character indicating the file type  printed after filenames if
+the tt(LIST_TYPES) option is set
+)
+item(tt(sp) var(0))(
+for the spaces printed after matches to align the next column
+)
 item(tt(ec) var(none))(
 for the end code
 )
@@ -60,6 +78,37 @@ 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.
+The var(name) may also be a equal sign (`tt(=)') followed by a
+pattern. The var(value) given for this pattern will be used for all
+matches (not only filenames) whose display string are matched by
+the pattern. Definitions for both of these take precedence over the
+values defined for file types and the form with the leading asterisk 
+takes precedence over the form with the leading equal sign.
+
+The last form also allows to color separate parts of the displayed
+strings using different colors. For this, the pattern has to use the
+`tt((#b))' globbing flag and pairs of parentheses surrounding the
+parts of the strings that are to be colored differently. In this case 
+the var(value) may consist of more than one color code separated by
+equal signs. The first code will be used for all parts for which no
+explicit code is specified and the following codes will be used for
+the parts matched by the sub-patterns in parentheses. For example,
+the specification `tt(=(#b)(?)*(?)=0=3=7)' will be used for all
+matches which are at least two characters long and will make the use
+the code `tt(3)' for the first character, `tt(7)' for the last
+character and `tt(0)' for the rest.
+
+All three forms of var(name) may be preceded by a pattern in
+parentheses. If such a pattern is given, the var(value) will be used
+only for matches in groups whose names are matched by the pattern
+given in the parentheses. E.g. `tt((g*)m*=43)' says to highlight all
+matches beginning with `tt(m)' in groups whose names  begin with
+`tt(g)' using the color code `tt(43)'. In case of the `tt(lc)',
+`tt(rc)', and `tt(ec)' codes, the group pattern is ignored.
+
+Note also that all patterns are tried in the order in which they
+appear in the parameter value until the first one matches which is
+then used.
 
 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(*)',
@@ -71,41 +120,196 @@ 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.
+If the shell function based completion system is used, these
+parameters should not be set directly because the system controls them 
+itself. Instead, the tt(list-colors) style should be used (see
+ifzman(the section `Completion System Configuration' in zmanref(zshcompsys))\
+ifnzman(noderef(Completion System Configuration))\
+).
+
+subsect(Scrolling in completion listings)
+vindex(LISTMAX)
+To be able to scroll through a completion list, the tt(LISTMAX)
+parameter has to be set to the string `tt(scroll)'. If it has this
+value, the completion code will not ask if the list should be
+shown. Instead it immediately starts displaying the list, stopping
+after the first screenful, showing a simple prompt at the bottom,
+waiting for a keypress. The following keys have a special meaning:
+
+startitem()
+item(tt(Space), tt(Tab))(
+scroll forward one screenful
+)
+item(tt(Return), tt(Newline))(
+scroll forward one line
+)
+item(tt(q))(
+stops listing and redisplays the command line without inserting the
+`tt(q)'
+)
+enditem()
+
+Every other character stops listing and immediately processes the key
+as usual.
+
+If the parameter tt(LISTSTATUS) is set, its value will be used as the
+prompt.  The value may contain escapes of the form `tt(%x)'. It
+supports the escapes `tt(%B)', `tt(%b)', `tt(%S)', `tt(%s)', `tt(%U)',
+`tt(%u)' and `tt(%{...%})' known from the shell prompts and the
+additional sequence `tt(%l)' which is replaced by the number of the
+last line shown and the total number of lines in the form
+`var(number)tt(/)var(total)'.
 
 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
+cindex(completion, selecting by cursor)
+vindex(SELECTMIN)
+tindex(menu-select)
+The tt(zsh/complist) module also offers an alternative style of selecting
+matches from a list, called menu-selection, which can be used if the
+shell is set up to return to the last prompt after showing a
+completion list (see the tt(ALWAYS_LAST_PROMPT) option in
+ifzman(zmanref(zshoptions))\
+ifnzman(noderef(Options))\
+). 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
+the parameter tt(SELECTMIN) can be set to an integer, which give the
+minimum number of matches that 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.
+started during an ambiguous menu completion.
 
-After menu-selection is started, the matches will be listed. The
+When using the shell function based completion system, the
+tt(SELECTMIN) parameter should not be used (like the tt(ZLS_COLORS)
+and tt(ZLS_COLOURS) parameters described above). Instead, the tt(menu) 
+style should be used.
+
+After menu-selection is started, the matches will be listed. If there
+are more matches than fit on the screen, only the first screenful is
+shown. 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.
+value for this is `tt(7)' which forces the selected match to be
+highlighted using standout mode on a vt100-compatible terminal. If
+neither tt(ZLS_COLORS) nor tt(ZLS_COLOURS) is set, the same terminal
+control sequence as for the `tt(%S)' escape in prompts is used.
+
+If there are more matches than fit on the screen and the parameter
+tt(SELECTSTATUS) is set, its value will be shown below the
+matches. Next to the escape sequences understood for the
+tt(LISTSTATUS) parameter, a `tt(%m)' will be replaced by a string
+containing the number of the match the mark is on and the total number 
+of matches in the form `var(number)tt(/)var(total)' and the sequence
+`tt(%p)' will be replaced with `tt(Top)', `tt(Bottom)' or the position
+in percent of the total size when the mark is in the first line, in
+the last line or somewhere in between, respectively.
+
+The tt(SELECTSCROLL) parameter can be used to specify how the list is
+scrolled. If the parameter is unset, this is done line by line, if it
+is set to `tt(0)' (zero), the list will scrolled half the number of
+lines of the screen. If the value is positive, it gives the number of
+lines to scroll and if it is negative, the list will be scrolled one
+the number of lines of the screen minus the (absolute) value.
+
+The completion code sometimes decides not to show all of the matches
+in the list. These hidden matches are either matches for which the
+completion function which added them explicitly requested that they
+not appear in the list (using the tt(-n) option of the tt(compadd)
+builtin command) or they are matches which duplicate a string already
+in the list (because they differ only in things like prefixes or
+suffixes that are not displayed). In the list used for menu-selection,
+however, even these matches are shown so that it is possible to select
+them. To highlight such matches the tt(hi) and tt(du) capabilities in
+the tt(ZLS_COLORS) and tt(ZLS_COLOURS) parameters are supported for
+hidden matches of the first and second kind, respectively.
 
 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.
+functions. When not all matches can be shown on the screen at the same 
+time, the list will scroll up and down when crossing the top or
+bottom line. The following zle functions have special meaning during
+menu selection:
+
+startitem()
+item(tt(accept-line))(
+accepts the current match and leaves menu selection
+)
+item(tt(send-break))(
+leaves menu selection and continues with normal menu completion
+)
+item(tt(redisplay), tt(clear-screen))(
+execute their normal function without leaving menu selection
+)
+item(tt(accept-and-hold), tt(accept-and-menu-complete))(
+accept the currently inserted match and continue selection allowing to 
+select the next match to insert into the line
+)
+item(tt(accept-and-infer-next-history))(
+accepts the current match and then tries completion with
+menu-selection again;  in the case of files this allows one to select
+a directory and immediately attempt to complete files in it
+)
+item(tt(undo))(
+removes matches inserted during the menu selection by one of the three 
+functions before
+)
+xitem(tt(down-history), tt(down-line-or-history))
+item(tt(vi-down-line-or-history),  tt(down-line-or-search))(
+moves the mark one line down
+)
+xitem(tt(up-history), tt(up-line-or-history))
+item(tt(vi-up-line-or-history), tt(up-line-or-search))(
+moves the mark one line up
+)
+item(tt(forward-char), tt(vi-forward-char))(
+moves the mark one column right
+)
+item(tt(backward-char), tt(vi-backward-char))(
+moves the mark one column left
+)
+xitem(tt(forward-word), tt(vi-forward-word))
+item(tt(vi-forward-word-end), tt(emacs-forward-word))(
+moves the mark one screenful down
+)
+item(tt(backward-word), tt(vi-backward-word), tt(emacs-backward-word))(
+moves the mark one screenful up
+)
+item(tt(vi-forward-blank-word), tt(vi-forward-blank-word-end))(
+moves the mark to the first line of the next group of matches
+)
+item(tt(vi-backward-blank-word))(
+moves the mark to the last line of the previous group of matches
+)
+item(tt(beginning-of-history))(
+moves the mark to the first line
+)
+item(tt(end-of-history))(
+moves the mark to the last line
+)
+xitem(tt(beginning-of-buffer-or-history), tt(beginning-of-line))
+item(tt(beginning-of-line-hist), tt(vi-beginning-of-line))(
+moves the mark to the leftmost column
+)
+xitem(tt(end-of-buffer-or-history), tt(end-of-line))
+item(tt(end-of-line-hist), tt(vi-end-of-line))(
+moves the mark to the rightmost column
+)
+xitem(tt(complete-word), tt(menu-complete), tt(expand-or-complete))
+item(tt(expand-or-complete-prefix), tt(menu-expand-or-complete))(
+moves the mark to the next match
+)
+item(tt(reverse-menu-omplete))(
+moves the mark to the previous match
+)
+enditem()
+
+All movement function do wrap-around at the edges and
+any other zle function leaves menu-selection and executes that function.
+It is possible to make widgets in the above list do the same by using the
+form of the widget with a `tt(.)' in front.  For example, the widget
+`tt(.accept-line)' has the effect of leaving menu selection and accepting
+the entire command line.
 
 During this selection the widget uses the keymap tt(menuselect). Any
 key that is not defined in this keymap or that is bound to
@@ -115,5 +319,10 @@ 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))\
-).
+ifnzman(noderef(The zsh/zle Module))\
+). For example, to make the return key leave menu-selection and
+continue with normal menu-completion one can call
+
+indent(tt(bindkey -M menuselect '^M' send-break))
+
+after loading the tt(zsh/complist) module.
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index 6e2cbf537..d5dee0dca 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -296,7 +296,8 @@ do_completion(Hookdef dummy, Compldat dat)
     comppatinsert = ztrdup("menu");
     forcelist = 0;
     haspattern = 0;
-    complistmax = getiparam("LISTMAX");
+    zsfree(complistmax);
+    complistmax = ztrdup(getsparam("LISTMAX"));
     zsfree(complastprompt);
     complastprompt = ztrdup(((isset(ALWAYSLASTPROMPT) && zmult == 1) ||
 			     (unset(ALWAYSLASTPROMPT) && zmult != 1)) ?
diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c
index ef961eeba..58cd8a216 100644
--- a/Src/Zle/complete.c
+++ b/Src/Zle/complete.c
@@ -35,8 +35,7 @@
 /**/
 mod_export zlong compcurrent;
 /**/
-zlong complistmax,
-      complistlines,
+zlong complistlines,
       compignored;
 
 /**/
@@ -50,7 +49,8 @@ char **compwords,
      *compquote,
      *compqstack,
      *comppatmatch,
-     *complastprompt;
+     *complastprompt,
+     *complistmax;
 /**/
 char *compiprefix,
      *compcontext,
@@ -904,7 +904,7 @@ static struct compparam compkparams[] = {
     { "unambiguous", PM_SCALAR | PM_READONLY, NULL, NULL, VAL(get_unambig) },
     { "unambiguous_cursor", PM_INTEGER | PM_READONLY, NULL, NULL,
       VAL(get_unambig_curs) },
-    { "list_max", PM_INTEGER, VAL(complistmax), NULL, NULL },
+    { "list_max", PM_SCALAR, VAL(complistmax), NULL, NULL },
     { "last_prompt", PM_SCALAR, VAL(complastprompt), NULL, NULL },
     { "to_end", PM_SCALAR, VAL(comptoend), NULL, NULL },
     { "old_list", PM_SCALAR, VAL(compoldlist), NULL, NULL },
@@ -1292,7 +1292,7 @@ setup_(Module m)
     comprpms = compkpms = NULL;
     compwords = NULL;
     compprefix = compsuffix = compiprefix = compisuffix = 
-	compqiprefix = compqisuffix =
+	compqiprefix = compqisuffix = complistmax = 
 	compcontext = compparameter = compredirect = compquote =
 	compquoting = comprestore = complist = compinsert =
 	compexact = compexactstr = comppatmatch = comppatinsert =
@@ -1347,6 +1347,7 @@ finish_(Module m)
 {
     if (compwords)
 	freearray(compwords);
+    zsfree(complistmax);
     zsfree(compprefix);
     zsfree(compsuffix);
     zsfree(compiprefix);
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 63bc4c6e3..f3363cc97 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -54,22 +54,52 @@ static Keymap mskeymap;
 #define COL_LC 10
 #define COL_RC 11
 #define COL_EC 12
-#define COL_MA 13
+#define COL_TC 13
+#define COL_SP 14
+#define COL_MA 15
+#define COL_HI 16
+#define COL_DU 17
+#define COL_ST 18
 
-#define NUM_COLS 14
+#define NUM_COLS 19
+
+/* Maximum number of in-string colours supported. */
+
+#define MAX_POS 11
 
 /* Names of the terminal strings. */
 
 static char *colnames[] = {
     "no", "fi", "di", "ln", "pi", "so", "bd", "cd", "ex", "mi",
-    "lc", "rc", "ec", "ma", NULL
+    "lc", "rc", "ec", "tc", "sp", "ma", "hi", "du", "st", 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"
+    "\033[", "m", NULL, "0", "0", "7", "0", "0", "7"
+};
+
+/* This describes a terminal string for a file type. */
+
+typedef struct filecol *Filecol;
+
+struct filecol {
+    Patprog prog;		/* group pattern */
+    char *col;			/* color string */
+    Filecol next;		/* next one */
+};
+
+/* This describes a terminal string for a pattern. */
+
+typedef struct patcol *Patcol;
+
+struct patcol {
+    Patprog prog;
+    Patprog pat;		/* pattern for match */
+    char *cols[MAX_POS + 1];
+    Patcol next;
 };
 
 /* This describes a terminal string for a filename extension. */
@@ -77,6 +107,7 @@ static char *defcols[] = {
 typedef struct extcol *Extcol;
 
 struct extcol {
+    Patprog prog;		/* group pattern or NULL */
     char *ext;			/* the extension */
     char *col;			/* the terminal color string */
     Extcol next;		/* the next one in the list */
@@ -87,7 +118,8 @@ struct extcol {
 typedef struct listcols *Listcols;
 
 struct listcols {
-    char *cols[NUM_COLS];	/* strings for file types */
+    Filecol files[NUM_COLS];	/* strings for file types */
+    Patcol pats;		/* strings for patterns */
     Extcol exts;		/* strings for extensions */
 };
 
@@ -95,11 +127,11 @@ struct listcols {
  * The return value is a pointer to the character after it. */
 
 static char *
-getcolval(char *s)
+getcolval(char *s, int multi)
 {
     char *p;
 
-    for (p = s; *s && *s != ':'; p++, s++) {
+    for (p = s; *s && *s != ':' && (!multi || *s != '='); p++, s++) {
 	if (*s == '\\' && s[1]) {
 	    switch (*++s) {
 	    case 'a': *p = '\007'; break;
@@ -150,8 +182,33 @@ getcolval(char *s)
 static char *
 getcoldef(Listcols c, char *s)
 {
+    Patprog gprog = NULL;
+
+    if (*s == '(') {
+	char *p;
+	int l = 0;
+
+	for (p = s + 1, l = 0; *p && (*p != ')' || l); p++)
+	    if (*p == '\\' && p[1])
+		p++;
+	    else if (*p == '(')
+		l++;
+	    else if (*p == ')')
+		l--;
+
+	if (*p == ')') {
+	    char sav = p[1];
+
+	    p[1] = '\0';
+	    tokenize(s);
+	    gprog = patcompile(s, 0, NULL);
+	    p[1]  =sav;
+
+	    s = p + 1;
+	}
+    }
     if (*s == '*') {
-	Extcol ec;
+	Extcol ec, eo;
 	char *n, *p;
 
 	/* This is for an extension. */
@@ -159,20 +216,67 @@ getcoldef(Listcols c, char *s)
 	n = ++s;
 	while (*s && *s != '=')
 	    s++;
-	if (!*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;
+	p = getcolval(s, 0);
+	ec = (Extcol) zhalloc(sizeof(*ec));
+	ec->prog = gprog;
+	ec->ext = n;
+	ec->col = s;
+	ec->next = NULL;
+	if ((eo = c->exts)) {
+	    while (eo->next)
+		eo = eo->next;
+	    eo->next = ec;
+	} else
 	    c->exts = ec;
-	}
 	if (*p)
 	    *p++ = '\0';
 	return p;
+    } else if (*s == '=') {
+	char *p = ++s, *t, *cols[MAX_POS];
+	int ncols = 0;
+	Patprog prog;
+
+	/* This is for a pattern. */
+
+	while (*s && *s != '=')
+	    s++;
+	if (!*s)
+	    return s;
+	*s++ = '\0';
+	while (1) {
+	    t = getcolval(s, 1);
+	    if (ncols < MAX_POS)
+		cols[ncols++] = s;
+	    s = t;
+	    if (*s != '=')
+		break;
+	    *s++ = '\0';
+	}
+	tokenize(p);
+	if ((prog = patcompile(p, 0, NULL))) {
+	    Patcol pc, po;
+	    int i;
+
+	    pc = (Patcol) zhalloc(sizeof(*pc));
+	    pc->prog = gprog;
+	    pc->pat = prog;
+	    for (i = 0; i < ncols; i++)
+		pc->cols[i] = cols[i];
+	    pc->cols[i] = NULL;
+	    pc->next = NULL;
+	    if ((po = c->pats)) {
+		while (po->next)
+		    po = po->next;
+		po->next = pc;
+	    } else
+		c->pats = pc;
+	}
+	if (*t)
+	    *t++ = '\0';
+	return t;
     } else {
 	char *n = s, *p, **nn;
 	int i;
@@ -185,37 +289,76 @@ getcoldef(Listcols c, char *s)
 	    return s;
 	*s++ = '\0';
 	for (i = 0, nn = colnames; *nn; i++, nn++)
-	    if (!strcmp(n ,*nn))
+	    if (!strcmp(n, *nn))
 		break;
-	p = getcolval(s);
-	if (*nn)
-	    c->cols[i] = s;
+	p = getcolval(s, 0);
+	if (*nn) {
+	    Filecol fc, fo;
+
+	    fc = (Filecol) zhalloc(sizeof(*fc));
+	    fc->prog = (i == COL_EC || i == COL_LC || i == COL_RC ?
+			NULL : gprog);
+	    fc->col = s;
+	    fc->next = NULL;
+	    if ((fo = c->files[i])) {
+		while (fo->next)
+		    fo = fo->next;
+		fo->next = fc;
+	    } else
+		c->files[i] = fc;
+	}
 	if (*p)
 	    *p++ = '\0';
 	return p;
     }
 }
 
+static Filecol
+filecol(char *col)
+{
+    Filecol fc;
+
+    fc = (Filecol) zhalloc(sizeof(*fc));
+    fc->prog = NULL;
+    fc->col = col;
+    fc->next = NULL;
+
+    return fc;
+}
+
+/* Combined length of LC and RC, maximum length of capability strings. */
+
+static int lr_caplen, max_caplen;
+
 /* This initializes the given terminal color structure. */
 
-static int
+static void
 getcols(Listcols c)
 {
     char *s;
-    int i;
+    int i, l;
 
     if (!(s = getsparam("ZLS_COLORS")) &&
 	!(s = getsparam("ZLS_COLOURS"))) {
-	if (!c)
-	    return 1;
 	for (i = 0; i < NUM_COLS; i++)
-	    c->cols[i] = "";
-	
+	    c->files[i] = filecol("");
+	c->pats = NULL;
 	c->exts = NULL;
-	return 1;
+	
+	if ((s = tcstr[TCSTANDOUTBEG]) && s[0]) {
+	    c->files[COL_MA] = filecol(s);
+	    c->files[COL_ST] = filecol(s);
+	    c->files[COL_EC] = filecol(tcstr[TCSTANDOUTEND]);
+	} else {
+	    c->files[COL_MA] = filecol(defcols[COL_MA]);
+	    c->files[COL_ST] = filecol(defcols[COL_ST]);
+	}
+	lr_caplen = 0;
+	if ((max_caplen = strlen(c->files[COL_MA]->col)) <
+	    (l = strlen(c->files[COL_EC]->col)))
+	    max_caplen = l;
+	return;
     }
-    if (!c)
-	return 0;
     /* We have one of the parameters, use it. */
     memset(c, 0, sizeof(*c));
     s = dupstring(s);
@@ -223,312 +366,796 @@ getcols(Listcols c)
 	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];
+    max_caplen = lr_caplen = 0;
+    for (i = 0; i < NUM_COLS; i++) {
+	if (!c->files[i] || !c->files[i]->col)
+	    c->files[i] = filecol(defcols[i]);
+	if (c->files[i] && c->files[i]->col &&
+	    (l = strlen(c->files[i]->col)) > max_caplen)
+	    max_caplen = l;
+    }
+    lr_caplen = strlen(c->files[COL_LC]->col) + strlen(c->files[COL_RC]->col);
+
     /* 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;
+    if (!c->files[COL_MI] || !c->files[COL_MI]->col)
+	c->files[COL_MI] = c->files[COL_FI];
+
+    return;
+}
+
+/* Information about the list shown. */
+
+static int noselect, mselect, inselect, mcol, mline, mcols, mlines, mmlen;
+static int selected, mlbeg = -1, mlend = 9999999, mscroll, mrestlines;
+static int mnew, mlastcols, mlastlines, mhasstat;
+static char *mstatus;
+static Cmatch **mtab, **mmtabp;
+static Cmgroup *mgtab, *mgtabp;
+static struct listcols mcolors;
+
+/* Information for in-string colours. */
+
+static int nrefs;
+static int begpos[MAX_POS], curisbeg;
+static int endpos[MAX_POS];
+static int sendpos[MAX_POS], curissend; /* sorted end positions */
+static char **patcols, *curiscols[MAX_POS];
+static int curiscol;
+
+/* The last color used. */
+
+static char *last_cap;
+
+static void
+zlrputs(Listcols c, char *cap)
+{
+    if (!*last_cap || strcmp(last_cap, cap)) {
+	VARARR(char, buf, lr_caplen + max_caplen + 1);
+
+	strcpy(buf, c->files[COL_LC]->col);
+	strcat(buf, cap);
+	strcat(buf, c->files[COL_RC]->col);
+
+	tputs(buf, 1, putshout);
+
+	strcpy(last_cap, cap);
     }
+}
+
+static void
+zcputs(Listcols c, char *group, int colour)
+{
+    Filecol fc;
+
+    for (fc = c->files[colour]; fc; fc = fc->next)
+	if (fc->col &&
+	    (!fc->prog || !group || pattry(fc->prog, group))) {
+	    zlrputs(c, fc->col);
+
+	    return;
+	}
+}
+
+/* Turn off colouring. */
+
+static void
+zcoff(void)
+{
+    if (mcolors.files[COL_EC] && mcolors.files[COL_EC]->col) {
+	tputs(mcolors.files[COL_EC]->col, 1, putshout);
+	*last_cap = '\0';
+    } else
+	zcputs(&mcolors, NULL, COL_NO);
+}
+
+
+static void
+initiscol(Listcols c)
+{
+    int i;
+
+    zlrputs(c, patcols[0]);
+
+    curiscols[curiscol = 0] = *patcols++;
+
+    curisbeg = curissend = 0;
+
+    for (i = 0; i < nrefs; i++)
+	sendpos[i] = 0xfffffff;
+    for (; i < MAX_POS; i++)
+	begpos[i] = endpos[i] = sendpos[i] = 0xfffffff;
+}
+
+static void
+doiscol(Listcols c, int pos)
+{
+    int fi;
+
+    while (pos > sendpos[curissend]) {
+	curissend++;
+	if (curiscol) {
+	    zcputs(c, NULL, COL_NO);
+	    zlrputs(c, curiscols[--curiscol]);
+	}
+    }
+    while (((fi = (endpos[curisbeg] < begpos[curisbeg] || 
+		  begpos[curisbeg] == -1)) ||
+	    pos == begpos[curisbeg]) && *patcols) {
+	if (!fi) {
+	    int i, j, e = endpos[curisbeg];
+	    
+	    /* insert e in sendpos */
+	    for (i = curissend; sendpos[i] <= e; ++i)
+		;
+	    for (j = i + 1; j < MAX_POS; ++j)
+		sendpos[j] = sendpos[j-1];
+	    sendpos[i] = e;
+	    
+	    zcputs(c, NULL, COL_NO);
+	    zlrputs(c, *patcols);
+	    curiscols[++curiscol] = *patcols;
+	}
+	++patcols;
+	++curisbeg;
+    }
+}
+
+/* Stripped-down version of printfmt(). But can do in-string colouring. */
+
+static int
+clprintfmt(Listcols c, char *p, int ml)
+{
+    int cc = 0, i = 0, ask, beg;
+
+    initiscol(c);
+
+    for (; *p; p++) {
+	doiscol(c, i++);
+	cc++;
+	if (*p == '\n') {
+	    if (mlbeg >= 0 && tccan(TCCLEAREOL))
+		tcout(TCCLEAREOL);
+	    cc = 0;
+	}
+	if (ml == mlend - 1 && (cc % columns) == columns - 1)
+	    return 0;
+
+	putc(*p, shout);
+	if ((beg = !(cc % columns)))
+	    ml++;
+	if (mscroll && !(cc % columns) &&
+	    !--mrestlines && (ask = asklistscroll(ml)))
+	    return ask;
+    }
+    if (mlbeg >= 0 && tccan(TCCLEAREOL))
+	tcout(TCCLEAREOL);
+    return 0;
+}
+
+/* Local version of nicezputs() with in-string colouring. */
+
+static int
+clnicezputs(Listcols c, char *s, int ml)
+{
+    int cc, i = 0, col = 0, ask;
+    char *t;
+
+    initiscol(c);
+
+    while ((cc = *s++)) {
+	doiscol(c, i++);
+	if (itok(cc)) {
+	    if (cc <= Comma)
+		cc = ztokens[cc - Pound];
+	    else 
+		continue;
+	}
+	if (cc == Meta)
+	    cc = *s++ ^ 32;
+
+	for (t = nicechar(cc); *t; t++) {
+	    if (ml == mlend - 1 && col == columns - 1)
+		return 0;
+	    putc(*t, shout);
+	    if (++col == columns) {
+		ml++;
+		if (mscroll && !--mrestlines && (ask = asklistscroll(ml)))
+		    return ask;
+
+		col = 0;
+	    }
+	}
+    }
+    return 0;
+}
+
+/* Get the terminal color string for the given match. */
+
+static int
+putmatchcol(Listcols c, char *group, char *n)
+{
+    Patcol pc;
+
+    nrefs = MAX_POS - 1;
+
+    for (pc = c->pats; pc; pc = pc->next)
+	if ((!pc->prog || !group || pattry(pc->prog, group)) &&
+	    pattryrefs(pc->pat, n, &nrefs, begpos, endpos)) {
+	    if (pc->cols[1]) {
+		patcols = pc->cols;
+
+		return 1;
+	    }
+	    zlrputs(c, pc->cols[0]);
+
+	    return 0;
+	}
+
+    zcputs(c, group, COL_NO);
+
     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)
+static int
+putfilecol(Listcols c, char *group, char *n, mode_t m)
 {
-    Extcol e;
+    int colour;
+    Extcol ec;
+    Patcol pc;
+
+    for (ec = c->exts; ec; ec = ec->next)
+	if (strsfx(ec->ext, n) &&
+	    (!ec->prog || !group || pattry(ec->prog, group))) {
+	    zlrputs(c, ec->col);
+
+	    return 0;
+	}
 
-    for (e = c->exts; e; e = e->next)
-	if (strsfx(e->ext, n))
-	    return e->col;
+    nrefs = MAX_POS - 1;
+
+    for (pc = c->pats; pc; pc = pc->next)
+	if ((!pc->prog || !group || pattry(pc->prog, group)) &&
+	    pattryrefs(pc->pat, n, &nrefs, begpos, endpos)) {
+	    if (pc->cols[1]) {
+		patcols = pc->cols;
+
+		return 1;
+	    }
+	    zlrputs(c, pc->cols[0]);
+
+	    return 0;
+	}
 
     if (S_ISDIR(m))
-	return c->cols[COL_DI];
+	colour = COL_DI;
     else if (S_ISLNK(m))
-	return c->cols[COL_LN];
+	colour = COL_LN;
     else if (S_ISFIFO(m))
-	return c->cols[COL_PI];
+	colour = COL_PI;
     else if (S_ISSOCK(m))
-	return c->cols[COL_SO];
+	colour = COL_SO;
     else if (S_ISBLK(m))
-	return c->cols[COL_BD];
+	colour = COL_BD;
     else if (S_ISCHR(m))
-	return c->cols[COL_CD];
+	colour = COL_CD;
     else if (S_ISREG(m) && (m & S_IXUGO))
-	return c->cols[COL_EX];
+	colour = COL_EX;
+    else
+	colour = COL_FI;
+
+    zcputs(c, group, colour);
 
-    return c->cols[COL_FI];
+    return 0;
 }
 
-/* Information about the list shown. */
+static Cmgroup last_group;
+
+/**/
+static int
+asklistscroll(int ml)
+{
+    int v, i;
+
+    compprintfmt(NULL, -1, 1, 1, ml, NULL);
 
-static int noselect, mselect, inselect, mcol, mline, mcols, mlines;
-static Cmatch *mmatch, **mtab;
-static Cmgroup mgroup, *mgtab;
+    fflush(shout);
+    zsetterm();
+    v = getzlequery(0);
+    settyinfo(&shttyinfo);
+    putc('\r', shout);
+    for (i = columns - 1; i--; )
+	putc(' ', shout);
 
-/* List the matches. Most of this is just taken from ilistmatches(),
- * of course. */
+    putc('\r', shout);
 
+    if (v == '\n' || v == '\r') {
+	mrestlines = 1;
+	return 0;
+    }
+    mrestlines = lines - 1;
+
+    if (v == ' ' || v == '\t')
+	return 0;
+    if (v != 'q')
+	ungetkey(v);
+
+    return 1;
+}
+
+#define dolist(X)   ((X) >= mlbeg && (X) < mlend)
+#define dolistcl(X) ((X) >= mlbeg && (X) < mlend + 1)
+#define dolistnl(X) ((X) >= mlbeg && (X) < mlend - 1)
+
+/**/
 static int
-complistmatches(Hookdef dummy, Chdata dat)
+compprintnl(int ml)
 {
-    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;
+    int ask;
 
-    if (minfo.asked == 2) {
-	showinglist = 0;
-	return (noselect = 1);
-    }
-    getcols(&col);
+    if (mlbeg >= 0 && tccan(TCCLEAREOL))
+	tcout(TCCLEAREOL);
+    putc('\n', shout);
 
-    /* Set the cursor below the prompt. */
-    if (inselect)
-	clearflag = 0;
-    trashzle();
-    showinglist = listshown = 0;
+    if (mscroll && !--mrestlines && (ask = asklistscroll(ml)))
+	return ask;
 
-    clearflag = (isset(USEZLE) && !termflags &&
-		 complastprompt && *complastprompt);
+    return 0;
+}
 
-    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;
+/* This is used to print the strings (e.g. explanations). *
+ * It returns the number of lines printed.       */
+
+/**/
+static int
+compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
+{
+    char *p, nc[2*DIGBUFSIZE + 12], nbuf[2*DIGBUFSIZE + 12];
+    int l = 0, cc = 0, b = 0, s = 0, u = 0, m, ask, beg, stat;
+
+    if ((stat = !fmt)) {
+	if (mlbeg >= 0) {
+	    if (!(fmt = mstatus))
+		return 0;
+	    cc = -1;
+	} else if (!(fmt = getsparam("LISTSTATUS")))
+	    fmt = "continue? ";
+    }
+    for (p = fmt; *p; p++) {
+	if (doesc && *p == '%') {
+	    if (*++p) {
+		m = 0;
+		switch (*p) {
+		case '%':
+		    if (dopr == 1)
+			putc('%', shout);
+		    cc++;
+		    break;
+		case 'n':
+		    if (!stat) {
+			sprintf(nc, "%d", n);
+			if (dopr == 1)
+			    fputs(nc, shout);
+			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;
+		case 'm':
+		    if (stat && n >= 0) {
+			sprintf(nbuf, "%d/%d", mselect, listdat.nlist);
+			sprintf(nc, "%-9s", nbuf);
+			m = 2;
+		    }
+		    break;
+		case 'l':
+		    if (stat) {
+			sprintf(nbuf, "%d/%d", ml + 1, listdat.nlines);
+			sprintf(nc, "%-9s", nbuf);
+			m = 2;
+		    }
+		    break;
+		case 'p':
+		    if (stat && n >= 0) {
+			if (ml == listdat.nlines - 1)
+			    strcpy(nc, "Bottom");
+			else if (mlbeg || ml != n)
+			    sprintf(nc, "%d%%",
+				    ((ml + 1) * 100) / listdat.nlines);
+			else
+			    strcpy(nc, "Top");
+			m = 2;
 		    }
-		    nlines++;
-		    pp++;
+		    break;
 		}
-		nlines--;
-	    } else {
-		while (*pp) {
-		    if ((l = strlen(*pp)) > longest)
-			longest = l;
-		    nlist++;
-		    pp++;
+		if (m == 2 && dopr == 1) {
+		    int l = strlen(nc);
+
+		    if (l + cc > columns - 2)
+			nc[l -= l + cc - (columns - 2)] = '\0';
+		    fputs(nc, shout);
+		    cc += l;
+		} else if (dopr && m == 1) {
+		    if (b)
+			tcout(TCBOLDFACEBEG);
+		    if (s)
+			tcout(TCSTANDOUTBEG);
+		    if (u)
+			tcout(TCUNDERLINEBEG);
 		}
-	    }
+	    } else
+		break;
 	} 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 ((++cc == columns - 2 || *p == '\n') && stat)
+		dopr = 2;
+	    if (*p == '\n') {
+		if (dopr == 1 && mlbeg >= 0 && tccan(TCCLEAREOL))
+		    tcout(TCCLEAREOL);
+		l += 1 + (cc / columns);
+		cc = 0;
 	    }
-	}
-	if ((e = g->expls)) {
-	    while (*e) {
-		if ((*e)->count)
-		    nlines += 1 + printfmt((*e)->str, (*e)->count, 0);
-		e++;
+	    if (dopr == 1) {
+		if (ml == mlend - 1 && (cc % columns) == columns - 1) {
+		    dopr = 0;
+		    continue;
+		}
+		putc(*p, shout);
+		if ((beg = !(cc % columns)) && !stat)
+		    ml++;
+		if (mscroll && beg && !--mrestlines && (ask = asklistscroll(ml))) {
+		    *stop = 1;
+		    return l + (cc / columns);
+		}
 	    }
 	}
     }
-    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);
+    if (dopr && mlbeg >= 0 && tccan(TCCLEAREOL))
+	tcout(TCCLEAREOL);
+
+    return l + (cc / columns);
+}
+
+/* This is like zputs(), but allows scrolling. */
+
+/**/
+static int
+compzputs(char const *s, int ml)
+{
+    int c, col = 0, ask;
+
+    while (*s) {
+	if (*s == Meta)
+	    c = *++s ^ 32;
+	else if(itok(*s)) {
+	    s++;
+	    continue;
+	} else
+	    c = *s;
+	s++;
+	putc(c, shout);
+	if (c == '\n' && mlbeg >= 0 && tccan(TCCLEAREOL))
+	    tcout(TCCLEAREOL);
+	if (mscroll && (++col == columns || c == '\n')) {
+	    ml++;
+	    if (!--mrestlines && (ask = asklistscroll(ml)))
+		return ask;
+
+	    col = 0;
 	}
     }
+    return 0;
+}
 
-    /* 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;
+/* This is like nicezputs(), but allows scrolling. */
+
+/**/
+static int
+compnicezputs(char *s, int ml)
+{
+    int c, col = 0, ask;
+    char *t;
+
+    while ((c = *s++)) {
+	if (itok(c)) {
+	    if (c <= Comma)
+		c = ztokens[c - Pound];
+	    else 
+		continue;
+	}
+	if (c == Meta)
+	    c = *s++ ^ 32;
+
+	for (t = nicechar(c); *t; t++) {
+	    if (ml == mlend - 1 && col == columns - 1)
+		return 0;
+	    putc(*t, shout);
+	    if (++col == columns) {
+		ml++;
+		if (mscroll && !--mrestlines && (ask = asklistscroll(ml)))
+		    return ask;
+
+		col = 0;
+	    }
 	}
-	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;
+    return 0;
+}
 
-	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;
+/**/
+static int
+compprintlist(int showall)
+{
+    static int lasttype = 0, lastbeg = 0, lastml = 0;
+    static lastn = 0, lastnl = 0;
+    static Cmgroup lastg = NULL;
+    static Cmatch *lastp = NULL;
+    static Cexpl *lastexpl = NULL;
+
+    Cmgroup g;
+    Cmatch *p, m;
+    Cexpl *e;
+    int pnl = 0, cl, mc = 0, ml = 0, printed = 0, stop = 0, asked = 1;
+    int lastused = 0, fl = -1;
+
+    if (mnew || lastbeg != mlbeg || mlbeg < 0) {
+	lasttype = 0;
+	lastg = NULL;
+	lastexpl = NULL;
+	lastml = 0;
     }
-    /* Now print the matches. */
-    g = amatches;
+    cl = (listdat.nlines > lines - nlnct - mhasstat ?
+	  lines - nlnct - mhasstat : listdat.nlines);
+    mrestlines = lines - 1;
+
+    if (cl < 2) {
+	cl = -1;
+	if (tccan(TCCLEAREOD))
+	    tcout(TCCLEAREOD);
+    } else if (mlbeg >= 0 && !tccan(TCCLEAREOL) && tccan(TCCLEAREOD))
+	tcout(TCCLEAREOD);
+
+    g = ((lasttype && lastg) ? lastg : amatches);
     while (g) {
 	char **pp = g->ylist;
 
 	if ((e = g->expls)) {
+	    int l;
+
+	    if (!lastused && lasttype == 1) {
+		e = lastexpl;
+		ml = lastml;
+		lastused = 1;
+	    }
 	    while (*e) {
 		if ((*e)->count) {
 		    if (pnl) {
-			putc('\n', shout);
+			if (dolistnl(ml) && compprintnl(ml))
+			    goto end;
 			pnl = 0;
 			ml++;
+			if (dolistcl(ml) && cl >= 0 && --cl <= 1) {
+			    cl = -1;
+			    if (tccan(TCCLEAREOD))
+				tcout(TCCLEAREOD);
+			}
+		    }
+		    l = compprintfmt((*e)->str, (*e)->count, dolist(ml), 1,
+				     ml, &stop);
+		    if (stop)
+			goto end;
+		    if (!lasttype && ml >= mlbeg) {
+			lasttype = 1;
+			lastg = g;
+			lastbeg = mlbeg;
+			lastml = ml;
+			lastexpl = e;
+			lastp = NULL;
+			lastused = 1;
+		    }
+		    ml += l;
+		    if (dolistcl(ml) && cl >= 0 && (cl -= l) <= 1) {
+			cl = -1;
+			if (tccan(TCCLEAREOD))
+			    tcout(TCCLEAREOD);
 		    }
-		    ml += printfmt((*e)->str, (*e)->count, 1);
 		    pnl = 1;
 		}
 		e++;
+		if (!mnew && ml > mlend)
+		    goto end;
 	    }
 	}
-	if (pp && *pp) {
+	if (!listdat.onlyexpl && mlbeg < 0 && pp && *pp) {
 	    if (pnl) {
-		putc('\n', shout);
+		if (dolistnl(ml) && compprintnl(ml))
+		    goto end;
 		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);
+		    if (compzputs(*pp, ml))
+			goto end;
+		    if (*++pp && compprintnl(ml))
+			goto end;
 		}
 	    } else {
-		int n = g->lcount, nl = (n + ncols - 1) / ncols, nc = nl, i, a;
+		int n = g->lcount, nl, nc, i, a;
 		char **pq;
 
+		nl = nc = g->lins;
+
 		while (n && nl--) {
-		    i = ncols;
+		    i = g->cols;
 		    mc = 0;
 		    pq = pp;
 		    while (n && i--) {
 			if (pq - g->ylist >= g->lcount)
 			    break;
-			zputs(*pq, shout);
+			if (compzputs(*pq, mscroll))
+			    goto end;
 			if (i) {
-			    a = longest - strlen(*pq);
+			    a = (g->widths ? g->widths[mc] : g->width) -
+				strlen(*pq);
 			    while (a--)
 				putc(' ', shout);
 			}
-			pq += nc;
+			pq += ((g->flags & CGF_ROWS) ? 1 : nc);
+			mc++;
 			n--;
 		    }
 		    if (n) {
-			putc('\n', shout);
+			if (compprintnl(ml))
+			    goto end;
 			ml++;
+			if (cl >= 0 && --cl <= 1) {
+			    cl = -1;
+			    if (tccan(TCCLEAREOD))
+				tcout(TCCLEAREOD);
+			}
 		    }
-		    pp++;
+		    pp += ((g->flags & CGF_ROWS) ? g->cols : 1);
 		}
 	    }
-	} else if (g->lcount) {
-	    int n = g->lcount, nl = (n + ncols - 1) / ncols, nc = nl, i, j, a = 0;
-	    int zt;
+	} else if (!listdat.onlyexpl &&
+		   (g->lcount || (showall && g->mcount))) {
+	    int n = g->dcount, nl, nc, i, j, wid;
 	    Cmatch *q;
 
+	    nl = nc = g->lins;
+
+	    if ((g->flags & CGF_HASDL) &&
+		(lastused || !lasttype || lasttype == 2)) {
+		if (!lastused && lasttype == 2) {
+		    p = lastp;
+		    ml = lastml;
+		    n = lastn;
+		    nl = lastnl;
+		    lastused = 1;
+		} else
+		    p = g->matches;
+
+		for (; (m = *p); p++) {
+		    if (m->disp && (m->flags & CMF_DISPLINE)) {
+			if (!lasttype && ml >= mlbeg) {
+			    lasttype = 2;
+			    lastg = g;
+			    lastbeg = mlbeg;
+			    lastml = ml;
+			    lastp = p;
+			    lastn = n;
+			    lastnl = nl;
+			    lastused = 1;
+			}
+			if (pnl) {
+			    if (dolistnl(ml) && compprintnl(ml))
+				goto end;
+			    pnl = 0;
+			    ml++;
+			    if (dolistcl(ml) && cl >= 0 && --cl <= 1) {
+				cl = -1;
+				if (tccan(TCCLEAREOD))
+				    tcout(TCCLEAREOD);
+			    }
+			}
+			if (fl < 0)
+			    fl = ml;
+			if (dolist(ml))
+			    printed++;
+			if (clprintm(g, p, 0, ml, 1, 0, NULL, NULL))
+			    goto end;
+			pnl = 1;
+		    }
+		    if (!mnew && ml > mlend)
+			goto end;
+		}
+	    }
 	    if (n && pnl) {
-		putc('\n', shout);
+		if (dolistnl(ml) && compprintnl(ml))
+		    goto end;
 		pnl = 0;
 		ml++;
+		if (dolistcl(ml) && cl >= 0 && --cl <= 1) {
+		    cl = -1;
+		    if (tccan(TCCLEAREOD))
+			tcout(TCCLEAREOD);
+		}
 	    }
-	    for (p = skipnolist(g->matches); n && nl--;) {
-		i = ncols;
+	    if (!lastused && lasttype == 3) {
+		p = lastp;
+		n = lastn;
+		nl = lastnl;
+		ml = lastml;
+		lastused = 1;
+	    } else
+		p = skipnolist(g->matches, showall);
+
+	    while (n && nl--) {
+		if (!lasttype && ml >= mlbeg) {
+		    lasttype = 3;
+		    lastg = g;
+		    lastbeg = mlbeg;
+		    lastml = ml;
+		    lastp = p;
+		    lastn = n;
+		    lastnl = nl + 1;
+		    lastused = 1;
+		}
+		i = g->cols;
 		mc = 0;
 		q = p;
 		while (n && i--) {
-		    fputs(col.cols[COL_LC], shout);
+		    wid = (g->widths ? g->widths[mc] : g->width);
 		    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);
+			if (clprintm(g, NULL, mc, ml, (!i), wid, NULL, NULL))
+			    goto end;
 			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) {
+		    if (!m->disp && (m->flags & CMF_FILE)) {
 			struct stat buf;
 			char *pb;
 
@@ -537,112 +1164,419 @@ complistmatches(Hookdef dummy, Chdata dat)
 			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 (ztat(pb, &buf, 1) ?
+			    clprintm(g, q, mc, ml, (!i), wid, NULL, NULL) :
+			    clprintm(g, q, mc, ml, (!i), wid, pb, &buf))
+			    goto end;
+		    } else if (clprintm(g, q, mc, ml, (!i), wid, NULL, NULL))
+			goto end;
+
+		    if (dolist(ml))
+			printed++;
+		    if (fl < 0)
+			fl = ml;
+
 		    if (--n)
-			for (j = nc; j && *q; j--)
-			    q = skipnolist(q + 1);
+			for (j = ((g->flags & CGF_ROWS) ? 1 : nc);
+			     j && *q; j--)
+			    q = skipnolist(q + 1, showall);
 		    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);
+		while (i-- > 0) {
+		    if (clprintm(g, NULL, mc, ml, (!i),
+				 (g->widths ? g->widths[mc] : g->width),
+				 NULL, NULL))
+			goto end;
+		    mc++;
 		}
 		if (n) {
-		    putc('\n', shout);
+		    if (dolistnl(ml) && compprintnl(ml))
+			goto end;
 		    ml++;
-		    if (n && nl)
-			p = skipnolist(p + 1);
+		    if (dolistcl(ml) && cl >= 0 && --cl <= 1) {
+			cl = -1;
+			if (tccan(TCCLEAREOD))
+			    tcout(TCCLEAREOD);
+		    }
+		    if (nl)
+			for (j = ((g->flags & CGF_ROWS) ? g->cols : 1);
+			     j && *p; j--)
+			    p = skipnolist(p + 1, showall);
 		}
+		if (!mnew && ml > mlend)
+		    goto end;
 	    }
 	}
-	if (g->lcount)
+	if (g->lcount || (showall && g->mcount))
 	    pnl = 1;
 	g = g->next;
     }
-
+    asked = 0;
+ end:
+    lastlistlen = 0;
+    if (nlnct <= 1)
+	mscroll = 0;
     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);
+	if (mlbeg >= 0) {
+	    if ((ml = listdat.nlines + nlnct) >= lines) {
+		if (mhasstat) {
+		    putc('\n', shout);
+		    compprintfmt(NULL, fl, 1, 1, mline, NULL);
+		}
+		ml = lines - 1;
+	    } else
+		ml--;
+	    tcmultout(TCUP, TCMULTUP, ml);
+	    showinglist = -1;
+
+	    lastlistlen = listdat.nlines;
+	} else if ((ml = listdat.nlines + nlnct - 1) < lines) {
+	    if (mlbeg >= 0 && tccan(TCCLEAREOL))
+		tcout(TCCLEAREOL);
+	    tcmultout(TCUP, TCMULTUP, ml);
 	    showinglist = -1;
-	    listshown = 1;
+
+	    lastlistlen = listdat.nlines;
+	} else {
+	    clearflag = 0;
+	    if (!asked)
+		compprintnl(ml);
+	}
+    } else if (!asked)
+	compprintnl(ml);
+
+    listshown = (clearflag ? 1 : -1);
+    mnew = 0;
+
+    return printed;
+}
+
+/**/
+static int
+clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
+	 char *path, struct stat *buf)
+{
+    Cmatch m;
+    int len, subcols = 0, stop = 0, ret = 0;
+
+    if (g != last_group)
+        *last_cap = '\0';
+
+    last_group = g;
+
+    if (!mp) {
+	if (dolist(ml)) {
+	    zcputs(&mcolors, g->name, COL_SP);
+	    len = width - 2;
+	    while (len-- > 0)
+		putc(' ', shout);
+	    zcoff();
+	}
+	return 0;
+    }
+    m = *mp;
+    if (m->disp && (m->flags & CMF_DISPLINE)) {
+	if (mselect >= 0) {
+	    int mm = (mcols * ml), i;
+
+	    for (i = mcols; i--; ) {
+		mtab[mm + i] = mp;
+		mgtab[mm + i] = g;
+	    }
+	}
+	if (!dolist(ml))
+	    return 0;
+	if (m->gnum == mselect) {
+	    int mm = (mcols * ml);
+	    mline = ml;
+	    mcol = 0;
+	    mmtabp = mtab + mm;
+	    mgtabp = mgtab + mm;
+	    mmlen = mcols;
+	    zcputs(&mcolors, g->name, COL_MA);
+	} else if (m->flags & CMF_NOLIST)
+	    zcputs(&mcolors, g->name, COL_HI);
+	else if (mselect >= 0 && (m->flags & (CMF_MULT | CMF_FMULT)))
+	    zcputs(&mcolors, g->name, COL_DU);
+	else
+	    subcols = putmatchcol(&mcolors, g->name, m->disp);
+	if (subcols)
+	    ret = clprintfmt(&mcolors, m->disp, ml);
+	else {
+	    compprintfmt(m->disp, 0, 1, 0, ml, &stop);
+	    if (stop)
+		ret = 1;
+	}
+	zcoff();
+    } else {
+	int mx;
+
+	if (g->widths) {
+	    int i;
+
+	    for (i = mx = 0; i < mc; i++)
+		mx += g->widths[i];
 	} else
-	    clearflag = 0, putc('\n', shout);
+	    mx = mc * g->width;
+
+	if (mselect >= 0) {
+	    int mm = mcols * ml, i;
+
+	    for (i = (width ? width : mcols); i--; ) {
+		mtab[mx + mm + i] = mp;
+		mgtab[mx + mm + i] = g;
+	    }
+	}
+	if (!dolist(ml))
+	    return 0;
+	if (m->gnum == mselect) {
+	    int mm = mcols * ml;
+
+	    mcol = mx;
+	    mline = ml;
+	    mmtabp = mtab + mx + mm;
+	    mgtabp = mgtab + mx + mm;
+	    mmlen = width;
+	    zcputs(&mcolors, g->name, COL_MA);
+	} else if (m->flags & CMF_NOLIST)
+	    zcputs(&mcolors, g->name, COL_HI);
+	else if (mselect >= 0 && (m->flags & (CMF_MULT | CMF_FMULT)))
+	    zcputs(&mcolors, g->name, COL_DU);
+	else if (buf)
+	    subcols = putfilecol(&mcolors, g->name, m->str, buf->st_mode);
+	else
+	    subcols = putmatchcol(&mcolors, g->name, (m->disp ? m->disp : m->str));
+
+	if (subcols)
+	    ret = clnicezputs(&mcolors, (m->disp ? m->disp : m->str), ml);
+	else
+	    ret = compnicezputs((m->disp ? m->disp : m->str), ml);
+	if (ret) {
+	    zcoff();
+	    return 1;
+	}
+	len = niceztrlen(m->disp ? m->disp : m->str);
+
+	 if (isset(LISTTYPES) && buf) {
+	    if (m->gnum != mselect) {
+		zcoff();
+		zcputs(&mcolors, g->name, COL_TC);
+	    }
+	    putc(file_type(buf->st_mode), shout);
+	    len++;
+        }
+	if ((len = width - len - 2) > 0) {
+	    if (m->gnum != mselect) {
+		zcoff();
+		zcputs(&mcolors, g->name, COL_SP);
+	    }
+	    while (len-- > 0)
+		putc(' ', shout);
+	}
+	zcoff();
+	if (!lastc) {
+	    zcputs(&mcolors, g->name, COL_SP);
+	    fputs("  ", shout);
+	    zcoff();
+	}
+    }
+    return ret;
+}
+
+static int
+complistmatches(Hookdef dummy, Chdata dat)
+{
+    Cmgroup oamatches = amatches;
+    char *p = NULL;
+
+    amatches = dat->matches;
+
+    if ((minfo.asked == 2 && mselect < 0) || nlnct >= lines) {
+	showinglist = 0;
+	amatches = oamatches;
+	return (noselect = 1);
+    }
+    getcols(&mcolors);
+
+    mnew = ((calclist(mselect >= 0) || mlastcols != columns ||
+	     mlastlines != listdat.nlines) && mselect >= 0);
+
+    if (!listdat.nlines || (mselect >= 0 &&
+			    !(isset(USEZLE) && !termflags &&
+			      complastprompt && *complastprompt))) {
+	showinglist = listshown = 0;
+	noselect = 1;
+	amatches = oamatches;
+	return 1;
+    }
+    if (inselect)
+	clearflag = 0;
+
+    mscroll = 0;
+
+    if (mselect >= 0 || mlbeg >= 0 ||
+	((p = getsparam("LISTMAX")) && !strcmp(p, "scroll"))) {
+	trashzle();
+	showinglist = listshown = 0;
+
+	lastlistlen = 0;
+
+	if (p) {
+	    clearflag = (isset(USEZLE) && !termflags && dolastprompt);
+	    mscroll = 1;
+	} else {
+	    clearflag = 1;
+	    minfo.asked = (listdat.nlines + nlnct <= lines);
+	}
+    } else if (asklist()) {
+	amatches = oamatches;
+	return (noselect = 1);
+    }
+    if (mlbeg >= 0) {
+	mlend = mlbeg + lines - nlnct - mhasstat;
+	while (mline >= mlend)
+	    mlbeg++, mlend++;
     } else
-	putc('\n', shout);
-    if (!hasm || nlines >= lines)
+	mlend = 9999999;
+
+    if (mnew) {
+	int i;
+
+	i = columns * listdat.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));
+	mlastcols = mcols = columns;
+	mlastlines = mlines = listdat.nlines;
+    }
+    last_cap = (char *) zhalloc(max_caplen + 1);
+    *last_cap = '\0';
+
+    if (!compprintlist(mselect >= 0) || !clearflag)
 	noselect = 1;
+
+    amatches = oamatches;
+
     return noselect;
 }
 
+static int
+adjust_mcol(int wish, Cmatch ***tabp, Cmgroup **grp)
+{
+    Cmatch **tab = *tabp;
+    int p, n, c;
+
+    tab -= mcol;
+
+    for (p = wish; p >= 0 && !tab[p]; p--);
+    for (n = wish; n < mcols && !tab[n]; n++);
+    if (n == mcols)
+	n = -1;
+
+    if (p < 0) {
+	if (n < 0)
+	    return 1;
+	c = n;
+    } else if (n < 0)
+	c = p;
+    else
+	c = ((mcol - p) < (n - mcol) ? p : n);
+
+    *tabp = tab + c;
+    if (grp)
+	*grp = *grp + c - mcol;
+
+    mcol = c;
+    
+    return 0;
+}
+
 typedef struct menustack *Menustack;
 
 struct menustack {
     Menustack prev;
     char *line;
-    int cs;
+    Brinfo brbeg;
+    Brinfo brend;
+    int nbrbeg, nbrend;
+    int cs, acc, nmatches, mline, mlbeg;
     struct menuinfo info;
+    Cmgroup amatches, pmatches, lastmatches, lastlmatches;
 };
 
 static int
 domenuselect(Hookdef dummy, Chdata dat)
 {
+    static Chdata fdat = NULL;
     Cmatch **p;
     Cmgroup *pg;
     Thingy cmd;
     Menustack u = NULL;
-    int i = 0;
+    int i = 0, acc = 0, wishcol = 0, setwish = 0, oe = onlyexpl, wasnext = 0;
+    int space, lbeg = 0, step = 1;
     char *s;
 
-    if (getcols(NULL) || (dummy && (!(s = getsparam("SELECTMIN")) ||
-				    (dat && dat->num < atoi(s)))))
-	return 1;
-
+    if (fdat || (dummy && (!(s = getsparam("SELECTMIN")) ||
+			   (dat && dat->num < atoi(s))))) {
+	if (fdat) {
+	    fdat->matches = dat->matches;
+	    fdat->num = dat->num;
+	}
+	return 0;
+    }
+    if ((s = getsparam("SELECTSCROLL"))) {
+	if (!(step = mathevali(s)))
+	    step = (lines - nlnct) >> 1;
+	else if (step < 0)
+	    if ((step += lines - nlnct) < 0)
+		step = 1;
+    }
+    mstatus = getsparam("SELECTSTATUS");
+    mhasstat = !!mstatus;
+    fdat = dat;
     selectlocalmap(mskeymap);
     noselect = 0;
     mselect = (*(minfo.cur))->gnum;
+    mline = 0;
+    mlines = 999999;
+    mlbeg = 0;
     for (;;) {
+	space = lines - nlnct - mhasstat;
+	while (mline < mlbeg)
+	    if ((mlbeg -= step) < 0)
+		mlbeg = 0;
+
+	if (mlbeg && lbeg != mlbeg) {
+	    Cmatch **p = mtab + ((mlbeg - 1) * columns), **q;
+	    int c;
+
+	    while (mlbeg) {
+		for (q = p, c = columns; c; q++, c--)
+		    if (*q)
+			break;
+		if (c)
+		    break;
+		p -= columns;
+		mlbeg--;
+	    }
+	}
+	while (mline >= mlbeg + space)
+	    if ((mlbeg += step) + space > mlines)
+		mlbeg = mlines - space;
+
+	lbeg = mlbeg;
+	onlyexpl = 0;
 	showinglist = -2;
 	zrefresh();
 	inselect = 1;
 	if (noselect)
 	    break;
+	selected = 1;
 	if (!i) {
 	    i = mcols * mlines;
 	    while (i--)
@@ -652,28 +1586,107 @@ domenuselect(Hookdef dummy, Chdata dat)
 		break;
 	    i = 1;
 	}
-	p = mtab + mcol + (mline * mcols);
-	pg = mgtab + mcol + (mline * mcols);
+	p = mmtabp;
+	pg = mgtabp;
 	minfo.cur = *p;
 	minfo.group = *pg;
+	if (setwish)
+	    wishcol = mcol;
+	else if (mcol > wishcol) {
+	    while (mcol > 0 && p[-1] == minfo.cur)
+		mcol--, p--, pg--;
+	} else if (mcol < wishcol) {
+	    while (mcol < mcols - 1 && p[1] == minfo.cur)
+		mcol++, p++, pg++;
+	}
+	setwish = wasnext = 0;
 
     getk:
 
-	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak) ||
-	    cmd == Th(z_acceptline))
+	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak))
+	    break;
+	else if (cmd == Th(z_acceptline)) {
+	    acc = 1;
 	    break;
-	else if (cmd == Th(z_acceptandhold) ||
-		 cmd == Th(z_acceptandmenucomplete)) {
+	} else if (cmd == Th(z_acceptandinfernexthistory)) {
 	    Menustack s = (Menustack) zhalloc(sizeof(*s));
 
 	    s->prev = u;
 	    u = s;
 	    s->line = dupstring((char *) line);
 	    s->cs = cs;
+	    s->mline = mline;
+	    s->mlbeg = mlbeg;
 	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
-	    acceptlast();
+	    s->amatches = amatches;
+	    s->pmatches = pmatches;
+	    s->lastmatches = lastmatches;
+	    s->lastlmatches = lastlmatches;
+	    s->acc = menuacc;
+	    s->brbeg = dupbrinfo(brbeg, NULL, 1);
+	    s->brend = dupbrinfo(brend, NULL, 1);
+	    s->nbrbeg = nbrbeg;
+	    s->nbrend = nbrend;
+	    s->nmatches = nmatches;
+	    menucmp = menuacc = hasoldlist = 0;
+	    fixsuffix();
+	    validlist = 0;
+	    amatches = pmatches = lastmatches = NULL;
+	    invalidate_list();
+	    menucomplete(zlenoargs);
+	    if (dat->num < 2 || !minfo.cur || !*(minfo.cur)) {
+		noselect = clearlist = listshown = 1;
+		onlyexpl = 0;
+		zrefresh();
+		break;
+	    }
+	    clearlist = listshown = 1;
+	    mselect = (*(minfo.cur))->gnum;
+	    setwish = wasnext = 1;
+	    mline = 0;
+	    continue;
+	} else if (cmd == Th(z_acceptandhold) ||
+		   cmd == Th(z_acceptandmenucomplete)) {
+	    Menustack s = (Menustack) zhalloc(sizeof(*s));
+	    int ol;
+
+	    s->prev = u;
+	    u = s;
+	    s->line = dupstring((char *) line);
+	    s->cs = cs;
+	    s->mline = mline;
+	    s->mlbeg = mlbeg;
+	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
+	    s->amatches = s->pmatches =
+		s->lastmatches = s->lastlmatches = NULL;
+	    s->acc = menuacc;
+	    s->brbeg = dupbrinfo(brbeg, NULL, 1);
+	    s->brend = dupbrinfo(brend, NULL, 1);
+	    s->nbrbeg = nbrbeg;
+	    s->nbrend = nbrend;
+	    s->nmatches = nmatches;
+	    accept_last();
 	    do_menucmp(0);
 	    mselect = (*(minfo.cur))->gnum;
+
+	    p -= mcol;
+	    mcol = 0;
+	    ol = mline;
+	    do {
+		for (mcol = 0; mcol < mcols; mcol++, p++)
+		    if (*p == minfo.cur)
+			break;
+		if (mcol != mcols)
+		    break;
+		mline++;
+	    } while (mline != ol);
+	    if (*p != minfo.cur) {
+		noselect = clearlist = listshown = 1;
+		onlyexpl = 0;
+		zrefresh();
+		break;
+	    }
+	    setwish = 1;
 	    continue;
 	} else if (cmd == Th(z_undo)) {
 	    int l;
@@ -686,9 +1699,32 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    spaceinline(l = strlen(u->line));
 	    strncpy((char *) line, u->line, l);
 	    cs = u->cs;
+	    menuacc = u->acc;
 	    memcpy(&minfo, &(u->info), sizeof(struct menuinfo));
 	    p = &(minfo.cur);
+	    mline = u->mline;
+	    mlbeg = u->mlbeg;
+	    if (u->lastmatches && lastmatches != u->lastmatches) {
+		if (lastmatches)
+		    freematches(lastmatches);
+		amatches = u->amatches;
+		pmatches = u->pmatches;
+		lastmatches = u->lastmatches;
+		lastlmatches = u->lastlmatches;
+		nmatches = u->nmatches;
+		hasoldlist = 1;
+	    }
+	    freebrinfo(brbeg);
+	    freebrinfo(brend);
+	    brbeg = dupbrinfo(u->brbeg, &lastbrbeg, 0);
+	    brend = dupbrinfo(u->brend, &lastbrend, 0);
+	    nbrbeg = u->nbrbeg;
+	    nbrend = u->nbrend;
+
 	    u = u->prev;
+	    clearlist = 1;
+	    setwish = 1;
+	    listdat.valid = 0;
 	} else if (cmd == Th(z_redisplay)) {
 	    redisplay(zlenoargs);
 	    continue;
@@ -707,6 +1743,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 		    mline++;
 		    p += mcols;
 		}
+		if (adjust_mcol(wishcol, &p, NULL))
+		    continue;
 	    } while (!*p);
 	} else if (cmd == Th(z_uphistory) ||
 		   cmd == Th(z_uplineorhistory) ||
@@ -720,8 +1758,104 @@ domenuselect(Hookdef dummy, Chdata dat)
 		    mline--;
 		    p -= mcols;
 		}
+		if (adjust_mcol(wishcol, &p, NULL))
+		    continue;
 	    } while (!*p);
+	} else if (cmd == Th(z_emacsforwardword) ||
+		   cmd == Th(z_viforwardword) ||
+		   cmd == Th(z_viforwardwordend) ||
+		   cmd == Th(z_forwardword)) {
+	    int i = lines - nlnct - 1, oi = i, ll = 0;
+	    Cmatch **lp = NULL;
+
+	    if (mline == mlines - 1)
+		goto top;
+	    while (i > 0) {
+		if (mline == mlines - 1) {
+		    if (i != oi && lp)
+			break;
+		    goto top;
+		} else {
+		    mline++;
+		    p += mcols;
+		}
+		if (adjust_mcol(wishcol, &p, NULL))
+		    continue;
+		if (*p) {
+		    i--;
+		    lp = p;
+		    ll = mline;
+		}
+	    }
+	    p = lp;
+	    mline = ll;
+	} else if (cmd == Th(z_emacsbackwardword) ||
+		   cmd == Th(z_vibackwardword) ||
+		   cmd == Th(z_backwardword)) {
+	    int i = lines - nlnct - 1, oi = i, ll = 0;
+	    Cmatch **lp = NULL;
+
+	    if (!mline)
+		goto bottom;
+	    while (i > 0) {
+		if (!mline) {
+		    if (i != oi && lp)
+			break;
+		    goto bottom;
+		} else {
+		    mline--;
+		    p -= mcols;
+		}
+		if (adjust_mcol(wishcol, &p, NULL))
+		    continue;
+		if (*p) {
+		    i--;
+		    lp = p;
+		    ll = mline;
+		}
+	    }
+	    p = lp;
+	    mline = ll;
+	} else if (cmd == Th(z_beginningofhistory)) {
+	    int ll;
+	    Cmatch **lp;
+	top:
+	    ll = mline;
+	    lp = p;
+	    while (mline) {
+		mline--;
+		p -= mcols;
+		if (adjust_mcol(wishcol, &p, NULL))
+		    continue;
+		if (*p) {
+		    lp = p;
+		    ll = mline;
+		}
+	    }
+	    mline = ll;
+	    p = lp;
+	} else if (cmd == Th(z_endofhistory)) {
+	    int ll;
+	    Cmatch **lp;
+	bottom:
+	    ll = mline;
+	    lp = p;
+	    while (mline < mlines - 1) {
+		mline++;
+		p += mcols;
+		if (adjust_mcol(wishcol, &p, NULL))
+		    continue;
+		if (*p) {
+		    lp = p;
+		    ll = mline;
+		}
+	    }
+	    mline = ll;
+	    p = lp;
 	} else if (cmd == Th(z_forwardchar) || cmd == Th(z_viforwardchar)) {
+	    int omcol = mcol;
+	    Cmatch *op = *p;
+
 	    do {
 		if (mcol == mcols - 1) {
 		    p -= mcol;
@@ -730,8 +1864,12 @@ domenuselect(Hookdef dummy, Chdata dat)
 		    mcol++;
 		    p++;
 		}
-	    } while (!*p);
+	    } while (!*p || (mcol != omcol && *p == op));
+	    wishcol = mcol;
 	} else if (cmd == Th(z_backwardchar) || cmd == Th(z_vibackwardchar)) {
+	    int omcol = mcol;
+	    Cmatch *op = *p;
+
 	    do {
 		if (!mcol) {
 		    mcol = mcols - 1;
@@ -740,7 +1878,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 		    mcol--;
 		    p--;
 		}
-	    } while (!*p);
+	    } while (!*p || (mcol != omcol && *p == op));
+	    wishcol = mcol;
 	} else if (cmd == Th(z_beginningofbufferorhistory) ||
 		   cmd == Th(z_beginningofline) ||
 		   cmd == Th(z_beginningoflinehist) ||
@@ -751,6 +1890,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 		mcol++;
 		p++;
 	    }
+	    wishcol = 0;
 	} else if (cmd == Th(z_endofbufferorhistory) ||
 		   cmd == Th(z_endofline) ||
 		   cmd == Th(z_endoflinehist) ||
@@ -761,10 +1901,9 @@ domenuselect(Hookdef dummy, Chdata dat)
 		mcol--;
 		p--;
 	    }
-	} else if (cmd == Th(z_forwardword) ||
-		   cmd == Th(z_emacsforwardword) ||
-		   cmd == Th(z_viforwardword) ||
-		   cmd == Th(z_viforwardwordend)) {
+	    wishcol = mcols - 1;
+	} else if (cmd == Th(z_viforwardblankword) ||
+		   cmd == Th(z_viforwardblankwordend)) {
 	    Cmgroup g = *pg;
 	    int ol = mline;
 
@@ -778,10 +1917,10 @@ domenuselect(Hookdef dummy, Chdata dat)
 		    p += mcols;
 		    pg += mcols;
 		}
+		if (adjust_mcol(wishcol, &p, &pg))
+		    continue;
 	    } while (ol != mline && (*pg == g || !*pg));
-	} else if (cmd == Th(z_backwardword) ||
-		   cmd == Th(z_emacsbackwardword) ||
-		   cmd == Th(z_vibackwardword)) {
+	} else if (cmd == Th(z_vibackwardblankword)) {
 	    Cmgroup g = *pg;
 	    int ol = mline;
 
@@ -795,6 +1934,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 		    p -= mcols;
 		    pg -= mcols;
 		}
+		if (adjust_mcol(wishcol, &p, &pg))
+		    continue;
 	    } while (ol != mline && (*pg == g || !*pg));
 	} else if (cmd == Th(z_completeword) ||
 		   cmd == Th(z_expandorcomplete) ||
@@ -809,11 +1950,13 @@ domenuselect(Hookdef dummy, Chdata dat)
 		   !strcmp(cmd->nam, "menu-expand-or-complete")) {
 	    do_menucmp(0);
 	    mselect = (*(minfo.cur))->gnum;
+	    setwish = 1;
 	    continue;
 	} else if (cmd == Th(z_reversemenucomplete) ||
 		   !strcmp(cmd->nam, "reverse-menu-complete")) {
 	    reversemenucomplete(zlenoargs);
 	    mselect = (*(minfo.cur))->gnum;
+	    setwish = 1;
 	    continue;
 	} else {
 	    ungetkeycmd();
@@ -822,14 +1965,35 @@ domenuselect(Hookdef dummy, Chdata dat)
 	do_single(**p);
 	mselect = (**p)->gnum;
     }
+    if (u)
+	for (; u; u = u->prev)
+	    if (u->lastmatches != lastmatches)
+		freematches(u->lastmatches);
+
     selectlocalmap(NULL);
-    mselect = -1;
-    inselect = 0;
+    mselect = mlastcols = mlastlines = -1;
+    mstatus = NULL;
+    inselect = mhasstat = 0;
+    if (acc) {
+	menucmp = lastambig = hasoldlist = 0;
+	do_single(*(minfo.cur));
+    }
+    if (wasnext) {
+	menucmp = 2;
+	showinglist = -2;
+	minfo.asked = 0;
+    }
     if (!noselect) {
 	showinglist = -2;
+	onlyexpl = oe;
+	if (!smatches)
+	    clearlist = 1;
 	zrefresh();
     }
-    return noselect;
+    mlbeg = -1;
+    fdat = NULL;
+
+    return (!noselect ^ acc);
 }
 
 /* The widget function. */
@@ -840,8 +2004,9 @@ menuselect(char **args)
     int d = 0;
 
     if (!minfo.cur) {
+	selected = 0;
 	menucomplete(args);
-	if ((minfo.cur && minfo.asked == 2) || getsparam("ZLS_SELECT"))
+	if ((minfo.cur && minfo.asked == 2) || selected)
 	    return 0;
 	d = 1;
     }
@@ -853,14 +2018,14 @@ menuselect(char **args)
 
 /**/
 int
-setup_complist(Module m)
+setup_(Module m)
 {
     return 0;
 }
 
 /**/
 int
-boot_complist(Module m)
+boot_(Module m)
 {
     mtab = NULL;
     mgtab = NULL;
@@ -874,7 +2039,7 @@ boot_complist(Module m)
 		 NULL, 0);
 	return -1;
     }
-    addhookfunc("list_matches", (Hookfn) complistmatches);
+    addhookfunc("comp_list_matches", (Hookfn) complistmatches);
     addhookfunc("menu_start", (Hookfn) domenuselect);
     mskeymap = newkeymap(NULL, "menuselect");
     linkkeymap(mskeymap, "menuselect", 1);
@@ -892,17 +2057,15 @@ boot_complist(Module m)
     return 0;
 }
 
-#ifdef MODULE
-
 /**/
 int
-cleanup_complist(Module m)
+cleanup_(Module m)
 {
     free(mtab);
     free(mgtab);
 
     deletezlefunction(w_menuselect);
-    deletehookfunc("list_matches", (Hookfn) complistmatches);
+    deletehookfunc("comp_list_matches", (Hookfn) complistmatches);
     deletehookfunc("menu_start", (Hookfn) domenuselect);
     unlinkkeymap("menuselect", 1);
     return 0;
@@ -910,9 +2073,7 @@ cleanup_complist(Module m)
 
 /**/
 int
-finish_complist(Module m)
+finish_(Module m)
 {
     return 0;
 }
-
-#endif
diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c
index e56110a70..87e1f7278 100644
--- a/Src/Zle/compresult.c
+++ b/Src/Zle/compresult.c
@@ -1157,7 +1157,7 @@ skipnolist(Cmatch *p, int showall)
 }
 
 /**/
-mod_export void
+mod_export int
 calclist(int showall)
 {
     Cmgroup g;
@@ -1170,7 +1170,7 @@ calclist(int showall)
     if (listdat.valid && onlyexpl == listdat.onlyexpl &&
 	menuacc == listdat.menuacc && showall == listdat.showall &&
 	lines == listdat.lines && columns == listdat.columns)
-	return;
+	return 0;
 
     for (g = amatches; g; g = g->next) {
 	char **pp = g->ylist;
@@ -1572,11 +1572,16 @@ calclist(int showall)
     listdat.columns = columns;
     listdat.lines = lines;
     listdat.showall = showall;
+
+    return 1;
 }
 
 /**/
-mod_export int asklist(void)
+mod_export int
+asklist(void)
 {
+    int lmax = (complistmax ? (int) mathevali(complistmax) : 0);
+
     /* Set the cursor below the prompt. */
     trashzle();
     showinglist = listshown = 0;
@@ -1586,9 +1591,9 @@ mod_export int asklist(void)
 
     /* Maybe we have to ask if the user wants to see the list. */
     if ((!minfo.cur || !minfo.asked) &&
-	((complistmax > 0 && listdat.nlist >= complistmax) ||
-	 (complistmax < 0 && listdat.nlines <= -complistmax) ||
-	 (!complistmax && listdat.nlines >= lines))) {
+	((lmax > 0 && listdat.nlist >= lmax) ||
+	 (lmax < 0 && listdat.nlines <= -lmax) ||
+	 (!lmax && listdat.nlines >= lines))) {
 	int qup, l;
 
 	zsetterm();
@@ -1599,7 +1604,7 @@ mod_export int asklist(void)
 		     listdat.nlines));
 	qup = ((l + columns - 1) / columns) - 1;
 	fflush(shout);
-	if (getzlequery() != 'y') {
+	if (getzlequery(1) != 'y') {
 	    if (clearflag) {
 		putc('\r', shout);
 		tcmultout(TCUP, TCMULTUP, qup);
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 51a77b224..f18babbbd 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -1997,7 +1997,7 @@ listlist(LinkList l)
 	     fprintf(shout, "zsh: do you wish to see all %d lines? ", nlines));
 	qup = ((l + columns - 1) / columns) - 1;
 	fflush(shout);
-	if (getzlequery() != 'y') {
+	if (getzlequery(1) != 'y') {
 	    if (clearflag) {
 		putc('\r', shout);
 		tcmultout(TCUP, TCMULTUP, qup);
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
index 51af32e0b..ece9a23ae 100644
--- a/Src/Zle/zle_utils.c
+++ b/Src/Zle/zle_utils.c
@@ -277,6 +277,7 @@ hstrnstr(char *haystack, int pos, char *needle, int len, int dir, int sens)
  * question is assumed to have been printed already, and the    *
  * cursor is left immediately after the response echoed.        *
  * (Might cause a problem if this takes it onto the next line.) *
+ * If yesno is non-zero:                                        *
  * <Tab> is interpreted as 'y'; any other control character is  *
  * interpreted as 'n'.  If there are any characters in the      *
  * buffer, this is taken as a negative response, and no         *
@@ -284,31 +285,35 @@ hstrnstr(char *haystack, int pos, char *needle, int len, int dir, int sens)
 
 /**/
 mod_export int
-getzlequery(void)
+getzlequery(int yesno)
 {
     int c;
 #ifdef FIONREAD
     int val;
 
-    /* check for typeahead, which is treated as a negative response */
-    ioctl(SHTTY, FIONREAD, (char *)&val);
-    if (val) {
-	putc('n', shout);
-	return 'n';
+    if (yesno) {
+	/* check for typeahead, which is treated as a negative response */
+	ioctl(SHTTY, FIONREAD, (char *)&val);
+	if (val) {
+	    putc('n', shout);
+	    return 'n';
+	}
     }
 #endif
 
     /* get a character from the tty and interpret it */
     c = getkey(0);
-    if (c == '\t')
-	c = 'y';
-    else if (icntrl(c) || c == EOF)
-	c = 'n';
-    else
-	c = tulower(c);
-
+    if (yesno) {
+	if (c == '\t')
+	    c = 'y';
+	else if (icntrl(c) || c == EOF)
+	    c = 'n';
+	else
+	    c = tulower(c);
+    }
     /* echo response and return */
-    putc(c, shout);
+    if (c != '\n')
+	putc(c, shout);
     return c;
 }
 
diff --git a/Src/params.c b/Src/params.c
index 74c4ee1c8..79bc5e9a9 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -483,10 +483,10 @@ createparamtable(void)
     setiparam("MAILCHECK", 60);
     setiparam("LOGCHECK", 60);
     setiparam("KEYTIMEOUT", 40);
-    setiparam("LISTMAX", 100);
 #ifdef HAVE_SELECT
     setiparam("BAUD", getbaudrate(&shttyinfo));  /* get the output baudrate */
 #endif
+    setsparam("LISTMAX", ztrdup("100"));
     setsparam("FCEDIT", ztrdup(DEFAULT_FCEDIT));
     setsparam("TMPPREFIX", ztrdup(DEFAULT_TMPPREFIX));
     setsparam("TIMEFMT", ztrdup(DEFAULT_TIMEFMT));