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

Re: possibly useful zsh_directory_name implementation



On Fri, 11 Mar 2011 10:44:02 +0000
Peter Stephenson <Peter.Stephenson@xxxxxxx> wrote:
> I've been meaning to add a zsh_directory_name hook, so you can have
> multiple functions implementing this and the function here would
> interact nicely with other functions that implemented naming without
> the "/" or "." at the start.  However, I need to think about the
> consequences --- it's more complicated than the existing hooks.

Seems straightforward, in fact; the call sequence is different from the
other hooks just owing to the arguments and the return value but
otherwise there doesn't appear to be anything funny going on.

This has allowed me to make the example use of cdr dynamic directories
into a contributed function that just needs adding to a hook.

Index: Completion/Zsh/Context/_dynamic_directory_name
===================================================================
RCS file: /cvsroot/zsh/zsh/Completion/Zsh/Context/_dynamic_directory_name,v
retrieving revision 1.2
diff -p -u -r1.2 _dynamic_directory_name
--- Completion/Zsh/Context/_dynamic_directory_name	11 Jun 2010 22:28:22 -0000	1.2
+++ Completion/Zsh/Context/_dynamic_directory_name	11 Mar 2011 16:24:24 -0000
@@ -1,7 +1,14 @@
 #autoload
 
-if [[ -n $functions[zsh_directory_name] ]]; then
-  zsh_directory_name c
+local func
+
+if [[ -n $functions[zsh_directory_name] || \
+  ${+zsh_directory_name_functions} -ne 0 ]] ; then
+  zsh_directory_name c && return 0
+  for func in $zsh_directory_name_functions; do
+    $func c && return 0
+  done
+  return 1
 else
   _message 'dynamic directory name: implemented as zsh_directory_name c'
 fi
Index: Doc/Zsh/contrib.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/contrib.yo,v
retrieving revision 1.126
diff -p -u -r1.126 contrib.yo
--- Doc/Zsh/contrib.yo	4 Mar 2011 13:25:26 -0000	1.126
+++ Doc/Zsh/contrib.yo	11 Mar 2011 16:24:25 -0000
@@ -302,10 +302,13 @@ called at the same point; these are so-c
 The shell function tt(add-zsh-hook) provides a simple way of adding or
 removing functions from the array.
 
-var(hook) is one of tt(chpwd), tt(periodic), tt(precmd) or tt(preexec),
-the special functions in question.
+var(hook) is one of tt(chpwd), tt(periodic), tt(precmd), tt(preexec),
+tt(zshaddhistory), tt(zshexit), or tt(zsh_directory_name),
+the special functions in question.  Note that tt(zsh_directory_name)
+is called in a different way from the other functions, but may
+still be manipulated as a hook.
 
-var(functions) is name of an ordinary shell function.  If no options
+var(function) is name of an ordinary shell function.  If no options
 are given this will be added to the array of functions to be executed
 in the given context.
 
@@ -315,6 +318,10 @@ the array of functions to be executed.
 If the option tt(-D) is given, the var(function) is treated as a pattern
 and any matching names of functions are removed from the array of
 functions to be executed.
+
+The options tt(-U), tt(-z) and tt(-k) are passed as arguments to
+tt(autoload) for var(function).  For functions contributed with zsh, the
+options tt(-Uz) are appropriate.
 )
 enditem()
 
@@ -544,36 +551,15 @@ enditem()
 subsect(Use with dynamic directory naming)
 
 It is possible to refer to recent directories using the dynamic directory
