about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-10-13 13:43:02 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-10-13 13:43:02 +0000
commitace2616432ae930d29966bbb29a827a57e198dc3 (patch)
treecd0b7ebc0af5c79ad2891605d402f1d5b8651b1c
parent18e3d1903d7978be38af678cf4414dd1a655a9e8 (diff)
downloadzsh-ace2616432ae930d29966bbb29a827a57e198dc3.tar.gz
zsh-ace2616432ae930d29966bbb29a827a57e198dc3.tar.xz
zsh-ace2616432ae930d29966bbb29a827a57e198dc3.zip
zsh-workers/8227
-rw-r--r--Doc/Zsh/compsys.yo14
-rw-r--r--Doc/Zsh/compwid.yo29
-rw-r--r--Doc/Zsh/zle.yo6
-rw-r--r--Functions/Zle/incremental-complete-word186
-rw-r--r--Src/Zle/comp.h11
-rw-r--r--Src/Zle/comp1.c14
-rw-r--r--Src/Zle/compctl.c42
-rw-r--r--Src/Zle/complist.c51
-rw-r--r--Src/Zle/zle_main.c6
-rw-r--r--Src/Zle/zle_params.c9
-rw-r--r--Src/Zle/zle_tricky.c311
11 files changed, 458 insertions, 221 deletions
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 8c393620e..82265ffd0 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -318,6 +318,12 @@ of the completer functions to decide if other completers should be
 called. If the return value is zero, no other completers are tried and the
 tt(_main_complete) function returns.
 
+Immediately before returning the tt(_main_complete) function calls all
+functions whose names are given in the tt(comppostfuncs) array and
+then resets it to an empty array. This can be used by completion
+functions or by other ZLE widgets calling completion to register code
+that is to be executed after all matches have been added.
+
 The widget function tt(_main_complete) also uses the configuration key 
 tt(last_prompt). If this is set to tt(always), the cursor is moved up
 to the last prompt after printing a list of matches even if a numeric
@@ -1718,10 +1724,14 @@ common prefix different from the word on the line or if there is such
 a common prefix, respectively. The sequence `tt(%c)' is replaced by
 the name of the completer function that generated the matches (without
 the leading underscore). Finally, `tt(%n)' is replaced by the number
-of matches generated and `tt(%a)' is replaced by an empty string if
+of matches generated, `tt(%a)' is replaced by an empty string if
 the matches are in the normal set (i.e. the one without file names
 with one of the suffixes from the tt(fignore) array) and with `tt(
--alt-)' if the matches are in the alternate set.
+-alt-)' if the matches are in the alternate set, and if the
+tt(incremental_list) key (see below) is set, `tt(%l)' is replaced by
+`tt(...)' if the list of matches is too long to fit on the screen and
+with an empty string otherwise. If tt(incremental_list) is not set or
+set to an empty string, `tt(%l)' will always be removed.
 )
 item(tt(incremental_stop))(
 This gives a pattern matching (keyboard-) keys which will cause
diff --git a/Doc/Zsh/compwid.yo b/Doc/Zsh/compwid.yo
index 6681e7b15..e0ab37fb5 100644
--- a/Doc/Zsh/compwid.yo
+++ b/Doc/Zsh/compwid.yo
@@ -184,10 +184,13 @@ When completing inside quotes, this contains the quotation character
 is unset.
 )
 item(tt(nmatches))(
-The number of matches generated and accepted by the completion code so far.
+The number of matches generated and accepted by the completion code so
+far, excluding those matches that are only accepted by ignoring the
+tt(fignore) parameter and the tt(-a) option of the tt(compadd) builtin 
+command.
 )
