Subject: zle hook conventions (was Re: bracket-paste-magic ...)
From: Bart Schaefer <[email-masked]>
To: Zsh hackers list <[email-masked]>
Date: Thu, 2 Jun 2016 00:06:00 -0700
List-Id: Zsh Workers List <zsh-workers.zsh.org>

On May 19,  9:14pm, Daniel Shahaf wrote:
}
} Bart Schaefer wrote on Sun, May 15, 2016 at 04:59:27 -0700:
} > Hence I also think that a plugin registry for the special zle hook
} > widgets ought to be implemented externally, and the plugins agree to
} > make use of it -- because if the plugins don't follow such a
} > convention then the user has to update .zshrc in lockstep with the
} > plugin installation anyway.
} 
} I don't follow your reasoning: whether .zshrc needs to be updated
} manually is orthogonal to how the plugin hooks into zle.

If you have two or more plugins referenced from .zshrc, and not all of
them follow the same convention for how to hook into zle, then any time
one of them changes the user has to make sure it hasn't now interfered
with the hooks of the others.

} It is about the mechanism used by zsh to find and run the code that
} _installs_ the zle hook, not about whether said installation is done
} by [...]

That's exactly my point: A convention no one uses is no better than no
convention at all, even if there's specialty C code instead of shell
code.

Further, an externally-defined convention can be made backward-compatible
to at least some older versions of zsh.

} I'm happy to keep this external, although if widgets shipped with zsh
} used this "external" plugin, it'd probably become de facto official.

Currently there aren't any functions shipped with zsh that would need
this.  The only references to the zle-* hooks in the shipped code are
tests for whether zle-line-init is currently in progress, not for using
any hooks to set up specific behavior.

Crudely sketched, I'm thinking of something like this:

    zmodload zsh/parameter || {
	print -u2 "Need parameter module for zle hooks"
	return 1
    }
    for hook in isearch-exit isearch-update \
		line-pre-redraw line-init line-finish \
		history-line-set keymap-select
    do
	function zle-$hook {
	    local -a hook_functions 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] functions hook_functions
		zstyle -a $funcstack[2] widgets hook_widgets
	    '
	    for hook in "${(@)${(@on)hook_functions}#*:}"
	    do
		"$hook" || return
	    done
	    for hook in "${@${(@on)hook_widgets}#*:}"
	    do
		zle "$hook" -Nw || return
	    done
	    return 0
	}
    done

    # This stuff with "is it a function or is it a widget?" is
    # another reason we didn't define internal arrays for this.
    # Maybe just require widgets, and discard the functions bit.

    add-zle-hook-function() {
	local -aU extant_hooks
	local hook="$1"
	shift
	zstyle -g extant_hooks "$hook" functions
	extant_hooks+=("$@")
	zstyle -- "$hook" functions "${extant_hooks[@]}"
	if [[ -z "${widgets[$hook]}" ]]; then
	    zle -N $hook
	fi
    }

    add-zle-hook-widget() {
	local -aU extant_hooks
	local hook="$1"
	shift
	zstyle -g extant_hooks "$hook" widgets
	extant_hooks+=("$@")
	zstyle -- "$hook" widgets "${extant_hooks[@]}"
	if [[ -z "${widgets[$hook]}" ]]; then
	    zle -N $hook
	fi
    }

Usage would be e.g. (inventing a function for example purposes):

    add-zle-hook-widget zle-line-pre-redraw 30:z-sy-h-redraw

If you leave out the number: prefix, it's assumed you don't care in
what order it executes, but it'll always be after the ones that do
have number prefixes.  Left up to the plugins to work out what number
they need/get.  Note you can call the same widget more than once by
adding it with two different numbers.