|
| 1 | +# ------------------------------------------------------------------------------------------------- |
| 2 | +# Copyright (c) 2010-2016 zsh-syntax-highlighting contributors |
| 3 | +# Copyright (c) 2016-2017 Sebastian Gniazdowski (modifications) |
| 4 | +# All rights reserved. |
| 5 | +# |
| 6 | +# Redistribution and use in source and binary forms, with or without modification, are permitted |
| 7 | +# provided that the following conditions are met: |
| 8 | +# |
| 9 | +# * Redistributions of source code must retain the above copyright notice, this list of conditions |
| 10 | +# and the following disclaimer. |
| 11 | +# * Redistributions in binary form must reproduce the above copyright notice, this list of |
| 12 | +# conditions and the following disclaimer in the documentation and/or other materials provided |
| 13 | +# with the distribution. |
| 14 | +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors |
| 15 | +# may be used to endorse or promote products derived from this software without specific prior |
| 16 | +# written permission. |
| 17 | +# |
| 18 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR |
| 19 | +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| 20 | +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| 21 | +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 22 | +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 | +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
| 24 | +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| 25 | +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | +# ------------------------------------------------------------------------------------------------- |
| 27 | + |
| 28 | +typeset -gA __hsmw_highlight_main__command_type_cache |
| 29 | + |
| 30 | +# Define default styles. |
| 31 | +typeset -gA FAST_HIGHLIGHT_STYLES |
| 32 | +: ${FAST_HIGHLIGHT_STYLES[default]:=none} |
| 33 | +: ${FAST_HIGHLIGHT_STYLES[unknown-token]:=fg=red,bold} |
| 34 | +: ${FAST_HIGHLIGHT_STYLES[reserved-word]:=fg=yellow} |
| 35 | +: ${FAST_HIGHLIGHT_STYLES[alias]:=fg=green} |
| 36 | +: ${FAST_HIGHLIGHT_STYLES[suffix-alias]:=fg=green} |
| 37 | +: ${FAST_HIGHLIGHT_STYLES[builtin]:=fg=green} |
| 38 | +: ${FAST_HIGHLIGHT_STYLES[function]:=fg=green} |
| 39 | +: ${FAST_HIGHLIGHT_STYLES[command]:=fg=green} |
| 40 | +: ${FAST_HIGHLIGHT_STYLES[precommand]:=fg=green} |
| 41 | +: ${FAST_HIGHLIGHT_STYLES[commandseparator]:=none} |
| 42 | +: ${FAST_HIGHLIGHT_STYLES[hashed-command]:=fg=green} |
| 43 | +: ${FAST_HIGHLIGHT_STYLES[path]:=fg=magenta} |
| 44 | +: ${FAST_HIGHLIGHT_STYLES[path_pathseparator]:=} |
| 45 | +: ${FAST_HIGHLIGHT_STYLES[globbing]:=fg=blue,bold} |
| 46 | +: ${FAST_HIGHLIGHT_STYLES[history-expansion]:=fg=blue,bold} |
| 47 | +: ${FAST_HIGHLIGHT_STYLES[single-hyphen-option]:=fg=cyan} |
| 48 | +: ${FAST_HIGHLIGHT_STYLES[double-hyphen-option]:=fg=cyan} |
| 49 | +: ${FAST_HIGHLIGHT_STYLES[back-quoted-argument]:=none} |
| 50 | +: ${FAST_HIGHLIGHT_STYLES[single-quoted-argument]:=fg=yellow} |
| 51 | +: ${FAST_HIGHLIGHT_STYLES[double-quoted-argument]:=fg=yellow} |
| 52 | +: ${FAST_HIGHLIGHT_STYLES[dollar-quoted-argument]:=fg=yellow} |
| 53 | +: ${FAST_HIGHLIGHT_STYLES[back-or-dollar-double-quoted-argument]:=fg=cyan} |
| 54 | +: ${FAST_HIGHLIGHT_STYLES[back-dollar-quoted-argument]:=fg=cyan} |
| 55 | +: ${FAST_HIGHLIGHT_STYLES[assign]:=none} |
| 56 | +: ${FAST_HIGHLIGHT_STYLES[redirection]:=none} |
| 57 | +: ${FAST_HIGHLIGHT_STYLES[comment]:=fg=black,bold} |
| 58 | +: ${FAST_HIGHLIGHT_STYLES[variable]:=none} |
| 59 | + |
| 60 | + |
| 61 | +typeset -gA __HSMW_HIGHLIGHT_TOKENS_TYPES |
| 62 | + |
| 63 | +__HSMW_HIGHLIGHT_TOKENS_TYPES=( |
| 64 | + |
| 65 | + # Precommand |
| 66 | + |
| 67 | + 'builtin' 1 |
| 68 | + 'command' 1 |
| 69 | + 'exec' 1 |
| 70 | + 'nocorrect' 1 |
| 71 | + 'noglob' 1 |
| 72 | + 'pkexec' 1 # immune to #121 because it's usually not passed --option flags |
| 73 | + |
| 74 | + # Control flow |
| 75 | + # Tokens that, at (naively-determined) "command position", are followed by |
| 76 | + # a de jure command position. All of these are reserved words. |
| 77 | + |
| 78 | + $'\x7b' 2 # block |
| 79 | + $'\x28' 2 # subshell |
| 80 | + '()' 2 # anonymous function |
| 81 | + 'while' 2 |
| 82 | + 'until' 2 |
| 83 | + 'if' 2 |
| 84 | + 'then' 2 |
| 85 | + 'elif' 2 |
| 86 | + 'else' 2 |
| 87 | + 'do' 2 |
| 88 | + 'time' 2 |
| 89 | + 'coproc' 2 |
| 90 | + '!' 2 # reserved word; unrelated to $histchars[1] |
| 91 | + |
| 92 | + # Command separators |
| 93 | + |
| 94 | + '|' 3 |
| 95 | + '||' 3 |
| 96 | + ';' 3 |
| 97 | + '&' 3 |
| 98 | + '&&' 3 |
| 99 | + '|&' 3 |
| 100 | + '&!' 3 |
| 101 | + '&|' 3 |
| 102 | + # ### 'case' syntax, but followed by a pattern, not by a command |
| 103 | + # ';;' ';&' ';|' |
| 104 | +) |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | + |
| 109 | +# Get the type of a command. |
| 110 | +# |
| 111 | +# Uses the zsh/parameter module if available to avoid forks, and a |
| 112 | +# wrapper around 'type -w' as fallback. |
| 113 | +# |
| 114 | +# Takes a single argument. |
| 115 | +# |
| 116 | +# The result will be stored in REPLY. |
| 117 | +-fast-highlight-main-type() { |
| 118 | + REPLY=$__hsmw_highlight_main__command_type_cache[(e)$1] |
| 119 | + [[ -z "$REPLY" ]] && { |
| 120 | + |
| 121 | + if zmodload -e zsh/parameter; then |
| 122 | + if (( $+aliases[(e)$1] )); then |
| 123 | + REPLY=alias |
| 124 | + elif (( $+functions[(e)$1] )); then |
| 125 | + REPLY=function |
| 126 | + elif (( $+builtins[(e)$1] )); then |
| 127 | + REPLY=builtin |
| 128 | + elif (( $+commands[(e)$1] )); then |
| 129 | + REPLY=command |
| 130 | + elif (( $+saliases[(e)${1##*.}] )); then |
| 131 | + REPLY='suffix alias' |
| 132 | + elif (( $reswords[(Ie)$1] )); then |
| 133 | + REPLY=reserved |
| 134 | + # zsh 5.2 and older have a bug whereby running 'type -w ./sudo' implicitly |
| 135 | + # runs 'hash ./sudo=/usr/local/bin/./sudo' (assuming /usr/local/bin/sudo |
| 136 | + # exists and is in $PATH). Avoid triggering the bug, at the expense of |
| 137 | + # falling through to the $() below, incurring a fork. (Issue #354.) |
| 138 | + # |
| 139 | + # The second disjunct mimics the isrelative() C call from the zsh bug. |
| 140 | + elif [[ $1 != */* || "${+ZSH_ARGZERO}" = "1" ]] && ! builtin type -w -- $1 >/dev/null 2>&1; then |
| 141 | + REPLY=none |
| 142 | + fi |
| 143 | + fi |
| 144 | + |
| 145 | + [[ -z "$REPLY" ]] && REPLY="${$(LC_ALL=C builtin type -w -- $1 2>/dev/null)##*: }" |
| 146 | + |
| 147 | + __hsmw_highlight_main__command_type_cache[(e)$1]=$REPLY |
| 148 | + |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +# Below are variables that must be defined in outer |
| 153 | +# scope so that they are reachable in *-process() |
| 154 | +# |
| 155 | +# local right_brace_is_recognised_everywhere |
| 156 | +# integer path_dirs_was_set |
| 157 | +# integer multi_func_def |
| 158 | +# integer ointeractive_comments |
| 159 | +-fast-highlight-fill-option-variables() { |
| 160 | + if [[ -o ignore_braces ]] || eval '[[ -o ignore_close_braces ]] 2>/dev/null'; then |
| 161 | + right_brace_is_recognised_everywhere=0 |
| 162 | + else |
| 163 | + right_brace_is_recognised_everywhere=1 |
| 164 | + fi |
| 165 | + |
| 166 | + if [[ -o path_dirs ]]; then |
| 167 | + path_dirs_was_set=1 |
| 168 | + else |
| 169 | + path_dirs_was_set=0 |
| 170 | + fi |
| 171 | + |
| 172 | + if [[ -o multi_func_def ]]; then |
| 173 | + multi_func_def=1 |
| 174 | + else |
| 175 | + multi_func_def=0 |
| 176 | + fi |
| 177 | + |
| 178 | + if [[ -o interactive_comments ]]; then |
| 179 | + ointeractive_comments=1 |
| 180 | + else |
| 181 | + ointeractive_comments=0 |
| 182 | + fi |
| 183 | +} |
| 184 | + |
| 185 | +# Main syntax highlighting function. |
| 186 | +-fast-highlight-process() |
| 187 | +{ |
| 188 | + emulate -L zsh |
| 189 | + setopt extendedglob bareglobqual nonomatch noksharrays |
| 190 | + |
| 191 | + (( path_dirs_was_set )) && setopt PATH_DIRS |
| 192 | + (( ointeractive_comments )) && local interactive_comments= # _set_ to empty |
| 193 | + |
| 194 | + # Variable declarations and initializations |
| 195 | + # in_array_assignment true between 'a=(' and the matching ')' |
| 196 | + # braces_stack: "R" for round, "Q" for square, "Y" for curly |
| 197 | + # mybuf, cdpath_dir are used in sub-functions |
| 198 | + local start_pos=0 end_pos highlight_glob=1 arg style in_array_assignment=0 MATCH expanded_path braces_stack buf="$BUFFER" mybuf cdpath_dir cur_cmd alias_target |
| 199 | + # arg_type can be 0, 1, 2 or 3, i.e. precommand, control flow, command separator |
| 200 | + # idx and end_idx are used in sub-functions |
| 201 | + # for this_word and next_word look below at commented integers and at state machine description |
| 202 | + integer arg_type=0 MBEGIN MEND in_redirection len=${#buf} already_added offset idx end_idx this_word=1 next_word=0 insane_alias |
| 203 | + local -a match mbegin mend |
| 204 | + |
| 205 | + # integer BIT_start=1 BIT_regular=2 BIT_sudo_opt=4 BIT_sudo_arg=8 BIT_always=16 |
| 206 | + |
| 207 | + # State machine |
| 208 | + # |
| 209 | + # The states are: |
| 210 | + # - :start: Command word |
| 211 | + # - :sudo_opt: A leading-dash option to sudo (such as "-u" or "-i") |
| 212 | + # - :sudo_arg: The argument to a sudo leading-dash option that takes one, |
| 213 | + # when given as a separate word; i.e., "foo" in "-u foo" (two |
| 214 | + # words) but not in "-ufoo" (one word). |
| 215 | + # - :regular: "Not a command word", and command delimiters are permitted. |
| 216 | + # Mainly used to detect premature termination of commands. |
| 217 | + # - :always: The word 'always' in the «{ foo } always { bar }» syntax. |
| 218 | + # |
| 219 | + # When the kind of a word is not yet known, $this_word / $next_word may contain |
| 220 | + # multiple states. For example, after "sudo -i", the next word may be either |
| 221 | + # another --flag or a command name, hence the state would include both :start: |
| 222 | + # and :sudo_opt:. |
| 223 | + # |
| 224 | + # The tokens are always added with both leading and trailing colons to serve as |
| 225 | + # word delimiters (an improvised array); [[ $x == *:foo:* ]] and x=${x//:foo:/} |
| 226 | + # will DTRT regardless of how many elements or repetitions $x has.. |
| 227 | + # |
| 228 | + # Handling of redirections: upon seeing a redirection token, we must stall |
| 229 | + # the current state --- that is, the value of $this_word --- for two iterations |
| 230 | + # (one for the redirection operator, one for the word following it representing |
| 231 | + # the redirection target). Therefore, we set $in_redirection to 2 upon seeing a |
| 232 | + # redirection operator, decrement it each iteration, and stall the current state |
| 233 | + # when it is non-zero. Thus, upon reaching the next word (the one that follows |
| 234 | + # the redirection operator and target), $this_word will still contain values |
| 235 | + # appropriate for the word immediately following the word that preceded the |
| 236 | + # redirection operator. |
| 237 | + # |
| 238 | + # The "the previous word was a redirection operator" state is not communicated |
| 239 | + # to the next iteration via $next_word/$this_word as usual, but via |
| 240 | + # $in_redirection. The value of $next_word from the iteration that processed |
| 241 | + # the operator is discarded. |
| 242 | + # |
| 243 | + |
| 244 | + # Processing buffer |
| 245 | + local proc_buf="$buf" needle |
| 246 | + for arg in ${interactive_comments-${(z)buf}} \ |
| 247 | + ${interactive_comments+${(zZ+c+)buf}}; do |
| 248 | + # Initialize $next_word to its default value? |
| 249 | + (( in_redirection )) && (( --in_redirection )) |
| 250 | + (( in_redirection == 0 )) && next_word=2 # else Stall $next_word. |
| 251 | + |
| 252 | + # Initialize per-"simple command" [zshmisc(1)] variables: |
| 253 | + # |
| 254 | + # $already_added (see next paragraph) |
| 255 | + # $style how to highlight $arg |
| 256 | + # $in_array_assignment boolean flag for "between '(' and ')' of array assignment" |
| 257 | + # $highlight_glob boolean flag for "'noglob' is in effect" |
| 258 | + # |
| 259 | + # $already_added is set to 1 to disable adding an entry to region_highlight |
| 260 | + # for this iteration. Currently, that is done for "" and $'' strings, |
| 261 | + # which add the entry early so escape sequences within the string override |
| 262 | + # the string's color. |
| 263 | + already_added=0 |
| 264 | + style=unknown-token |
| 265 | + if (( this_word & 1 )); then |
| 266 | + in_array_assignment=0 |
| 267 | + [[ $arg == 'noglob' ]] && highlight_glob=0 |
| 268 | + fi |
| 269 | + |
| 270 | + # Compute the new $start_pos and $end_pos, skipping over whitespace in $buf. |
| 271 | + if [[ $arg == ';' ]] ; then |
| 272 | + # We're looking for either a semicolon or a newline, whichever comes |
| 273 | + # first. Both of these are rendered as a ";" (SEPER) by the ${(z)..} |
| 274 | + # flag. |
| 275 | + # |
| 276 | + # We can't use the (Z+n+) flag because that elides the end-of-command |
| 277 | + # token altogether, so 'echo foo\necho bar' (two commands) becomes |
| 278 | + # indistinguishable from 'echo foo echo bar' (one command with three |
| 279 | + # words for arguments). |
| 280 | + needle=$'[;\n]' |
| 281 | + offset=$(( ${proc_buf[(i)$needle]} - 1 )) |
| 282 | + (( start_pos += offset )) |
| 283 | + (( end_pos = start_pos + $#arg )) |
| 284 | + |
| 285 | + # Do not run default code for case when there is a new line |
| 286 | + # It shouldn't be treated as ';', i.e. shouldn't be highlighted |
| 287 | + # as unknown-token when appears after command-starting arg like "{" |
| 288 | + if [[ "${proc_buf[offset+1]}" = $'\n' ]]; then |
| 289 | + (( in_array_assignment )) && (( this_word = 2 )) || { (( this_word = 1 )); highlight_glob=1; } |
| 290 | + in_redirection=0 |
| 291 | + proc_buf="${proc_buf[offset + $#arg + 1,len]}" |
| 292 | + start_pos=$end_pos |
| 293 | + continue |
| 294 | + else |
| 295 | + # One more short path – for ';' command separator |
| 296 | + (( in_array_assignment )) && (( this_word = 2 )) || { (( this_word = 1 )); highlight_glob=1; } |
| 297 | + in_redirection=0 |
| 298 | + [[ "${FAST_HIGHLIGHT_STYLES[commandseparator]}" != "none" ]] && reply+=("$start_pos $end_pos ${FAST_HIGHLIGHT_STYLES[commandseparator]}") |
| 299 | + proc_buf="${proc_buf[offset + $#arg + 1,len]}" |
| 300 | + start_pos=$end_pos |
| 301 | + continue |
| 302 | + fi |
| 303 | + |
| 304 | + arg_type=3 |
| 305 | + else |
| 306 | + offset=0 |
| 307 | + if [[ "$proc_buf" = (#b)(#s)(([[:space:]]|\\[[:space:]])##)* ]]; then |
| 308 | + # The first, outer parenthesis |
| 309 | + offset="${mend[1]}" |
| 310 | + fi |
| 311 | + ((start_pos+=offset)) |
| 312 | + ((end_pos=start_pos+${#arg})) |
| 313 | + |
| 314 | + # No-hit will result in value 0 |
| 315 | + arg_type=${__HSMW_HIGHLIGHT_TOKENS_TYPES[$arg]} |
| 316 | + fi |
| 317 | + |
| 318 | + proc_buf="${proc_buf[offset + $#arg + 1,len]}" |
| 319 | + |
| 320 | + # Handle the INTERACTIVE_COMMENTS option. |
| 321 | + # |
| 322 | + # We use the (Z+c+) flag so the entire comment is presented as one token in $arg. |
| 323 | + if [[ -n ${interactive_comments+'set'} && $arg[1] == $histchars[3] ]]; then |
| 324 | + if (( this_word & 3 )); then |
| 325 | + style=comment |
| 326 | + else |
| 327 | + style=unknown-token # prematurely terminated |
| 328 | + fi |
| 329 | + # ADD |
| 330 | + reply+=("$start_pos $end_pos ${FAST_HIGHLIGHT_STYLES[$style]}") |
| 331 | + start_pos=$end_pos |
| 332 | + continue |
| 333 | + fi |
| 334 | + |
| 335 | + # Analyse the current word. |
| 336 | + if [[ $arg == (<0-9>|)(\<|\>)* ]] && [[ $arg != (\<|\>)$'\x28'* ]]; then |
| 337 | + # A '<' or '>', possibly followed by a digit |
| 338 | + in_redirection=2 |
| 339 | + fi |
| 340 | + |
| 341 | + # Special-case the first word after 'sudo'. |
| 342 | + if (( ! in_redirection )); then |
| 343 | + if (( this_word & 4 )) && [[ $arg != -* ]]; then |
| 344 | + (( this_word = this_word ^ 4 )) |
| 345 | + fi |
| 346 | + |
| 347 | + # Parse the sudo command line |
| 348 | + if (( this_word & 4 )); then |
| 349 | + case "$arg" in |
| 350 | + # Flag that requires an argument |
| 351 | + '-'[Cgprtu]) |
| 352 | + (( this_word & 1 )) && (( this_word = this_word ^ 1 )) |
| 353 | + (( next_word = 8 )) |
| 354 | + ;; |
| 355 | + # This prevents misbehavior with sudo -u -otherargument |
| 356 | + '-'*) |
| 357 | + (( this_word & 1 )) && (( this_word = this_word ^ 1 )) |
| 358 | + (( next_word = next_word | 1 )) |
| 359 | + (( next_word = next_word | 4 )) |
| 360 | + ;; |
| 361 | + *) ;; |
| 362 | + esac |
| 363 | + elif (( this_word & 8 )); then |
| 364 | + (( next_word = next_word | 4 )) |
| 365 | + (( next_word = next_word | 1 )) |
| 366 | + fi |
| 367 | + fi |
| 368 | + |
| 369 | + expanded_path="" |
| 370 | + |
| 371 | + # The Great Fork: is this a command word? Is this a non-command word? |
| 372 | + if (( this_word & 16 )) && [[ $arg == 'always' ]]; then |
| 373 | + # try-always construct |
| 374 | + style=reserved-word # de facto a reserved word, although not de jure |
| 375 | + (( next_word = 1 )) |
| 376 | + elif (( this_word & 1 )) && (( in_redirection == 0 )); then # $arg is the command word |
| 377 | + cur_cmd="$arg" |
| 378 | + if (( arg_type == 1 )); then |
| 379 | + style=precommand |
| 380 | + elif [[ "$arg" = "sudo" ]]; then |
| 381 | + style=precommand |
| 382 | + (( next_word & 2 )) && (( next_word = next_word ^ 2 )) |
| 383 | + (( next_word = next_word | 4 )) |
| 384 | + (( next_word = next_word | 1 )) |
| 385 | + else |
| 386 | + # Special-case: command word is '$foo', like that, without braces or anything. |
| 387 | + # |
| 388 | + # That's not entirely correct --- if the parameter's value happens to be a reserved |
| 389 | + # word, the parameter expansion will be highlighted as a reserved word --- but that |
| 390 | + # incorrectness is outweighed by the usability improvement of permitting the use of |
| 391 | + # parameters that refer to commands, functions, and builtins. |
| 392 | + if [[ ${arg[1]} == \$ ]] && (( ${+parameters} )) && [[ ${arg:1} = (#m)([a-zA-Z_][a-zA-Z0-9_]#|[0-9]##) ]] && (( ${+parameters[${MATCH}]} )); then |
| 393 | + -fast-highlight-main-type ${(P)MATCH} |
| 394 | + else |
| 395 | + : ${expanded_path::=${(Q)~arg}} |
| 396 | + -fast-highlight-main-type $expanded_path |
| 397 | + fi |
| 398 | + |
| 399 | + case $REPLY in |
| 400 | + reserved) # reserved word |
| 401 | + style=reserved-word |
| 402 | + if [[ $arg == $'\x7b' ]]; then |
| 403 | + braces_stack='Y'"$braces_stack" |
| 404 | + elif [[ $arg == $'\x7d' && $braces_stack[1] == "Y" ]]; then |
| 405 | + # We're at command word, so no need to check $right_brace_is_recognised_everywhere |
| 406 | + braces_stack[1]="" |
| 407 | + style=reserved-word |
| 408 | + (( next_word = next_word | 16 )) |
| 409 | + fi |
| 410 | + ;; |
| 411 | + 'suffix alias') style=suffix-alias;; |
| 412 | + alias) |
| 413 | + insane_alias=0 |
| 414 | + case $arg in |
| 415 | + # Issue #263: aliases with '=' on their LHS. |
| 416 | + # |
| 417 | + # There are three cases: |
| 418 | + # |
| 419 | + # - Unsupported, breaks 'alias -L' output, but invokable: |
| 420 | + ('='*) :;; |
| 421 | + # - Unsupported, not invokable: |
| 422 | + (*'='*) insane_alias=1;; |
| 423 | + # - The common case: |
| 424 | + (*) :;; |
| 425 | + esac |
| 426 | + if (( insane_alias )); then |
| 427 | + style=unknown-token |
| 428 | + else |
| 429 | + style=alias |
| 430 | + zmodload -e zsh/parameter && alias_target=${aliases[$arg]} || alias_target="${"$(alias -- $arg)"#*=}" |
| 431 | + [[ ${__HSMW_HIGHLIGHT_TOKENS_TYPES[$alias_target]} = "1" && "$arg_type" != "1" ]] && __HSMW_HIGHLIGHT_TOKENS_TYPES[$arg]="1" |
| 432 | + fi |
| 433 | + ;; |
| 434 | + builtin) style=builtin;; |
| 435 | + function) style=function;; |
| 436 | + command) style=command;; |
| 437 | + hashed) style=hashed-command;; |
| 438 | + none) # Assign? |
| 439 | + if [[ $arg == [[:alpha:]_][[:alnum:]_]#(|\[[^\]]#\])(|[+])=* ]] || [[ $arg == [0-9]##(|[+])=* ]]; then |
| 440 | + style=assign |
| 441 | + # Assignment to a scalar parameter or to array |
| 442 | + # (For array assignments, the command doesn't start until the ")" token.) |
| 443 | + [[ $arg[-1] == '(' ]] && in_array_assignment=1 || (( next_word = next_word | 1 )) |
| 444 | + elif [[ $arg[1] = $histchars[1] && -n "${arg[2]}" ]]; then |
| 445 | + style=history-expansion |
| 446 | + elif [[ $arg[1] == $histchars[2] ]]; then |
| 447 | + style=history-expansion |
| 448 | + elif (( arg_type == 3 )); then |
| 449 | + # This highlights empty commands (semicolon follows nothing) as an error. |
| 450 | + # Zsh accepts them, though. |
| 451 | + (( this_word & 2 )) && style=commandseparator |
| 452 | + elif [[ $arg[1,2] == '((' ]]; then |
| 453 | + # Arithmetic evaluation. |
| 454 | + # |
| 455 | + # Note: prior to zsh-5.1.1-52-g4bed2cf (workers/36669), the ${(z)...} |
| 456 | + # splitter would only output the '((' token if the matching '))' had |
| 457 | + # been typed. Therefore, under those versions of zsh, BUFFER="(( 42" |
| 458 | + # would be highlighted as an error until the matching "))" are typed. |
| 459 | + # |
| 460 | + # We highlight just the opening parentheses, as a reserved word; this |
| 461 | + # is how [[ ... ]] is highlighted, too. |
| 462 | + |
| 463 | + # ADD |
| 464 | + reply+=("$start_pos $(( start_pos + 2 )) ${FAST_HIGHLIGHT_STYLES[reserved-word]}") |
| 465 | + already_added=1 |
| 466 | + # ADD |
| 467 | + [[ $arg[-2,-1] == '))' ]] && reply+=("$(( end_pos - 2 )) $end_pos ${FAST_HIGHLIGHT_STYLES[reserved-word]}") |
| 468 | + elif [[ $arg == '()' ]]; then |
| 469 | + # anonymous function |
| 470 | + style=reserved-word |
| 471 | + elif [[ $arg == $'\x28' ]]; then |
| 472 | + # subshell |
| 473 | + style=reserved-word |
| 474 | + braces_stack='R'"$braces_stack" |
| 475 | + elif (( this_word & 14 )); then |
| 476 | + style=default |
| 477 | + fi |
| 478 | + ;; |
| 479 | + *) |
| 480 | + # ADD |
| 481 | + # reply+=("$start_pos $end_pos commandtypefromthefuture-$REPLY") |
| 482 | + already_added=1 |
| 483 | + ;; |
| 484 | + esac |
| 485 | + fi |
| 486 | + # in_redirection || BIT_regular || BIT_sudo_opt || BIT_sudo_arg |
| 487 | + elif (( in_redirection + this_word & 14 )) |
| 488 | + then # $arg is a non-command word |
| 489 | + case $arg in |
| 490 | + ']]') |
| 491 | + style=reserved-word |
| 492 | + ;; |
| 493 | + $'\x29') # subshell or end of array assignment |
| 494 | + if (( in_array_assignment )); then |
| 495 | + style=assign |
| 496 | + in_array_assignment=0 |
| 497 | + (( next_word = next_word | 1 )) |
| 498 | + elif [[ $braces_stack[1] == "R" ]]; then |
| 499 | + braces_stack[1]="" |
| 500 | + style=reserved-word |
| 501 | + fi;; |
| 502 | + $'\x28\x29') # possibly a function definition |
| 503 | + # || false # TODO: or if the previous word was a command word |
| 504 | + (( multi_func_def )) && (( next_word = next_word | 1 )) |
| 505 | + style=reserved-word |
| 506 | + # Remove possible annoying unknown-token style, or misleading function style |
| 507 | + reply[-1]=() |
| 508 | + ;; |
| 509 | + '--'*) style=double-hyphen-option;; |
| 510 | + '-'*) style=single-hyphen-option;; |
| 511 | + "'"*) style=single-quoted-argument;; |
| 512 | + '"'*) |
| 513 | + # ADD |
| 514 | + reply+=("$start_pos $end_pos ${FAST_HIGHLIGHT_STYLES[double-quoted-argument]}") |
| 515 | + -fast-highlight-string |
| 516 | + already_added=1 |
| 517 | + ;; |
| 518 | + \$\'*) |
| 519 | + # ADD |
| 520 | + reply+=("$start_pos $end_pos ${FAST_HIGHLIGHT_STYLES[dollar-quoted-argument]}") |
| 521 | + -fast-highlight-dollar-string |
| 522 | + already_added=1 |
| 523 | + ;; |
| 524 | + \$[^\(]*) |
| 525 | + style=variable |
| 526 | + ;; |
| 527 | + '`'*) style=back-quoted-argument;; |
| 528 | + [*?]*|*[^\\][*?]*) |
| 529 | + (( highlight_glob )) && style=globbing || style=default;; |
| 530 | + *) if [[ $arg = $'\x7d' && $braces_stack[1] == "Y" && "$right_brace_is_recognised_everywhere" = "1" ]]; then |
| 531 | + # right brace |
| 532 | + # Parsing rule: # { |
| 533 | + # |
| 534 | + # Additionally, `tt(})' is recognized in any position if neither the |
| 535 | + # tt(IGNORE_BRACES) option nor the tt(IGNORE_CLOSE_BRACES) option is set.""" |
| 536 | + braces_stack[1]="" |
| 537 | + style=reserved-word |
| 538 | + (( next_word = next_word | 16 )) |
| 539 | + elif [[ $arg[1] = $histchars[1] && -n "${arg[2]}" ]]; then |
| 540 | + style=history-expansion |
| 541 | + elif (( arg_type == 3 )); then |
| 542 | + style=commandseparator |
| 543 | + elif (( in_redirection == 2 )); then |
| 544 | + style=redirection |
| 545 | + else |
| 546 | + if (( __hsmw_no_check_paths == 0 )) && -fast-highlight-check-path; then |
| 547 | + # ADD |
| 548 | + reply+=("$start_pos $end_pos ${FAST_HIGHLIGHT_STYLES[path]}") |
| 549 | + already_added=1 |
| 550 | + |
| 551 | + [[ -n "$FAST_HIGHLIGHT_STYLES[path_pathseparator]" && "$FAST_HIGHLIGHT_STYLES[path]" != "$FAST_HIGHLIGHT_STYLES[path_pathseparator]" ]] && { |
| 552 | + local pos |
| 553 | + for (( pos = start_pos; pos <= end_pos; pos++ )) ; do |
| 554 | + # ADD |
| 555 | + [[ ${buf[pos]} == "/" ]] && reply+=("$(( pos - 1 )) $pos ${FAST_HIGHLIGHT_STYLES[path_pathseparator]}") |
| 556 | + done |
| 557 | + } |
| 558 | + else |
| 559 | + style=default |
| 560 | + fi |
| 561 | + fi |
| 562 | + ;; |
| 563 | + esac |
| 564 | + fi |
| 565 | + |
| 566 | + # ADD |
| 567 | + (( already_added == 0 )) && [[ "${FAST_HIGHLIGHT_STYLES[$style]}" != "none" ]] && reply+=("$start_pos $end_pos ${FAST_HIGHLIGHT_STYLES[$style]}") |
| 568 | + |
| 569 | + if (( arg_type == 3 )); then |
| 570 | + if [[ $arg == ';' ]] && (( in_array_assignment )); then |
| 571 | + # literal newline inside an array assignment |
| 572 | + (( next_word = 2 )) |
| 573 | + else |
| 574 | + (( next_word = 1 )) |
| 575 | + highlight_glob=1 |
| 576 | + fi |
| 577 | + elif (( arg_type == 1 || arg_type == 2 )) && (( this_word & 1 )); then |
| 578 | + (( next_word = 1 )) |
| 579 | + elif [[ $arg == "repeat" ]] && (( this_word & 1 )); then |
| 580 | + # skip the repeat-count word |
| 581 | + in_redirection=2 |
| 582 | + # The redirection mechanism assumes $this_word describes the word |
| 583 | + # following the redirection. Make it so. |
| 584 | + # |
| 585 | + # That word can be a command word with shortloops (`repeat 2 ls`) |
| 586 | + # or a command separator (`repeat 2; ls` or `repeat 2; do ls; done`). |
| 587 | + # |
| 588 | + # The repeat-count word will be handled like a redirection target. |
| 589 | + (( this_word = 3 )) |
| 590 | + fi |
| 591 | + start_pos=$end_pos |
| 592 | + # This is the default/common codepath. |
| 593 | + (( in_redirection == 0 )) && (( this_word = next_word )) #else # Stall $this_word. |
| 594 | + done |
| 595 | +} |
| 596 | + |
| 597 | +# Check if $arg is a path. |
| 598 | +# If yes, return 0 and in $REPLY the style to use. |
| 599 | +# Else, return non-zero (and the contents of $REPLY is undefined). |
| 600 | +-fast-highlight-check-path() |
| 601 | +{ |
| 602 | + : ${expanded_path:=${(Q)~arg}} |
| 603 | + |
| 604 | + [[ -z $expanded_path ]] && return 1 |
| 605 | + [[ -e $expanded_path ]] && return 0 |
| 606 | + |
| 607 | + # Search the path in CDPATH, only for CD command |
| 608 | + [[ "$cur_cmd" = "cd" ]] && for cdpath_dir in $cdpath ; do |
| 609 | + [[ -e "$cdpath_dir/$expanded_path" ]] && return 0 |
| 610 | + done |
| 611 | + |
| 612 | + # It's not a path. |
| 613 | + return 1 |
| 614 | +} |
| 615 | + |
| 616 | +# Highlight special chars inside double-quoted strings |
| 617 | +-fast-highlight-string() |
| 618 | +{ |
| 619 | + mybuf="$arg" |
| 620 | + idx=start_pos |
| 621 | + |
| 622 | + while [[ "$mybuf" = (#b)[^\$\\]#((\$(#B)([a-zA-Z_:][a-zA-Z0-9_:]#|[0-9]##)(#b)(\[[^\]]#\])(#c0,1))|(\$[{](\([a-zA-Z0@%#]##\))(#c0,1)[a-zA-Z0-9_:#]##(\[[^\]]#\])(#c0,1)[}])|[\\][\'\"\$]|[\\](*))(*) ]]; do |
| 623 | + [[ -n "${match[7]}" ]] && { |
| 624 | + # Skip following char – it is quoted. Choice is |
| 625 | + # made to not highlight such quoting |
| 626 | + idx+=${mbegin[1]}+1 |
| 627 | + mybuf="${match[7]:1}" |
| 628 | + } || { |
| 629 | + idx+=${mbegin[1]}-1 |
| 630 | + end_idx=idx+${mend[1]}-${mbegin[1]}+1 |
| 631 | + mybuf="${match[8]}" |
| 632 | + |
| 633 | + # ADD |
| 634 | + reply+=("$idx $end_idx ${FAST_HIGHLIGHT_STYLES[back-or-dollar-double-quoted-argument]}") |
| 635 | + |
| 636 | + idx=end_idx |
| 637 | + } |
| 638 | + done |
| 639 | +} |
| 640 | + |
| 641 | +# Highlight special chars inside dollar-quoted strings |
| 642 | +-fast-highlight-dollar-string() |
| 643 | +{ |
| 644 | + local i j k style |
| 645 | + local AA |
| 646 | + integer c |
| 647 | + # Starting dollar-quote is at 1:2, so start parsing at offset 3 in the string. |
| 648 | + for (( i = 3 ; i < end_pos - start_pos ; i += 1 )) ; do |
| 649 | + (( j = i + start_pos - 1 )) |
| 650 | + (( k = j + 1 )) |
| 651 | + case "$arg[$i]" in |
| 652 | + "\\") style=back-dollar-quoted-argument |
| 653 | + for (( c = i + 1 ; c <= end_pos - start_pos ; c += 1 )); do |
| 654 | + [[ "$arg[$c]" != ([0-9xXuUa-fA-F]) ]] && break |
| 655 | + done |
| 656 | + AA=$arg[$i+1,$c-1] |
| 657 | + # Matching for HEX and OCT values like \0xA6, \xA6 or \012 |
| 658 | + if [[ "$AA" =~ "^(x|X)[0-9a-fA-F]{1,2}" |
| 659 | + || "$AA" =~ "^[0-7]{1,3}" |
| 660 | + || "$AA" =~ "^u[0-9a-fA-F]{1,4}" |
| 661 | + || "$AA" =~ "^U[0-9a-fA-F]{1,8}" |
| 662 | + ]]; then |
| 663 | + (( k += $#MATCH )) |
| 664 | + (( i += $#MATCH )) |
| 665 | + else |
| 666 | + if (( $#arg > $i+1 )) && [[ $arg[$i+1] == [xXuU] ]]; then |
| 667 | + # \x not followed by hex digits is probably an error |
| 668 | + style=unknown-token |
| 669 | + fi |
| 670 | + (( k += 1 )) # Color following char too. |
| 671 | + (( i += 1 )) # Skip parsing the escaped char. |
| 672 | + fi |
| 673 | + ;; |
| 674 | + *) continue ;; |
| 675 | + |
| 676 | + esac |
| 677 | + # ADD |
| 678 | + reply+=("$j $k ${FAST_HIGHLIGHT_STYLES[$style]}") |
| 679 | + done |
| 680 | +} |
| 681 | + |
| 682 | +# ------------------------------------------------------------------------------------------------- |
| 683 | +# Main highlighter initialization |
| 684 | +# ------------------------------------------------------------------------------------------------- |
| 685 | + |
| 686 | +-fast-highlight-init() { |
| 687 | + __hsmw_highlight_main__command_type_cache=() |
| 688 | +} |
| 689 | + |
| 690 | +__HSMW_MH_SOURCED=1 |
| 691 | + |
| 692 | +# vim:ft=zsh:sw=2:sts=2 |
| 693 | +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- |
0 commit comments