Subject: Re: add-zle-hook-widget Re: Next release (5.3)
From: Bart Schaefer <[email-masked]>
To: "Zsh Hackers' List" <[email-masked]>
Date: Wed, 13 Jul 2016 17:11:52 -0700
List-Id: Zsh Workers List <zsh-workers.zsh.org>

On Jul 13,  5:00am, Daniel Shahaf wrote:
} Subject: add-zle-hook-widget Re: Next release (5.3)
}
} Daniel Shahaf wrote on Thu, Jul 07, 2016 at 02:00:21 +0000:
} > How about including add-zle-hook-widget in 5.3, but without any indices
} > or before:/after: support
} 
} I've heard neither ayes nor nays, so here's a patch we can use if we
} choose this approach.
} 
} Shall we go for this patch then, or something else (what?)?

Other things have required my attention.

Here's a patch to the add-zle-hook-widget function which deletes the
reference to INDEX:NAME syntax and its implementation in the argument
list.  It's still visible in the stored record if you pass the -L
option just so I didn't have to rewrite too much of the rest of the
code and because a future patch might revive it.

This also handles some edge cases, like, what happens if you do

    autoload add-zle-hook-widget
    add-zle-hook-widget zle-line-init some-new-widget
    zle -N zle-line-finish my-line-finish
    add-zle-hook-widget zle-line-finish some-other-widget

Previously the second a-z-h-w would have been ignored because the check
for existing user widgets was only done at the first call.  Now it
checks on each call to see if the user has stepped on it and tries to
do the right thing.

I've also tried to fix the kshautoload handling.  Note that if you're
in the habit of doing goofy stuff like

	unsetopt kshautoload
	autoload -k +X somefunc
	somefunc

then you get what you deserve, i.e., somefunc will run twice the first
time you use it.  In the case of add-zsh-hook-widget, that's harmless.

The contrib.yo file needs to be updated, I wanted first to see whether
anyone spots any obvious problems with this rendition of the function.

diff --git a/Functions/Zle/add-zle-hook-widget b/Functions/Zle/add-zle-hook-widget
index 608a776..760e26d 100644
--- a/Functions/Zle/add-zle-hook-widget
+++ b/Functions/Zle/add-zle-hook-widget
@@ -1,18 +1,15 @@
 # Add to HOOK the given WIDGET
 # 
 # HOOK is one of isearch-exit, isearch-update, line-pre-redraw, line-init,
-# line-finish, history-line-set, keymap-select (the zle- prefix is not
-# required).
+# line-finish, history-line-set, keymap-select (the zle- prefix is allowed
+# but not required).  If a widget corresponding to HOOK already exists, it
+# is preserved and called first in the new set of HOOK widgets.
 #
-# 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.
+# With -d, remove the WIDGET from the hook instead; deletes the hook
+# linkage if it is empty.
 #
-# -D behaves like -d, but pattern characters are active in the
-# widget name, so any matching widget will be deleted from the hook.
+# -D behaves like -d, but pattern characters are active in WIDGET, so
+# any matching widget will be deleted from the hook.
 #
 # Without -d, if the WIDGET is not already defined, a function having the
 # same name is marked for autoload; -U is passed down to autoload if that
@@ -47,16 +44,15 @@ do
       # and we run them in number order
       zstyle -a $WIDGET widgets hook_widgets
       for hook in "${(@)${(@on)hook_widgets[@]}#<->:}"; do
-	  zle "$hook" -Nw "$@" || return
+	  if [[ "$hook" = user:* ]]; then
+	      # Preserve $WIDGET within the renamed widget
+	      zle "$hook" -N "$@"
+	  else
+	      zle "$hook" -Nw "$@"
+	  fi || 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
@@ -133,17 +129,19 @@ function add-zle-hook-widget {
     else
 	integer i=${#options[ksharrays]}-2
 	zstyle -g extant_hooks "$hook" widgets
-	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
+        # Check for an existing widget, add it as the first hook
+	if [[ ${widgets[$hook]} != "user:azhw:$hook" ]]; then
+	    zle -A "$hook" "${widgets[$hook]}"
+	    extant_hooks=(0:"${widgets[$hook]}" "${extant_hooks[@]}")
+	    zle -N "$hook" azhw:"$hook"
+	fi
+	# Add new widget only if not already in the hook list
+	if [[ -z ${(M)extant_hooks[@]:#(<->:|)$fn} ]]; then
+       	    # no index and not already hooked
+            # assign largest existing index plus 1
+	    i=${${(On@)${(@M)extant_hooks[@]#<->:}%:}[i]}+1
 	else
-	    i=${${(M)fn#<->:}%:}
-	    fn=${fn#<->:}
+	    return 0
 	fi
 	extant_hooks+=("${i}:${fn}")
 	zstyle -- "$hook" widgets "${extant_hooks[@]}"
@@ -157,7 +155,17 @@ function add-zle-hook-widget {
     fi
 }
 
-# Handle zsh autoloading conventions
-if [[ "$zsh_eval_context" = *loadautofunc && ! -o kshautoload ]]; then
-    add-zle-hook-widget "$@"
-fi
+# Handle zsh autoloading conventions:
+# - "file" appears last in zsh_eval_context when "source"-ing
+# - "evalautofunc" appears with kshautoload set or autoload -k
+# - "loadautofunc" appears with kshautoload unset or autoload -z
+# - use of autoload +X cannot reliably be detected, use best guess
+case "$zsh_eval_context" in
+*file) ;;
+*evalautofunc) ;;
+*loadautofunc) add-zle-hook-widget "$@";;
+*) [[ -o kshautoload ]] || add-zle-hook-widget "$@";;
+esac
+# Note fallback here is equivalent to the usual best-guess used by
+# functions written for zsh before $zsh_eval_context was available
+# so this case-statement is backward-compatible.