Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm
Precedence: bulk
X-No-Archive: yes
List-Id: Zsh Workers List <zsh-workers.zsh.org>
List-Post: <mailto:zsh-workers@zsh.org>
List-Help: <mailto:zsh-workers-help@zsh.org>
X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on f.primenet.com.au
X-Spam-Level: 
X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,T_DKIM_INVALID
	autolearn=ham autolearn_force=no version=3.4.1
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=brasslantern-com.20150623.gappssmtp.com; s=20150623;
        h=from:message-id:date:to:subject:mime-version;
        bh=+zCrLCmXjT2AIRNIQBD5df9APzYjN2Ifmcu5PsAIgrw=;
        b=lTXY43pUC0OwW+NRDAtiA7qxvfCQXRVm6+bBavAh+kipqor+VHuQvRqKsaTQyiBVUl
         4WtwwSzRhbdde0r7halg1XMuhl/lCayGk848yJ93XP5YaI1CE0OGGUDhTwzt+l2B0jBU
         LvS9GX0Pbfl91nrengjtnTAuuui0u3w7fBgRE0FfUrsqEK97Rq0eJDMkxW/mgCgvrrlw
         tXyxDnai+yCWK/pSh0APKUhkm7uE00RbRj8nlYSEjNFrlbrpy95F4NXr8x6HkUZNKfJU
         VxJaWTwuXB081x6D4A14aJcGCpv/StkX3dw2VWVdgR0R5gnvQ/GElnNUEE3kD4jDGiZr
         mXVg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20130820;
        h=x-gm-message-state:from:message-id:date:to:subject:mime-version;
        bh=+zCrLCmXjT2AIRNIQBD5df9APzYjN2Ifmcu5PsAIgrw=;
        b=ih/CI7y/QUQ33j3WHM/QNdjJjdXkoK8oQu+MfqIvvt9aITdreIbGnOUtrS7lJt58Mq
         vJCrR0X2ZVQMOjhpPeBEqpY4PJoEdVenaKMeGTYD3VcI46z1WyPqHlfC1UKOSj2vcuWm
         NV7fn1G9ormxYzZkHxH92gYOYLG1GflP9HcrsTnGF/oJL1/H0V8uJhuhy3Zi9RuO9a+U
         SfzssD2qi5nDpFZOoqNkllMbwwT+jSN3VA8TGQvrsuXax7rdO9PtFo2nF/8I6PTFfb0X
         o8kVQ0Y6Ki+8Ng+85sxgydywtCaqylL3a59NMfNJThqnuFE8J9SeShKZ5OYoxsvM11B7
         Qv7A==
X-Gm-Message-State: ALyK8tIW3PEKJOOMMxPsnwol+LLY6SaVSUaot42vP3G+V4+UWqrcMeEIvPrTqpTduI36qA==
X-Received: by 10.66.152.164 with SMTP id uz4mr18845246pab.9.1465782276456;
        Sun, 12 Jun 2016 18:44:36 -0700 (PDT)
From: Bart Schaefer <schaefer@brasslantern.com>
Message-Id: <160612184453.ZM11316@torch.brasslantern.com>
Date: Sun, 12 Jun 2016 18:44:53 -0700
X-Mailer: OpenZMail Classic (0.9.2 24April2005)
To: zsh-workers@zsh.org
Subject: [PATCH] add-zle-hook-widget
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
X-Seq: zsh-workers 38670

The idea and ultimate implementation are the same as in my "crude sketch"
but the function has been rewritten to parallel add-zsh-hook very closely.
This means among other things that you can only add one hook widget per
call to add-zle-hook-widget.

Also as briefly discussed, I removed add-zle-hook-function entirely and
made it a requirement that all the hooks be actual widgets.  However, as
a parallel to add-zsh-hook, the given name is both defined as a widget
and marked as an autoload function if the widget doesn't yet exist.

There are probably still bugs in there somewhere, but all worked as I
expected in a few simple tests.  I have not made much attempt to make
this backward-compatible with past zsh versions, but I think the only
conflict is with the arraname+=(value list) appending syntax.

Patch includes repairs to the add-zsh-hook documentation.


diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 923bb29..dd643a1 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -292,11 +292,11 @@ cindex(hook function utility)
 
 startitem()
 findex(add-zsh-hook)
