Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: scrolling completion lists (was: Re: Questions)



Peter Stephenson wrote:

> ...
> 
> > And did anyone have the time to try the patch for scrolling completion 
> > lists? (I have a new version which is faster and allows status lines --
> > it's getting silly ;-)
> 
> It seems fine.  The natural way of showing that text is missing would be to
> put an ellipsis (`...' in English) at the top or bottom, like it does when
> a line is too long. 

I had been thinking about doing this, but the patch below uses a
simple statusline below the menu when it is too big to fit on the
screen and the $SELECTSTATUS parameter is set. That may contain some
%-escaped (%l - line information, %m - match information, %p -
Top/Bottom/%). I *don't* want to say that this is better, I was just
playing with it. If we agree to do that differently, I'll change
it. No problem. Maybe using the `...'s (that would sometimes take two
lines, hm) and a ZLS_COLO(|U)RS capability to be able to highlight it
or something (complist.c still contains code for the `st' capability I 
added when playing with something like this; I'll leave it in for now
and remove either that or the $*STATUS stuff when we've decided what
we want). Trivial.

Note also that I consider scrolling in completion lists without being
in a menu-selection and when being in one to be different things. In
fact, I was never happy that menu-selection didn't work with too long
lists and that's why I brought this up again.

For the listing (not selection), there is a $LISTSTATUS (which
supports only the %l) that can be set to the prompt that should be
shown below the current screenful.

Both *STATUS parameters support the %[sSbBuB{}] known from everywhere
else.

> It's most confusing when you wrap around the bottom
> without warning.  A scroll-step style would certainly be useful, and in the
> long run a special keymap. 

The parameter $SCROLLSTEP can be used to define the scroll step one
wants (0 - half a screen, positive - that many lines, negative -
screen height minus that many lines). But it is only used in
menu-selection.

Using a keymap for normal listing would be easy to add...

> I have doubts about `return' forcing the
> display to scroll a line, since in menu-selection it already has another
> meaning (and you have to hit it twice to execute a line, which took me a
> long time to get used to), so this may be overloading it too much.

But return is only used for the normal listing, it hasn't changed in
menu-selection. And I was thinking about pagers which normally allow
this... but I wouldn't be against removing it. I definitely prefer the 
solution with the extra keymap anyway, especially to get rid of that
ugly special case for `q'.

> ...
> 
> I don't see that it has to be that much more sophisticated.  I'm against
> implementing a rewrite of `less' in the shell, at least unless it goes into
> a completely self-contained library.

Right, that's why I want to keep it small, too.

> ...
> 
> Would people rejoice or complain if we had this on by default (other
> settings permitting)?

For menu-selection-scrolling there shouldn't be a way around it, I
think. For normal lists: I would like to hear more opinions, too.


Zefram wrote:

> Peter Stephenson wrote:
> >Would people rejoice or complain if we had this on by default (other
> >settings permitting)?
> 
> I'd like it.  I've often cursed at too-long completion listings that (a)
> take a long time to scroll past at 9600 baud (well that was a while ago)
> and (b) aren't readable because they've scrolled past.  And I've often
> used completion listing to get a quick directory listing; having that
> work for more than one screenful would be good.
> 
> My only real concern here is that the pager shouldn't get in the way of
> normal typing.  What I'd like is that repeated <tab>s show successive
> pages of listing, after the <tab> that shows the first page. 

The hard-wired key-handling for scrolling non-menu-selection lists did 
that from the first patch on.

> That seems
> intuitive to me, but it might be difficult to fit in with the form of
> menu completion where the first match is inserted at the same time the
> list is shown.  Not to mention the case of an explicit list requested
> during completion.  That needs some thinking about.

I'm not exactly sure what you mean here...


Anyway here is the patch and I committed it (or will be committing it
soon), so that everone can easily try it. I don't think we'll need
more than this week to find answers to the open questions.

Bye
 Sven

Index: Doc/Zsh/mod_complist.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/mod_complist.yo,v
retrieving revision 1.1.1.20
diff -u -r1.1.1.20 mod_complist.yo
--- Doc/Zsh/mod_complist.yo	2000/01/24 09:38:23	1.1.1.20
+++ Doc/Zsh/mod_complist.yo	2000/04/17 11:06:14
@@ -3,17 +3,18 @@
 !MOD!)
 cindex(completion, listing)
 cindex(completion, coloured listings)
-The tt(zsh/complist) module offers two extensions to completion listings:
-the ability to highlight matches in such a list and a different
-style of menu-completion.
+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.
 
-subsect(Parameters)
 vindex(ZLS_COLORS)
 vindex(ZLS_COLOURS)
 The parameters tt(ZLS_COLORS) and tt(ZLS_COLOURS) describe how matches
@@ -126,6 +127,39 @@
 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)
 cindex(completion, selecting by cursor)
 vindex(SELECTMIN)
