about summary refs log tree commit diff
path: root/Src/Zle/complist.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Zle/complist.c')
-rw-r--r--Src/Zle/complist.c1336
1 files changed, 1233 insertions, 103 deletions
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index a7cab9807..f76e54116 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -37,7 +37,7 @@
 
 
 static Widget w_menuselect;
-static Keymap mskeymap;
+static Keymap mskeymap, lskeymap;
 
 /* Indixes into the terminal string arrays. */
 
@@ -76,7 +76,7 @@ static char *colnames[] = {
 /* Default values. */
 
 static char *defcols[] = {
-    "0", "0", "1;34", "1;36", "33", "1;35", "1;33", "1;33", "1;32", NULL,
+    "0", "0", "1;31", "1;36", "33", "1;35", "1;33", "1;33", "1;32", NULL,
     "\033[", "m", NULL, "0", "0", "7", "0", "0"
 };
 
@@ -122,13 +122,17 @@ struct listcols {
     Extcol exts;		/* strings for extensions */
 };
 
+/* Combined length of LC and RC, maximum length of capability strings. */
+
+static int lr_caplen, max_caplen;
+
 /* 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, int multi)
 {
-    char *p;
+    char *p, *o = s;
 
     for (p = s; *s && *s != ':' && (!multi || *s != '='); p++, s++) {
 	if (*s == '\\' && s[1]) {
@@ -172,6 +176,8 @@ getcolval(char *s, int multi)
     }
     if (p != s)
 	*p = '\0';
+    if ((s - o) > max_caplen)
+	max_caplen = s - o;
     return s;
 }
 
@@ -325,10 +331,6 @@ filecol(char *col)
     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 void
@@ -337,6 +339,8 @@ getcols(Listcols c)
     char *s;
     int i, l;
 
+    max_caplen = lr_caplen = 0;
+    queue_signals();
     if (!(s = getsparam("ZLS_COLORS")) &&
 	!(s = getsparam("ZLS_COLOURS"))) {
 	for (i = 0; i < NUM_COLS; i++)
@@ -348,21 +352,25 @@ getcols(Listcols c)
 	    c->files[COL_MA] = filecol(s);
 	    c->files[COL_EC] = filecol(tcstr[TCSTANDOUTEND]);
 	} else
-	    c->files[COL_MA] = filecol("");
+	    c->files[COL_MA] = filecol(defcols[COL_MA]);
 	lr_caplen = 0;
 	if ((max_caplen = strlen(c->files[COL_MA]->col)) <
 	    (l = strlen(c->files[COL_EC]->col)))
 	    max_caplen = l;
+	unqueue_signals();
 	return;
     }
     /* We have one of the parameters, use it. */
     memset(c, 0, sizeof(*c));
     s = dupstring(s);
     while (*s)
-	s = getcoldef(c, s);
+	if (*s == ':')
+	    s++;
+	else
+	    s = getcoldef(c, s);
+    unqueue_signals();
 
     /* Use default values for those that aren't set explicitly. */
-    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]);
@@ -382,11 +390,19 @@ getcols(Listcols c)
 /* Information about the list shown. */
 
 static int noselect, mselect, inselect, mcol, mline, mcols, mlines, mmlen;
-static int selected;
+static int selected, mlbeg = -1, mlend = 9999999, mscroll, mrestlines;
+static int mnew, mlastcols, mlastlines, mhasstat, mfirstl, mlastm;
+static int mlprinted;
+static char *mstatus, *mlistp;
 static Cmatch **mtab, **mmtabp;
 static Cmgroup *mgtab, *mgtabp;
 static struct listcols mcolors;
 
+/* Used in mtab/mgtab, for explanations. */
+
+#define mtexpl ((Cmatch *) 1)
+#define mgexpl ((Cmgroup) 1)
+
 /* Information for in-string colours. */
 
 static int nrefs;
@@ -481,7 +497,7 @@ doiscol(Listcols c, int pos)
 	    /* insert e in sendpos */
 	    for (i = curissend; sendpos[i] <= e; ++i)
 		;
-	    for (j = i + 1; j < MAX_POS; ++j)
+	    for (j = MAX_POS - 1; j > i; --j)
 		sendpos[j] = sendpos[j-1];
 	    sendpos[i] = e;
 	    
@@ -496,10 +512,10 @@ doiscol(Listcols c, int pos)
 
 /* Stripped-down version of printfmt(). But can do in-string colouring. */
 
-static void
-clprintfmt(Listcols c, char *p)
+static int
+clprintfmt(Listcols c, char *p, int ml)
 {
-    int cc = 0, i = 0;
+    int cc = 0, i = 0, ask, beg;
 
     initiscol(c);
 
@@ -507,34 +523,32 @@ clprintfmt(Listcols c, char *p)
 	doiscol(c, i++);
 	cc++;
 	if (*p == '\n') {
-	    if (tccan(TCCLEAREOL))
+	    if (mlbeg >= 0 && tccan(TCCLEAREOL))
 		tcout(TCCLEAREOL);
-	    else {
-		int s = columns - 1 - (cc % columns);
-
-		while (s-- > 0)
-		    putc(' ', shout);
-	    }
 	    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 (tccan(TCCLEAREOL))
+    if (mlbeg >= 0 && tccan(TCCLEAREOL))
 	tcout(TCCLEAREOL);
-    else {
-	int s = columns - 1 - (cc % columns);
-
-	while (s-- > 0)
-	    putc(' ', shout);
-    }
+    return 0;
 }
 
 /* Local version of nicezputs() with in-string colouring. */
 
-static void
-clnicezputs(Listcols c, char *s)
+static int
+clnicezputs(Listcols c, char *s, int ml)
 {
-    int cc, i = 0;
+    int cc, i = 0, col = 0, ask, oml = ml;
+    char *t;
 
     initiscol(c);
 
@@ -548,8 +562,26 @@ clnicezputs(Listcols c, char *s)
 	}
 	if (cc == Meta)
 	    cc = *s++ ^ 32;
-	fputs(nicechar(cc), shout);
+
+	for (t = nicechar(cc); *t; t++) {
+	    if (ml == mlend - 1 && col == columns - 1) {
+		mlprinted = ml - oml;
+		return 0;
+	    }
+	    putc(*t, shout);
+	    if (++col == columns) {
+		ml++;
+		if (mscroll && !--mrestlines && (ask = asklistscroll(ml))) {
+		    mlprinted = ml - oml;
+		    return ask;
+		}
+		col = 0;
+                fputs(" \010", shout);
+	    }
+	}
     }