-item(tt(normal_nmatches))(
-Like tt(nmatches), but counts only matches in the normal set. I.e. file
+item(tt(alternate_nmatches))(
+Like tt(nmatches), but counts only matches in the alternate set. I.e. file
 names with one of the suffixes from the tt(fignore) array and matches
 put into the alternate set using the tt(-a) option of the tt(compadd)
 builtin command (see below) are not counted.
@@ -216,11 +219,14 @@ sets it to any other string, they will not be restored.
 )
 item(tt(list))(
 This controls whether or how the list of matches will be displayed.  If it
-is unset or empty they will never be listed; if is set to tt(list), they
-will always be listed; if tt(autolist) or tt(ambiguous), they will be
-listed when the tt(AUTO_LIST) or tt(LIST_AMBIGUOUS) options respectively
-would normally cause them to be.  It will be set appropriately on entry to
-a completion widget and may be changed there.
+is unset or empty they will never be listed; if its value begins with
+tt(list), they will always be listed; if it begins with tt(autolist)
+or tt(ambiguous), they will be listed when the tt(AUTO_LIST) or
+tt(LIST_AMBIGUOUS) options respectively would normally cause them to
+be.  Finally, if the value contains the string tt(explanations), only
+the explanation strings, if any, will be listed. It will be set
+appropriately on entry to a completion widget and may be changed
+there.
 )
 item(tt(force_list))(
 If the value for the tt(list) key is tt(autolist) or tt(ambiguous), the list will
@@ -233,6 +239,13 @@ Initially this is set to the value of the tt(LISTMAX) parameter.
 It may be set to any other numeric value; when the widget exits this value
 will be used in the same way as the value of tt(LISTMAX).
 )
+item(tt(list_lines))(
+This gives the number of lines that are needed to display the full
+list of completions. Note that to calculate the total number of lines
+to display you need to add the number of lines needed for the command
+line to this value, this is available as the value of the tt(BLINES)
+special parameter.
+)
 item(tt(last_prompt))(
 If this is set to an non-empty string, the completion code will move
 the cursor back to the previous prompt after the list of completions
diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index e04737da9..86de87fff 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -141,6 +141,12 @@ The part of the buffer that lies to the right of the cursor position.
 If it is assigned to, only that part of the buffer is replaced, and the
 cursor remains between the old tt($LBUFFER) and the new tt($RBUFFER).
 )
+vindex(BLINES)
+item(tt(BLINES))(
+The number of screen lines needed for the edit buffer currently
+displayed on screen (i.e. without any changes to the preceding
+parameters done after the last redisplay).
+)
 vindex(PREBUFFER)
 item(tt(PREBUFFER) (scalar))(
 In a multi-line input at the secondary prompt, this read-only parameter
diff --git a/Functions/Zle/incremental-complete-word b/Functions/Zle/incremental-complete-word
index 3831ecaa6..3eaed1a9b 100644
--- a/Functions/Zle/incremental-complete-word
+++ b/Functions/Zle/incremental-complete-word
@@ -1,8 +1,7 @@
-# incremental-complete-word() {
-
 # Autoload this function, run `zle -N <func-name>' and bind <func-name>
 # to a key.
 
+
 # This allows incremental completion of a word.  After starting this
 # command, a list of completion choices can be shown after every character
 # you type, which you can delete with ^h or DEL.  RET will accept the
@@ -23,10 +22,12 @@
 #    such a common prefix, respectively. The sequence `%c' is replaced
 #    by the name of the completer function that generated the matches
 #    (without the leading underscore). Finally, `%n' is replaced by the
-#    number of matches generated and `%a' is replaced by an empty string
+#    number of matches generated, `%a' is replaced by an empty string
 #    if the matches are in the normal set (i.e. the one without file names
 #    with one of the suffixes from `fignore') and with ` -alt-' if the
-#    matches are in the alternate set.
+#    matches are in the alternate set, and if the `incremental_list' key
+#    (see below) is set, `%l' is replaced by `...' if the list of matches
+#    is too long to fit on the screen and with an empty string otherwise.
 #
 #  incremental_stop
 #    Pattern matching keys which will cause icompletion to stop and the
@@ -44,68 +45,37 @@
 #    key-press.
 
 
-emulate -L zsh
-unsetopt autolist menucomplete automenu # doesn't work well
-
-local key lbuf="$LBUFFER" rbuf="$RBUFFER" pmpt word
-local lastl lastr wid twid num alt
-
-[[ -n "$compconfig[incremental_completer]" ]] &&
-    set ${(s.:.)compconfig[incremental_completer]}
-pmpt="${compconfig[incremental_prompt]-incremental (%c): %u%s}"
-
-if [[ -n "$compconfig[incremental_list]" ]]; then
-  wid=list-choices
-else
-  wid=complete-word
-fi
-
-zle $wid "$@"
-LBUFFER="$lbuf"
-RBUFFER="$rbuf"
-if (( ! _lastcomp[nmatches] )); then
-  word=''
-  state='-no match-'
-elif [[ "${LBUFFER}${RBUFFER}" = *${_lastcomp[unambiguous]}* ]]; then
-  word=''
-  state='-no prefix-'
-else
-  word="${_lastcomp[unambiguous]}"
-  state=''
-fi
-num=$_lastcomp[normal_nmatches]
-if (( ! num )); then
-  num="${_lastcomp[nmatches]}"
-  alt=' -alt-'
-fi
-zle -R "${${${${${pmpt//\\%u/$word}//\\%s/$state}//\\%c/${_lastcomp[completer][2,-1]}}//\\%n/$num}//\\%a/$alt}"
-read -k key
-
-while [[ '#key' -ne '#\\r' && '#key' -ne '#\\n' &&
-         '#key' -ne '#\\C-g' ]]; do
-  twid=$wid
-  if [[ "$key" = ${~compconfig[incremental_stop]} ]]; then
-    zle -U "$key"
-    return
-  elif [[ "$key" = ${~compconfig[incremental_break]} ]]; then
-    return
-  elif [[ '#key' -eq '#\\C-h' || '#key' -eq '#\\C-?' ]]; then
-    [[ $#LBUFFER -gt $#l ]] && LBUFFER="$LBUFFER[1,-2]"
-  elif [[ '#key' -eq '#\\t' ]]; then
-    zle complete-word "$@"
-    lbuf="$LBUFFER"
-    rbuf="$RBUFFER"
-  elif [[ '#key' -eq '#\\C-d' ]]; then
-    twid=list-choices
+# The main widget function.
+
+incremental-complete-word() {
+  emulate -L zsh
+  unsetopt autolist menucomplete automenu # doesn't work well
+
+  local key lbuf="$LBUFFER" rbuf="$RBUFFER" pmpt word
+  local lastl lastr wid twid num alt post toolong
+
+  [[ -n "$compconfig[incremental_completer]" ]] &&
+      set ${(s.:.)compconfig[incremental_completer]}
+  pmpt="${compconfig[incremental_prompt]-incremental (%c): %u%s  %l}"
+
+  if [[ -n "$compconfig[incremental_list]" ]]; then
+    wid=list-choices
+    post=( icw-list-helper )
   else
-    LBUFFER="$LBUFFER$key"
+    wid=complete-word
+    post=()
+  fi
+
+  comppostfuncs=( "$post[@]" )
+  zle $wid "$@"
+  LBUFFER="$lbuf"
+  RBUFFER="$rbuf"
+  num=$_lastcomp[nmatches]
+  if (( ! num )); then
+    num="${_lastcomp[alternate_nmatches]}"
+    alt=' -alt-'
   fi
-  lastl="$LBUFFER"
-  lastr="$RBUFFER"
-  zle $twid "$@"
-  LBUFFER="$lastl"
-  RBUFFER="$lastr"
-  if (( ! _lastcomp[nmatches] )); then
+  if (( ! num )); then
     word=''
     state='-no match-'
   elif [[ "${LBUFFER}${RBUFFER}" = *${_lastcomp[unambiguous]}* ]]; then
@@ -115,20 +85,78 @@ while [[ '#key' -ne '#\\r' && '#key' -ne '#\\n' &&
     word="${_lastcomp[unambiguous]}"
     state=''
   fi
-  num=$_lastcomp[normal_nmatches]
-  if (( ! num )); then
-    num="${_lastcomp[nmatches]}"
-    alt=' -alt-'
-  else
-    alt=''
-  fi
-  zle -R "${${${${${pmpt//\\%u/$word}//\\%s/$state}//\\%c/${_lastcomp[completer][2,-1]}}//\\%n/$num}//\\%a/$alt}"
+  zle -R "${${${${${${pmpt//\\%u/$word}//\\%s/$state}//\\%c/${_lastcomp[completer][2,-1]}}//\\%n/$num}//\\%a/$alt}//\\%l/$toolong}"
   read -k key
-done
 
-if [[ '#key' -eq '#\\C-g' ]]; then
-  LBUFFER="$lbuf"
-  RBUFFER="$rbuf"
-fi
-zle -Rc
-# }
+  while [[ '#key' -ne '#\\r' && '#key' -ne '#\\n' &&
+           '#key' -ne '#\\C-g' ]]; do
+    twid=$wid
+    if [[ "$key" = ${~compconfig[incremental_stop]} ]]; then
+      zle -U "$key"
+      return
+    elif [[ "$key" = ${~compconfig[incremental_break]} ]]; then
+      return
+    elif [[ '#key' -eq '#\\C-h' || '#key' -eq '#\\C-?' ]]; then
+      [[ $#LBUFFER -gt $#l ]] && LBUFFER="$LBUFFER[1,-2]"
+    elif [[ '#key' -eq '#\\t' ]]; then
+      zle complete-word "$@"
+      lbuf="$LBUFFER"
+      rbuf="$RBUFFER"
+    elif [[ '#key' -eq '#\\C-d' ]]; then
+      twid=list-choices
+    else
+      LBUFFER="$LBUFFER$key"
+    fi
+    lastl="$LBUFFER"
+    lastr="$RBUFFER"
+    [[ "$twid" = "$wid" ]] && comppostfuncs=( "$post[@]" )
+    toolong=''
+    zle $twid "$@"
+    LBUFFER="$lastl"
+    RBUFFER="$lastr"
+    num=$_lastcomp[nmatches]
+    if (( ! num )); then
+      num="${_lastcomp[alternate_nmatches]}"
+      alt=' -alt-'
+    else
+      alt=''
+    fi
+    if (( ! num )); then
+      word=''
+      state='-no match-'
+    elif [[ "${LBUFFER}${RBUFFER}" = *${_lastcomp[unambiguous]}* ]]; then
+      word=''
+      state='-no prefix-'
+    else
+      word="${_lastcomp[unambiguous]}"
+      state=''
+    fi
+    zle -R "${${${${${${pmpt//\\%u/$word}//\\%s/$state}//\\%c/${_lastcomp[completer][2,-1]}}//\\%n/$num}//\\%a/$alt}//\\%l/$toolong}"
+    read -k key
+  done
+
+  if [[ '#key' -eq '#\\C-g' ]]; then
+    LBUFFER="$lbuf"
+    RBUFFER="$rbuf"
+  fi
+  zle -Rc
+}
+
+# Helper function used as a completion post-function used to make sure that
+# the list of matches in only shown if it fits on the screen.
+
+icw-list-helper() {
+
+  # +1 for the status line we will add...
+
+  if [[ compstate[list_lines]+BUFFERLINES+1 -gt LINES ]]; then
+    compstate[list]='list explanations'
+    if [[ compstate[list_lines]+BUFFERLINES+1 -gt LINES ]]; then
+      compstate[list]=''
+      compstate[force_list]=yes
+    fi
+    toolong='...'
+  fi
+}
+
+incremental-complete-word "$@"
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
index 201469872..091b04476 100644
--- a/Src/Zle/comp.h
+++ b/Src/Zle/comp.h
@@ -350,6 +350,7 @@ struct cldata {
     int nlist;			/* number of matches to list */
     int nlines;			/* number of lines needed */
     int hidden;			/* != 0 if there are hidden matches */
+    int onlyexpl;		/* != 0 if only explanations to print */
 };
 
 typedef void (*CLPrintFunc)(Cmgroup, Cmatch *, int, int, int, int,
@@ -440,8 +441,10 @@ struct chdata {
 #define CP_OLDINS      (1 << CPN_OLDINS)
 #define CPN_VARED      24
 #define CP_VARED       (1 << CPN_VARED)
-#define CPN_NNMATCHES  25
-#define CP_NNMATCHES   (1 << CPN_NNMATCHES)
+#define CPN_ANMATCHES  25
+#define CP_ANMATCHES   (1 << CPN_ANMATCHES)
+#define CPN_LISTLINES  26
+#define CP_LISTLINES   (1 << CPN_LISTLINES)
 
-#define CP_KEYPARAMS   26
-#define CP_ALLKEYS     ((unsigned int) 0xffffff)
+#define CP_KEYPARAMS   27
+#define CP_ALLKEYS     ((unsigned int) 0x7ffffff)
diff --git a/Src/Zle/comp1.c b/Src/Zle/comp1.c
index c1e2bfb57..f2b51c877 100644
--- a/Src/Zle/comp1.c
+++ b/Src/Zle/comp1.c
@@ -67,6 +67,15 @@ int (*makecomplistcallptr) _((Compctl));
 int (*makecomplistctlptr) _((int));
 
 /**/
+zlong (*num_matchesptr) _((int));
+
+/**/
+zlong (*list_linesptr) _((void));
+
+/**/
+void (*comp_listptr) _((char *));
+
+/**/
 char *(*unambig_dataptr) _((int *));
 
 /**/
@@ -104,11 +113,10 @@ int incompfunc;
 
 /**/
 zlong compcurrent,
-      compnmatches,
-      compnnmatches,
       compmatcher,
       compmatchertot,
-      complistmax;
+      complistmax,
+      complistlines;
 
 /**/
 char **compwords,
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index ce80bbee5..1b72fa923 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -2249,7 +2249,7 @@ static struct compparam comprparams[] = {
 };
 
 static struct compparam compkparams[] = {
-    { "nmatches", PM_INTEGER, VAL(compnmatches), NULL, NULL },
+    { "nmatches", PM_INTEGER | PM_READONLY, NULL, NULL, VAL(get_nmatches) },
     { "matcher", PM_INTEGER, VAL(compmatcher), NULL, NULL },
     { "matcher_string", PM_SCALAR, VAL(compmatcherstr), NULL, NULL },
     { "total_matchers", PM_INTEGER, VAL(compmatchertot), NULL, NULL },
@@ -2259,7 +2259,7 @@ static struct compparam compkparams[] = {
     { "quote", PM_SCALAR | PM_READONLY, VAL(compquote), NULL, NULL },
     { "quoting", PM_SCALAR | PM_READONLY, VAL(compquoting), NULL, NULL },
     { "restore", PM_SCALAR, VAL(comprestore), NULL, NULL },
-    { "list", PM_SCALAR, VAL(complist), NULL, NULL },
+    { "list", PM_SCALAR, NULL, VAL(set_complist), VAL(get_complist) },
     { "force_list", PM_SCALAR, VAL(compforcelist), NULL, NULL },
     { "insert", PM_SCALAR, VAL(compinsert), NULL, NULL },
     { "exact", PM_SCALAR, VAL(compexact), NULL, NULL },
@@ -2275,7 +2275,8 @@ static struct compparam compkparams[] = {
     { "old_list", PM_SCALAR, VAL(compoldlist), NULL, NULL },
     { "old_insert", PM_SCALAR, VAL(compoldins), NULL, NULL },
     { "vared", PM_SCALAR, VAL(compvared), NULL, NULL },
-    { "normal_nmatches", PM_INTEGER, VAL(compnnmatches), NULL, NULL },
+    { "alternate_nmatches", PM_INTEGER | PM_READONLY, NULL, NULL, VAL(get_anmatches) },
+    { "list_lines", PM_INTEGER | PM_READONLY, NULL, NULL, VAL(get_listlines) },
     { NULL, 0, NULL, NULL, NULL }
 };
 
@@ -2383,6 +2384,41 @@ set_compstate(Param pm, HashTable ht)
 }
 
 /**/
+static zlong
+get_nmatches(Param pm)
+{
+    return num_matchesptr(1);
+}
+
+/**/
+static zlong
+get_anmatches(Param pm)
+{
+    return num_matchesptr(0);
+}
+
+/**/
+static zlong
+get_listlines(Param pm)
+{
+    return list_linesptr();
+}
+
+/**/
+static void
+set_complist(Param pm, char *v)
+{
+    comp_listptr(v);
+}
+
+/**/
+static char *
+get_complist(Param pm)
+{
+    return complist;
+}
+
+/**/
 static char *
 get_unambig(Param pm)
 {
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 112dea530..b9b0ca79d 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -525,9 +525,9 @@ struct menustack {
     char *line;
     char *brbeg;
     char *brend;
-    int cs, acc;
+    int cs, acc, nmatches;
     struct menuinfo info;
-    Cmgroup amatches, pmatches, lmatches;
+    Cmgroup amatches, pmatches, lastmatches, lastlmatches;
 };
 
 static int
@@ -538,7 +538,7 @@ domenuselect(Hookdef dummy, Chdata dat)
     Cmgroup *pg;
     Thingy cmd;
     Menustack u = NULL;
-    int i = 0, acc = 0, wishcol = 0, setwish = 0;
+    int i = 0, acc = 0, wishcol = 0, setwish = 0, oe = onlyexpl;
     char *s;
 
     HEAPALLOC {
@@ -555,6 +555,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 	noselect = 0;
 	mselect = (*(minfo.cur))->gnum;
 	for (;;) {
+	    onlyexpl = 0;
 	    showinglist = -2;
 	    zrefresh();
 	    inselect = 1;
@@ -601,18 +602,23 @@ domenuselect(Hookdef dummy, Chdata dat)
 		memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
 		s->amatches = amatches;
 		s->pmatches = pmatches;
-		s->lmatches = lmatches;
+		s->lastmatches = lastmatches;
+		s->lastlmatches = lastlmatches;
 		s->acc = menuacc;
 		s->brbeg = dupstring(brbeg);
 		s->brend = dupstring(brend);
+		s->nmatches = nmatches;
 		menucmp = menuacc = 0;
 		fixsuffix();
 		validlist = 0;
-		pmatches = NULL;
+		amatches = pmatches = lastmatches = NULL;
 		invalidatelist();
-		menucomplete(zlenoargs);
+		PERMALLOC {
+		    menucomplete(zlenoargs);
+		} LASTALLOC;
 		if (dat->num < 2 || !minfo.cur || !*(minfo.cur)) {
 		    noselect = clearlist = listshown = 1;
+		    onlyexpl = 0;
 		    zrefresh();
 		    break;
 		}
@@ -629,10 +635,12 @@ domenuselect(Hookdef dummy, Chdata dat)
 		s->line = dupstring((char *) line);
 		s->cs = cs;
 		memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
-		s->amatches = s->pmatches = s->lmatches = NULL;
+		s->amatches = s->pmatches =
+		    s->lastmatches = s->lastlmatches = NULL;
 		s->acc = menuacc;
 		s->brbeg = dupstring(brbeg);
 		s->brend = dupstring(brend);
+		s->nmatches = nmatches;
 		acceptlast();
 		do_menucmp(0);
 		mselect = (*(minfo.cur))->gnum;
@@ -652,12 +660,15 @@ domenuselect(Hookdef dummy, Chdata dat)
 		menuacc = u->acc;
 		memcpy(&minfo, &(u->info), sizeof(struct menuinfo));
 		p = &(minfo.cur);
-		if (u->pmatches && pmatches != u->pmatches) {
-		    freematches();
+		if (u->lastmatches && lastmatches != u->lastmatches) {
+		    if (lastmatches)
+			freematches(lastmatches);
 		    amatches = u->amatches;
 		    pmatches = u->pmatches;
-		    lmatches = u->lmatches;
-		    hasperm = 1;
+		    lastmatches = u->lastmatches;
+		    lastlmatches = u->lastlmatches;
+		    nmatches = u->nmatches;
+		    hasoldlist = 1;
 		}
 		zsfree(brbeg);
 		zsfree(brend);
@@ -666,6 +677,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 		u = u->prev;
 		clearlist = 1;
 		setwish = 1;
+		listdat.valid = 0;
 	    } else if (cmd == Th(z_redisplay)) {
 		redisplay(zlenoargs);
 		continue;
@@ -819,19 +831,11 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    do_single(**p);
 	    mselect = (**p)->gnum;
 	}
-	if (u) {
-	    int hp = hasperm;
-	    Cmgroup m = pmatches;
+	if (u)
+	    for (; u; u = u->prev)
+		if (u->lastmatches != lastmatches)
+		    freematches(u->lastmatches);
 
-	    for (; u; u = u->prev) {
-		if (u->pmatches != m) {
-		    pmatches = u->pmatches;
-		    freematches();
-		}
-	    }
-	    pmatches = m;
-	    hasperm = hp;
-	}
 	selectlocalmap(NULL);
 	mselect = -1;
 	inselect = 0;
@@ -842,6 +846,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 	}
 	if (!noselect) {
 	    showinglist = -2;
+	    onlyexpl = oe;
 	    zrefresh();
 	}
 	fdat = NULL;
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index beef708fb..d7ffbe259 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -979,6 +979,9 @@ setup_zle(Module m)
     getcpatptr = getcpat;
     makecomplistcallptr = makecomplistcall;
     makecomplistctlptr = makecomplistctl;
+    num_matchesptr = num_matches;
+    list_linesptr = list_lines;
+    comp_listptr = comp_list;
     unambig_dataptr = unambig_data;
     set_comp_sepptr = set_comp_sep;
 
@@ -1059,6 +1062,9 @@ finish_zle(Module m)
     getcpatptr = NULL;
     makecomplistcallptr = NULL;
     makecomplistctlptr = NULL;
+    num_matchesptr = NULL;
+    list_linesptr = NULL;
+    comp_listptr = NULL;
     unambig_dataptr = NULL;
     set_comp_sepptr = NULL;
 
diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c
index 5ed846cd6..aa62786f5 100644
--- a/Src/Zle/zle_params.c
+++ b/Src/Zle/zle_params.c
@@ -73,6 +73,8 @@ static struct zleparam {
         unset_numeric, NULL },
     { "HISTNO", PM_INTEGER | PM_READONLY, NULL, FN(get_histno),
         zleunsetfn, NULL },
+    { "BUFFERLINES", PM_INTEGER | PM_READONLY, NULL, FN(get_bufferlines),
+        zleunsetfn, NULL },
     { NULL, 0, NULL, NULL, NULL, NULL }
 };
 
@@ -285,3 +287,10 @@ get_histno(Param pm)
 {
     return histline;
 }
+
+/**/
+static zlong
+get_bufferlines(Param pm)
+{
+    return nlnct;
+}
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 6e42a7d65..ebae044b9 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -136,16 +136,24 @@ static int brpl, brsl, brpcs, brscs, qbrpl, qbrsl, hasunqu;
 
 static LinkList matches, fmatches;
 
-/* This holds the list of matches-groups. lmatches is a pointer to the  *
- * last element in this list. */
+/* This holds the list of matches-groups. lastmatches holds the last list of 
+ * permanently allocated matches, pmatches is the same for the list
+ * currently built, amatches is the heap allocated stuff during completion
+ * (after all matches have been generated it is an alias for pmatches), and
+ * lmatches/lastlmatches is a pointer to the last element in the lists. */
 
 /**/
-Cmgroup pmatches, amatches, lmatches;
+Cmgroup lastmatches, pmatches, amatches, lmatches, lastlmatches;
 
-/* Non-zero if we have permanently allocated matches. */
+/* Non-zero if we have permanently allocated matches (old and new). */
 
 /**/
-int hasperm;
+int hasoldlist, hasperm;
+
+/* Non-zero if we have newly added matches. */
+
+/**/
+int newmatches;
 
 /* Number of permanently allocated matches and groups. */
 
@@ -153,13 +161,19 @@ static int permmnum, permgnum;
 
 /* The total number of matches and the number of matches to be listed. */
 
-static int nmatches, smatches;
+/**/
+int nmatches, smatches;
 
 /* !=0 if we have a valid completion list. */
 
 /**/
 int validlist;
 
+/* != 0 if only explanation strings should be printed */
+
+/**/
+int onlyexpl;
+
 /* Information about the matches for listing. */
 
 /**/
@@ -234,7 +248,7 @@ static Cmgroup mgroup;
 
 /* Match counters: all matches, normal matches (not alternate set). */
 
-static int mnum, nmnum;
+static int mnum;
 
 /* The match counter when unambig_data() was called. */
 
@@ -3899,8 +3913,6 @@ add_match_data(int alt, char *str, Cline line,
     ai->line = join_clines(ai->line, line);
 
     mnum++;
-    if (!alt)
-	nmnum++;
     ai->count++;
     
     /* Allocate and fill the match structure. */
@@ -3929,6 +3941,8 @@ add_match_data(int alt, char *str, Cline line,
     cm->rems = cm->remf = cm->disp = NULL;
     addlinknode((alt ? fmatches : matches), cm);
 
+    newmatches = 1;
+
     /* One more match for this explanation. */
     if (expl) {
 	if (alt)
@@ -4283,8 +4297,6 @@ addmatches(Cadata dat, char **argv)
 		    free_cline(lc);
 		}
 	    }
-	    compnmatches = mnum;
-	    compnnmatches = nmnum;
 	    if (dat->apar)
 		set_list_array(dat->apar, aparl);
 	    if (dat->opar)
@@ -4843,13 +4855,18 @@ docompletion(char *s, int lst, int incmd)
 		minfo.cur = NULL;
 		minfo.asked = 0;
 		do_single(m->matches[0]);
-		if (compforcelist && *compforcelist && uselist)
-		    showinglist = -2;
-		else
+		if (compforcelist && *compforcelist) {
+		    if (uselist)
+			showinglist = -2;
+		    else
+			clearlist = 1;
+		} else
 		    invalidatelist();
 	    }
 	} else {
 	    invalidatelist();
+	    if (compforcelist && *compforcelist)
+		clearlist = 1;
 	    cs = 0;
 	    foredel(ll);
 	    inststr(origline);
@@ -4858,6 +4875,9 @@ docompletion(char *s, int lst, int incmd)
 	/* Print the explanation strings if needed. */
 	if (!showinglist && validlist && usemenu != 2 && nmatches != 1 &&
 	    useline != 2 && (!oldlist || !listshown)) {
+	    onlyexpl = 1;
+	    showinglist = -2;
+#if 0
 	    Cmgroup g = amatches;
 	    Cexpl *e;
 	    int up = 0, tr = 1, nn = 0;
@@ -4894,6 +4914,7 @@ docompletion(char *s, int lst, int incmd)
 		    putc('\n', shout);
 		fflush(shout);
 	    }
+#endif
 	}
       compend:
 	for (n = firstnode(matchers); n; incnode(n))
@@ -5067,8 +5088,6 @@ callcompfunc(char *s, char *fn)
 	zsfree(compqisuffix);
 	compqisuffix = ztrdup(qisuf ? qisuf : "");
 	compcurrent = (usea ? (clwpos + 1 - aadd) : 0);
-	compnmatches = mnum;
-	compnnmatches = nmnum;
 
 	zsfree(complist);
 	switch (uselist) {
@@ -5103,7 +5122,7 @@ callcompfunc(char *s, char *fn)
 	    comptoend = ztrdup("match");
 	zsfree(compoldlist);
 	zsfree(compoldins);
-	if (hasperm && permmnum) {
+	if (hasoldlist && permmnum) {
 	    if (listshown)
 		compoldlist = "shown";
 	    else
@@ -5150,14 +5169,17 @@ callcompfunc(char *s, char *fn)
 
 	if (!complist)
 	    uselist = 0;
-	else if (!strcmp(complist, "list"))
+	else if (!strncmp(complist, "list", 4))
 	    uselist = 1;
-	else if (!strcmp(complist, "auto") || !strcmp(complist, "autolist"))
+	else if (!strncmp(complist, "auto", 4))
 	    uselist = 2;
-	else if (!strcmp(complist, "ambig") || !strcmp(complist, "ambiguous"))
+	else if (!strncmp(complist, "ambig", 5))
 	    uselist = 3;
 	else
 	    uselist = 0;
+
+	onlyexpl = (complist && strstr(complist, "expl"));
+
 	if (!compinsert)
 	    useline = 0;
 	else if (!strcmp(compinsert, "unambig") ||
@@ -5193,8 +5215,8 @@ callcompfunc(char *s, char *fn)
 	else
 	    movetoend = 2;
 
-	oldlist = (hasperm && compoldlist && !strcmp(compoldlist, "keep"));
-	oldins = (hasperm && minfo.cur &&
+	oldlist = (hasoldlist && compoldlist && !strcmp(compoldlist, "keep"));
+	oldins = (hasoldlist && minfo.cur &&
 		  compoldins && !strcmp(compoldins, "keep"));
 
 	zfree(comprpms, CP_REALPARAMS * sizeof(Param));
@@ -5287,13 +5309,13 @@ makecomplist(char *s, int incmd, int lst)
 	if (!validlist)
 	    lastambig = 0;
 	amatches = NULL;
-	mnum = nmnum = 0;
+	mnum = 0;
 	unambig_mnum = -1;
 	isuf = NULL;
 	insmnum = insgnum = 1;
 	insgroup = oldlist = oldins = 0;
 	begcmgroup("default", 0);
-	menucmp = menuacc = 0;
+	menucmp = menuacc = newmatches = onlyexpl = 0;
 
 	ccused = newlinklist();
 	ccstack = newlinklist();
@@ -5318,16 +5340,32 @@ makecomplist(char *s, int incmd, int lst)
 	if (oldlist) {
 	    nmatches = onm;
 	    validlist = 1;
-	    amatches = pmatches;
-
+	    amatches = lastmatches;
+	    lmatches = lastlmatches;
+	    if (pmatches) {
+		freematches(pmatches);
+		pmatches = NULL;
+		hasperm = 0;
+	    }
 	    redup(osi, 0);
 
 	    return 0;
 	}
 	PERMALLOC {
-	    permmatches();
+	    if (lastmatches) {
+		freematches(lastmatches);
+		lastmatches = NULL;
+	    }
+	    permmatches(1);
+	    amatches = pmatches;
 	} LASTALLOC;
 
+	lastmatches = pmatches;
+	lastlmatches = lmatches;
+	pmatches = NULL;
+	hasperm = 0;
+	hasoldlist = 1;
+
 	if (nmatches && !errflag) {
 	    validlist = 1;
 
@@ -5709,8 +5747,6 @@ makecomplistcall(Compctl cc)
 	    instring = ois;
 	    inbackt = oib;
 	    autoq = oaq;
-	    compnmatches = mnum;
-	    compnnmatches = nmnum;
 	} LASTALLOC;
     } SWITCHBACKHEAPS;
 
@@ -5785,8 +5821,6 @@ makecomplistctl(int flags)
 	    inbackt = oib;
 	    autoq = oaq;
 	    offs = ooffs;
-	    compnmatches = mnum;
-	    compnnmatches = nmnum;
 	    zsfree(cmdstr);
 	    freearray(clwords);
 	    cmdstr = os;
@@ -7145,8 +7179,11 @@ invalidatelist(void)
 {
     if (showinglist == -2)
 	listmatches();
-    if (validlist)
-	freematches();
+    if (validlist) {
+	freematches(lastmatches);
+	lastmatches = NULL;
+	hasoldlist = 0;
+    }
     lastambig = menucmp = menuacc = validlist = showinglist = fromcomp = 0;
     listdat.valid = 0;
     if (listshown < 0)
@@ -7463,6 +7500,7 @@ addexpl(void)
 	}
     }
     addlinknode(expls, expl);
+    newmatches = 1;
 }
 
 /* This duplicates one match. */
@@ -7500,32 +7538,42 @@ dupmatch(Cmatch m)
 /* This duplicates all groups of matches. */
 
 /**/
-static void
-permmatches(void)
+static int
+permmatches(int last)
 {
     Cmgroup g = amatches, n;
     Cmatch *p, *q;
     Cexpl *ep, *eq, e, o;
     Compctl *cp, *cq;
-    int nn, nl, ll, fi = 0, gn = 1, mn = 1, rn;
+    LinkList mlist;
+    static int fi = 0;
+    int nn, nl, ll, gn = 1, mn = 1, rn;
+
+    if (pmatches && !newmatches)
+	return fi;
+
+    newmatches = fi = 0;
 
-    if (hasperm)
-	freematches();
+    if (pmatches)
+	freematches(pmatches);
 
-    amatches = lmatches = NULL;
+    pmatches = lmatches = NULL;
     nmatches = smatches = 0;
 
     if (!ainfo->count) {
-	ainfo = fainfo;
+	if (last)
+	    ainfo = fainfo;
 	fi = 1;
     }
     while (g) {
 	HEAPALLOC {
 	    if (empty(g->lmatches))
 		/* We have no matches, try ignoring fignore. */
-		g->lmatches = g->lfmatches;
+		mlist = g->lfmatches;
+	    else
+		mlist = g->lmatches;
 
-	    g->matches = makearray(g->lmatches, 1, g->flags, &nn, &nl, &ll);
+	    g->matches = makearray(mlist, 1, g->flags, &nn, &nl, &ll);
 	    g->mcount = nn;
 	    if ((g->lcount = nn - nl) < 0)
 		g->lcount = 0;
@@ -7548,10 +7596,10 @@ permmatches(void)
 
 	if (!lmatches)
 	    lmatches = n;
-	if (amatches)
-	    amatches->prev = n;
-	n->next = amatches;
-	amatches = n;
+	if (pmatches)
+	    pmatches->prev = n;
+	n->next = pmatches;
+	pmatches = n;
 	n->prev = 0;
 	n->num = gn++;
 
@@ -7594,18 +7642,71 @@ permmatches(void)
 
 	g = g->next;
     }
-    for (g = amatches; g; g = g->next) {
+    for (g = pmatches; g; g = g->next) {
 	for (rn = 1, q = g->matches; *q; q++) {
 	    (*q)->rnum = rn++;
 	    (*q)->gnum = mn++;
 	}
     }
-    pmatches = amatches;
     hasperm = 1;
     permmnum = mn - 1;
     permgnum = gn - 1;
+
+    return fi;
+}
+
+/* Return the real number of matches. */
+
+/**/
+zlong
+num_matches(int normal)
+{
+    int alt;
+
+    PERMALLOC {
+	alt = permmatches(0);
+    } LASTALLOC;
+
+    if (normal)
+	return (alt ? 0 : nmatches);
+    else
+	return (alt ? nmatches : 0);
+}
+
+/* Return the number of screen lines needed for the list. */
+
+/**/
+zlong
+list_lines(void)
+{
+    Cmgroup oam;
+
+    PERMALLOC {
+	permmatches(0);
+    } LASTALLOC;
+
+    oam = amatches;
+    amatches = pmatches;
+    listdat.valid = 0;
+    calclist();
+    listdat.valid = 0;
+    amatches = oam;
+
+    return listdat.nlines;
 }
 
+/**/
+void
+comp_list(char *v)
+{
+    zsfree(complist);
+    complist = ztrdup(v);
+
+    onlyexpl = (v && strstr(v, "expl"));
+}
+
+/**/
+
 /* This frees one match. */
 
 /**/
@@ -7634,9 +7735,9 @@ freematch(Cmatch m)
 
 /**/
 void
-freematches(void)
+freematches(Cmgroup g)
 {
-    Cmgroup g = pmatches, n;
+    Cmgroup n;
     Cmatch *m;
     Cexpl *e;
     Compctl *c;
@@ -7670,8 +7771,6 @@ freematches(void)
 
 	g = n;
     }
-    hasperm = 0;
-    listdat.valid = 0;
 }
 
 /* Insert the given string into the command line.  If move is non-zero, *
@@ -8576,27 +8675,33 @@ printfmt(char *fmt, int n, int dopr, int doesc)
 		    break;
 		case 'B':
 		    b = 1;
-		    tcout(TCBOLDFACEBEG);
+		    if (dopr)
+			tcout(TCBOLDFACEBEG);
 		    break;
 		case 'b':
 		    b = 0; m = 1;
-		    tcout(TCALLATTRSOFF);
+		    if (dopr)
+			tcout(TCALLATTRSOFF);
 		    break;
 		case 'S':
 		    s = 1;
-		    tcout(TCSTANDOUTBEG);
+		    if (dopr)
+			tcout(TCSTANDOUTBEG);
 		    break;
 		case 's':
 		    s = 0; m = 1;
-		    tcout(TCSTANDOUTEND);
+		    if (dopr)
+			tcout(TCSTANDOUTEND);
 		    break;
 		case 'U':
 		    u = 1;
-		    tcout(TCUNDERLINEBEG);
+		    if (dopr)
+			tcout(TCUNDERLINEBEG);
 		    break;
 		case 'u':
 		    u = 0; m = 1;
-		    tcout(TCUNDERLINEEND);
+		    if (dopr)
+			tcout(TCUNDERLINEEND);
 		    break;
 		case '{':
 		    for (p++; *p && (*p != '%' || p[1] != '}'); p++, cc++)
@@ -8608,7 +8713,7 @@ printfmt(char *fmt, int n, int dopr, int doesc)
 			p--;
 		    break;
 		}
-		if (m) {
+		if (dopr && m) {
 		    if (b)
 			tcout(TCBOLDFACEBEG);
 		    if (s)
@@ -8697,7 +8802,8 @@ calclist(void)
     int max = 0, i;
     VARARR(int, mlens, nmatches + 1);
 
-    if (listdat.valid && menuacc == listdat.menuacc &&
+    if (listdat.valid && onlyexpl == listdat.onlyexpl &&
+	menuacc == listdat.menuacc &&
 	lines == listdat.lines && columns == listdat.columns)
 	return;
 
@@ -8705,7 +8811,7 @@ calclist(void)
 	char **pp = g->ylist;
 	int nl = 0, l, glong = 1, gshort = columns, ndisp = 0, totl = 0;
 
-	if (pp) {
+	if (!onlyexpl && pp) {
 	    /* We have an ylist, lets see, if it contains newlines. */
 	    hidden = 1;
 	    while (!nl && *pp)
@@ -8742,7 +8848,7 @@ calclist(void)
 		    pp++;
 		}
 	    }
-	} else {
+	} else if (!onlyexpl) {
 	    for (p = g->matches; (m = *p); p++) {
 		if (menuacc && !hasbrpsfx(m, minfo.prebr, minfo.postbr)) {
 		    m->flags |= CMF_HIDE;
@@ -8798,50 +8904,52 @@ calclist(void)
 		max = i;
 	}
     }
-    for (g = amatches; g; g = g->next) {
-	char **pp;
-	int glines = 0;
+    if (!onlyexpl) {
+	for (g = amatches; g; g = g->next) {
+	    char **pp;
+	    int glines = 0;
 
-	zfree(g->widths, 0);
-	g->widths = NULL;
+	    zfree(g->widths, 0);
+	    g->widths = NULL;
 
-	if ((pp = g->ylist)) {
-	    if (!(g->flags & CGF_LINES)) {
+	    if ((pp = g->ylist)) {
+		if (!(g->flags & CGF_LINES)) {
+		    if (g->cols) {
+			glines += (arrlen(pp) + g->cols - 1) / g->cols;
+			if (g->cols > 1)
+			    g->width += (max - (g->width * g->cols - add)) / g->cols;
+		    } else {
+			g->cols = 1;
+			g->width = 1;
+			
+			while (*pp)
+			    glines += 1 + (strlen(*pp++) / columns);
+		    }
+		}
+	    } else {
 		if (g->cols) {
-		    glines += (arrlen(pp) + g->cols - 1) / g->cols;
+		    glines += (g->dcount + g->cols - 1) / g->cols;
 		    if (g->cols > 1)
 			g->width += (max - (g->width * g->cols - add)) / g->cols;
-		} else {
+		} else if (!(g->flags & CGF_LINES)) {
 		    g->cols = 1;
-		    g->width = 1;
-
-		    while (*pp)
-			glines += 1 + (strlen(*pp++) / columns);
+		    g->width = 0;
+		    
+		    for (p = g->matches; (m = *p); p++)
+			if (!(m->flags & CMF_HIDE)) {
+			    if (m->disp) {
+				if (!(m->flags & CMF_DISPLINE))
+				    glines += 1 + (mlens[m->gnum] / columns);
+			    } else if (!(m->flags & CMF_NOLIST))
+				glines += 1 + ((1 + mlens[m->gnum]) / columns);
+			}
 		}
 	    }
-	} else {
-	    if (g->cols) {
-		glines += (g->dcount + g->cols - 1) / g->cols;
-		if (g->cols > 1)
-		    g->width += (max - (g->width * g->cols - add)) / g->cols;
-	    } else if (!(g->flags & CGF_LINES)) {
-		g->cols = 1;
-		g->width = 0;
-
-		for (p = g->matches; (m = *p); p++)
-		    if (!(m->flags & CMF_HIDE)) {
-			if (m->disp) {
-			    if (!(m->flags & CMF_DISPLINE))
-				glines += 1 + (mlens[m->gnum] / columns);
-			} else if (!(m->flags & CMF_NOLIST))
-			    glines += 1 + ((1 + mlens[m->gnum]) / columns);
-		    }
-	    }
+	    g->lins = glines;
+	    nlines += glines;
 	}
-	g->lins = glines;
-	nlines += glines;
     }
-    if (isset(LISTPACKED)) {
+    if (!onlyexpl && isset(LISTPACKED)) {
 	char **pp;
 	int *ws, tlines, tline, tcols, maxlen, nth, width;
 
@@ -9017,6 +9125,9 @@ calclist(void)
     listdat.nlist = nlist;
     listdat.nlines = nlines;
     listdat.menuacc = menuacc;
+    listdat.onlyexpl = onlyexpl;
+    listdat.columns = columns;
+    listdat.lines = lines;
 }
 
 /**/
@@ -9111,7 +9222,7 @@ printlist(int over, CLPrintFunc printm)
 		e++;
 	    }
 	}
-	if (pp && *pp) {
+	if (!listdat.onlyexpl && pp && *pp) {
 	    if (pnl) {
 		putc('\n', shout);
 		pnl = 0;
@@ -9164,7 +9275,7 @@ printlist(int over, CLPrintFunc printm)
 		    pp += (isset(LISTROWSFIRST) ? g->cols : 1);
 		}
 	    }
-	} else if (g->lcount) {
+	} else if (!listdat.onlyexpl && g->lcount) {
 	    int n = g->dcount, nl, nc, i, j, wid;
 	    Cmatch *q;
 
@@ -9329,7 +9440,7 @@ int
 listlist(LinkList l)
 {
     struct cmgroup dg;
-    int vl = validlist, sm = smatches;
+    int vl = validlist, sm = smatches, nm = nmatches;
     char *oclp = complastprompt;
     Cmgroup am = amatches;
 
@@ -9341,12 +9452,14 @@ listlist(LinkList l)
     validlist = 1;
     memset(&dg, 0, sizeof(struct cmgroup));
     dg.ylist = (char **) makearray(l, 0, 1, &(dg.lcount), NULL, NULL);
+    nmatches = dg.lcount;
     amatches = &dg;
     ilistmatches(NULL, NULL);
     amatches = am;
 
     validlist = vl;
     smatches = sm;
+    nmatches = nm;
     complastprompt = oclp;
 
     return !dg.lcount;