@@ -151,7 +185,9 @@
 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. The
+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
@@ -160,6 +196,23 @@
 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
@@ -173,25 +226,86 @@
 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.  In the case of tt(accept-line), the match currently inserted
-will be accepted and a new completion may be attempted.
-Using tt(send-break) leaves menu-selection and continues with normal
-menu-completion.  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 from the same list. The
-function 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.  Matches inserted in one of these ways 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.
+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()
 
-Any other zle function leaves menu-selection and executes that function.
+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
Index: Src/params.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/params.c,v
retrieving revision 1.5
diff -u -r1.5 params.c
--- Src/params.c	2000/04/13 17:57:45	1.5
+++ Src/params.c	2000/04/17 11:06:16
@@ -483,10 +483,10 @@
     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));
Index: Src/Zle/compcore.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/compcore.c,v
retrieving revision 1.8
diff -u -r1.8 compcore.c
--- Src/Zle/compcore.c	2000/04/13 08:09:13	1.8
+++ Src/Zle/compcore.c	2000/04/17 11:06:18
@@ -296,7 +296,8 @@
     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)) ?
Index: Src/Zle/complete.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/complete.c,v
retrieving revision 1.2
diff -u -r1.2 complete.c
--- Src/Zle/complete.c	2000/04/01 20:49:48	1.2
+++ Src/Zle/complete.c	2000/04/17 11:06:18
@@ -35,8 +35,7 @@
 /**/
 mod_export zlong compcurrent;
 /**/
-zlong complistmax,
-      complistlines,
+zlong complistlines,
       compignored;
 
 /**/
@@ -50,7 +49,8 @@
      *compquote,
      *compqstack,
      *comppatmatch,
-     *complastprompt;
+     *complastprompt,
+     *complistmax;
 /**/
 char *compiprefix,
      *compcontext,
@@ -904,7 +904,7 @@
     { "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 @@
     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 @@
 {
     if (compwords)
 	freearray(compwords);
+    zsfree(complistmax);
     zsfree(compprefix);
     zsfree(compsuffix);
     zsfree(compiprefix);
Index: Src/Zle/complist.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/complist.c,v
retrieving revision 1.1.1.44
diff -u -r1.1.1.44 complist.c
--- Src/Zle/complist.c	2000/03/01 14:03:20	1.1.1.44
+++ Src/Zle/complist.c	2000/04/17 11:06:19
@@ -59,8 +59,9 @@
 #define COL_MA 15
 #define COL_HI 16
 #define COL_DU 17
+#define COL_ST 18
 
-#define NUM_COLS 18
+#define NUM_COLS 19
 
 /* Maximum number of in-string colours supported. */
 
@@ -70,14 +71,14 @@
 
 static char *colnames[] = {
     "no", "fi", "di", "ln", "pi", "so", "bd", "cd", "ex", "mi",
-    "lc", "rc", "ec", "tc", "sp", "ma", "hi", "du", 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, "0", "0", "7", "0", "0"
+    "\033[", "m", NULL, "0", "0", "7", "0", "0", "7"
 };
 
 /* This describes a terminal string for a file type. */
@@ -346,9 +347,12 @@
 	
 	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("");
+	} 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)))
@@ -382,7 +386,9 @@
 /* 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;
+static char *mstatus;
 static Cmatch **mtab, **mmtabp;
 static Cmgroup *mgtab, *mgtabp;
 static struct listcols mcolors;
@@ -496,10 +502,10 @@
 
 /* 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 +513,32 @@
 	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;
+    char *t;
 
     initiscol(c);
 
@@ -548,8 +552,21 @@
 	}
 	if (cc == Meta)
 	    cc = *s++ ^ 32;
-	fputs(nicechar(cc), shout);
+
+	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. */
@@ -636,12 +653,613 @@
 
 static Cmgroup last_group;
 
-static void
+/**/
+static int
+asklistscroll(int ml)
+{
+    int v, i;
+
+    compprintfmt(NULL, -1, 1, 1, ml, NULL);
+
+    fflush(shout);
+    zsetterm();
+    v = getzlequery(0);
+    settyinfo(&shttyinfo);
+    putc('\r', shout);
+    for (i = columns - 1; i--; )
+	putc(' ', shout);
+
+    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
+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))
+		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;
+		    }
+		    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 / 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++;
+		if (mscroll && beg && !--mrestlines && (ask = asklistscroll(ml))) {
+		    *stop = 1;
+		    return l + (cc / 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;
+}
+
+/* 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;
+	    }
+	}
+    }
+    return 0;
+}
+
+/**/
+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;
+    }
+    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) {
+			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);
+		    }
+		    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 (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;
+		} 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) {
+		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)) {
+			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++;
+		    if (fl < 0)
+			fl = 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) {
+	/* Move the cursor up to the prompt, if always_last_prompt *
+	 * is set and all that...                                  */
+	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;
+
+	    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;
+    int len, subcols = 0, stop = 0, ret = 0;
 
     if (g != last_group)
         *last_cap = '\0';
@@ -649,12 +1267,14 @@
     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();
+	}
+	return 0;
     }
     m = *mp;
     if (m->disp && (m->flags & CMF_DISPLINE)) {
@@ -666,6 +1286,8 @@
 		mgtab[mm + i] = g;
 	    }
 	}