+    mlprinted = ml - oml;
+    return 0;
 }
 
 /* Get the terminal color string for the given match. */
@@ -636,12 +668,706 @@ putfilecol(Listcols c, char *group, char *n, mode_t m)
 
 static Cmgroup last_group;
 
-static void
+/**/
+static int
+asklistscroll(int ml)
+{
+    Thingy cmd;
+    int i, ret = 0;
+
+    compprintfmt(NULL, 1, 1, 1, ml, NULL);
+
+    fflush(shout);
+    zsetterm();
+    selectlocalmap(lskeymap);
+    if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak))
+	ret = 1;
+    else if (cmd == Th(z_acceptline) ||
+	     cmd == Th(z_downhistory) ||
+	     cmd == Th(z_downlineorhistory) ||
+	     cmd == Th(z_downlineorsearch) ||
+	     cmd == Th(z_vidownlineorhistory))
+	mrestlines = 1;
+    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"))
+	mrestlines = lines - 1;
+    else {
+	ungetkeycmd();
+	ret = 1;
+    }
+    selectlocalmap(NULL);
+    settyinfo(&shttyinfo);
+    putc('\r', shout);
+    for (i = columns - 1; i--; )
+	putc(' ', shout);
+    putc('\r', shout);
+
+    return ret;
+}
+
+#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
+compprintnl(int ml)
+{
+    int ask;
+
+    if (mlbeg >= 0 && tccan(TCCLEAREOL))
+	tcout(TCCLEAREOL);
+    putc('\n', shout);
+
+    if (mscroll && !--mrestlines && (ask = asklistscroll(ml)))
+	return ask;
+
+    return 0;
+}
+
+/* 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)) {
+		mlprinted = 0;
+		return 0;
+	    }
+	    cc = -1;
+	} else
+	    fmt = mlistp;
+    }
+    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) {
+			sprintf(nc, "%d/%d", (n ? mlastm : mselect),
+				listdat.nlist);
+			m = 2;
+		    }
+		    break;
+		case 'M':
+		    if (stat) {
+			sprintf(nbuf, "%d/%d", (n ? mlastm : mselect),
+				listdat.nlist);
+			sprintf(nc, "%-9s", nbuf);
+			m = 2;
+		    }
+		    break;
+		case 'l':
+		    if (stat) {
+			sprintf(nc, "%d/%d", ml + 1, listdat.nlines);
+			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) {
+			if (ml == listdat.nlines - 1)
+			    strcpy(nc, "Bottom");
+			else if (n ? mfirstl : (mlbeg > 0 || ml != mfirstl))
+			    sprintf(nc, "%d%%",
+				    ((ml + 1) * 100) / listdat.nlines);
+			else
+			    strcpy(nc, "Top");
+			m = 2;
+		    }
+		    break;
+		case 'P':
+		    if (stat) {
+			if (ml == listdat.nlines - 1)
+			    strcpy(nc, "Bottom");
+			else if (n ? mfirstl : (mlbeg > 0 || ml != mfirstl))
+			    sprintf(nc, "%2d%%   ",
+				    ((ml + 1) * 100) / listdat.nlines);
+			else
+			    strcpy(nc, "Top   ");
+			m = 2;
+		    }
+		    break;
+		}
+		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 {
+	    if ((++cc == columns - 2 || *p == '\n') && stat)
+		dopr = 2;
+	    if (*p == '\n') {
+		if (dopr == 1 && mlbeg >= 0 && tccan(TCCLEAREOL))
+		    tcout(TCCLEAREOL);
+		l += 1 + ((cc - 1) / columns);
+		cc = 0;
+	    }
+	    if (dopr == 1) {
+		if (ml == mlend - 1 && (cc % columns) == columns - 1) {
+		    dopr = 0;
+		    continue;
+		}
+		putc(*p, shout);
+		if ((beg = !(cc % columns)) && !stat) {
+		    ml++;
+                    fputs(" \010", shout);
+                }
+		if (mscroll && beg && !--mrestlines && (ask = asklistscroll(ml))) {
+		    *stop = 1;
+		    if (stat && n)
+			mfirstl = -1;
+		    return (mlprinted = l + (cc / columns));
+		}
+	    }
+	}
+    }
+    if (dopr) {
+        if (!(cc % columns))
+            fputs(" \010", shout);
+        if (mlbeg >= 0 && tccan(TCCLEAREOL))
+            tcout(TCCLEAREOL);
+    }
+    if (stat && n)
+	mfirstl = -1;
+
+    return (mlprinted = 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;
+}
+
+/* This is like nicezputs(), but allows scrolling. */
+
+/**/
+static int
+compnicezputs(char *s, int ml)
+{
+    int c, col = 0, ask, oml = ml;
+    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) {
+		mlprinted = ml - oml;
+		return 0;
+	    }
+	    putc(*t, shout);
+	    if (++col == columns) {
+		ml++;
+		if (mscroll && !--mrestlines && (ask = asklistscroll(ml))) {
+		    mlprinted = ml - oml;
+		    return ask;
+		}
+		col = 0;
+	    }
+	}
+    }
+    mlprinted = ml - oml;
+    return 0;
+}
+
+/**/
+static int
+compprintlist(int showall)
+{
+    static int lasttype = 0, lastbeg = 0, lastml = 0, lastinvcount = -1;
+    static int lastn = 0, lastnl = 0, lastnlnct = -1;
+    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;
+
+    mfirstl = -1;
+    if (mnew || lastinvcount != invcount || lastbeg != mlbeg || mlbeg < 0) {
+	lasttype = 0;
+	lastg = NULL;
+	lastexpl = NULL;
+	lastml = 0;
+	lastnlnct = -1;
+    }
+    cl = (listdat.nlines > lines - nlnct - mhasstat ?
+	  lines - nlnct - mhasstat : listdat.nlines) - (lastnlnct > nlnct);
+    lastnlnct = nlnct;
+    mrestlines = lines - 1;
+    lastinvcount = invcount;
+
+    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 &&
+		    (!listdat.onlyexpl ||
+		     (listdat.onlyexpl & ((*e)->count > 0 ? 1 : 2)))) {
+		    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 (mlbeg < 0 && mfirstl < 0)
+			mfirstl = ml;
+		    l = compprintfmt((*e)->str, (*e)->count, dolist(ml), 1,
+				     ml, &stop);
+		    if (mselect >= 0) {
+			int mm = (mcols * ml), i;
+
+			for (i = mcols; i--; ) {
+			    mtab[mm + i] = mtexpl;
+			    mgtab[mm + i] = mgexpl;
+			}
+		    }
+		    if (stop)
+			goto end;
+		    if (!lasttype && ml >= mlbeg) {
+			lasttype = 1;
+			lastg = g;
+			lastbeg = mlbeg;
+			lastml = ml;
+			lastexpl = e;
+			lastp = NULL;
+			lastused = 1;
+		    }
+		    ml += mlprinted;
+		    if (dolistcl(ml) && cl >= 0 && (cl -= mlprinted) <= 1) {
+			cl = -1;
+			if (tccan(TCCLEAREOD))
+			    tcout(TCCLEAREOD);
+		    }
+		    pnl = 1;
+		}
+		e++;
+		if (!mnew && ml > mlend)
+		    goto end;
+	    }
+	}
+	if (!listdat.onlyexpl && mlbeg < 0 && pp && *pp) {
+	    if (pnl) {
+		if (dolistnl(ml) && compprintnl(ml))
+		    goto end;
+		pnl = 0;
+		ml++;
+		if (cl >= 0 && --cl <= 1) {
+		    cl = -1;
+		    if (tccan(TCCLEAREOD))
+			tcout(TCCLEAREOD);
+		}
+	    }
+	    if (mlbeg < 0 && mfirstl < 0)
+		mfirstl = ml;
+	    if (g->flags & CGF_LINES) {
+		while (*pp) {
+		    if (compzputs(*pp, ml))
+			goto end;
+		    if (*++pp && compprintnl(ml))
+			goto end;
+		}
+	    } else {
+		int n = g->lcount, nl, nc, i, a;
+		char **pq;
+
+		nl = nc = g->lins;
+
+		while (n && nl--) {
+		    i = g->cols;
+		    mc = 0;
+		    pq = pp;
+		    while (n && i--) {
+			if (pq - g->ylist >= g->lcount)
+			    break;
+			if (compzputs(*pq, mscroll))
+			    goto end;
+			if (i) {
+			    a = (g->widths ? g->widths[mc] : g->width) -
+				strlen(*pq);
+			    while (a--)
+				putc(' ', shout);
+			}
+			pq += ((g->flags & CGF_ROWS) ? 1 : nc);
+			mc++;
+			n--;
+		    }
+		    if (n) {
+			if (compprintnl(ml))
+			    goto end;
+			ml++;
+			if (cl >= 0 && --cl <= 1) {
+			    cl = -1;
+			    if (tccan(TCCLEAREOD))
+				tcout(TCCLEAREOD);
+			}
+		    }
+		    pp += ((g->flags & CGF_ROWS) ? g->cols : 1);
+		}
+	    }
+	} 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;
+		    pnl = 0;
+		} else
+		    p = g->matches;
+
+		for (; (m = *p); p++) {
+		    if (m->disp && (m->flags & CMF_DISPLINE)) {
+			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 (!lasttype && ml >= mlbeg) {
+			    lasttype = 2;
+			    lastg = g;
+			    lastbeg = mlbeg;
+			    lastml = ml;
+			    lastp = p;
+			    lastn = n;
+			    lastnl = nl;
+			    lastused = 1;
+			}
+			if (mfirstl < 0)
+			    mfirstl = ml;
+			if (dolist(ml))
+			    printed++;
+			if (clprintm(g, p, 0, ml, 1, 0, NULL, NULL))
+			    goto end;
+			ml += mlprinted;
+			if (dolistcl(ml) && (cl -= mlprinted) <= 1) {
+			    cl = -1;
+			    if (tccan(TCCLEAREOD))
+				tcout(TCCLEAREOD);
+			}
+			pnl = 1;
+		    }
+		    if (!mnew && ml > mlend)
+			goto end;
+		}
+	    }
+	    if (n && 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 (!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--) {
+		    wid = (g->widths ? g->widths[mc] : g->width);
+		    if (!(m = *q)) {
+			if (clprintm(g, NULL, mc, ml, (!i), wid, NULL, NULL))
+			    goto end;
+			break;
+		    }
+		    if (!m->disp && (m->flags & CMF_FILE) &&
+			m->str[0] && m->str[strlen(m->str) - 1] != '/') {
+			struct stat buf;
+			char *pb;
+
+			pb = (char *) zhalloc((m->prpre ? strlen(m->prpre) : 0) +
+					     3 + strlen(m->str));
+			sprintf(pb, "%s%s", (m->prpre ? m->prpre : "./"),
+				m->str);
+
+			if (ztat(pb, &buf, 1) ?
+			    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++;
+		    ml += mlprinted;
+		    if (dolistcl(ml) && (cl -= mlprinted) < 1) {
+			cl = -1;
+			if (tccan(TCCLEAREOD))
+			    tcout(TCCLEAREOD);
+		    }
+		    if (mfirstl < 0)
+			mfirstl = ml;
+
+		    if (--n)
+			for (j = ((g->flags & CGF_ROWS) ? 1 : nc);
+			     j && *q; j--)
+			    q = skipnolist(q + 1, showall);
+		    mc++;
+		}
+		while (i-- > 0) {
+		    if (clprintm(g, NULL, mc, ml, (!i),
+				 (g->widths ? g->widths[mc] : g->width),
+				 NULL, NULL))
+			goto end;
+		    mc++;
+		}
+		if (n) {
+		    if (dolistnl(ml) && compprintnl(ml))
+			goto end;
+		    ml++;
+		    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 || (showall && g->mcount))
+	    pnl = 1;
+	g = g->next;
+    }
+    asked = 0;
+ end:
+    lastlistlen = 0;
+    if (nlnct <= 1)
+	mscroll = 0;
+    if (clearflag) {
+	int nl;
+
+	/* Move the cursor up to the prompt, if always_last_prompt *
+	 * is set and all that...                                  */
+	if (mlbeg >= 0) {
+	    if ((nl = listdat.nlines + nlnct) >= lines) {
+		if (mhasstat) {
+		    putc('\n', shout);
+		    compprintfmt(NULL, 0, 1, 1, mline, NULL);
+		}
+		nl = lines - 1;
+	    } else
+		nl--;
+	    tcmultout(TCUP, TCMULTUP, nl);
+	    showinglist = -1;
+
+	    lastlistlen = listdat.nlines;
+	} else if ((nl = listdat.nlines + nlnct - 1) < lines) {
+	    if (mlbeg >= 0 && tccan(TCCLEAREOL))
+		tcout(TCCLEAREOL);
+	    tcmultout(TCUP, TCMULTUP, nl);
+	    showinglist = -1;
+
+	    lastlistlen = listdat.nlines;
+	} else {
+	    clearflag = 0;
+	    if (!asked) {
+		mrestlines = (ml + nlnct > lines);
+		compprintnl(ml);
+	    }
+	}
+    } else if (!asked) {
+	mrestlines = (ml + nlnct > lines);
+	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;
+    int len, subcols = 0, stop = 0, ret = 0;
 
     if (g != last_group)
         *last_cap = '\0';
@@ -649,14 +1375,22 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
     last_group = g;
 
     if (!mp) {
-	zcputs(&mcolors, g->name, COL_SP);
-	len = width - 2;
-	while (len-- > 0)
-	    putc(' ', shout);
-	zcoff();
-	return;
+	if (dolist(ml)) {
+	    zcputs(&mcolors, g->name, COL_SP);
+	    len = width - 2;
+	    while (len-- > 0)
+		putc(' ', shout);
+	    zcoff();
+	}
+	mlprinted = 0;
+	return 0;
     }
     m = *mp;
+
+    if ((m->flags & CMF_ALL) && (!m->disp || !m->disp[0]))
+	bld_all_str(m);
+
+    mlastm = m->gnum;
     if (m->disp && (m->flags & CMF_DISPLINE)) {
 	if (mselect >= 0) {
 	    int mm = (mcols * ml), i;
@@ -666,6 +1400,10 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 		mgtab[mm + i] = g;
 	    }
 	}
+	if (!dolist(ml)) {
+	    mlprinted = printfmt(m->disp, 0, 0, 0) / columns;
+	    return 0;
+	}
 	if (m->gnum == mselect) {
 	    int mm = (mcols * ml);
 	    mline = ml;
@@ -681,9 +1419,12 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 	else
 	    subcols = putmatchcol(&mcolors, g->name, m->disp);
 	if (subcols)
-	    clprintfmt(&mcolors, m->disp);
-	else
-	    printfmt(m->disp, 0, 1, 0);
+	    ret = clprintfmt(&mcolors, m->disp, ml);
+	else {
+	    compprintfmt(m->disp, 0, 1, 0, ml, &stop);
+	    if (stop)
+		ret = 1;
+	}
 	zcoff();
     } else {
 	int mx;
@@ -704,6 +1445,10 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 		mgtab[mx + mm + i] = g;
 	    }
 	}
+	if (!dolist(ml)) {
+	    mlprinted = niceztrlen(m->disp ? m->disp : m->str) / columns;
+	    return 0;
+	}
 	if (m->gnum == mselect) {
 	    int mm = mcols * ml;
 
@@ -723,12 +1468,17 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 	    subcols = putmatchcol(&mcolors, g->name, (m->disp ? m->disp : m->str));
 
 	if (subcols)
-	    clnicezputs(&mcolors, (m->disp ? m->disp : m->str));
+	    ret = clnicezputs(&mcolors, (m->disp ? m->disp : m->str), ml);
 	else
-	    nicezputs((m->disp ? m->disp : m->str), shout);
+	    ret = compnicezputs((m->disp ? m->disp : m->str), ml);
+	if (ret) {
+	    zcoff();
+	    return 1;
+	}
 	len = niceztrlen(m->disp ? m->disp : m->str);
+	mlprinted = len / columns;
 
-	 if (isset(LISTTYPES) && buf) {
+	if (isset(LISTTYPES) && buf) {
 	    if (m->gnum != mselect) {
 		zcoff();
 		zcputs(&mcolors, g->name, COL_TC);
@@ -751,6 +1501,7 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 	    zcoff();
 	}
     }
+    return ret;
 }
 
 static int
@@ -760,32 +1511,66 @@ complistmatches(Hookdef dummy, Chdata dat)
 
     amatches = dat->matches;
 
-    if (minfo.asked == 2) {
+    noselect = 0;
+
+    if ((minfo.asked == 2 && mselect < 0) || nlnct >= lines) {
 	showinglist = 0;
 	amatches = oamatches;
 	return (noselect = 1);
     }
     getcols(&mcolors);
 
-    calclist(mselect >= 0);
+    mnew = ((calclist(mselect >= 0) || mlastcols != columns ||
+	     mlastlines != listdat.nlines) && mselect >= 0);
 
     if (!listdat.nlines || (mselect >= 0 &&
-			    (!(isset(USEZLE) && !termflags &&
-			       complastprompt && *complastprompt) ||
-			     (listdat.nlines + nlnct - 1) >= lines))) {
+			    !(isset(USEZLE) && !termflags &&
+			      complastprompt && *complastprompt))) {
 	showinglist = listshown = 0;
 	noselect = 1;
 	amatches = oamatches;
 	return 1;
     }
-    if (inselect)
+    if (inselect || mlbeg >= 0)
 	clearflag = 0;
 
-    if (asklist()) {
-	amatches = oamatches;
-	return (noselect = 1);
+    mscroll = 0;
+    mlistp = NULL;
+
+    queue_signals();
+    if (mselect >= 0 || mlbeg >= 0 ||
+	(mlistp = dupstring(getsparam("LISTPROMPT")))) {
+	unqueue_signals();
+	if (mlistp && !*mlistp)
+	    mlistp = "%SAt %p: Hit TAB for more, or the character to insert%s";
+	trashzle();
+	showinglist = listshown = 0;
+
+	lastlistlen = 0;
+
+	if (mlistp) {
+	    clearflag = (isset(USEZLE) && !termflags && dolastprompt);
+	    mscroll = 1;
+	} else {
+	    clearflag = 1;
+	    minfo.asked = (listdat.nlines + nlnct <= lines);
+	}
+    } else {
+	unqueue_signals();
+	mlistp = NULL;
+	if (asklist()) {
+	    amatches = oamatches;
+	    return (noselect = 1);
+	}
     }
-    if (mselect >= 0) {
+    if (mlbeg >= 0) {
+	mlend = mlbeg + lines - nlnct - mhasstat;
+	while (mline >= mlend)
+	    mlbeg++, mlend++;
+    } else
+	mlend = 9999999;
+
+    if (mnew) {
 	int i;
 
 	i = columns * listdat.nlines;
@@ -795,14 +1580,13 @@ complistmatches(Hookdef dummy, Chdata dat)
 	free(mgtab);
 	mgtab = (Cmgroup *) zalloc(i * sizeof(Cmgroup));
 	memset(mgtab, 0, i * sizeof(Cmgroup));
-	mcols = columns;
-	mlines = listdat.nlines;
+	mlastcols = mcols = columns;
+	mlastlines = mlines = listdat.nlines;
     }
     last_cap = (char *) zhalloc(max_caplen + 1);
     *last_cap = '\0';
 
-    if (!printlist(1, clprintm, (mselect >= 0)) || listdat.nlines >= lines ||
-	!clearflag)
+    if (!compprintlist(mselect >= 0) || !clearflag)
 	noselect = 1;
 
     amatches = oamatches;
@@ -818,8 +1602,8 @@ adjust_mcol(int wish, Cmatch ***tabp, Cmgroup **grp)
 
     tab -= mcol;
 
-    for (p = wish; p >= 0 && !tab[p]; p--);
-    for (n = wish; n < mcols && !tab[n]; n++);
+    for (p = wish; p >= 0 && (!tab[p] || tab[p] == mtexpl); p--);
+    for (n = wish; n < mcols && (!tab[n] || tab[n] == mtexpl); n++);
     if (n == mcols)
 	n = -1;
 
@@ -849,9 +1633,11 @@ struct menustack {
     Brinfo brbeg;
     Brinfo brend;
     int nbrbeg, nbrend;
-    int cs, acc, nmatches;
+    int cs, acc, nmatches, mline, mlbeg;
     struct menuinfo info;
     Cmgroup amatches, pmatches, lastmatches, lastlmatches;
+    char *origline;
+    int origcs, origll;
 };
 
 static int
@@ -863,27 +1649,108 @@ domenuselect(Hookdef dummy, Chdata dat)
     Thingy cmd;
     Menustack u = NULL;
     int i = 0, acc = 0, wishcol = 0, setwish = 0, oe = onlyexpl, wasnext = 0;
+    int space, lbeg = 0, step = 1, wrap, pl = nlnct, broken = 0, first = 1;
+    int nolist = 0;
     char *s;
 
-    if (fdat || (dummy && (!(s = getsparam("SELECTMIN")) ||
+    queue_signals();
+    if (fdat || (dummy && (!(s = getsparam("MENUSELECT")) ||
 			   (dat && dat->num < atoi(s))))) {
 	if (fdat) {
 	    fdat->matches = dat->matches;
 	    fdat->num = dat->num;
+	    fdat->nmesg = dat->nmesg;
 	}
+	unqueue_signals();
 	return 0;
     }
+    if ((s = getsparam("MENUSCROLL"))) {
+	if (!(step = mathevali(s)))
+	    step = (lines - nlnct) >> 1;
+	else if (step < 0)
+	    if ((step += lines - nlnct) < 0)
+		step = 1;
+    }
+    if ((mstatus = dupstring(getsparam("MENUPROMPT"))) && !*mstatus)
+	mstatus = "%SScrolling active: current selection at %p%s";
+    unqueue_signals();
+    mhasstat = (mstatus && *mstatus);
     fdat = dat;
     selectlocalmap(mskeymap);
-    noselect = 0;
+    noselect = 1;
+    while ((menuacc &&
+	    !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr)) ||
+	   (((*minfo.cur)->flags & (CMF_NOLIST | CMF_MULT)) &&
+	    (!(*minfo.cur)->str || !*(*minfo.cur)->str)))
+	do_menucmp(0);
+
     mselect = (*(minfo.cur))->gnum;
+    mline = 0;
+    mlines = 999999;
+    mlbeg = 0;
     for (;;) {
+	if (mline < 0) {
+	    int x, y;
+	    Cmatch **p = mtab;
+
+	    for (y = 0; y < mlines; y++) {
+		for (x = mcols; x; x--, p++)
+		    if (*p && *p != mtexpl && **p && mselect == (**p)->gnum)
+			break;
+		if (x)
+		    break;
+	    }
+	    if (y < mlines)
+		mline = y;
+	}
+	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 && *q != mtexpl)
+			break;
+		if (c)
+		    break;
+		p -= columns;
+		mlbeg--;
+	    }
+	}
+	if ((space = lines - pl - mhasstat))
+	    while (mline >= mlbeg + space)
+		if ((mlbeg += step) + space > mlines)
+		    mlbeg = mlines - space;
+	if (lbeg != mlbeg) {
+	    Cmatch **p = mtab + (mlbeg * columns), **q;
+	    int c;
+
+	    while (mlbeg < mlines) {
+		for (q = p, c = columns; c; q++, c--)
+		    if (*q)
+			break;
+		if (c)
+		    break;
+		p += columns;
+		mlbeg++;
+	    }
+	}
+	lbeg = mlbeg;
 	onlyexpl = 0;
 	showinglist = -2;
+	if (first && !listshown && isset(LISTBEEP))
+	    zbeep();
+	first = 0;
 	zrefresh();
 	inselect = 1;
-	if (noselect)
+	if (noselect) {
+	    broken = 1;
 	    break;
+	}
 	selected = 1;
 	if (!i) {
 	    i = mcols * mlines;
@@ -911,9 +1778,13 @@ domenuselect(Hookdef dummy, Chdata dat)
 
     getk:
 
-	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak))
+	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
+	    zbeep();
 	    break;
-	else if (cmd == Th(z_acceptline)) {
+	} else if (nolist && cmd != Th(z_undo)) {
+	    ungetkeycmd();
+	    break;
+	} else if (cmd == Th(z_acceptline)) {
 	    acc = 1;
 	    break;
 	} else if (cmd == Th(z_acceptandinfernexthistory)) {
@@ -923,6 +1794,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    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 = amatches;
 	    s->pmatches = pmatches;
@@ -934,30 +1807,57 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    s->nbrbeg = nbrbeg;
 	    s->nbrend = nbrend;
 	    s->nmatches = nmatches;
+	    s->origline = origline;
+	    s->origcs = origcs;
+	    s->origll = origll;
 	    menucmp = menuacc = hasoldlist = 0;
+	    minfo.cur = NULL;
 	    fixsuffix();
+	    handleundo();
 	    validlist = 0;
 	    amatches = pmatches = lastmatches = NULL;
 	    invalidate_list();
+	    iforcemenu = 1;
+	    comprecursive = 1;
 	    menucomplete(zlenoargs);
-	    if (dat->num < 2 || !minfo.cur || !*(minfo.cur)) {
-		noselect = clearlist = listshown = 1;
-		onlyexpl = 0;
-		zrefresh();
-		break;
+	    iforcemenu = 0;
+
+	    if (dat->num < 1 || !minfo.cur || !*(minfo.cur)) {
+		nolist = 1;
+		if (dat->nmesg || nmessages) {
+		    showinglist = -2;
+		    zrefresh();
+		} else {
+		    trashzle();
+		    zsetterm();
+		    if (tccan(TCCLEAREOD))
+			tcout(TCCLEAREOD);
+		    fputs("no matches\r", shout);
+		    fflush(shout);
+		    tcmultout(TCUP, TCMULTUP, nlnct);
+		    showinglist = clearlist = 0;
+		    clearflag = 1;
+		    zrefresh();
+		    showinglist = clearlist = 0;
+		}
+		goto getk;
 	    }
 	    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;
@@ -967,18 +1867,45 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    s->nbrbeg = nbrbeg;
 	    s->nbrend = nbrend;
 	    s->nmatches = nmatches;
+	    s->origline = origline;
+	    s->origcs = origcs;
+	    s->origll = origll;
 	    accept_last();
+	    handleundo();
+	    comprecursive = 1;
 	    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;
+		if (++mline == mlines) {
+		    mline = 0;
+		    p -= mlines * mcols;
+		}
+	    } 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;
 
 	    if (!u)
-		goto getk;
+		break;
 
-	    cs = 0;
+	    handleundo();
+	    cs = nolist = 0;
 	    foredel(ll);
 	    spaceinline(l = strlen(u->line));
 	    strncpy((char *) line, u->line, l);
@@ -986,15 +1913,17 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    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);
+		    freematches(lastmatches, 0);
 		amatches = u->amatches;
 		pmatches = u->pmatches;
 		lastmatches = u->lastmatches;
 		lastlmatches = u->lastlmatches;
 		nmatches = u->nmatches;
-		hasoldlist = 1;
+		hasoldlist = validlist = 1;
 	    }
 	    freebrinfo(brbeg);
 	    freebrinfo(brend);
@@ -1002,6 +1931,9 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    brend = dupbrinfo(u->brend, &lastbrend, 0);
 	    nbrbeg = u->nbrbeg;
 	    nbrend = u->nbrend;
+	    origline = u->origline;
+	    origcs = u->origcs;
+	    origll = u->origll;
 
 	    u = u->prev;
 	    clearlist = 1;
@@ -1017,67 +1949,240 @@ domenuselect(Hookdef dummy, Chdata dat)
 		   cmd == Th(z_downlineorhistory) ||
 		   cmd == Th(z_downlineorsearch) ||
 		   cmd == Th(z_vidownlineorhistory)) {
+	    int omline;
+	    Cmatch **op;
+
+	    wrap = 0;
+
+	down:
+
+	    omline = mline;
+	    op = p;
+
 	    do {
 		if (mline == mlines - 1) {
+		    if (wrap & 2) {
+			mline = omline; 
+			p = op;
+			break;
+		    }
 		    p -= mline * mcols;
 		    mline = 0;
+		    wrap |= 1;
 		} else {
 		    mline++;
 		    p += mcols;
 		}
 		if (adjust_mcol(wishcol, &p, NULL))
 		    continue;
-	    } while (!*p);
+	    } while (!*p || *p == mtexpl);
+
+	    if (wrap == 1)
+		goto right;
 	} else if (cmd == Th(z_uphistory) ||
 		   cmd == Th(z_uplineorhistory) ||
 		   cmd == Th(z_uplineorsearch) ||
 		   cmd == Th(z_viuplineorhistory)) {
+	    int omline;
+	    Cmatch **op;
+
+	    wrap = 0;
+
+	up:
+
+	    omline = mline;
+	    op = p;
+
 	    do {
 		if (!mline) {
+		    if (wrap & 2) {
+			mline = omline; 
+			p = op;
+			break;
+		    }
 		    mline = mlines - 1;
 		    p += mline * mcols;
+		    wrap |= 1;
+		} else {
+		    mline--;
+		    p -= mcols;
+		}
+		if (adjust_mcol(wishcol, &p, NULL))
+		    continue;
+	    } while (!*p || *p == mtexpl);
+
+	    if (wrap == 1) {
+		if (mcol == wishcol)
+		    goto left;
+
+		wishcol = mcol;
+	    }
+	} else if (cmd == Th(z_emacsforwardword) ||
+		   cmd == Th(z_viforwardword) ||
+		   cmd == Th(z_viforwardwordend) ||
+		   cmd == Th(z_forwardword)) {
+	    int i = lines - pl - 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 && *p != mtexpl) {
+		    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 - pl - 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;
-	    } while (!*p);
+		if (*p || *p != mtexpl) {
+		    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 && *p != mtexpl) {
+		    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 && *p != mtexpl) {
+		    lp = p;
+		    ll = mline;
+		}
+	    }
+	    mline = ll;
+	    p = lp;
 	} else if (cmd == Th(z_forwardchar) || cmd == Th(z_viforwardchar)) {
-	    int omcol = mcol;
-	    Cmatch *op = *p;
+	    int omcol;
+	    Cmatch **op;
+
+	    wrap = 0;
+
+	right:
+
+	    omcol = mcol;
+	    op = p;
 
 	    do {
 		if (mcol == mcols - 1) {
+		    if (wrap & 1) {
+			p = op;
+			mcol = omcol;
+			break;
+		    }
 		    p -= mcol;
 		    mcol = 0;
+		    wrap |= 2;
 		} else {
 		    mcol++;
 		    p++;
 		}
-	    } while (!*p || (mcol != omcol && *p == op));
+	    } while (!*p || *p == mtexpl || (mcol != omcol && *p == *op));
 	    wishcol = mcol;
