@@ -49,6 +49,52 @@ if true; then
4949 fi
5050fi
5151
52+ # This function takes a single argument F and returns True iff F is an autoload stub.
53+ _zsh_highlight__function_is_autoload_stub_p () {
54+ if zmodload -e zsh/parameter; then
55+ # (( ${+functions[$1]} )) &&
56+ [[ " $functions [$1 ]" == * " builtin autoload -X" * ]]
57+ else
58+ # [[ $(type -wa -- "$1") == *'function'* ]] &&
59+ [[ " ${${(@ f)" $( which -- " $1 " ) " } [2]} " == $' \t ' $histchars [3]' undefined' ]]
60+ fi
61+ # Do nothing here: return the exit code of the if.
62+ }
63+
64+ # Return True iff the argument denotes a function name.
65+ _zsh_highlight__is_function_p () {
66+ if zmodload -e zsh/parameter; then
67+ (( ${+functions[$1]} ))
68+ else
69+ [[ $( type -wa -- " $1 " ) == * ' function' * ]]
70+ fi
71+ }
72+
73+ # This function takes a single argument F and returns True iff F denotes the
74+ # name of a callable function. A function is callable if it is fully defined
75+ # or if it is marked for autoloading and autoloading it at the first call to it
76+ # will succeed. In particular, if a function has been marked for autoloading
77+ # but is not available in $fpath, then this function will return False therefor.
78+ #
79+ # See users/21671 http://www.zsh.org/cgi-bin/mla/redirect?USERNUMBER=21671
80+ _zsh_highlight__function_callable_p () {
81+ if _zsh_highlight__is_function_p " $1 " &&
82+ ! _zsh_highlight__function_is_autoload_stub_p " $1 "
83+ then
84+ # Already fully loaded.
85+ return 0 # true
86+ else
87+ # "$1" is either an autoload stub, or not a function at all.
88+ #
89+ # Use a subshell to avoid affecting the calling shell.
90+ #
91+ # We expect 'autoload +X' to return non-zero if it fails to fully load
92+ # the function.
93+ ( autoload -U +X -- " $1 " 2> /dev/null )
94+ return $?
95+ fi
96+ }
97+
5298# -------------------------------------------------------------------------------------------------
5399# Core highlighting update system
54100# -------------------------------------------------------------------------------------------------
@@ -347,76 +393,120 @@ _zsh_highlight_add_highlight()
347393# $1 is name of widget to call
348394_zsh_highlight_call_widget ()
349395{
350- builtin zle " $@ " &&
396+ builtin zle " $@ " &&
351397 _zsh_highlight
352398}
353399
354- # Rebind all ZLE widgets to make them invoke _zsh_highlights.
355- _zsh_highlight_bind_widgets ()
356- {
357- setopt localoptions noksharrays
358- typeset -F SECONDS
359- local prefix=orig-s$SECONDS -r$RANDOM # unique each time, in case we're sourced more than once
360-
361- # Load ZSH module zsh/zleparameter, needed to override user defined widgets.
362- zmodload zsh/zleparameter 2> /dev/null || {
363- print -r -- >&2 ' zsh-syntax-highlighting: failed loading zsh/zleparameter.'
364- return 1
400+ # Decide whether to use the zle-line-pre-redraw codepath (colloquially known as
401+ # "feature/redrawhook", after the topic branch's name) or the legacy "bind all
402+ # widgets" codepath.
403+ #
404+ # We use the new codepath under two conditions:
405+ #
406+ # 1. If it's available, which we check by testing for add-zle-hook-widget's availability.
407+ #
408+ # 2. If zsh has the memo= feature, which is required for interoperability reasons.
409+ # See issues #579 and #735, and the issues referenced from them.
410+ #
411+ # We check this with a plain version number check, since a functional check,
412+ # as done by _zsh_highlight, can only be done from inside a widget
413+ # function — a catch-22.
414+ #
415+ # See _zsh_highlight for the magic version number. (The use of 5.8.0.2
416+ # rather than 5.8.0.3 as in the _zsh_highlight is deliberate.)
417+ if is-at-least 5.8.0.2 && _zsh_highlight__function_callable_p add-zle-hook-widget
418+ then
419+ autoload -U add-zle-hook-widget
420+ _zsh_highlight__zle-line-finish () {
421+ # Reset $WIDGET since the 'main' highlighter depends on it.
422+ #
423+ # Since $WIDGET is declared by zle as read-only in this function's scope,
424+ # a nested function is required in order to shadow its built-in value;
425+ # see "User-defined widgets" in zshall.
426+ () {
427+ local -h -r WIDGET=zle-line-finish
428+ _zsh_highlight
429+ }
365430 }
431+ _zsh_highlight__zle-line-pre-redraw () {
432+ # Set $? to 0 for _zsh_highlight. Without this, subsequent
433+ # zle-line-pre-redraw hooks won't run, since add-zle-hook-widget happens to
434+ # call us with $? == 1 in the common case.
435+ true && _zsh_highlight " $@ "
436+ }
437+ _zsh_highlight_bind_widgets (){}
438+ if [[ -o zle ]]; then
439+ add-zle-hook-widget zle-line-pre-redraw _zsh_highlight__zle-line-pre-redraw
440+ add-zle-hook-widget zle-line-finish _zsh_highlight__zle-line-finish
441+ fi
442+ else
443+ # Rebind all ZLE widgets to make them invoke _zsh_highlights.
444+ _zsh_highlight_bind_widgets ()
445+ {
446+ setopt localoptions noksharrays
447+ typeset -F SECONDS
448+ local prefix=orig-s$SECONDS -r$RANDOM # unique each time, in case we're sourced more than once
449+
450+ # Load ZSH module zsh/zleparameter, needed to override user defined widgets.
451+ zmodload zsh/zleparameter 2> /dev/null || {
452+ print -r -- >&2 ' zsh-syntax-highlighting: failed loading zsh/zleparameter.'
453+ return 1
454+ }
366455
367- # Override ZLE widgets to make them invoke _zsh_highlight.
368- local -U widgets_to_bind
369- widgets_to_bind=(${${(k)widgets} :# (.* |run-help|which-command|beep|set-local-history|yank|yank-pop)} )
370-
371- # Always wrap special zle-line-finish widget. This is needed to decide if the
372- # current line ends and special highlighting logic needs to be applied.
373- # E.g. remove cursor imprint, don't highlight partial paths, ...
374- widgets_to_bind+=(zle-line-finish)
375-
376- # Always wrap special zle-isearch-update widget to be notified of updates in isearch.
377- # This is needed because we need to disable highlighting in that case.
378- widgets_to_bind+=(zle-isearch-update)
379-
380- local cur_widget
381- for cur_widget in $widgets_to_bind ; do
382- case ${widgets[$cur_widget]:- " " } in
383-
384- # Already rebound event: do nothing.
385- user:_zsh_highlight_widget_* );;
386-
387- # The "eval"'s are required to make $cur_widget a closure: the value of the parameter at function
388- # definition time is used.
389- #
390- # We can't use ${0/_zsh_highlight_widget_} because these widgets are always invoked with
391- # NO_function_argzero, regardless of the option's setting here.
392-
393- # User defined widget: override and rebind old one with prefix "orig-".
394- user:* ) zle -N $prefix -$cur_widget ${widgets[$cur_widget]#*: }
395- eval " _zsh_highlight_widget_${(q)prefix} -${(q)cur_widget} () { _zsh_highlight_call_widget ${(q)prefix} -${(q)cur_widget} -- \"\$ @\" }"
396- zle -N $cur_widget _zsh_highlight_widget_$prefix -$cur_widget ;;
397-
398- # Completion widget: override and rebind old one with prefix "orig-".
399- completion:* ) zle -C $prefix -$cur_widget ${${(s.: .)widgets[$cur_widget]} [2,3]}
400- eval " _zsh_highlight_widget_${(q)prefix} -${(q)cur_widget} () { _zsh_highlight_call_widget ${(q)prefix} -${(q)cur_widget} -- \"\$ @\" }"
401- zle -N $cur_widget _zsh_highlight_widget_$prefix -$cur_widget ;;
402-
403- # Builtin widget: override and make it call the builtin ".widget".
404- builtin) eval " _zsh_highlight_widget_${(q)prefix} -${(q)cur_widget} () { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$ @\" }"
405- zle -N $cur_widget _zsh_highlight_widget_$prefix -$cur_widget ;;
406-
407- # Incomplete or nonexistent widget: Bind to z-sy-h directly.
408- * )
409- if [[ $cur_widget == zle-* ]] && (( ! ${+widgets[$cur_widget]} )) ; then
410- _zsh_highlight_widget_${cur_widget} () { : ; _zsh_highlight }
411- zle -N $cur_widget _zsh_highlight_widget_$cur_widget
412- else
413- # Default: unhandled case.
414- print -r -- >&2 " zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget} "
415- print -r -- >&2 " zsh-syntax-highlighting: (This is sometimes caused by doing \` bindkey <keys> ${(q-)cur_widget} \` without creating the ${(qq)cur_widget} widget with \` zle -N\` or \` zle -C\` .)"
416- fi
417- esac
418- done
419- }
456+ # Override ZLE widgets to make them invoke _zsh_highlight.
457+ local -U widgets_to_bind
458+ widgets_to_bind=(${${(k)widgets} :# (.* |run-help|which-command|beep|set-local-history|yank|yank-pop)} )
459+
460+ # Always wrap special zle-line-finish widget. This is needed to decide if the
461+ # current line ends and special highlighting logic needs to be applied.
462+ # E.g. remove cursor imprint, don't highlight partial paths, ...
463+ widgets_to_bind+=(zle-line-finish)
464+
465+ # Always wrap special zle-isearch-update widget to be notified of updates in isearch.
466+ # This is needed because we need to disable highlighting in that case.
467+ widgets_to_bind+=(zle-isearch-update)
468+
469+ local cur_widget
470+ for cur_widget in $widgets_to_bind ; do
471+ case ${widgets[$cur_widget]:- " " } in
472+
473+ # Already rebound event: do nothing.
474+ user:_zsh_highlight_widget_* );;
475+
476+ # The "eval"'s are required to make $cur_widget a closure: the value of the parameter at function
477+ # definition time is used.
478+ #
479+ # We can't use ${0/_zsh_highlight_widget_} because these widgets are always invoked with
480+ # NO_function_argzero, regardless of the option's setting here.
481+
482+ # User defined widget: override and rebind old one with prefix "orig-".
483+ user:* ) zle -N $prefix -$cur_widget ${widgets[$cur_widget]#*: }
484+ eval " _zsh_highlight_widget_${(q)prefix} -${(q)cur_widget} () { _zsh_highlight_call_widget ${(q)prefix} -${(q)cur_widget} -- \"\$ @\" }"
485+ zle -N $cur_widget _zsh_highlight_widget_$prefix -$cur_widget ;;
486+
487+ # Completion widget: override and rebind old one with prefix "orig-".
488+ completion:* ) zle -C $prefix -$cur_widget ${${(s.: .)widgets[$cur_widget]} [2,3]}
489+ eval " _zsh_highlight_widget_${(q)prefix} -${(q)cur_widget} () { _zsh_highlight_call_widget ${(q)prefix} -${(q)cur_widget} -- \"\$ @\" }"
490+ zle -N $cur_widget _zsh_highlight_widget_$prefix -$cur_widget ;;
491+
492+ # Builtin widget: override and make it call the builtin ".widget".
493+ builtin) eval " _zsh_highlight_widget_${(q)prefix} -${(q)cur_widget} () { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$ @\" }"
494+ zle -N $cur_widget _zsh_highlight_widget_$prefix -$cur_widget ;;
495+
496+ # Incomplete or nonexistent widget: Bind to z-sy-h directly.
497+ * )
498+ if [[ $cur_widget == zle-* ]] && (( ! ${+widgets[$cur_widget]} )) ; then
499+ _zsh_highlight_widget_${cur_widget} () { : ; _zsh_highlight }
500+ zle -N $cur_widget _zsh_highlight_widget_$cur_widget
501+ else
502+ # Default: unhandled case.
503+ print -r -- >&2 " zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget} "
504+ print -r -- >&2 " zsh-syntax-highlighting: (This is sometimes caused by doing \` bindkey <keys> ${(q-)cur_widget} \` without creating the ${(qq)cur_widget} widget with \` zle -N\` or \` zle -C\` .)"
505+ fi
506+ esac
507+ done
508+ }
509+ fi
420510
421511# Load highlighters from directory.
422512#
0 commit comments