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

PATCH: User-defined completion listing



This implements something like the outcome of the recent discussion of
a new option for showing a user-defined list instead of the actual
completions.  First, the bad news: this patch is simply against my own
current adaption of 3.1.2, which probably doesn't correspond to anyone
else's.  In particular, it has my compctl -/ and -W patches in, but it
doesn't have Sven's big patch, which may make a difference.  I could
probably do it against vanilla 3.1.2 if that becomes sensible.

Now the good news:  the syntax (see manual patch for more) is either

  compctl ... -Y '$array'

or

  compctl ... -Y 'func'

In the first case, $array is, err, an array, in the second, func is
called and must set $reply, just like a -K function.  (They're
evaluated at different times, so there's no clash.)  func is also
passed the complete list of matches, with full prefix expansion, so it
can process the matches to produce a display. For example,

  llfn() { reply=("$(ls -fld $*)"); }
  compctl -f -Y llfn foo

foo has ordinary file name completion, but the list of matches will be
made line by line in ls -l format.  read -c and read -l are available
if you want them.

The output is literal in this case, since you have control over what
comes out, though only newline is specially handled; other control
sequences could mess up the formatting and are best avoided (at least
with always_last_prompt set).  Note, however, that all the other
formatting capabilities are retained, so a -Y function like

  func() { reply=($*); }

produces full columnated output just as without the -Y func (assuming
there are no funny characters in the $* which would be massaged in
that case).

So the suggestion for job completion is along the lines of:

  joblist() {
      local jf=/tmp/zjobs$$ line
      jobs >& $jf
      reply=() joblist=()
      while read line; do
  	  joblist=($joblist $line)
  	  reply=($reply ${${line#\[}%%\]*})
      done < $jf
  # The above allows the output to appear in columns.  If you want the
  # exact output of 'jobs', you can instead use:
  #    joblist=("$(<$jf)")
  # It must still be an array, though.
      rm -f $jf
  }
  compctl -P% -K joblist -Y '$joblist' \
      -x 's[-]' -k signals -- kill   # ... and friends

Note that in the current implementation you need a completion list: if
there are no completions, nothing is displayed, so you can't just
display a memo and let the user complete by hand.  That could probably
be remedied.  Obviously, '$joblist' could be a static variable as well.

One thing to check for with the patch is that I haven't somehow gummed
up the logic for displaying other types of completion in my zeal to
get -Y displays to appear OK; I obviously couldn't check everything
directly myself.  (I think I caught the only memory leak, though.)
Also, a -Y func is blindly passed the fully expanded completion
possibilities (necessary for the llfn example above, which needs the
full path to the candidate file): maybe there are occasions where
that's wrong.

Another thing under the `could perhaps be better, but it's time I did
some work' heading:  literal arrays are allowed as with -k, i.e. -Y
'(option1 option2 ...)', but there's no way of getting a literal
string there.  It might be nice to enhance get_user_var() to understand
a double quote at the beginning and return the text as an array with a
single element.  (The name of a real array with just one element is
fine, of course.)

While I'm getting picky:  the fact that the completion function gets
the full path could be something of a memory hog if you are completing
in a full directory with a long path name, but until the Commodore 64
version of zsh appears...

*** Doc/Zsh/compctl.yo.YoY	Thu Sep 18 09:58:39 1997
--- Doc/Zsh/compctl.yo	Wed Nov 19 17:34:51 1997
***************
*** 103,111 ****
    [ tt(-s) var(subststring) ])
  list([ tt(-K) var(function) ] [ tt(-H) var(num pattern) ])
  list([ tt(-Q) ] [ tt(-P) var(prefix) ] [ tt(-S) var(suffix) ])
! liat([ tt(-W) var(file-prefix) ]
  list([ tt(-q) ] [ tt(-X) var(explanation) ])
! list([ tt(-l) var(cmd) ] [ tt(-U) ])
  endlist()
  
  The remaining var(options) specify the type of command arguments
--- 103,111 ----
    [ tt(-s) var(subststring) ])
  list([ tt(-K) var(function) ] [ tt(-H) var(num pattern) ])
  list([ tt(-Q) ] [ tt(-P) var(prefix) ] [ tt(-S) var(suffix) ])
! list([ tt(-W) var(file-prefix) ])
  list([ tt(-q) ] [ tt(-X) var(explanation) ])
! list([ tt(-Y) var(func-or-var) ] [ tt(-l) var(cmd) ] [ tt(-U) ])
  endlist()
  
  The remaining var(options) specify the type of command arguments
***************
*** 360,365 ****
--- 360,385 ----
  options. A `tt(%n)' in this string is replaced by the number of matches.
  The explanation only appears if completion was tried and there was
  no unique match.
+ )
+ item(tt(-Y) var(func-or-var))(
+ The list provided by var(func-or-var) is displayed instead of the list
+ of completions whenever a listing is required; the actual completions
+ to be inserted are not affected.  It can be provided in two
+ ways. Firstly, if var(func-or-var) begins with a tt($) it defines an
+ array variable, or if it begins with a left parenthesis a literal
+ array, which contains the list.  A variable may have been set by a
+ call to a function using the tt(-K) option.  Otherwise it contains the
+ name of a function which will be executed to create the list.  The
+ function will be passed as an argument list all matching completions,
+ including prefixes and suffixes expanded in full, and should set the
+ array var(reply) to the result.  In both cases, the display list will
+ only be retrieved after a complete list of matches has been created.
+ 
+ Note that the returned list does not have to correspond, even in
+ length, to the original set of matches; the use of an array is purely
+ a convenience for formatting.  No special formatting of characters is
+ performed on the output in this case; in particular, newlines are
+ printed literally and if they appear output in columns is suppressed.
  )
  enditem()
  texinode(Alternative Completion)(Extended Completion)(Option Flags)(Programmable Completion)
*** Src/Zle/comp.h.YoY	Thu Sep 18 09:58:39 1997
--- Src/Zle/comp.h	Wed Nov 19 11:45:01 1997
***************
*** 102,107 ****
--- 102,108 ----
      char *str;			/* for -s (expansion)                      */
      char *func;			/* for -K (function)                       */
      char *explain;		/* for -X (explanation)                    */
+     char *ylist;		/* for -Y (user-defined desc. for listing) */
      char *prefix, *suffix;	/* for -P and -S (prefix, suffix)          */
      char *subcmd;		/* for -l (command name to use)            */
      char *withd;		/* for -w (with directory                  */
*** Src/Zle/comp1.c.YoY	Thu Sep 18 09:58:40 1997
--- Src/Zle/comp1.c	Wed Nov 19 12:18:44 1997
***************
*** 80,85 ****
--- 80,86 ----
      zsfree(cc->str);
      zsfree(cc->func);
      zsfree(cc->explain);
+     zsfree(cc->ylist);
      zsfree(cc->prefix);
      zsfree(cc->suffix);
      zsfree(cc->hpat);
*** Src/Zle/compctl.c.YoY	Thu Sep 18 09:59:17 1997
--- Src/Zle/compctl.c	Wed Nov 19 12:18:41 1997
***************
*** 217,222 ****
--- 217,234 ----
  		    *argv = "" - 1;
  		}
  		break;
+ 	    case 'Y':
+ 		if ((*argv)[1]) {
+ 		    cct.ylist = (*argv) + 1;
+ 		    *argv = "" - 1;
+ 		} else if (!argv[1]) {
+ 		    zwarnnam(name, "function/variable expect after -%c",
+ 			     NULL, **argv);
+ 		} else {
+ 		    cct.ylist = *++argv;
+ 		    *argv = "" - 1;
+ 		}
+ 		break;
  	    case 'P':
  		if ((*argv)[1]) {
  		    cct.prefix = (*argv) + 1;
***************
*** 687,693 ****
      Compctl cc;
  
      if (cct->subcmd && (cct->keyvar || cct->glob || cct->str ||
! 			cct->func || cct->explain || cct->prefix)) {
  	zwarnnam(name, "illegal combination of options", NULL, 0);
  	return 1;
      }
--- 699,706 ----
      Compctl cc;
  
      if (cct->subcmd && (cct->keyvar || cct->glob || cct->str ||
! 			cct->func || cct->explain || cct->ylist ||
! 			cct->prefix)) {
  	zwarnnam(name, "illegal combination of options", NULL, 0);
  	return 1;
      }
***************
*** 725,730 ****
--- 738,744 ----
      zsfree(cc->str);
      zsfree(cc->func);
      zsfree(cc->explain);
+     zsfree(cc->ylist);
      zsfree(cc->prefix);
      zsfree(cc->suffix);
      zsfree(cc->subcmd);
***************
*** 740,745 ****
--- 754,760 ----
      cc->str = ztrdup(cct->str);
      cc->func = ztrdup(cct->func);
      cc->explain = ztrdup(cct->explain);
+     cc->ylist = ztrdup(cct->ylist);
      cc->prefix = ztrdup(cct->prefix);
      cc->suffix = ztrdup(cct->suffix);
      cc->subcmd = ztrdup(cct->subcmd);
***************
*** 857,862 ****
--- 872,878 ----
      printif(cc->keyvar, 'k');
      printif(cc->func, 'K');
      printif(cc->explain, 'X');
+     printif(cc->ylist, 'Y');
      printif(cc->prefix, 'P');
      printif(cc->suffix, 'S');
      printif(cc->glob, 'g');
*** Src/Zle/zle_tricky.c.YoY	Thu Sep 18 10:01:00 1997
--- Src/Zle/zle_tricky.c	Wed Nov 19 17:46:32 1997
***************
*** 123,128 ****
--- 123,133 ----
  
  static int nmatches;
  
+ /* A list of user-defined explanations for the completions to be shown *
+  * instead of amatches when listing completions.                       */
+ 
+ static char **aylist;
+ 
  /* !=0 if we have a valid completion list. */
  
  static int validlist;
***************
*** 2996,3003 ****
      ccsuffix = cc->suffix;
  
      validlist = 1;
!     if ((nmatches || expl) && !errflag)
  	return 0;
  
      if ((isf || cc->xor) && !parampre) {
  	/* We found no matches, but there is a xor'ed completion: *
--- 3001,3045 ----
      ccsuffix = cc->suffix;
  
      validlist = 1;
!     if ((nmatches || expl) && !errflag) {
! 	/* generating the user-defined explanations must happen last: *
! 	 * if anything fails, we silently allow the normal completion *
! 	 * list to be used.                                           */
! 	if (cc->ylist) {
! 	    char **yaptr, *uv = NULL;
! 	    List list;
! 
! 	    if (cc->ylist[0] == '$' || cc->ylist[0] == '(') {
! 		/* from variable: must be an array */
! 		uv = cc->ylist + (cc->ylist[0] == '$');
! 	    } else if ((list = getshfunc(cc->ylist)) != &dummy_list) {
! 		/* from function:  pass completions as arg list */
! 		LinkList args = newlinklist();
! 		int addlen = strlen(rpre) + strlen(rsuf) + 1;
! 
! 		addlinknode(args, cc->ylist);
! 		for (yaptr = amatches; *yaptr; yaptr++) {
! 		    /* can't use tricat(). rats. */
! 		    char *ptr = (char *)halloc(addlen + strlen(*yaptr));
! 		    sprintf(ptr, "%s%s%s", rpre, *yaptr, rsuf);
! 		    addlinknode(args, ptr);
! 		}
! 
! 		/* No harm in allowing read -l and -c here, too */
! 		incompctlfunc = 1;
! 		doshfunc(list, args, 0, 1);
! 		incompctlfunc = 0;
! 		uv = "reply";
! 	    } else
! 		return 0;
! 	    if (uv && (yaptr = get_user_var(uv))) {
! 		PERMALLOC {
! 		    aylist = arrdup(yaptr);
! 		} LASTALLOC;
! 	    }
! 	}
  	return 0;
+     }
  
      if ((isf || cc->xor) && !parampre) {
  	/* We found no matches, but there is a xor'ed completion: *
***************
*** 3045,3050 ****
--- 3087,3095 ----
  	listmatches();
      if(validlist) {
  	freearray(amatches);
+ 	if (aylist)
+ 	    freearray(aylist);
+ 	aylist = 0;
  	zsfree(rpre);
  	zsfree(rsuf);
  	zsfree(lpre);
***************
*** 3516,3526 ****
  listmatches(void)
  {
      int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0;
!     int off, boff, nboff;
!     int of = (isset(LISTTYPES) && !(haswhat & HAS_MISC));
      char **arr, **ap, sav;
      int nfpl, nfsl, nlpl, nlsl;
!     int listmax = getiparam("LISTMAX");
  
  #ifdef DEBUG
      /* Sanity check */
--- 3561,3572 ----
  listmatches(void)
  {
      int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0;
!     int off = 0, boff = 0, nboff = 0;
!     int of = (!aylist && isset(LISTTYPES) && !(haswhat & HAS_MISC));
      char **arr, **ap, sav;
      int nfpl, nfsl, nlpl, nlsl;
!     int listmax = getiparam("LISTMAX"), litnl = 0;
!     size_t (*strlenfn) _((char const *));
  
  #ifdef DEBUG
      /* Sanity check */
***************
*** 3538,3549 ****
  
      /* Calculate the lengths of the prefixes/suffixes we have to ignore
         during printing. */
!     off = ispattern && ppre && *ppre &&
! 	!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? strlen(ppre) : 0;
!     boff = ispattern && psuf && *psuf &&
! 	!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? strlen(psuf) : 0;
!     nboff = ispattern && psuf && *psuf &&
! 	!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? niceztrlen(psuf) : 0;
  
      /* When called from expandorcompleteprefix, we probably have to
         remove a space now. */
--- 3584,3597 ----
  
      /* Calculate the lengths of the prefixes/suffixes we have to ignore
         during printing. */
!     if (ispattern && !aylist && !(haswhat & (HAS_MISC | HAS_PATHPAT))) {
! 	if (ppre && *ppre)
! 	    off = strlen(ppre);
! 	if (psuf && *psuf) {
! 	    boff = strlen(psuf);
! 	    nboff = niceztrlen(psuf);
! 	}
!     }
  
      /* When called from expandorcompleteprefix, we probably have to
         remove a space now. */
***************
*** 3558,3595 ****
  
      /* Set the cursor below the prompt. */
      trashzle();
-     ct = nmatches;
      showinglist = 0;
  
      clearflag = (isset(USEZLE) && !termflags &&
  		 (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
  	(unset(ALWAYSLASTPROMPT) && zmult != 1);
  
!     arr = amatches;
  
!     /* Calculate the column width, the number of columns and the number
!        of lines. */
!     for (ap = arr; *ap; ap++)
! 	if ((cl = niceztrlen(*ap + off) - nboff +
! 	     (ispattern ? 0 :
! 	      (!(haswhat & HAS_MISC) ? nfpl + nfsl : nlpl + nlsl))) > longest)
! 	    longest = cl;
!     if (of)
! 	longest++;
! 
!     fw = longest + 2;
!     fct = (columns + 1) / fw;
!     if (fct == 0) {
! 	fct = 1;
! 	colsz = ct;
! 	up = colsz + nlnct - clearflag;
  	for (ap = arr; *ap; ap++)
! 	    up += (niceztrlen(*ap + off) - nboff + of +
! 		(ispattern ? 0 :
! 		(!(haswhat & HAS_MISC) ? nfpl + nfsl : nlpl + nlsl))) / columns;
!     } else {
! 	colsz = (ct + fct - 1) / fct;
! 	up = colsz + nlnct - clearflag + (ct == 0);
      }
  
      /* Print the explanation string, if any. */
--- 3606,3683 ----
  
      /* Set the cursor below the prompt. */
      trashzle();
      showinglist = 0;
  
      clearflag = (isset(USEZLE) && !termflags &&
  		 (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
  	(unset(ALWAYSLASTPROMPT) && zmult != 1);
  
!     /* just to keep gcc happy */
!     fw = colsz = up = 0;
!     if (aylist) {
! 	arr = aylist;
! 	/* If no literal newlines, the remaining code should use strlen() */
! 	strlenfn = (size_t (*) _((char const *)))strlen;
! 
! 	/* The hard bit here is that we are handling newlines literally.   *
! 	 * In fact, we are in principle handling all characters literally, *
! 	 * but it's quite enough work with just newlines.                  *
! 	 * If there are such, we give up trying to print the list as       *
! 	 * columns and print as rows, counting the extra newlines.         */
! 	ct = 0;
! 	for (ap = arr; *ap; ap++) {
! 	    ct++;
! 	    if (strchr(*ap, '\n'))
! 		litnl++;
! 	}
! 	if (litnl) {
! 	    colsz = ct;
! 	    up = colsz + nlnct - clearflag;
! 	    /* Count real newlines, as well as overflowing lines. */
! 	    for (ap = arr; *ap; ap++) {
! 		char *nlptr, *sptr = *ap;
! 		while (sptr && *sptr) {
! 		    up += (nlptr = strchr(sptr, '\n'))
! 			? 1 + (nlptr-sptr)/columns
! 			   : strlen(sptr)/columns;
! 		    sptr = nlptr ? nlptr+1 : NULL;
! 		}
! 	    }
! 	}
!     } else {
! 	arr = amatches;
! 	ct = nmatches;
! 	strlenfn = niceztrlen;
!     }
  
! 
!     if (!litnl) {
! 	/* Calculate the column width, the number of columns and the
! 	   number of lines. */
  	for (ap = arr; *ap; ap++)
! 	    if ((cl = strlenfn(*ap + off) - nboff +
! 		 ((ispattern || aylist) ? 0 :
! 		  (!(haswhat & HAS_MISC) ?
! 		   nfpl + nfsl : nlpl + nlsl))) > longest)
! 		longest = cl;
! 	if (of)
! 	    longest++;
! 
! 	fw = longest + 2;
! 	fct = (columns + 1) / fw;
! 	if (fct == 0) {
! 	    fct = 1;
! 	    colsz = ct;
! 	    up = colsz + nlnct - clearflag;
! 	    for (ap = arr; *ap; ap++)
! 		up += (strlenfn(*ap + off) - nboff + of +
! 		       ((ispattern || aylist) ? 0 :
! 			(!(haswhat & HAS_MISC) ?
! 			 nfpl + nfsl : nlpl + nlsl))) / columns;
! 	} else {
! 	    colsz = (ct + fct - 1) / fct;
! 	    up = colsz + nlnct - clearflag + (ct == 0);
! 	}
      }
  
      /* Print the explanation string, if any. */
***************
*** 3671,3677 ****
  	    while (*ap) {
  		int t2;
  
! 		if (ispattern) {
  		    int cut = strlen(*ap) - boff;
  
  		    sav = ap[0][cut];
--- 3759,3768 ----
  	    while (*ap) {
  		int t2;
  
! 		if (aylist) {
! 		    zputs(*ap, shout);
! 		    t2 = strlen(*ap);		    
! 		} else if (ispattern) {
  		    int cut = strlen(*ap) - boff;
  
  		    sav = ap[0][cut];

-- 
Peter Stephenson <pws@xxxxxx>       Tel: +49 33762 77366
WWW:  http://www.ifh.de/~pws/       Fax: +49 33762 77413
Deutsches Elektronen-Synchrotron --- Institut fuer Hochenergiephysik Zeuthen
DESY-IfH, Platanenallee 6, 15738 Zeuthen, Germany.



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