+
+	    if (wrap == 2)
+		goto down;
 	} else if (cmd == Th(z_backwardchar) || cmd == Th(z_vibackwardchar)) {
-	    int omcol = mcol;
-	    Cmatch *op = *p;
+	    int omcol;
+	    Cmatch **op;
+
+	    wrap = 0;
+
+	left:
+
+	    omcol = mcol;
+	    op = p;
 
 	    do {
 		if (!mcol) {
+		    if (wrap & 1) {
+			p = op;
+			mcol = omcol;
+			break;
+		    }
 		    mcol = mcols - 1;
 		    p += mcol;
+		    wrap |= 2;
 		} else {
 		    mcol--;
 		    p--;
 		}
-	    } while (!*p || (mcol != omcol && *p == op));
+	    } while (!*p || *p == mtexpl || (mcol != omcol && *p == *op));
 	    wishcol = mcol;
+
+	    if (wrap == 2) {
+		p += mcols - 1 - mcol;
+		wishcol = mcol = mcols - 1;
+		adjust_mcol(wishcol, &p, NULL);
+		goto up;
+	    }
 	} else if (cmd == Th(z_beginningofbufferorhistory) ||
 		   cmd == Th(z_beginningofline) ||
 		   cmd == Th(z_beginningoflinehist) ||
 		   cmd == Th(z_vibeginningofline)) {
 	    p -= mcol;
 	    mcol = 0;
-	    while (!*p) {
+	    while (!*p || *p == mtexpl) {
 		mcol++;
 		p++;
 	    }
@@ -1088,15 +2193,13 @@ domenuselect(Hookdef dummy, Chdata dat)
 		   cmd == Th(z_viendofline)) {
 	    p += mcols - mcol - 1;
 	    mcol = mcols - 1;
-	    while (!*p) {
+	    while (!*p || *p == mtexpl) {
 		mcol--;
 		p--;
 	    }
 	    wishcol = mcols - 1;
-	} else if (cmd == Th(z_forwardword) ||
-		   cmd == Th(z_emacsforwardword) ||
-		   cmd == Th(z_viforwardword) ||
-		   cmd == Th(z_viforwardwordend)) {
+	} else if (cmd == Th(z_viforwardblankword) ||
+		   cmd == Th(z_viforwardblankwordend)) {
 	    Cmgroup g = *pg;
 	    int ol = mline;
 
@@ -1112,10 +2215,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 		}
 		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)) {
+	    } while (ol != mline && (*pg == g || !*pg || *pg == mgexpl));
+	} else if (cmd == Th(z_vibackwardblankword)) {
 	    Cmgroup g = *pg;
 	    int ol = mline;
 
@@ -1131,7 +2232,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 		}
 		if (adjust_mcol(wishcol, &p, &pg))
 		    continue;
