|
| 1 | +# ------------------------------------------------------------------------------------------------- |
| 2 | +# Copyright (c) 2010-2020 zsh-syntax-highlighting contributors |
| 3 | +# All rights reserved. |
| 4 | +# |
| 5 | +# Redistribution and use in source and binary forms, with or without modification, are permitted |
| 6 | +# provided that the following conditions are met: |
| 7 | +# |
| 8 | +# * Redistributions of source code must retain the above copyright notice, this list of conditions |
| 9 | +# and the following disclaimer. |
| 10 | +# * Redistributions in binary form must reproduce the above copyright notice, this list of |
| 11 | +# conditions and the following disclaimer in the documentation and/or other materials provided |
| 12 | +# with the distribution. |
| 13 | +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors |
| 14 | +# may be used to endorse or promote products derived from this software without specific prior |
| 15 | +# written permission. |
| 16 | +# |
| 17 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR |
| 18 | +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| 19 | +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| 20 | +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 21 | +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 22 | +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
| 23 | +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| 24 | +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 25 | +# ------------------------------------------------------------------------------------------------- |
| 26 | +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- |
| 27 | +# vim: ft=zsh sw=2 ts=2 et |
| 28 | +# ------------------------------------------------------------------------------------------------- |
| 29 | + |
| 30 | +# Set $0 to the expected value, regardless of functionargzero. |
| 31 | +0=${(%):-%N} |
| 32 | +fpath+=(${0:a:h}) |
| 33 | +if true; then |
| 34 | + # $0 is reliable |
| 35 | + typeset -g ZSH_HIGHLIGHT_VERSION=$(<"${0:A:h}"/.version) |
| 36 | + typeset -g ZSH_HIGHLIGHT_REVISION=$(<"${0:A:h}"/.revision-hash) |
| 37 | + if [[ $ZSH_HIGHLIGHT_REVISION == \$Format:* ]]; then |
| 38 | + # When running from a source tree without 'make install', $ZSH_HIGHLIGHT_REVISION |
| 39 | + # would be set to '$Format:%H$' literally. That's an invalid value, and obtaining |
| 40 | + # the valid value (via `git rev-parse HEAD`, as Makefile does) might be costly, so: |
| 41 | + ZSH_HIGHLIGHT_REVISION=HEAD |
| 42 | + fi |
| 43 | +fi |
| 44 | + |
| 45 | +# This function takes a single argument F and returns True iff F is an autoload stub. |
| 46 | +_zsh_highlight__function_is_autoload_stub_p() { |
| 47 | + if zmodload -e zsh/parameter; then |
| 48 | + #(( ${+functions[$1]} )) && |
| 49 | + [[ "$functions[$1]" == *"builtin autoload -X"* ]] |
| 50 | + else |
| 51 | + #[[ $(type -wa -- "$1") == *'function'* ]] && |
| 52 | + [[ "${${(@f)"$(which -- "$1")"}[2]}" == $'\t'$histchars[3]' undefined' ]] |
| 53 | + fi |
| 54 | + # Do nothing here: return the exit code of the if. |
| 55 | +} |
| 56 | + |
| 57 | +# Return True iff the argument denotes a function name. |
| 58 | +_zsh_highlight__is_function_p() { |
| 59 | + if zmodload -e zsh/parameter; then |
| 60 | + (( ${+functions[$1]} )) |
| 61 | + else |
| 62 | + [[ $(type -wa -- "$1") == *'function'* ]] |
| 63 | + fi |
| 64 | +} |
| 65 | + |
| 66 | +# This function takes a single argument F and returns True iff F denotes the |
| 67 | +# name of a callable function. A function is callable if it is fully defined |
| 68 | +# or if it is marked for autoloading and autoloading it at the first call to it |
| 69 | +# will succeed. In particular, if a function has been marked for autoloading |
| 70 | +# but is not available in $fpath, then this function will return False therefor. |
| 71 | +# |
| 72 | +# See users/21671 http://www.zsh.org/cgi-bin/mla/redirect?USERNUMBER=21671 |
| 73 | +_zsh_highlight__function_callable_p() { |
| 74 | + if _zsh_highlight__is_function_p "$1" && |
| 75 | + ! _zsh_highlight__function_is_autoload_stub_p "$1" |
| 76 | + then |
| 77 | + # Already fully loaded. |
| 78 | + return 0 # true |
| 79 | + else |
| 80 | + # "$1" is either an autoload stub, or not a function at all. |
| 81 | + # |
| 82 | + # Use a subshell to avoid affecting the calling shell. |
| 83 | + # |
| 84 | + # We expect 'autoload +X' to return non-zero if it fails to fully load |
| 85 | + # the function. |
| 86 | + ( autoload -U +X -- "$1" 2>/dev/null ) |
| 87 | + return $? |
| 88 | + fi |
| 89 | +} |
| 90 | + |
| 91 | +# ------------------------------------------------------------------------------------------------- |
| 92 | +# Core highlighting update system |
| 93 | +# ------------------------------------------------------------------------------------------------- |
| 94 | + |
| 95 | +# Use workaround for bug in ZSH? |
| 96 | +# zsh-users/zsh@48cadf4 http://www.zsh.org/mla/workers//2017/msg00034.html |
| 97 | +autoload -Uz is-at-least |
| 98 | +if is-at-least 5.4; then |
| 99 | + typeset -g zsh_highlight__pat_static_bug=false |
| 100 | +else |
| 101 | + typeset -g zsh_highlight__pat_static_bug=true |
| 102 | +fi |
| 103 | + |
| 104 | +# Array declaring active highlighters names. |
| 105 | +typeset -ga ZSH_HIGHLIGHT_HIGHLIGHTERS |
| 106 | + |
| 107 | + |
| 108 | +# ------------------------------------------------------------------------------------------------- |
| 109 | +# API/utility functions for highlighters |
| 110 | +# ------------------------------------------------------------------------------------------------- |
| 111 | + |
| 112 | +# Array used by highlighters to declare user overridable styles. |
| 113 | +typeset -gA ZSH_HIGHLIGHT_STYLES |
| 114 | + |
| 115 | +# Whether the command line buffer has been modified or not. |
| 116 | +# |
| 117 | +# Returns 0 if the buffer has changed since _zsh_highlight was last called. |
| 118 | +_zsh_highlight_buffer_modified() |
| 119 | +{ |
| 120 | + [[ "${_ZSH_HIGHLIGHT_PRIOR_BUFFER:-}" != "$BUFFER" ]] |
| 121 | +} |
| 122 | + |
| 123 | +# Whether the cursor has moved or not. |
| 124 | +# |
| 125 | +# Returns 0 if the cursor has moved since _zsh_highlight was last called. |
| 126 | +_zsh_highlight_cursor_moved() |
| 127 | +{ |
| 128 | + [[ -n $CURSOR ]] && [[ -n ${_ZSH_HIGHLIGHT_PRIOR_CURSOR-} ]] && (($_ZSH_HIGHLIGHT_PRIOR_CURSOR != $CURSOR)) |
| 129 | +} |
| 130 | + |
| 131 | +# Add a highlight defined by ZSH_HIGHLIGHT_STYLES. |
| 132 | +# |
| 133 | +# Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). |
| 134 | +# Overwritten in tests/test-highlighting.zsh when testing. |
| 135 | +_zsh_highlight_add_highlight() |
| 136 | +{ |
| 137 | + local -i start end |
| 138 | + local highlight |
| 139 | + start=$1 |
| 140 | + end=$2 |
| 141 | + shift 2 |
| 142 | + for highlight; do |
| 143 | + if (( $+ZSH_HIGHLIGHT_STYLES[$highlight] )); then |
| 144 | + region_highlight+=("$start $end $ZSH_HIGHLIGHT_STYLES[$highlight], memo=zsh-syntax-highlighting") |
| 145 | + break |
| 146 | + fi |
| 147 | + done |
| 148 | +} |
| 149 | + |
| 150 | +# ------------------------------------------------------------------------------------------------- |
| 151 | +# Setup functions |
| 152 | +# ------------------------------------------------------------------------------------------------- |
| 153 | + |
| 154 | +# Helper for _zsh_highlight_bind_widgets |
| 155 | +# $1 is name of widget to call |
| 156 | +_zsh_highlight_call_widget() |
| 157 | +{ |
| 158 | + builtin zle "$@" && |
| 159 | + _zsh_highlight |
| 160 | +} |
| 161 | + |
| 162 | +# Decide whether to use the zle-line-pre-redraw codepath (colloquially known as |
| 163 | +# "feature/redrawhook", after the topic branch's name) or the legacy "bind all |
| 164 | +# widgets" codepath. |
| 165 | +# |
| 166 | +# We use the new codepath under two conditions: |
| 167 | +# |
| 168 | +# 1. If it's available, which we check by testing for add-zle-hook-widget's availability. |
| 169 | +# |
| 170 | +# 2. If zsh has the memo= feature, which is required for interoperability reasons. |
| 171 | +# See issues #579 and #735, and the issues referenced from them. |
| 172 | +# |
| 173 | +# We check this with a plain version number check, since a functional check, |
| 174 | +# as done by _zsh_highlight, can only be done from inside a widget |
| 175 | +# function — a catch-22. |
| 176 | +# |
| 177 | +# See _zsh_highlight for the magic version number. (The use of 5.8.0.2 |
| 178 | +# rather than 5.8.0.3 as in the _zsh_highlight is deliberate.) |
| 179 | +if is-at-least 5.8.0.2 && _zsh_highlight__function_callable_p add-zle-hook-widget |
| 180 | +then |
| 181 | + autoload -U add-zle-hook-widget |
| 182 | + _zsh_highlight__zle-line-finish() { |
| 183 | + # Reset $WIDGET since the 'main' highlighter depends on it. |
| 184 | + # |
| 185 | + # Since $WIDGET is declared by zle as read-only in this function's scope, |
| 186 | + # a nested function is required in order to shadow its built-in value; |
| 187 | + # see "User-defined widgets" in zshall. |
| 188 | + () { |
| 189 | + local -h -r WIDGET=zle-line-finish |
| 190 | + _zsh_highlight |
| 191 | + } |
| 192 | + } |
| 193 | + _zsh_highlight__zle-line-pre-redraw() { |
| 194 | + # Set $? to 0 for _zsh_highlight. Without this, subsequent |
| 195 | + # zle-line-pre-redraw hooks won't run, since add-zle-hook-widget happens to |
| 196 | + # call us with $? == 1 in the common case. |
| 197 | + true && _zsh_highlight "$@" |
| 198 | + } |
| 199 | + _zsh_highlight_bind_widgets(){} |
| 200 | + if [[ -o zle ]]; then |
| 201 | + add-zle-hook-widget zle-line-pre-redraw _zsh_highlight__zle-line-pre-redraw |
| 202 | + add-zle-hook-widget zle-line-finish _zsh_highlight__zle-line-finish |
| 203 | + fi |
| 204 | +else |
| 205 | + # Rebind all ZLE widgets to make them invoke _zsh_highlights. |
| 206 | + _zsh_highlight_bind_widgets() |
| 207 | + { |
| 208 | + setopt localoptions noksharrays |
| 209 | + typeset -F SECONDS |
| 210 | + local prefix=orig-s$SECONDS-r$RANDOM # unique each time, in case we're sourced more than once |
| 211 | + |
| 212 | + # Load ZSH module zsh/zleparameter, needed to override user defined widgets. |
| 213 | + zmodload zsh/zleparameter 2>/dev/null || { |
| 214 | + print -r -- >&2 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' |
| 215 | + return 1 |
| 216 | + } |
| 217 | + |
| 218 | + # Override ZLE widgets to make them invoke _zsh_highlight. |
| 219 | + local -U widgets_to_bind |
| 220 | + widgets_to_bind=(${${(k)widgets}:#(.*|run-help|which-command|beep|set-local-history|yank|yank-pop)}) |
| 221 | + |
| 222 | + # Always wrap special zle-line-finish widget. This is needed to decide if the |
| 223 | + # current line ends and special highlighting logic needs to be applied. |
| 224 | + # E.g. remove cursor imprint, don't highlight partial paths, ... |
| 225 | + widgets_to_bind+=(zle-line-finish) |
| 226 | + |
| 227 | + # Always wrap special zle-isearch-update widget to be notified of updates in isearch. |
| 228 | + # This is needed because we need to disable highlighting in that case. |
| 229 | + widgets_to_bind+=(zle-isearch-update) |
| 230 | + |
| 231 | + local cur_widget |
| 232 | + for cur_widget in $widgets_to_bind; do |
| 233 | + case ${widgets[$cur_widget]:-""} in |
| 234 | + |
| 235 | + # Already rebound event: do nothing. |
| 236 | + user:_zsh_highlight_widget_*);; |
| 237 | + |
| 238 | + # The "eval"'s are required to make $cur_widget a closure: the value of the parameter at function |
| 239 | + # definition time is used. |
| 240 | + # |
| 241 | + # We can't use ${0/_zsh_highlight_widget_} because these widgets are always invoked with |
| 242 | + # NO_function_argzero, regardless of the option's setting here. |
| 243 | + |
| 244 | + # User defined widget: override and rebind old one with prefix "orig-". |
| 245 | + user:*) zle -N $prefix-$cur_widget ${widgets[$cur_widget]#*:} |
| 246 | + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" |
| 247 | + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; |
| 248 | + |
| 249 | + # Completion widget: override and rebind old one with prefix "orig-". |
| 250 | + completion:*) zle -C $prefix-$cur_widget ${${(s.:.)widgets[$cur_widget]}[2,3]} |
| 251 | + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" |
| 252 | + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; |
| 253 | + |
| 254 | + # Builtin widget: override and make it call the builtin ".widget". |
| 255 | + builtin) eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$@\" }" |
| 256 | + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; |
| 257 | + |
| 258 | + # Incomplete or nonexistent widget: Bind to z-sy-h directly. |
| 259 | + *) |
| 260 | + if [[ $cur_widget == zle-* ]] && (( ! ${+widgets[$cur_widget]} )); then |
| 261 | + _zsh_highlight_widget_${cur_widget}() { :; _zsh_highlight } |
| 262 | + zle -N $cur_widget _zsh_highlight_widget_$cur_widget |
| 263 | + else |
| 264 | + # Default: unhandled case. |
| 265 | + print -r -- >&2 "zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget}" |
| 266 | + 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\`.)" |
| 267 | + fi |
| 268 | + esac |
| 269 | + done |
| 270 | + } |
| 271 | +fi |
| 272 | + |
| 273 | +# Load highlighters from directory. |
| 274 | +# |
| 275 | +# Arguments: |
| 276 | +# 1) Path to the highlighters directory. |
| 277 | +_zsh_highlight_load_highlighters() |
| 278 | +{ |
| 279 | + setopt localoptions noksharrays bareglobqual |
| 280 | + |
| 281 | + # Check the directory exists. |
| 282 | + [[ -d "$1" ]] || { |
| 283 | + print -r -- >&2 "zsh-syntax-highlighting: highlighters directory ${(qq)1} not found." |
| 284 | + return 1 |
| 285 | + } |
| 286 | + |
| 287 | + # Load highlighters from highlighters directory and check they define required functions. |
| 288 | + local highlighter highlighter_dir |
| 289 | + for highlighter_dir ($1/*/(/)); do |
| 290 | + highlighter="${highlighter_dir:t}" |
| 291 | + [[ -f "$highlighter_dir${highlighter}-highlighter.zsh" ]] && |
| 292 | + . "$highlighter_dir${highlighter}-highlighter.zsh" |
| 293 | + if [[ -f "${highlighter_dir}_zsh_highlight_highlighter_${highlighter}_paint" ]] && |
| 294 | + [[ -f "${highlighter_dir}_zsh_highlight_highlighter_${highlighter}_predicate" ]]; then |
| 295 | + # New (0.8.0) autoload style highlighter |
| 296 | + fpath+=(${highlighter_dir%/}) |
| 297 | + autoload -Uz "_zsh_highlight_highlighter_${highlighter}_paint" "_zsh_highlight_highlighter_${highlighter}_predicate" |
| 298 | + else |
| 299 | + if type "_zsh_highlight_highlighter_${highlighter}_paint" &> /dev/null && |
| 300 | + type "_zsh_highlight_highlighter_${highlighter}_predicate" &> /dev/null; |
| 301 | + then |
| 302 | + # New (0.5.0) function names |
| 303 | + elif type "_zsh_highlight_${highlighter}_highlighter" &> /dev/null && |
| 304 | + type "_zsh_highlight_${highlighter}_highlighter_predicate" &> /dev/null; |
| 305 | + then |
| 306 | + # Old (0.4.x) function names |
| 307 | + if false; then |
| 308 | + # TODO: only show this warning for plugin authors/maintainers, not for end users |
| 309 | + print -r -- >&2 "zsh-syntax-highlighting: warning: ${(qq)highlighter} highlighter uses deprecated entry point names; please ask its maintainer to update it: https://github.com/zsh-users/zsh-syntax-highlighting/issues/329" |
| 310 | + fi |
| 311 | + # Make it work. |
| 312 | + eval "_zsh_highlight_highlighter_${(q)highlighter}_paint() { _zsh_highlight_${(q)highlighter}_highlighter \"\$@\" }" |
| 313 | + eval "_zsh_highlight_highlighter_${(q)highlighter}_predicate() { _zsh_highlight_${(q)highlighter}_highlighter_predicate \"\$@\" }" |
| 314 | + else |
| 315 | + print -r -- >&2 "zsh-syntax-highlighting: ${(qq)highlighter} highlighter should define both required functions '_zsh_highlight_highlighter_${highlighter}_paint' and '_zsh_highlight_highlighter_${highlighter}_predicate' in ${(qq):-"$highlighter_dir${highlighter}-highlighter.zsh"}." |
| 316 | + fi |
| 317 | + fi |
| 318 | + done |
| 319 | +} |
| 320 | + |
| 321 | + |
| 322 | +# ------------------------------------------------------------------------------------------------- |
| 323 | +# Setup |
| 324 | +# ------------------------------------------------------------------------------------------------- |
| 325 | + |
| 326 | +autoload -Uz _zsh_highlight_internal |
| 327 | + |
| 328 | +# Try binding widgets. |
| 329 | +_zsh_highlight_bind_widgets || { |
| 330 | + print -r -- >&2 'zsh-syntax-highlighting: failed binding ZLE widgets, exiting.' |
| 331 | + return 1 |
| 332 | +} |
| 333 | + |
| 334 | +# Resolve highlighters directory location. |
| 335 | +_zsh_highlight_load_highlighters "${ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR:-${${0:A}:h}/highlighters}" || { |
| 336 | + print -r -- >&2 'zsh-syntax-highlighting: failed loading highlighters, exiting.' |
| 337 | + return 1 |
| 338 | +} |
| 339 | + |
| 340 | +# Reset scratch variables when commandline is done. |
| 341 | +_zsh_highlight_preexec_hook() |
| 342 | +{ |
| 343 | + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER= |
| 344 | + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR= |
| 345 | +} |
| 346 | +autoload -Uz add-zsh-hook |
| 347 | +add-zsh-hook preexec _zsh_highlight_preexec_hook 2>/dev/null || { |
| 348 | + print -r -- >&2 'zsh-syntax-highlighting: failed loading add-zsh-hook.' |
| 349 | + } |
| 350 | + |
| 351 | +# Load zsh/parameter module if available |
| 352 | +zmodload zsh/parameter 2>/dev/null || true |
| 353 | + |
| 354 | +# Initialize the array of active highlighters if needed. |
| 355 | +[[ $#ZSH_HIGHLIGHT_HIGHLIGHTERS -eq 0 ]] && ZSH_HIGHLIGHT_HIGHLIGHTERS=(main) |
| 356 | + |
| 357 | +if (( $+X_ZSH_HIGHLIGHT_DIRS_BLACKLIST )); then |
| 358 | + print >&2 'zsh-syntax-highlighting: X_ZSH_HIGHLIGHT_DIRS_BLACKLIST is deprecated. Please use ZSH_HIGHLIGHT_DIRS_BLACKLIST.' |
| 359 | + ZSH_HIGHLIGHT_DIRS_BLACKLIST=($X_ZSH_HIGHLIGHT_DIRS_BLACKLIST) |
| 360 | + unset X_ZSH_HIGHLIGHT_DIRS_BLACKLIST |
| 361 | +fi |
0 commit comments