-item(tt(add-zsh-hook) [ tt(-dD) ] [ tt(-Uzk) ] var(hook) var(function))(
+item(tt(add-zsh-hook) [ tt(-L) | tt(-dD) ] [ tt(-Uzk) ] var(hook) var(function))(
 Several functions are special to the shell, as described in the section
 ifnzman(Special Functions, noderef(Functions))\
 ifzman(SPECIAL FUNCTIONS, see zmanref(zshmisc)),
-in that they are automatic called at a specific point during shell execution.
+in that they are automatically called at specific points during shell execution.
 Each has an associated array consisting of names of functions to be
 called at the same point; these are so-called `hook functions'.
 The shell function tt(add-zsh-hook) provides a simple way of adding or
@@ -312,6 +312,9 @@ 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.
 
+If the option tt(-L) is given, the current values for the hook arrays
+are listed with tt(typeset).
+
 If the option tt(-d) is given, the var(function) is removed from
 the array of functions to be executed.
 
@@ -323,6 +326,55 @@ 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.
 )
+findex(add-zle-hook-widget)
+item(tt(add-zle-hook-widget) [ tt(-L) | tt(-dD) ] [ tt(-Uzk) ] var(hook) var(widgetname))(
+Several widget names are special to the line editor, as described in the section
+ifnzman(Special Widgets, noderef(Zle Widgets))\
+ifzman(Special Widgets, see zmanref(zshzle)),
+in that they are automatically called at specific points during editing.
+Unlike function hooks, these do not use a predefined array of other names
+to call at the same point; the shell function tt(add-zle-hook-widget)
+maintains a similar array and arranges for the special widget to invoke
+those additional widgets.
+
+var(hook) is one of tt(isearch-exit), tt(isearch-update),
+tt(line-pre-redraw), tt(line-init), tt(line-finish), tt(history-line-set),
+or tt(keymap-select), corresponding to each of the special widgets
+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) -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.
+
+If the option tt(-d) is given, the var(widgename) is removed from
+the array of widgets to be executed.
+
+If the option tt(-D) is given, the var(widgetname) is treated as a pattern
+and any matching names of widgets are removed from the array.
+
+If var(widgetname) does not name an existing widget when added to the
+array, it is assumed that a shell function also named var(widgetname) is
+meant to provide the implementation of the widget.  This name is therefore
+marked for autoloading, and the options tt(-U), tt(-z) and tt(-k) are
+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.
+)
 enditem()
 
 texinode(Recent Directories)(Other Directory Functions)(Utilities)(User Contributions)
diff --git a/Functions/Zle/add-zle-hook-widget b/Functions/Zle/add-zle-hook-widget
new file mode 100644
index 0000000..eeb0191
--- /dev/null
+++ b/Functions/Zle/add-zle-hook-widget
@@ -0,0 +1,140 @@
+# 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).
+#
+# 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.
+# 
+# With -d, remove the widget from the hook instead; delete the hook
+# variable 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.
+#
+# 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
+# is given, as are -z and -k.  (This is harmless if the function is
+# already defined.)  The WIDGET is then created with zle -N.
+
+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"
+    return 1
+}
+
+local -a hooktypes=( isearch-exit isearch-update
+                     line-pre-redraw line-init line-finish
+                     history-line-set keymap-select )
+# Stash in zstyle to make it global
+zstyle zle-hook types $hooktypes
+
+for hook in $hooktypes
+do
+  function zle-$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
+      done
+      return 0
+  }
+done
+
+# Redefine ourself with the setup left out
+
+function add-zle-hook-widget {
+    local -a hooktypes
+    zstyle -a zle-hook types hooktypes
+
+    # This part copied from add-zsh-hook
+    local usage="Usage: $0 hook widgetname\nValid hooks are:\n  $hooktypes"
+
+    local opt
+    local -a autoopts
+    integer del list help
+
+    while getopts "dDhLUzk" opt; do
+	case $opt in
+	    (d)
+	    del=1
+	    ;;
+
+	    (D)
+	    del=2
+	    ;;
+
+	    (h)
+	    help=1
+	    ;;
+
+	    (L)
+	    list=1
+	    ;;
+
+	    ([Uzk])
+	    autoopts+=(-$opt)
+	    ;;
+
+	    (*)
+	    return 1
+	    ;;
+	esac
+    done
+    shift $(( OPTIND - 1 ))
+
+    if (( list )); then
+	zstyle -L "zle-(${1:-${(@j:|:)hooktypes}})" widgets
+	return $?
+    elif (( help || $# != 2 || ${hooktypes[(I)${1#zle-}]} == 0 )); then
+	print -u$(( 2 - help )) $usage
+	return $(( 1 - help ))
+    fi
+
+    local -aU extant_hooks
+    local hook="zle-${1#zle-}"
+    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}}
+	    else
+		set -A extant_hooks ${extant_hooks:#(<->:|)$fn}
+	    fi
+            # unset if no remaining entries
+	    if (( ${#extant_hooks} )); then
+		zstyle "$hook" widgets "${extant_hooks[@]}"
+	    else
+		zstyle -d "$hook" widgets
+	    fi
+	fi
+    else
+	zstyle -g extant_hooks "$hook" widgets
+	extant_hooks+=("$fn")
+	zstyle -- "$hook" widgets "${extant_hooks[@]}"
+	if [[ -z "${widgets[$fn]}" ]]; then
+	    autoload "${autoopts[@]}" -- "$fn"
+	    zle -N "$fn"
+	fi
+	if [[ -z "${widgets[$hook]}" ]]; then
+	    zle -N "$hook"
+	fi
+    fi
+}
+
+# Handle zsh autoloading conventions
+if [[ "$zsh_eval_context" = *loadautofunc && ! -o kshautoload ]]; then
+    add-zle-hook-widget "$@"
+fi