-	    } while (ol != mline && (*pg == g || !*pg));
+	    } while (ol != mline && (*pg == g || !*pg || *pg == mgexpl));
 	} else if (cmd == Th(z_completeword) ||
 		   cmd == Th(z_expandorcomplete) ||
 		   cmd == Th(z_expandorcompleteprefix) ||
@@ -1143,18 +2244,29 @@ domenuselect(Hookdef dummy, Chdata dat)
 		   !strcmp(cmd->nam, "expand-or-complete-prefix") ||
 		   !strcmp(cmd->nam, "menu-complete") ||
 		   !strcmp(cmd->nam, "menu-expand-or-complete")) {
+	    comprecursive = 1;
 	    do_menucmp(0);
 	    mselect = (*(minfo.cur))->gnum;
 	    setwish = 1;
+	    mline = -1;
 	    continue;
 	} else if (cmd == Th(z_reversemenucomplete) ||
 		   !strcmp(cmd->nam, "reverse-menu-complete")) {
+	    comprecursive = 1;
 	    reversemenucomplete(zlenoargs);
 	    mselect = (*(minfo.cur))->gnum;
 	    setwish = 1;
+	    mline = -1;
+	    continue;
+	} else if (cmd == Th(z_undefinedkey)) {
 	    continue;
 	} else {
 	    ungetkeycmd();
+	    if (cmd->widget && (cmd->widget->flags & WIDGET_NCOMP)) {
+		acc = 0;
+		broken = 2;
+	    } else
+		acc = 1;
 	    break;
 	}
 	do_single(**p);