-name syntax that appeared in zsh version 4.3.7.  If you create and
-autoload a function tt(zsh_directory_name) containing the following code,
-tt(~[1]) will refer to the most recent directory other than $PWD, and so on.
-This also includes completion.
-
-example(if [[ $1 = n ]]; then
-  if [[ $2 = <-> ]]; then
-    # Recent directory
-    typeset -ga reply
-    autoload -Uz cdr
-    cdr -r
-    if [[ -n ${reply[$2]} ]]; then
-      reply=LPAR()${reply[$2]}RPAR()
-      return 0
-    else
-      reply=LPAR()RPAR()
-      return 1
-    fi
-  fi
-elif [[ $1 = c ]]; then
-  if [[ $PREFIX = <-> || -z $PREFIX ]]; then
-    typeset -a keys values
-    values=LPAR()${${(f)"$+LPAR()cdr -l+RPAR()"}/ ##/:}RPAR()
-    keys=LPAR()${values%%:*}RPAR()
-    _describe -t dir-index 'recent directory index' \
-      values keys -V unsorted -S']'
-    return
-  fi
-fi
-return 1)
+name syntax by using the supplied function tt(zsh_directory_name_cdr)
+a hook:
+
+example(autoload -Uz add-zsh-hook
+add-zsh-hook -Uz zsh_directory_name zsh_directory_name_cdr)
+
+When this is done, tt(~[1]) will refer to the most recent
+directory other than $PWD, and so on.  Completion after tt(~[)var(...)
+also works.
 
 subsect(Details of directory handling)
 
Index: Doc/Zsh/expn.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/expn.yo,v
retrieving revision 1.132
diff -p -u -r1.132 expn.yo
--- Doc/Zsh/expn.yo	4 Mar 2011 13:25:26 -0000	1.132
+++ Doc/Zsh/expn.yo	11 Mar 2011 16:24:25 -0000
@@ -1517,8 +1517,12 @@ cindex(directories, named, dynamic)
 cindex(named directories, dynamic)
 cindex(dynamic named directories)
 
-The feature described here is only available if the shell function
-tt(zsh_directory_name) exists.
+If the function tt(zsh_directory_name) exists, or the shell variable
+tt(zsh_directory_name_functions) exists and contains an array of
+function names, then the functions are used to implement dynamic
+directory naming.  The functions are tried in order until one returns
+status zero, so it is important that functions test whether they can
+handle the case in question and return an appropriate status.
 
 A `tt(~)' followed by a string var(namstr) in unquoted square brackets is
 treated specially as a dynamic directory name.  Note that the first
@@ -1529,11 +1533,12 @@ which is the directory corresponding to 
 (executing an assignment as the last statement is usually sufficient), or
 it should return status non-zero.  In the former case the element of reply
 is used as the directory; in the latter case the substitution is deemed to
-have failed and tt(NOMATCH) handling is applied if the option is set.
+have failed.  If all functions fail and the option tt(NOMATCH) is set,
+an error results.
 
-The function tt(zsh_directory_name) is also used to see if a directory can
+The functions defined as above are also used to see if a directory can
 be turned into a name, for example when printing the directory stack or
-when expanding tt(%~) in prompts.  In this case the function is passed two
+when expanding tt(%~) in prompts.  In this case each function is passed two
 arguments: the string tt(d) (for directory) and the candidate for dynamic
 naming.  The function should either return non-zero status, if the
 directory cannot be named by the function, or it should set the array reply
@@ -1551,7 +1556,15 @@ parts of the directory path, as describe
 length matched (16 in the example) is longer than that matched by any
 static name.
 
-The completion system calls `tt(zsh_directory_name c)' in order to
+It is not a requirement that a function implements both
+tt(n) and tt(d) calls; for example, it might be appropriate for certain
+dynamic forms of expansion not to be contracted to names.  In that case
+any call with the first argument tt(d) should cause a non-zero status to
+be returned.
+
+The completion system calls `tt(zsh_directory_name c)' followed by
+equivalent calls to elements of the array
+tt(zsh_directory_name_functions), if it exists, in order to
 complete dynamic names for directories.  The code for this should be
 as for any other completion function as described in
 ifnzman(noderef(Completion System))\
Index: Functions/Chpwd/.distfiles
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/Chpwd/.distfiles,v
retrieving revision 1.1
diff -p -u -r1.1 .distfiles
--- Functions/Chpwd/.distfiles	9 Jul 2010 14:47:48 -0000	1.1
+++ Functions/Chpwd/.distfiles	11 Mar 2011 16:24:25 -0000
@@ -5,4 +5,5 @@ _cdr
 chpwd_recent_add
 chpwd_recent_dirs
 chpwd_recent_filehandler
+zsh_directory_name_cdr
 '
Index: Functions/Chpwd/zsh_directory_name_cdr
===================================================================
RCS file: Functions/Chpwd/zsh_directory_name_cdr
diff -N Functions/Chpwd/zsh_directory_name_cdr
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ Functions/Chpwd/zsh_directory_name_cdr	11 Mar 2011 16:24:25 -0000
@@ -0,0 +1,25 @@
+if [[ $1 = n ]]; then
+  if [[ $2 = <-> ]]; then
+    # Recent directory
+    typeset -ga reply
+    autoload -Uz cdr
+    cdr -r
+    if [[ -n ${reply[$2]} ]]; then
+      reply=(${reply[$2]})
+      return 0
+    else
+      reply=()
+      return 1
+    fi
+  fi
+elif [[ $1 = c ]]; then
+  if [[ $PREFIX = <-> || -z $PREFIX ]]; then
+    typeset -a keys values
+    values=(${${(f)"$(cdr -l)"}/ ##/:})
+    keys=(${values%%:*})
+    _describe -t dir-index 'recent directory index' \
+      values keys -V unsorted -S']'
+    return
+  fi
+fi
+return 1
Index: Functions/Misc/add-zsh-hook
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/Misc/add-zsh-hook,v
retrieving revision 1.5
diff -p -u -r1.5 add-zsh-hook
--- Functions/Misc/add-zsh-hook	1 Aug 2008 11:48:42 -0000	1.5
+++ Functions/Misc/add-zsh-hook	11 Mar 2011 16:24:25 -0000
@@ -1,6 +1,6 @@
 # Add to HOOK the given FUNCTION.
 # HOOK is one of chpwd, precmd, preexec, periodic, zshaddhistory,
-# zshexit (the _functions subscript is not required).
+# zshexit, zsh_directory_name (the _functions subscript is not required).
 #
 # With -d, remove the function from the hook instead; delete the hook
 # variable if it is empty.
@@ -15,7 +15,10 @@
 emulate -L zsh
 
 local -a hooktypes
-hooktypes=(chpwd precmd preexec periodic zshaddhistory zshexit)
+hooktypes=(
+  chpwd precmd preexec periodic zshaddhistory zshexit
+  zsh_directory_name
+)
 
 local opt
 local -a autoopts
Index: Src/subst.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/subst.c,v
retrieving revision 1.116
diff -p -u -r1.116 subst.c
--- Src/subst.c	18 Jan 2011 10:29:58 -0000	1.116
+++ Src/subst.c	11 Mar 2011 16:24:25 -0000
@@ -579,7 +579,6 @@ filesubstr(char **namptr, int assign)
     char *str = *namptr;
 
     if (*str == Tilde && str[1] != '=' && str[1] != Equals) {
-	Shfunc dirfunc;
 	char *ptr, *tmp, *res, *ptr2;
 	int val;
 
@@ -594,12 +593,11 @@ filesubstr(char **namptr, int assign)
 	    *namptr = dyncat((tmp = oldpwd) ? tmp : pwd, str + 2);
 	    return 1;
 	} else if (str[1] == Inbrack &&
-		   (dirfunc = getshfunc("zsh_directory_name")) &&
 		   (ptr2 = strchr(str+2, Outbrack))) {
 	    char **arr;
 	    untokenize(tmp = dupstrpfx(str+2, ptr2 - (str+2)));
 	    remnulargs(tmp);
-	    arr = subst_string_by_func(dirfunc, "n", tmp);
+	    arr = subst_string_by_hook("zsh_directory_name", "n", tmp);
 	    res = arr ? *arr : NULL;
 	    if (res) {
 		*namptr = dyncat(res, ptr2+1);
Index: Src/utils.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/utils.c,v
retrieving revision 1.255
diff -p -u -r1.255 utils.c
--- Src/utils.c	6 Jan 2011 21:31:06 -0000	1.255
+++ Src/utils.c	11 Mar 2011 16:24:25 -0000
@@ -890,7 +890,8 @@ finddir(char *s)
 {
     static struct nameddir homenode = { {NULL, "", 0}, NULL, 0 };
     static int ffsz;
-    Shfunc func = getshfunc("zsh_directory_name");
+    char **ares;
+    int len;
 
     /* Invalidate directory cache if argument is NULL.  This is called *
      * whenever a node is added to or removed from the hash table, and *
@@ -906,9 +907,16 @@ finddir(char *s)
 	return finddir_last = NULL;
     }
 
-    /* It's not safe to use the cache while we have function transformations.*/
-    if(!func && !strcmp(s, finddir_full) && *finddir_full)
+#if 0
+    /*
+     * It's not safe to use the cache while we have function
+     * transformations, and it's not clear it's worth the
+     * complexity of guessing here whether subst_string_by_hook
+     * is going to turn up the goods.
+     */
+    if (!strcmp(s, finddir_full) && *finddir_full)
 	return finddir_last;
+#endif
 
     if ((int)strlen(s) >= ffsz) {
 	free(finddir_full);
@@ -920,18 +928,15 @@ finddir(char *s)
     finddir_scan(&homenode.node, 0);
     scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0);
 
-    if (func) {
-	char **ares = subst_string_by_func(func, "d", finddir_full);
-	int len;
-	if (ares && arrlen(ares) >= 2 &&
-	    (len = (int)zstrtol(ares[1], NULL, 10)) > finddir_best) {
-	    /* better duplicate this string since it's come from REPLY */
-	    finddir_last = (Nameddir)hcalloc(sizeof(struct nameddir));
-	    finddir_last->node.nam = zhtricat("[", dupstring(ares[0]), "]");
-	    finddir_last->dir = dupstrpfx(finddir_full, len);
-	    finddir_last->diff = len - strlen(finddir_last->node.nam);
-	    finddir_best = len;
-	}
+    ares = subst_string_by_hook("zsh_directory_name", "d", finddir_full);
+    if (ares && arrlen(ares) >= 2 &&
+	(len = (int)zstrtol(ares[1], NULL, 10)) > finddir_best) {
+	/* better duplicate this string since it's come from REPLY */
+	finddir_last = (Nameddir)hcalloc(sizeof(struct nameddir));
+	finddir_last->node.nam = zhtricat("[", dupstring(ares[0]), "]");
+	finddir_last->dir = dupstrpfx(finddir_full, len);
+	finddir_last->diff = len - strlen(finddir_last->node.nam);
+	finddir_best = len;
     }
 
     return finddir_last;
@@ -3212,7 +3217,7 @@ getshfunc(char *nam)
 char **
 subst_string_by_func(Shfunc func, char *arg1, char *orig)
 {
-    int osc = sfcontext;
+    int osc = sfcontext, osm = stopmsg;
     LinkList l = newlinklist();
     char **ret;
 
@@ -3228,6 +3233,47 @@ subst_string_by_func(Shfunc func, char *
 	ret = getaparam("reply");
 
     sfcontext = osc;
+    stopmsg = osm;
+    return ret;
+}
+
+/**
+ * Front end to subst_string_by_func to use hook-like logic.
+ * name can refer to a function, and name + "_hook" can refer
+ * to an array containing a list of functions.  The functions
+ * are tried in order until one returns success.
+ */
+/**/
+char **
+subst_string_by_hook(char *name, char *arg1, char *orig)
+{
+    Shfunc func;
+    char **ret = NULL;
+
+    if ((func = getshfunc(name))) {
+	ret = subst_string_by_func(func, arg1, orig);
+    }
+
+    if (!ret) {
+	char **arrptr;
+	int namlen = strlen(name);
+	VARARR(char, arrnam, namlen + HOOK_SUFFIX_LEN);
+	memcpy(arrnam, name, namlen);
+	memcpy(arrnam + namlen, HOOK_SUFFIX, HOOK_SUFFIX_LEN);
+
+	if ((arrptr = getaparam(arrnam))) {
+	    /* Guard against internal modification of the array */
+	    arrptr = arrdup(arrptr);
+	    for (; *arrptr; arrptr++) {
+		if ((func = getshfunc(*arrptr))) {
+		    ret = subst_string_by_func(func, arg1, orig);
+		    if (ret)
+			break;
+		}
+	    }
+	}
+    }
+
     return ret;
 }
 


-- 
Peter Stephenson <pws@xxxxxxx>            Software Engineer
Tel: +44 (0)1223 692070                   Cambridge Silicon Radio Limited
Churchill House, Cambridge Business Park, Cowley Road, Cambridge, CB4 0WZ, UK


Member of the CSR plc group of companies. CSR plc registered in England and Wales, registered number 4187346, registered office Churchill House, Cambridge Business Park, Cowley Road, Cambridge, CB4 0WZ, United Kingdom



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