Subject: Re: zle hook conventions (was Re: bracket-paste-magic ...)
From: Daniel Shahaf <[email-masked]>
To: Zsh hackers list <[email-masked]>
Date: Fri, 03 Jun 2016 20:40:43 +0000
List-Id: Zsh Workers List <zsh-workers.zsh.org>

Bart Schaefer wrote on Thu, Jun 02, 2016 at 00:06:00 -0700:
> 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.
> 

I think it is possible for a plugin to hook into zle without interfering
with other plugins' hooks, without assuming anything about those other
plugins.  z-sy-h's hooking code attempts to achieve that (modulo a known
bug†):

https://github.com/zsh-users/zsh-syntax-highlighting/blob/47144ad9742a948337c6a0e315474b5fea94a6d3/zsh-syntax-highlighting.zsh#L243-L272

That said, I agree that having some an array of functions that are to be
called by a central dispatcher — as with add-zsh-hook and
add-zle-widget-hook — would sidestep this "devise a perfectly composable
wrapper" problem entirely.

> } 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.
> 

I think you're saying: if one plugin uses add-zle-hook-widget(), then
other plugins can coexist with that plugin by also using
add-zle-hook-widget().

I'm a bit wary of imposing dependencies on other plugins.  Might there
be a valid reason for those fourth-party plugins to prefer not to use
add-zle-hook-widget? 

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

Good point.

> } 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.
> 

Currently, no.  However, this subthread started when you mentioned (in
38485) that url-quote-magic considered hooking to zle-line-pre-redraw.

> Crudely sketched, I'm thinking of something like this:
> ⋮
> 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.

This appears to be a straightforward registrar API.

I would suggest making this idempotent by uniquifying the list of hooks
(in order to support 'source ~/.zshrc').

With my plugin author perspective, if I were to add a dependency on this
function, I'd want it to be easily available for users to install.
Where would add-zle-hook-widget's implementation live?  In Functions/ in
the zsh tree, or as a third-party project?

> 	zstyle -g extant_hooks "$hook" widgets
> 	extant_hooks+=("$@")
> 	zstyle -- "$hook" widgets "${extant_hooks[@]}"

Why use zstyle's instead of declaring a global array and appending to it?

Many thanks,

Daniel

† The linked excerpt's infinite recursion guard doesn't currently catch
the case that $cur_widget is a user-defined widget and ${widgets[$cur_widget]#user:}
is a function that calls «_zsh_highlight_widget_${cur_widget}»; however,
this should be simple to fix by embedding a nonce (e.g., $EPOCHREALTIME)
in the name of the wrapper functions and widgets.