Subject: Re: [PATCH] add-zle-hook-widget
From: Bart Schaefer <[email-masked]>
To: [email-masked]
Date: Sat, 18 Jun 2016 16:25:58 -0700
List-Id: Zsh Workers List <zsh-workers.zsh.org>

On Jun 16, 10:20pm, Bart Schaefer wrote:
}
} What you have convinced me is that in the absence of an explicit index
} there's some value in retaining the order in which the add-* calls
} were made, which the code as last pushed doesn't.

The following implements this, as well as fixing some bugs, most notably
that "setopt ksharrays" broke a bunch of stuff.

There may still be other problems, and of course the discussion with Daniel
hasn't really finished yet.

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 64d93f9..b9c1c0a 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -345,14 +345,16 @@ tt(zle-isearch-exit), etc.
 var(widgetname) is the name of a ZLE widget.  If no options are given this
 is added to the array of widgets to be invoked in the given hook context.
 Note that the hooks are called as widgets, that is, with
-`tt(zle )var(widgetname)tt( -Nw)' rather than as a function call.
+example(tt(zle )var(widgetname)tt( -Nw "$@"))
+rather than as a function call.
 
-The arrays of var(widgetname) are maintained in several tt(zstyle)
-contexts, one for each var(hook) context, with a style of `tt(widgets)'.
-If the tt(-L) option is given, this set of styles is listed with
-`tt(zstyle -L)'.  These styles may be updated directly with tt(zstyle)
-commands, but the special widgets that refer to the styles are created
-only if tt(add-zle-hook-widget) is called to add at least one widget.
+In typical usage, var(widgetname) has the form var(index)tt(:)var(name).
+In this case var(index) is an integer which determines the order in which
+the widget var(name) will be called relative to other widgets in the
+array.  Widgets having the same var(index) are called in unspecified
+order.  However, var(widgetname) may omit the index, in which case an
+index is computed for it to arrange for it to be called in the order
+in which it was added to the array.
 
 If the option tt(-d) is given, the var(widgename) is removed from
 the array of widgets to be executed.
@@ -368,12 +370,14 @@ passed as arguments to tt(autoload) as with tt(add-zsh-hook).  The
 widget is also created with `tt(zle -N )var(widgetname)' to cause the
 corresponding function to be loaded the first time the hook is called.
 
-In addition, var(widgetname) may be of the form var(index)tt(:)var(name).
-In this case var(index) is an integer which determines the order in
-which the widget var(name) will be called relative to other widgets in
-the array.  Widgets having the same var(index) are called in unspecified
-order, and all widgets declared with an index are called before any
-widgets that have no index.
+
+The arrays of var(widgetname) are currently maintained in tt(zstyle)
+contexts, one for each var(hook) context, with a style of `tt(widgets)'.
+If the tt(-L) option is given, this set of styles is listed with
+`tt(zstyle -L)'.  This implementation may change, and the special widgets
+that refer to the styles are created only if tt(add-zle-hook-widget) is
+called to add at least one widget, so if this function is used for any
+hooks, then all hooks should be managed only via this function.
 )
 enditem()
 
diff --git a/Functions/Zle/add-zle-hook-widget b/Functions/Zle/add-zle-hook-widget
index eeb0191..608a776 100644
--- a/Functions/Zle/add-zle-hook-widget
+++ b/Functions/Zle/add-zle-hook-widget
@@ -6,6 +6,7 @@
 #
 # WIDGET may be of the form INDEX:NAME in which case the INDEX determines
 # the order in which the widget executes relative to other hook widgets.
+# Othewise the widget is assigned an index that appends it to the array.
 # 
 # With -d, remove the widget from the hook instead; delete the hook
 # variable if it is empty.
@@ -17,38 +18,45 @@
 # same name is marked for autoload; -U is passed down to autoload if that
 # is given, as are -z and -k.  (This is harmless if the function is
 # already defined.)  The WIDGET is then created with zle -N.
+#
+# The -L option lists the hooks and their associated widgets.
 
 emulate -L zsh
 