@@ -1163,30 +2275,39 @@ domenuselect(Hookdef dummy, Chdata dat)
     if (u)
 	for (; u; u = u->prev)
 	    if (u->lastmatches != lastmatches)
-		freematches(u->lastmatches);
+		freematches(u->lastmatches, 0);
 
     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) {
+    if (wasnext || broken) {
 	menucmp = 2;
 	showinglist = -2;
 	minfo.asked = 0;
+	if (!noselect) {
+	    int nos = noselect;
+
+	    zrefresh();
+	    noselect = nos;
+	}
     }
-    if (!noselect) {
+    if (!noselect && (!dat || acc)) {
 	showinglist = -2;
 	onlyexpl = oe;
 	if (!smatches)
 	    clearlist = 1;
 	zrefresh();
     }
+    mlbeg = -1;
     fdat = NULL;
 
-    return (!noselect ^ acc);
+    return (broken == 2 ? 3 :
+	    ((dat && !broken) ? (acc ? 1 : 2) : (!noselect ^ acc)));
 }
 
 /* The widget function. */
@@ -1247,6 +2368,14 @@ boot_(Module m)
     bindkey(mskeymap, "\33OB",  refthingy(t_downlineorhistory), NULL);
     bindkey(mskeymap, "\33OC",  refthingy(t_forwardchar), NULL);
     bindkey(mskeymap, "\33OD",  refthingy(t_backwardchar), NULL);
+    lskeymap = newkeymap(NULL, "listscroll");
+    linkkeymap(lskeymap, "listscroll", 1);
+    bindkey(lskeymap, "\t", refthingy(t_completeword), NULL);
+    bindkey(lskeymap, " ", refthingy(t_completeword), NULL);
+    bindkey(lskeymap, "\n", refthingy(t_acceptline), NULL);
+    bindkey(lskeymap, "\r", refthingy(t_acceptline), NULL);
+    bindkey(lskeymap, "\33[B",  refthingy(t_downlineorhistory), NULL);
+    bindkey(lskeymap, "\33OB",  refthingy(t_downlineorhistory), NULL);
     return 0;
 }
 
@@ -1261,6 +2390,7 @@ cleanup_(Module m)
     deletehookfunc("comp_list_matches", (Hookfn) complistmatches);
     deletehookfunc("menu_start", (Hookfn) domenuselect);
     unlinkkeymap("menuselect", 1);
+    unlinkkeymap("listscroll", 1);
     return 0;
 }