+	if (!dolist(ml))
+	    return 0;
 	if (m->gnum == mselect) {
 	    int mm = (mcols * ml);
 	    mline = ml;
@@ -681,9 +1303,12 @@
 	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 +1329,8 @@
 		mgtab[mx + mm + i] = g;
 	    }
 	}
+	if (!dolist(ml))
+	    return 0;
 	if (m->gnum == mselect) {
 	    int mm = mcols * ml;
 
@@ -723,9 +1350,13 @@
 	    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);
 
 	 if (isset(LISTTYPES) && buf) {
@@ -751,28 +1382,30 @@
 	    zcoff();
 	}
     }
+    return ret;
 }
 
 static int
 complistmatches(Hookdef dummy, Chdata dat)
 {
     Cmgroup oamatches = amatches;
+    char *p = NULL;
 
     amatches = dat->matches;
 
-    if (minfo.asked == 2) {
+    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;
@@ -780,12 +1413,35 @@
     }
     if (inselect)
 	clearflag = 0;
+
+    mscroll = 0;
+
+    if (mselect >= 0 || mlbeg >= 0 ||
+	((p = getsparam("LISTMAX")) && !strcmp(p, "scroll"))) {
+	trashzle();
+	showinglist = listshown = 0;
 
-    if (asklist()) {
+	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 (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 +1451,13 @@
 	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;
@@ -849,7 +1504,7 @@
     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;
 };
@@ -863,6 +1518,7 @@
     Thingy cmd;
     Menustack u = NULL;
     int i = 0, acc = 0, wishcol = 0, setwish = 0, oe = onlyexpl, wasnext = 0;
+    int space, lbeg = 0, step = 1;
     char *s;
 
     if (fdat || (dummy && (!(s = getsparam("SELECTMIN")) ||
@@ -873,11 +1529,47 @@
 	}
 	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();
@@ -923,6 +1615,8 @@
 	    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;
@@ -949,15 +1643,19 @@
 	    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;
@@ -970,6 +1668,24 @@
 	    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)) {
@@ -986,6 +1702,8 @@
 	    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);
@@ -1043,6 +1761,97 @@
 		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;
@@ -1093,10 +1902,8 @@
 		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;
 
@@ -1113,9 +1920,7 @@
 		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;
 
@@ -1166,8 +1971,9 @@
 		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));
@@ -1184,6 +1990,7 @@
 	    clearlist = 1;
 	zrefresh();
     }
+    mlbeg = -1;
     fdat = NULL;
 
     return (!noselect ^ acc);
Index: Src/Zle/compresult.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/compresult.c,v
retrieving revision 1.7
diff -u -r1.7 compresult.c
--- Src/Zle/compresult.c	2000/04/13 08:09:13	1.7
+++ Src/Zle/compresult.c	2000/04/17 11:06:20
@@ -1157,7 +1157,7 @@
 }
 
 /**/
-mod_export void
+mod_export int
 calclist(int showall)
 {
     Cmgroup g;
@@ -1170,7 +1170,7 @@
     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 @@
     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 @@
 
     /* 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 @@
 		     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);
Index: Src/Zle/zle_tricky.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_tricky.c,v
retrieving revision 1.5
diff -u -r1.5 zle_tricky.c
--- Src/Zle/zle_tricky.c	2000/04/13 08:05:57	1.5
+++ Src/Zle/zle_tricky.c	2000/04/17 11:06:21
@@ -1997,7 +1997,7 @@
 	     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);
Index: Src/Zle/zle_utils.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_utils.c,v
retrieving revision 1.2
diff -u -r1.2 zle_utils.c
--- Src/Zle/zle_utils.c	2000/04/01 20:49:48	1.2
+++ Src/Zle/zle_utils.c	2000/04/17 11:06:21
@@ -277,6 +277,7 @@
  * 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 @@
 
 /**/
 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;
 }
 

--
Sven Wischnowsky                         wischnow@xxxxxxxxxxxxxxxxxxxxxxx



Messages sorted by: Reverse Date, Date, Thread, Author