-# Setup - create the base functions for hook widgets that call the others
-
-zmodload zsh/parameter || {
-    print -u2 "Need parameter module for zle hooks"
+# This is probably more safeguarding than necessary
+zmodload -e zsh/zle || return 1
+{ zmodload zsh/parameter && zmodload zsh/zleparameter } || {
+    print -u2 "Need parameter modules for zle hooks"
     return 1
 }
 
-local -a hooktypes=( isearch-exit isearch-update
-                     line-pre-redraw line-init line-finish
-                     history-line-set keymap-select )
+# Setup - create the base functions for hook widgets that call the others
+
+local -a hooktypes=( zle-isearch-exit zle-isearch-update
+                     zle-line-pre-redraw zle-line-init zle-line-finish
+                     zle-history-line-set zle-keymap-select )
 # Stash in zstyle to make it global
-zstyle zle-hook types $hooktypes
+zstyle zle-hook types ${hooktypes#zle-}
 
 for hook in $hooktypes
 do
-  function zle-$hook {
+  function azhw:$hook {
       local -a hook_widgets
       local hook
       # Values of these styles look like number:name
       # and we run them in number order
-      # $funcstack is more reliable than $0
-      # Also, ksh_arrays is annoying
-      emulate zsh -c 'zstyle -a $funcstack[2] widgets hook_widgets'
-      for hook in "${@${(@on)hook_widgets}#*:}"
-      do
-	zle "$hook" -Nw || return
+      zstyle -a $WIDGET widgets hook_widgets
+      for hook in "${(@)${(@on)hook_widgets[@]}#<->:}"; do
+	  zle "$hook" -Nw "$@" || return
       done
       return 0
   }
+  # Check for an existing widget, add it as the first hook
+  if [[ ${widgets[$hook]} = user:* ]]; then
+      zle -A "$hook" "${widgets[$hook]}"
+      zstyle -- "$hook" widgets 0:"${widgets[$hook]}"
+      zle -N "$hook" azhw:"$hook"
+  fi
 done
 
 # Redefine ourself with the setup left out
@@ -93,25 +101,27 @@ function add-zle-hook-widget {
     done
     shift $(( OPTIND - 1 ))
 
+    1=${1#zle-}	# Strip prefix not stored in zle-hook types style
+
     if (( list )); then
-	zstyle -L "zle-(${1:-${(@j:|:)hooktypes}})" widgets
+	zstyle -L "zle-(${1:-${(@j:|:)hooktypes[@]}})" widgets
 	return $?
-    elif (( help || $# != 2 || ${hooktypes[(I)${1#zle-}]} == 0 )); then
+    elif (( help || $# != 2 || ${hooktypes[(I)$1]} == 0 )); then
 	print -u$(( 2 - help )) $usage
 	return $(( 1 - help ))
     fi
 
     local -aU extant_hooks
-    local hook="zle-${1#zle-}"
+    local hook="zle-$1"
     local fn="$2"
 
     if (( del )); then
         # delete, if hook is set
 	if zstyle -g extant_hooks "$hook" widgets; then
 	    if (( del == 2 )); then
-		set -A extant_hooks ${extant_hooks:#(<->:|)${~fn}}
+		set -A extant_hooks ${extant_hooks[@]:#(<->:|)${~fn}}
 	    else
-		set -A extant_hooks ${extant_hooks:#(<->:|)$fn}
+		set -A extant_hooks ${extant_hooks[@]:#(<->:|)$fn}
 	    fi
             # unset if no remaining entries
 	    if (( ${#extant_hooks} )); then
@@ -121,15 +131,28 @@ function add-zle-hook-widget {
 	    fi
 	fi
     else
+	integer i=${#options[ksharrays]}-2
 	zstyle -g extant_hooks "$hook" widgets
-	extant_hooks+=("$fn")
+	if [[ "$fn" != <->:* ]]; then
+	    if [[ -z ${(M)extant_hooks[@]:#(<->:|)$fn} ]]; then
+	        # no index and not already hooked
+		# assign largest existing index plus 10
+		i=${${(On@)${(@M)extant_hooks[@]#<->:}%:}[i]}+10
+	    else
+		return 0
+	    fi
+	else
+	    i=${${(M)fn#<->:}%:}
+	    fn=${fn#<->:}
+	fi
+	extant_hooks+=("${i}:${fn}")
 	zstyle -- "$hook" widgets "${extant_hooks[@]}"
 	if [[ -z "${widgets[$fn]}" ]]; then
 	    autoload "${autoopts[@]}" -- "$fn"
-	    zle -N "$fn"
+	    zle -N -- "$fn"
 	fi
 	if [[ -z "${widgets[$hook]}" ]]; then
-	    zle -N "$hook"
+	    zle -N "$hook" azhw:"$hook"
 	fi
     fi
 }