dots

Just my dots
git clone git://git.wrpr.us/dots
Log | Files | Refs | README

main-highlighter.zsh (69836B)


      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 
     31 # Define default styles.
     32 : ${ZSH_HIGHLIGHT_STYLES[default]:=none}
     33 : ${ZSH_HIGHLIGHT_STYLES[unknown-token]:=fg=red,bold}
     34 : ${ZSH_HIGHLIGHT_STYLES[reserved-word]:=fg=yellow}
     35 : ${ZSH_HIGHLIGHT_STYLES[suffix-alias]:=fg=green,underline}
     36 : ${ZSH_HIGHLIGHT_STYLES[global-alias]:=fg=cyan}
     37 : ${ZSH_HIGHLIGHT_STYLES[precommand]:=fg=green,underline}
     38 : ${ZSH_HIGHLIGHT_STYLES[commandseparator]:=none}
     39 : ${ZSH_HIGHLIGHT_STYLES[autodirectory]:=fg=green,underline}
     40 : ${ZSH_HIGHLIGHT_STYLES[path]:=underline}
     41 : ${ZSH_HIGHLIGHT_STYLES[path_pathseparator]:=}
     42 : ${ZSH_HIGHLIGHT_STYLES[path_prefix_pathseparator]:=}
     43 : ${ZSH_HIGHLIGHT_STYLES[globbing]:=fg=blue}
     44 : ${ZSH_HIGHLIGHT_STYLES[history-expansion]:=fg=blue}
     45 : ${ZSH_HIGHLIGHT_STYLES[command-substitution]:=none}
     46 : ${ZSH_HIGHLIGHT_STYLES[command-substitution-delimiter]:=fg=magenta}
     47 : ${ZSH_HIGHLIGHT_STYLES[process-substitution]:=none}
     48 : ${ZSH_HIGHLIGHT_STYLES[process-substitution-delimiter]:=fg=magenta}
     49 : ${ZSH_HIGHLIGHT_STYLES[single-hyphen-option]:=none}
     50 : ${ZSH_HIGHLIGHT_STYLES[double-hyphen-option]:=none}
     51 : ${ZSH_HIGHLIGHT_STYLES[back-quoted-argument]:=none}
     52 : ${ZSH_HIGHLIGHT_STYLES[back-quoted-argument-delimiter]:=fg=magenta}
     53 : ${ZSH_HIGHLIGHT_STYLES[single-quoted-argument]:=fg=yellow}
     54 : ${ZSH_HIGHLIGHT_STYLES[double-quoted-argument]:=fg=yellow}
     55 : ${ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]:=fg=yellow}
     56 : ${ZSH_HIGHLIGHT_STYLES[rc-quote]:=fg=cyan}
     57 : ${ZSH_HIGHLIGHT_STYLES[dollar-double-quoted-argument]:=fg=cyan}
     58 : ${ZSH_HIGHLIGHT_STYLES[back-double-quoted-argument]:=fg=cyan}
     59 : ${ZSH_HIGHLIGHT_STYLES[back-dollar-quoted-argument]:=fg=cyan}
     60 : ${ZSH_HIGHLIGHT_STYLES[assign]:=none}
     61 : ${ZSH_HIGHLIGHT_STYLES[redirection]:=fg=yellow}
     62 : ${ZSH_HIGHLIGHT_STYLES[comment]:=fg=black,bold}
     63 : ${ZSH_HIGHLIGHT_STYLES[named-fd]:=none}
     64 : ${ZSH_HIGHLIGHT_STYLES[numeric-fd]:=none}
     65 : ${ZSH_HIGHLIGHT_STYLES[arg0]:=fg=green}
     66 
     67 # Whether the highlighter should be called or not.
     68 _zsh_highlight_highlighter_main_predicate()
     69 {
     70   # may need to remove path_prefix highlighting when the line ends
     71   [[ $WIDGET == zle-line-finish ]] || _zsh_highlight_buffer_modified
     72 }
     73 
     74 # Helper to deal with tokens crossing line boundaries.
     75 _zsh_highlight_main_add_region_highlight() {
     76   integer start=$1 end=$2
     77   shift 2
     78 
     79   if (( $#in_alias )); then
     80     [[ $1 == unknown-token ]] && alias_style=unknown-token
     81     return
     82   fi
     83   if (( in_param )); then
     84     if [[ $1 == unknown-token ]]; then
     85       param_style=unknown-token
     86     fi
     87     if [[ -n $param_style ]]; then
     88       return
     89     fi
     90     param_style=$1
     91     return
     92   fi
     93 
     94   # The calculation was relative to $buf but region_highlight is relative to $BUFFER.
     95   (( start += buf_offset ))
     96   (( end += buf_offset ))
     97 
     98   list_highlights+=($start $end $1)
     99 }
    100 
    101 _zsh_highlight_main_add_many_region_highlights() {
    102   for 1 2 3; do
    103     _zsh_highlight_main_add_region_highlight $1 $2 $3
    104   done
    105 }
    106 
    107 _zsh_highlight_main_calculate_fallback() {
    108   local -A fallback_of; fallback_of=(
    109       alias arg0
    110       suffix-alias arg0
    111       global-alias dollar-double-quoted-argument
    112       builtin arg0
    113       function arg0
    114       command arg0
    115       precommand arg0
    116       hashed-command arg0
    117       autodirectory arg0
    118       arg0_\* arg0
    119 
    120       # TODO: Maybe these? —
    121       #   named-fd file-descriptor
    122       #   numeric-fd file-descriptor
    123 
    124       path_prefix path
    125       # The path separator fallback won't ever be used, due to the optimisation
    126       # in _zsh_highlight_main_highlighter_highlight_path_separators().
    127       path_pathseparator path
    128       path_prefix_pathseparator path_prefix
    129 
    130       single-quoted-argument{-unclosed,}
    131       double-quoted-argument{-unclosed,}
    132       dollar-quoted-argument{-unclosed,}
    133       back-quoted-argument{-unclosed,}
    134 
    135       command-substitution{-quoted,,-unquoted,}
    136       command-substitution-delimiter{-quoted,,-unquoted,}
    137 
    138       command-substitution{-delimiter,}
    139       process-substitution{-delimiter,}
    140       back-quoted-argument{-delimiter,}
    141   )
    142   local needle=$1 value
    143   reply=($1)
    144   while [[ -n ${value::=$fallback_of[(k)$needle]} ]]; do
    145     unset "fallback_of[$needle]" # paranoia against infinite loops
    146     reply+=($value)
    147     needle=$value
    148   done
    149 }
    150 
    151 # Get the type of a command.
    152 #
    153 # Uses the zsh/parameter module if available to avoid forks, and a
    154 # wrapper around 'type -w' as fallback.
    155 #
    156 # If $2 is 0, do not consider aliases.
    157 #
    158 # The result will be stored in REPLY.
    159 _zsh_highlight_main__type() {
    160   integer -r aliases_allowed=${2-1}
    161   # We won't cache replies of anything that exists as an alias at all, to
    162   # ensure the cached value is correct regardless of $aliases_allowed.
    163   #
    164   # ### We probably _should_ cache them in a cache that's keyed on the value of
    165   # ### $aliases_allowed, on the assumption that aliases are the common case.
    166   integer may_cache=1
    167 
    168   # Cache lookup
    169   if (( $+_zsh_highlight_main__command_type_cache )); then
    170     REPLY=$_zsh_highlight_main__command_type_cache[(e)$1]
    171     if [[ -n "$REPLY" ]]; then
    172       return
    173     fi
    174   fi
    175 
    176   # Main logic
    177   if (( $#options_to_set )); then
    178     setopt localoptions $options_to_set;
    179   fi
    180   unset REPLY
    181   if zmodload -e zsh/parameter; then
    182     if (( $+aliases[(e)$1] )); then
    183       may_cache=0
    184     fi
    185     if (( ${+galiases[(e)$1]} )) && (( aliases_allowed )); then
    186       REPLY='global alias'
    187     elif (( $+aliases[(e)$1] )) && (( aliases_allowed )); then
    188       REPLY=alias
    189     elif [[ $1 == *.* && -n ${1%.*} ]] && (( $+saliases[(e)${1##*.}] )); then
    190       REPLY='suffix alias'
    191     elif (( $reswords[(Ie)$1] )); then
    192       REPLY=reserved
    193     elif (( $+functions[(e)$1] )); then
    194       REPLY=function
    195     elif (( $+builtins[(e)$1] )); then
    196       REPLY=builtin
    197     elif (( $+commands[(e)$1] )); then
    198       REPLY=command
    199     # None of the special hashes had a match, so fall back to 'type -w', for
    200     # forward compatibility with future versions of zsh that may add new command
    201     # types.
    202     #
    203     # zsh 5.2 and older have a bug whereby running 'type -w ./sudo' implicitly
    204     # runs 'hash ./sudo=/usr/local/bin/./sudo' (assuming /usr/local/bin/sudo
    205     # exists and is in $PATH).  Avoid triggering the bug, at the expense of
    206     # falling through to the $() below, incurring a fork.  (Issue #354.)
    207     #
    208     # The first disjunct mimics the isrelative() C call from the zsh bug.
    209     elif {  [[ $1 != */* ]] || is-at-least 5.3 } &&
    210          # Add a subshell to avoid a zsh upstream bug; see issue #606.
    211          # ### Remove the subshell when we stop supporting zsh 5.7.1 (I assume 5.8 will have the bugfix).
    212          ! (builtin type -w -- "$1") >/dev/null 2>&1; then
    213       REPLY=none
    214     fi
    215   fi
    216   if ! (( $+REPLY )); then
    217     # zsh/parameter not available or had no matches.
    218     #
    219     # Note that 'type -w' will run 'rehash' implicitly.
    220     #
    221     # We 'unalias' in a subshell, so the parent shell is not affected.
    222     #
    223     # The colon command is there just to avoid a command substitution that
    224     # starts with an arithmetic expression [«((…))» as the first thing inside
    225     # «$(…)»], which is area that has had some parsing bugs before 5.6
    226     # (approximately).
    227     REPLY="${$(:; (( aliases_allowed )) || unalias -- "$1" 2>/dev/null; LC_ALL=C builtin type -w -- "$1" 2>/dev/null)##*: }"
    228     if [[ $REPLY == 'alias' ]]; then
    229       may_cache=0
    230     fi
    231   fi
    232 
    233   # Cache population
    234   if (( may_cache )) && (( $+_zsh_highlight_main__command_type_cache )); then
    235     _zsh_highlight_main__command_type_cache[(e)$1]=$REPLY
    236   fi
    237   [[ -n $REPLY ]]
    238   return $?
    239 }
    240 
    241 # Checks whether $1 is something that can be run.
    242 #
    243 # Return 0 if runnable, 1 if not runnable, 2 if trouble.
    244 _zsh_highlight_main__is_runnable() {
    245   if _zsh_highlight_main__type "$1"; then
    246     [[ $REPLY != none ]]
    247   else
    248     return 2
    249   fi
    250 }
    251 
    252 # Check whether the first argument is a redirection operator token.
    253 # Report result via the exit code.
    254 _zsh_highlight_main__is_redirection() {
    255   # A redirection operator token:
    256   # - starts with an optional single-digit number;
    257   # - then, has a '<' or '>' character;
    258   # - is not a process substitution [<(...) or >(...)].
    259   # - is not a numeric glob <->
    260   [[ $1 == (<0-9>|)(\<|\>)* ]] && [[ $1 != (\<|\>)$'\x28'* ]] && [[ $1 != *'<'*'-'*'>'* ]]
    261 }
    262 
    263 # Resolve alias.
    264 #
    265 # Takes a single argument.
    266 #
    267 # The result will be stored in REPLY.
    268 _zsh_highlight_main__resolve_alias() {
    269   if zmodload -e zsh/parameter; then
    270     REPLY=${aliases[$arg]}
    271   else
    272     REPLY="${"$(alias -- $arg)"#*=}"
    273   fi
    274 }
    275 
    276 # Return true iff $1 is a global alias
    277 _zsh_highlight_main__is_global_alias() {
    278   if zmodload -e zsh/parameter; then
    279     (( ${+galiases[$arg]} ))
    280   elif [[ $arg == '='* ]]; then
    281     # avoid running into «alias -L '=foo'» erroring out with 'bad assignment'
    282     return 1
    283   else
    284     alias -L -g -- "$1" >/dev/null
    285   fi
    286 }
    287 
    288 # Check that the top of $braces_stack has the expected value.  If it does, set
    289 # the style according to $2; otherwise, set style=unknown-token.
    290 #
    291 # $1: character expected to be at the top of $braces_stack
    292 # $2: optional assignment to style it if matches
    293 # return value is 0 if there is a match else 1
    294 _zsh_highlight_main__stack_pop() {
    295   if [[ $braces_stack[1] == $1 ]]; then
    296     braces_stack=${braces_stack:1}
    297     if (( $+2 )); then
    298       style=$2
    299     fi
    300     return 0
    301   else
    302     style=unknown-token
    303     return 1
    304   fi
    305 }
    306 
    307 # Main syntax highlighting function.
    308 _zsh_highlight_highlighter_main_paint()
    309 {
    310   setopt localoptions extendedglob
    311 
    312   # At the PS3 prompt and in vared, highlight nothing.
    313   #
    314   # (We can't check this in _zsh_highlight_highlighter_main_predicate because
    315   # if the predicate returns false, the previous value of region_highlight
    316   # would be reused.)
    317   if [[ $CONTEXT == (select|vared) ]]; then
    318     return
    319   fi
    320 
    321   typeset -a ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR
    322   typeset -a ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW
    323   local -a options_to_set reply # used in callees
    324   local REPLY
    325 
    326   # $flags_with_argument is a set of letters, corresponding to the option letters
    327   # that would be followed by a colon in a getopts specification.
    328   local flags_with_argument
    329   # $flags_sans_argument is a set of letters, corresponding to the option letters
    330   # that wouldn't be followed by a colon in a getopts specification.
    331   local flags_sans_argument
    332   # $flags_solo is a set of letters, corresponding to option letters that, if
    333   # present, mean the precommand will not be acting as a precommand, i.e., will
    334   # not be followed by a :start: word.
    335   local flags_solo
    336   # $precommand_options maps precommand name to values of $flags_with_argument,
    337   # $flags_sans_argument, and flags_solo for that precommand, joined by a
    338   # colon.  (The value is NOT a getopt(3) spec, although it resembles one.)
    339   #
    340   # Currently, setting $flags_sans_argument is only important for commands that
    341   # have a non-empty $flags_with_argument; see test-data/precommand4.zsh.
    342   local -A precommand_options
    343   precommand_options=(
    344     # Precommand modifiers as of zsh 5.6.2 cf. zshmisc(1).
    345     '-' ''
    346     'builtin' ''
    347     'command' :pvV
    348     'exec' a:cl
    349     'noglob' ''
    350     # 'time' and 'nocorrect' shouldn't be added here; they're reserved words, not precommands.
    351 
    352     # miscellaneous commands
    353     'doas' aCu:Lns # as of OpenBSD's doas(1) dated September 4, 2016
    354     'nice' n: # as of current POSIX spec
    355     'pkexec' '' # doesn't take short options; immune to #121 because it's usually not passed --option flags
    356     # Not listed: -h, which has two different meanings.
    357     'sudo' Cgprtu:AEHPSbilns:eKkVv # as of sudo 1.8.21p2
    358     'stdbuf' ioe:
    359     'eatmydata' ''
    360     'catchsegv' ''
    361     'nohup' ''
    362     'setsid' :wc
    363     'env' u:i
    364     'ionice' cn:t:pPu # util-linux 2.33.1-0.1
    365     'strace' IbeaosXPpEuOS:ACdfhikqrtTvVxyDc # strace 4.26-0.2
    366     'proxychains' f:q # proxychains 4.4.0
    367     'torsocks' idq:upaP # Torsocks 2.3.0
    368     'torify' idq:upaP # Torsocks 2.3.0
    369     'ssh-agent' aEPt:csDd:k # As of OpenSSH 8.1p1
    370     'tabbed' gnprtTuU:cdfhs:v # suckless-tools v44
    371     'chronic' :ev # moreutils 0.62-1
    372     'ifne' :n # moreutils 0.62-1
    373     'grc' :se # grc - a "generic colouriser" (that's their spelling, not mine)
    374     'cpulimit' elp:ivz # cpulimit 0.2
    375   )
    376   # Commands that would need to skip one positional argument:
    377   #    flock
    378   #    ssh
    379   #    _wanted (skip two)
    380 
    381   if [[ $zsyh_user_options[ignorebraces] == on || ${zsyh_user_options[ignoreclosebraces]:-off} == on ]]; then
    382     local right_brace_is_recognised_everywhere=false
    383   else
    384     local right_brace_is_recognised_everywhere=true
    385   fi
    386 
    387   if [[ $zsyh_user_options[pathdirs] == on ]]; then
    388     options_to_set+=( PATH_DIRS )
    389   fi
    390 
    391   ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR=(
    392     '|' '||' ';' '&' '&&'
    393     $'\n' # ${(z)} returns ';' but we convert it to $'\n'
    394     '|&'
    395     '&!' '&|'
    396     # ### 'case' syntax, but followed by a pattern, not by a command
    397     # ';;' ';&' ';|'
    398   )
    399 
    400   # Tokens that, at (naively-determined) "command position", are followed by
    401   # a de jure command position.  All of these are reserved words.
    402   ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW=(
    403     $'\x7b' # block
    404     $'\x28' # subshell
    405     '()' # anonymous function
    406     'while'
    407     'until'
    408     'if'
    409     'then'
    410     'elif'
    411     'else'
    412     'do'
    413     'time'
    414     'coproc'
    415     '!' # reserved word; unrelated to $histchars[1]
    416   )
    417 
    418   if (( $+X_ZSH_HIGHLIGHT_DIRS_BLACKLIST )); then
    419     print >&2 'zsh-syntax-highlighting: X_ZSH_HIGHLIGHT_DIRS_BLACKLIST is deprecated. Please use ZSH_HIGHLIGHT_DIRS_BLACKLIST.'
    420     ZSH_HIGHLIGHT_DIRS_BLACKLIST=($X_ZSH_HIGHLIGHT_DIRS_BLACKLIST)
    421     unset X_ZSH_HIGHLIGHT_DIRS_BLACKLIST
    422   fi
    423 
    424   _zsh_highlight_main_highlighter_highlight_list -$#PREBUFFER '' 1 "$PREBUFFER$BUFFER"
    425 
    426   # end is a reserved word
    427   local start end_ style
    428   for start end_ style in $reply; do
    429     (( start >= end_ )) && { print -r -- >&2 "zsh-syntax-highlighting: BUG: _zsh_highlight_highlighter_main_paint: start($start) >= end($end_)"; return }
    430     (( end_ <= 0 )) && continue
    431     (( start < 0 )) && start=0 # having start<0 is normal with e.g. multiline strings
    432     _zsh_highlight_main_calculate_fallback $style
    433     _zsh_highlight_add_highlight $start $end_ $reply
    434   done
    435 }
    436 
    437 # Try to expand $1, if it's possible to do so safely.
    438 # 
    439 # Uses two parameters from the caller: $parameter_name_pattern and $res.
    440 #
    441 # If expansion was done, set $reply to the expansion and return true.
    442 # Otherwise, return false.
    443 _zsh_highlight_main_highlighter__try_expand_parameter()
    444 {
    445   local arg="$1"
    446   unset reply
    447   {
    448     # ### For now, expand just '$foo' or '${foo}', possibly with braces, but with
    449     # ### no other features of the parameter expansion syntax.  (No ${(x)foo},
    450     # ### no ${foo[x]}, no ${foo:-x}.)
    451     {
    452       local -a match mbegin mend
    453       local MATCH; integer MBEGIN MEND
    454       local parameter_name
    455       local -a words
    456       if [[ $arg[1] != '$' ]]; then
    457         return 1
    458       fi
    459       if [[ ${arg[2]} == '{' ]] && [[ ${arg[-1]} == '}' ]]; then
    460         parameter_name=${${arg:2}%?}
    461       else
    462         parameter_name=${arg:1}
    463       fi
    464       if [[ $res == none ]] && 
    465          [[ ${parameter_name} =~ ^${~parameter_name_pattern}$ ]] &&
    466          [[ ${(tP)MATCH} != *special* ]]
    467       then
    468         # Set $arg and update $res.
    469         case ${(tP)MATCH} in
    470           (*array*|*assoc*)
    471             words=( ${(P)MATCH} )
    472             ;;
    473           ("")
    474             # not set
    475             words=( )
    476             ;;
    477           (*)
    478             # scalar, presumably
    479             if [[ $zsyh_user_options[shwordsplit] == on ]]; then
    480               words=( ${(P)=MATCH} )
    481             else
    482               words=( ${(P)MATCH} )
    483             fi
    484             ;;
    485         esac
    486         reply=( "${words[@]}" )
    487       else
    488         return 1
    489       fi
    490     }
    491   }
    492 }
    493 
    494 # $1 is the offset of $4 from the parent buffer. Added to the returned highlights.
    495 # $2 is the initial braces_stack (for a closing paren).
    496 # $3 is 1 if $4 contains the end of $BUFFER, else 0.
    497 # $4 is the buffer to highlight.
    498 # Returns:
    499 # $REPLY: $buf[REPLY] is the last character parsed.
    500 # $reply is an array of region_highlight additions.
    501 # exit code is 0 if the braces_stack is empty, 1 otherwise.
    502 _zsh_highlight_main_highlighter_highlight_list()
    503 {
    504   integer start_pos end_pos=0 buf_offset=$1 has_end=$3
    505   # alias_style is the style to apply to an alias once $#in_alias == 0
    506   #     Usually 'alias' but set to 'unknown-token' if any word expanded from
    507   #     the alias would be highlighted as unknown-token
    508   # param_style is analogous for parameter expansions
    509   local alias_style param_style last_arg arg buf=$4 highlight_glob=true saw_assignment=false style
    510   local in_array_assignment=false # true between 'a=(' and the matching ')'
    511   # in_alias is an array of integers with each element equal to the number
    512   #     of shifts needed until arg=args[1] pops an arg from the next level up
    513   #     alias or from BUFFER.
    514   # in_param is analogous for parameter expansions
    515   integer in_param=0 len=$#buf
    516   local -a in_alias match mbegin mend list_highlights
    517   # seen_alias is a map of aliases already seen to avoid loops like alias a=b b=a
    518   local -A seen_alias
    519   # Pattern for parameter names
    520   readonly parameter_name_pattern='([A-Za-z_][A-Za-z0-9_]*|[0-9]+)'
    521   list_highlights=()
    522 
    523   # "R" for round
    524   # "Q" for square
    525   # "Y" for curly
    526   # "T" for [[ ]]
    527   # "S" for $( ), =( ), <( ), >( )
    528   # "D" for do/done
    529   # "$" for 'end' (matches 'foreach' always; also used with cshjunkiequotes in repeat/while)
    530   # "?" for 'if'/'fi'; also checked by 'elif'/'else'
    531   # ":" for 'then'
    532   local braces_stack=$2
    533 
    534   # State machine
    535   #
    536   # The states are:
    537   # - :start:      Command word
    538   # - :start_of_pipeline:      Start of a 'pipeline' as defined in zshmisc(1).
    539   #                Only valid when :start: is present
    540   # - :sudo_opt:   A leading-dash option to a precommand, whether it takes an
    541   #                argument or not.  (Example: sudo's "-u" or "-i".)
    542   # - :sudo_arg:   The argument to a precommand's leading-dash option,
    543   #                when given as a separate word; i.e., "foo" in "-u foo" (two
    544   #                words) but not in "-ufoo" (one word).
    545   #    Note:       :sudo_opt: and :sudo_arg: are used for any precommand
    546   #                declared in ${precommand_options}, not just for sudo(8).
    547   #                The naming is historical.
    548   # - :regular:    "Not a command word", and command delimiters are permitted.
    549   #                Mainly used to detect premature termination of commands.
    550   # - :always:     The word 'always' in the «{ foo } always { bar }» syntax.
    551   #
    552   # When the kind of a word is not yet known, $this_word / $next_word may contain
    553   # multiple states.  For example, after "sudo -i", the next word may be either
    554   # another --flag or a command name, hence the state would include both ':start:'
    555   # and ':sudo_opt:'.
    556   #
    557   # The tokens are always added with both leading and trailing colons to serve as
    558   # word delimiters (an improvised array); [[ $x == *':foo:'* ]] and x=${x//:foo:/}
    559   # will DTRT regardless of how many elements or repetitions $x has.
    560   #
    561   # Handling of redirections: upon seeing a redirection token, we must stall
    562   # the current state --- that is, the value of $this_word --- for two iterations
    563   # (one for the redirection operator, one for the word following it representing
    564   # the redirection target).  Therefore, we set $in_redirection to 2 upon seeing a
    565   # redirection operator, decrement it each iteration, and stall the current state
    566   # when it is non-zero.  Thus, upon reaching the next word (the one that follows
    567   # the redirection operator and target), $this_word will still contain values
    568   # appropriate for the word immediately following the word that preceded the
    569   # redirection operator.
    570   #
    571   # The "the previous word was a redirection operator" state is not communicated
    572   # to the next iteration via $next_word/$this_word as usual, but via
    573   # $in_redirection.  The value of $next_word from the iteration that processed
    574   # the operator is discarded.
    575   #
    576   # $in_redirection is currently used for:
    577   # - comments
    578   # - aliases
    579   # - redirections
    580   # - parameter elision in command position
    581   # - 'repeat' loops
    582   #
    583   local this_word next_word=':start::start_of_pipeline:'
    584   integer in_redirection
    585   # Processing buffer
    586   local proc_buf="$buf"
    587   local -a args
    588   if [[ $zsyh_user_options[interactivecomments] == on ]]; then
    589     args=(${(zZ+c+)buf})
    590   else
    591     args=(${(z)buf})
    592   fi
    593 
    594   # Special case: $(<*) isn't globbing.
    595   if [[ $braces_stack == 'S' ]] && (( $+args[3] && ! $+args[4] )) && [[ $args[3] == $'\x29' ]] &&
    596      [[ $args[1] == *'<'* ]] && _zsh_highlight_main__is_redirection $args[1]; then
    597     highlight_glob=false
    598   fi
    599 
    600   while (( $#args )); do
    601     last_arg=$arg
    602     arg=$args[1]
    603     shift args
    604     if (( $#in_alias )); then
    605       (( in_alias[1]-- ))
    606       # Remove leading 0 entries
    607       in_alias=($in_alias[$in_alias[(i)<1->],-1])
    608       if (( $#in_alias == 0 )); then
    609         seen_alias=()
    610         # start_pos and end_pos are of the alias (previous $arg) here
    611         _zsh_highlight_main_add_region_highlight $start_pos $end_pos $alias_style
    612       else
    613         # We can't unset keys that contain special characters (] \ and some others).
    614         # More details: https://www.zsh.org/workers/43269
    615         (){
    616           local alias_name
    617           for alias_name in ${(k)seen_alias[(R)<$#in_alias->]}; do
    618             seen_alias=("${(@kv)seen_alias[(I)^$alias_name]}")
    619           done
    620         }
    621       fi
    622     fi
    623     if (( in_param )); then
    624       (( in_param-- ))
    625       if (( in_param == 0 )); then
    626         # start_pos and end_pos are of the '$foo' word (previous $arg) here
    627         _zsh_highlight_main_add_region_highlight $start_pos $end_pos $param_style
    628         param_style=""
    629       fi
    630     fi
    631 
    632     # Initialize this_word and next_word.
    633     if (( in_redirection == 0 )); then
    634       this_word=$next_word
    635       next_word=':regular:'
    636     elif (( !in_param )); then
    637       # Stall $next_word.
    638       (( --in_redirection ))
    639     fi
    640 
    641     # Initialize per-"simple command" [zshmisc(1)] variables:
    642     #
    643     #   $style               how to highlight $arg
    644     #   $in_array_assignment boolean flag for "between '(' and ')' of array assignment"
    645     #   $highlight_glob      boolean flag for "'noglob' is in effect"
    646     #   $saw_assignment      boolean flag for "was preceded by an assignment"
    647     #
    648     style=unknown-token
    649     if [[ $this_word == *':start:'* ]]; then
    650       in_array_assignment=false
    651       if [[ $arg == 'noglob' ]]; then
    652         highlight_glob=false
    653       fi
    654     fi
    655 
    656     if (( $#in_alias == 0 && in_param == 0 )); then
    657       # Compute the new $start_pos and $end_pos, skipping over whitespace in $buf.
    658       [[ "$proc_buf" = (#b)(#s)(''([ $'\t']|[\\]$'\n')#)(?|)* ]]
    659       # The first, outer parenthesis
    660       integer offset="${#match[1]}"
    661       (( start_pos = end_pos + offset ))
    662       (( end_pos = start_pos + $#arg ))
    663 
    664       # The zsh lexer considers ';' and newline to be the same token, so
    665       # ${(z)} converts all newlines to semicolons. Convert them back here to
    666       # make later processing simpler.
    667       [[ $arg == ';' && ${match[3]} == $'\n' ]] && arg=$'\n'
    668 
    669       # Compute the new $proc_buf. We advance it
    670       # (chop off characters from the beginning)
    671       # beyond what end_pos points to, by skipping
    672       # as many characters as end_pos was advanced.
    673       #
    674       # end_pos was advanced by $offset (via start_pos)
    675       # and by $#arg. Note the `start_pos=$end_pos`
    676       # below.
    677       #
    678       # As for the [,len]. We could use [,len-start_pos+offset]
    679       # here, but to make it easier on eyes, we use len and
    680       # rely on the fact that Zsh simply handles that. The
    681       # length of proc_buf is len-start_pos+offset because
    682       # we're chopping it to match current start_pos, so its
    683       # length matches the previous value of start_pos.
    684       #
    685       # Why [,-1] is slower than [,length] isn't clear.
    686       proc_buf="${proc_buf[offset + $#arg + 1,len]}"
    687     fi
    688 
    689     # Handle the INTERACTIVE_COMMENTS option.
    690     #
    691     # We use the (Z+c+) flag so the entire comment is presented as one token in $arg.
    692     if [[ $zsyh_user_options[interactivecomments] == on && $arg[1] == $histchars[3] ]]; then
    693       if [[ $this_word == *(':regular:'|':start:')* ]]; then
    694         style=comment
    695       else
    696         style=unknown-token # prematurely terminated
    697       fi
    698       _zsh_highlight_main_add_region_highlight $start_pos $end_pos $style
    699       # Stall this arg
    700       in_redirection=1
    701       continue
    702     fi
    703 
    704     if [[ $this_word == *':start:'* ]] && ! (( in_redirection )); then
    705       # Expand aliases.
    706       # An alias is ineligible for expansion while it's being expanded (see #652/#653).
    707       _zsh_highlight_main__type "$arg" "$(( ! ${+seen_alias[$arg]} ))"
    708       local res="$REPLY"
    709       if [[ $res == "alias" ]]; then
    710         # Mark insane aliases as unknown-token (cf. #263).
    711         if [[ $arg == ?*=* ]]; then
    712           _zsh_highlight_main_add_region_highlight $start_pos $end_pos unknown-token
    713           continue
    714         fi
    715         seen_alias[$arg]=$#in_alias
    716         _zsh_highlight_main__resolve_alias $arg
    717         local -a alias_args
    718         # Elision is desired in case alias x=''
    719         if [[ $zsyh_user_options[interactivecomments] == on ]]; then
    720           alias_args=(${(zZ+c+)REPLY})
    721         else
    722           alias_args=(${(z)REPLY})
    723         fi
    724         args=( $alias_args $args )
    725         if (( $#in_alias == 0 )); then
    726           alias_style=alias
    727         else
    728           # Transfer the count of this arg to the new element about to be appended.
    729           (( in_alias[1]-- ))
    730         fi
    731         # Add one because we will in_alias[1]-- on the next loop iteration so
    732         # this iteration should be considered in in_alias as well
    733         in_alias=( $(($#alias_args + 1)) $in_alias )
    734         (( in_redirection++ )) # Stall this arg
    735         continue
    736       else
    737         _zsh_highlight_main_highlighter_expand_path $arg
    738         _zsh_highlight_main__type "$REPLY" 0
    739         res="$REPLY"
    740       fi
    741     fi
    742 
    743     # Analyse the current word.
    744     if _zsh_highlight_main__is_redirection $arg ; then
    745       if (( in_redirection == 1 )); then
    746         # Two consecutive redirection operators is an error.
    747         _zsh_highlight_main_add_region_highlight $start_pos $end_pos unknown-token
    748       else
    749         in_redirection=2
    750         _zsh_highlight_main_add_region_highlight $start_pos $end_pos redirection
    751       fi
    752       continue
    753     elif [[ $arg == '{'${~parameter_name_pattern}'}' ]] && _zsh_highlight_main__is_redirection $args[1]; then
    754       # named file descriptor: {foo}>&2
    755       in_redirection=3
    756       _zsh_highlight_main_add_region_highlight $start_pos $end_pos named-fd
    757       continue
    758     fi
    759 
    760     # Expand parameters.
    761     if (( ! in_param )) && _zsh_highlight_main_highlighter__try_expand_parameter "$arg"; then
    762       # That's not entirely correct --- if the parameter's value happens to be a reserved
    763       # word, the parameter expansion will be highlighted as a reserved word --- but that
    764       # incorrectness is outweighed by the usability improvement of permitting the use of
    765       # parameters that refer to commands, functions, and builtins.
    766       () {
    767         local -a words; words=( "${reply[@]}" )
    768         if (( $#words == 0 )) && (( ! in_redirection )); then
    769           # Parameter elision is happening
    770           (( ++in_redirection ))
    771           _zsh_highlight_main_add_region_highlight $start_pos $end_pos comment
    772           continue
    773         else
    774           (( in_param = 1 + $#words ))
    775           args=( $words $args )
    776           arg=$args[1]
    777           _zsh_highlight_main__type "$arg" 0
    778           res=$REPLY
    779         fi
    780       }
    781     fi
    782 
    783     # Parse the sudo command line
    784     if (( ! in_redirection )); then
    785       if [[ $this_word == *':sudo_opt:'* ]]; then
    786         if [[ -n $flags_with_argument ]] &&
    787            { 
    788              # Trenary
    789              if [[ -n $flags_sans_argument ]]
    790              then [[ $arg == '-'[$flags_sans_argument]#[$flags_with_argument] ]]
    791              else [[ $arg == '-'[$flags_with_argument] ]]
    792              fi
    793            } then
    794           # Flag that requires an argument
    795           this_word=${this_word//:start:/}
    796           next_word=':sudo_arg:'
    797         elif [[ -n $flags_with_argument ]] &&
    798              {
    799                # Trenary
    800                if [[ -n $flags_sans_argument ]]
    801                then [[ $arg == '-'[$flags_sans_argument]#[$flags_with_argument]* ]]
    802                else [[ $arg == '-'[$flags_with_argument]* ]]
    803                fi
    804              } then
    805           # Argument attached in the same word
    806           this_word=${this_word//:start:/}
    807           next_word+=':start:'
    808           next_word+=':sudo_opt:'
    809         elif [[ -n $flags_sans_argument ]] &&
    810              [[ $arg == '-'[$flags_sans_argument]# ]]; then
    811           # Flag that requires no argument
    812           this_word=':sudo_opt:'
    813           next_word+=':start:'
    814           next_word+=':sudo_opt:'
    815         elif [[ -n $flags_solo ]] && 
    816              {
    817                # Trenary
    818                if [[ -n $flags_sans_argument ]]
    819                then [[ $arg == '-'[$flags_sans_argument]#[$flags_solo]* ]]
    820                else [[ $arg == '-'[$flags_solo]* ]]
    821                fi
    822              } then
    823           # Solo flags
    824           this_word=':sudo_opt:'
    825           next_word=':regular:' # no :start:, nor :sudo_opt: since we don't know whether the solo flag takes an argument or not
    826         elif [[ $arg == '-'* ]]; then
    827           # Unknown flag.  We don't know whether it takes an argument or not,
    828           # so modify $next_word as we do for flags that require no argument.
    829           # With that behaviour, if the flag in fact takes no argument we'll
    830           # highlight the inner command word correctly, and if it does take an
    831           # argument we'll highlight the command word correctly if the argument
    832           # was given in the same shell word as the flag (as in '-uphy1729' or
    833           # '--user=phy1729' without spaces).
    834           this_word=':sudo_opt:'
    835           next_word+=':start:'
    836           next_word+=':sudo_opt:'
    837         else
    838           # Not an option flag; nothing to do.  (If the command line is
    839           # syntactically valid, ${this_word//:sudo_opt:/} should be
    840           # non-empty now.)
    841           this_word=${this_word//:sudo_opt:/}
    842         fi
    843       elif [[ $this_word == *':sudo_arg:'* ]]; then
    844         next_word+=':sudo_opt:'
    845         next_word+=':start:'
    846       fi
    847     fi
    848 
    849     # The Great Fork: is this a command word?  Is this a non-command word?
    850     if [[ -n ${(M)ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR:#"$arg"} ]] &&
    851        [[ $braces_stack != *T* || $arg != ('||'|'&&') ]]; then
    852 
    853       # First, determine the style of the command separator itself.
    854       if _zsh_highlight_main__stack_pop T || _zsh_highlight_main__stack_pop Q; then
    855         # Missing closing square bracket(s)
    856         style=unknown-token
    857       elif $in_array_assignment; then
    858         case $arg in
    859           # Literal newlines are just fine.
    860           ($'\n') style=commandseparator;;
    861           # Semicolons are parsed the same way as literal newlines.  Nevertheless,
    862           # highlight them as errors since they're probably unintended.  Compare
    863           # issue #691.
    864           (';') style=unknown-token;;
    865           # Other command separators aren't allowed.
    866           (*) style=unknown-token;;
    867         esac
    868       elif [[ $this_word == *':regular:'* ]]; then
    869         style=commandseparator
    870       elif [[ $this_word == *':start:'* ]] && [[ $arg == $'\n' ]]; then
    871         style=commandseparator
    872       elif [[ $this_word == *':start:'* ]] && [[ $arg == ';' ]] && (( $#in_alias )); then
    873         style=commandseparator 
    874       else
    875         # Empty commands (semicolon follows nothing) are valid syntax.
    876         # However, in interactive use they are likely to be erroneous;
    877         # therefore, we highlight them as errors.
    878         #
    879         # Alias definitions are exempted from this check to allow multiline aliases
    880         # with explicit (redundant) semicolons: «alias foo=$'bar;\nbaz'» (issue #677).
    881         #
    882         # See also #691 about possibly changing the style used here. 
    883         style=unknown-token
    884       fi
    885 
    886       # Second, determine the style of next_word.
    887       if [[ $arg == $'\n' ]] && $in_array_assignment; then
    888         # literal newline inside an array assignment
    889         next_word=':regular:'
    890       elif [[ $arg == ';' ]] && $in_array_assignment; then
    891         # literal semicolon inside an array assignment
    892         next_word=':regular:'
    893       else
    894         next_word=':start:'
    895         highlight_glob=true
    896         saw_assignment=false
    897         (){
    898           local alias_name
    899           for alias_name in ${(k)seen_alias[(R)<$#in_alias->]}; do
    900             # We can't unset keys that contain special characters (] \ and some others).
    901             # More details: https://www.zsh.org/workers/43269
    902             seen_alias=("${(@kv)seen_alias[(I)^$alias_name]}")
    903           done
    904         }
    905         if [[ $arg != '|' && $arg != '|&' ]]; then
    906           next_word+=':start_of_pipeline:'
    907         fi
    908       fi
    909 
    910     elif ! (( in_redirection)) && [[ $this_word == *':always:'* && $arg == 'always' ]]; then
    911       # try-always construct
    912       style=reserved-word # de facto a reserved word, although not de jure
    913       highlight_glob=true
    914       saw_assignment=false
    915       next_word=':start::start_of_pipeline:' # only left brace is allowed, apparently
    916     elif ! (( in_redirection)) && [[ $this_word == *':start:'* ]]; then # $arg is the command word
    917       if (( ${+precommand_options[$arg]} )) && _zsh_highlight_main__is_runnable $arg; then
    918         style=precommand
    919         () {
    920           set -- "${(@s.:.)precommand_options[$arg]}"
    921           flags_with_argument=$1
    922           flags_sans_argument=$2
    923           flags_solo=$3
    924         }
    925         next_word=${next_word//:regular:/}
    926         next_word+=':sudo_opt:'
    927         next_word+=':start:'
    928         if [[ $arg == 'exec' || $arg == 'env' ]]; then
    929           # To allow "exec 2>&1;" and "env | grep" where there's no command word
    930           next_word+=':regular:'
    931         fi
    932       else
    933         case $res in
    934           (reserved)    # reserved word
    935                         style=reserved-word
    936                         # Match braces and handle special cases.
    937                         case $arg in
    938                           (time|nocorrect)
    939                             next_word=${next_word//:regular:/}
    940                             next_word+=':start:'
    941                             ;;
    942                           ($'\x7b')
    943                             braces_stack='Y'"$braces_stack"
    944                             ;;
    945                           ($'\x7d')
    946                             # We're at command word, so no need to check $right_brace_is_recognised_everywhere
    947                             _zsh_highlight_main__stack_pop 'Y' reserved-word
    948                             if [[ $style == reserved-word ]]; then
    949                               next_word+=':always:'
    950                             fi
    951                             ;;
    952                           ($'\x5b\x5b')
    953                             braces_stack='T'"$braces_stack"
    954                             ;;
    955                           ('do')
    956                             braces_stack='D'"$braces_stack"
    957                             ;;
    958                           ('done')
    959                             _zsh_highlight_main__stack_pop 'D' reserved-word
    960                             ;;
    961                           ('if')
    962                             braces_stack=':?'"$braces_stack"
    963                             ;;
    964                           ('then')
    965                             _zsh_highlight_main__stack_pop ':' reserved-word
    966                             ;;
    967                           ('elif')
    968                             if [[ ${braces_stack[1]} == '?' ]]; then
    969                               braces_stack=':'"$braces_stack"
    970                             else
    971                               style=unknown-token
    972                             fi
    973                             ;;
    974                           ('else')
    975                             if [[ ${braces_stack[1]} == '?' ]]; then
    976                               :
    977                             else
    978                               style=unknown-token
    979                             fi
    980                             ;;
    981                           ('fi')
    982                             _zsh_highlight_main__stack_pop '?'
    983                             ;;
    984                           ('foreach')
    985                             braces_stack='$'"$braces_stack"
    986                             ;;
    987                           ('end')
    988                             _zsh_highlight_main__stack_pop '$' reserved-word
    989                             ;;
    990                           ('repeat')
    991                             # skip the repeat-count word
    992                             in_redirection=2
    993                             # The redirection mechanism assumes $this_word describes the word
    994                             # following the redirection.  Make it so.
    995                             #
    996                             # That word can be a command word with shortloops (`repeat 2 ls`)
    997                             # or a command separator (`repeat 2; ls` or `repeat 2; do ls; done`).
    998                             #
    999                             # The repeat-count word will be handled like a redirection target.
   1000                             this_word=':start::regular:'
   1001                             ;;
   1002                           ('!')
   1003                             if [[ $this_word != *':start_of_pipeline:'* ]]; then
   1004                               style=unknown-token
   1005                             else
   1006                               # '!' reserved word at start of pipeline; style already set above
   1007                             fi
   1008                             ;;
   1009                         esac
   1010                         if $saw_assignment && [[ $style != unknown-token ]]; then
   1011                           style=unknown-token
   1012                         fi
   1013                         ;;
   1014           ('suffix alias')
   1015                         style=suffix-alias
   1016                         ;;
   1017           ('global alias')
   1018                         style=global-alias
   1019                         ;;
   1020           (alias)       :;;
   1021           (builtin)     style=builtin
   1022                         [[ $arg == $'\x5b' ]] && braces_stack='Q'"$braces_stack"
   1023                         ;;
   1024           (function)    style=function;;
   1025           (command)     style=command;;
   1026           (hashed)      style=hashed-command;;
   1027           (none)        if (( ! in_param )) && _zsh_highlight_main_highlighter_check_assign; then
   1028                           _zsh_highlight_main_add_region_highlight $start_pos $end_pos assign
   1029                           local i=$(( arg[(i)=] + 1 ))
   1030                           saw_assignment=true
   1031                           if [[ $arg[i] == '(' ]]; then
   1032                             in_array_assignment=true
   1033                             _zsh_highlight_main_add_region_highlight start_pos+i-1 start_pos+i reserved-word
   1034                           else
   1035                             # assignment to a scalar parameter.
   1036                             # (For array assignments, the command doesn't start until the ")" token.)
   1037                             # 
   1038                             # Discard  :start_of_pipeline:, if present, as '!' is not valid
   1039                             # after assignments.
   1040                             next_word+=':start:'
   1041                             if (( i <= $#arg )); then
   1042                               () {
   1043                                 local highlight_glob=false
   1044                                 [[ $zsyh_user_options[globassign] == on ]] && highlight_glob=true
   1045                                 _zsh_highlight_main_highlighter_highlight_argument $i
   1046                               }
   1047                             fi
   1048                           fi
   1049                           continue
   1050                         elif (( ! in_param )) &&
   1051                              [[ $arg[0,1] = $histchars[0,1] ]] && (( $#arg[0,2] == 2 )); then
   1052                           style=history-expansion
   1053                         elif (( ! in_param )) &&
   1054                              [[ $arg[0,1] == $histchars[2,2] ]]; then
   1055                           style=history-expansion
   1056                         elif (( ! in_param )) &&
   1057                              ! $saw_assignment &&
   1058                              [[ $arg[1,2] == '((' ]]; then
   1059                           # Arithmetic evaluation.
   1060                           #
   1061                           # Note: prior to zsh-5.1.1-52-g4bed2cf (workers/36669), the ${(z)...}
   1062                           # splitter would only output the '((' token if the matching '))' had
   1063                           # been typed.  Therefore, under those versions of zsh, BUFFER="(( 42"
   1064                           # would be highlighted as an error until the matching "))" are typed.
   1065                           #
   1066                           # We highlight just the opening parentheses, as a reserved word; this
   1067                           # is how [[ ... ]] is highlighted, too.
   1068                           _zsh_highlight_main_add_region_highlight $start_pos $((start_pos + 2)) reserved-word
   1069                           if [[ $arg[-2,-1] == '))' ]]; then
   1070                             _zsh_highlight_main_add_region_highlight $((end_pos - 2)) $end_pos reserved-word
   1071                           fi
   1072                           continue
   1073                         elif (( ! in_param )) &&
   1074                              [[ $arg == '()' ]]; then
   1075                           # anonymous function
   1076                           style=reserved-word
   1077                         elif (( ! in_param )) &&
   1078                              ! $saw_assignment &&
   1079                              [[ $arg == $'\x28' ]]; then
   1080                           # subshell
   1081                           style=reserved-word
   1082                           braces_stack='R'"$braces_stack"
   1083                         elif (( ! in_param )) &&
   1084                              [[ $arg == $'\x29' ]]; then
   1085                           # end of subshell or command substitution
   1086                           if _zsh_highlight_main__stack_pop 'S'; then
   1087                             REPLY=$start_pos
   1088                             reply=($list_highlights)
   1089                             return 0
   1090                           fi
   1091                           _zsh_highlight_main__stack_pop 'R' reserved-word
   1092                         else
   1093                           if _zsh_highlight_main_highlighter_check_path $arg 1; then
   1094                             style=$REPLY
   1095                           else
   1096                             style=unknown-token
   1097                           fi
   1098                         fi
   1099                         ;;
   1100           (*)           _zsh_highlight_main_add_region_highlight $start_pos $end_pos arg0_$res
   1101                         continue
   1102                         ;;
   1103         esac
   1104       fi
   1105       if [[ -n ${(M)ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW:#"$arg"} ]]; then
   1106         next_word=':start::start_of_pipeline:'
   1107       fi
   1108     elif _zsh_highlight_main__is_global_alias "$arg"; then # $arg is a global alias that isn't in command position
   1109       style=global-alias
   1110     else # $arg is a non-command word
   1111       case $arg in
   1112         ($'\x29')
   1113                   # subshell or end of array assignment
   1114                   if $in_array_assignment; then
   1115                     _zsh_highlight_main_add_region_highlight $start_pos $end_pos assign
   1116                     _zsh_highlight_main_add_region_highlight $start_pos $end_pos reserved-word
   1117                     in_array_assignment=false
   1118                     next_word+=':start:'
   1119                     continue
   1120                   elif (( in_redirection )); then
   1121                     style=unknown-token
   1122                   else
   1123                     if _zsh_highlight_main__stack_pop 'S'; then
   1124                       REPLY=$start_pos
   1125                       reply=($list_highlights)
   1126                       return 0
   1127                     fi
   1128                     _zsh_highlight_main__stack_pop 'R' reserved-word
   1129                   fi
   1130                   ;;
   1131         ($'\x28\x29')
   1132                   # possibly a function definition
   1133                   if (( in_redirection )) || $in_array_assignment; then
   1134                     style=unknown-token
   1135                   else
   1136                     if [[ $zsyh_user_options[multifuncdef] == on ]] || false # TODO: or if the previous word was a command word
   1137                     then
   1138                       next_word+=':start::start_of_pipeline:'
   1139                     fi
   1140                     style=reserved-word
   1141                   fi
   1142                   ;;
   1143         (*)       if false; then
   1144                   elif [[ $arg = $'\x7d' ]] && $right_brace_is_recognised_everywhere; then
   1145                     # Parsing rule: {
   1146                     #
   1147                     #     Additionally, `tt(})' is recognized in any position if neither the
   1148                     #     tt(IGNORE_BRACES) option nor the tt(IGNORE_CLOSE_BRACES) option is set.
   1149                     if (( in_redirection )) || $in_array_assignment; then
   1150                       style=unknown-token
   1151                     else
   1152                       _zsh_highlight_main__stack_pop 'Y' reserved-word
   1153                       if [[ $style == reserved-word ]]; then
   1154                         next_word+=':always:'
   1155                       fi
   1156                     fi
   1157                   elif [[ $arg[0,1] = $histchars[0,1] ]] && (( $#arg[0,2] == 2 )); then
   1158                     style=history-expansion
   1159                   elif [[ $arg == $'\x5d\x5d' ]] && _zsh_highlight_main__stack_pop 'T' reserved-word; then
   1160                     :
   1161                   elif [[ $arg == $'\x5d' ]] && _zsh_highlight_main__stack_pop 'Q' builtin; then
   1162                     :
   1163                   else
   1164                     _zsh_highlight_main_highlighter_highlight_argument 1 $(( 1 != in_redirection ))
   1165                     continue
   1166                   fi
   1167                   ;;
   1168       esac
   1169     fi
   1170     _zsh_highlight_main_add_region_highlight $start_pos $end_pos $style
   1171   done
   1172   (( $#in_alias )) && in_alias=() _zsh_highlight_main_add_region_highlight $start_pos $end_pos $alias_style
   1173   (( in_param == 1 )) && in_param=0 _zsh_highlight_main_add_region_highlight $start_pos $end_pos $param_style
   1174   [[ "$proc_buf" = (#b)(#s)(([[:space:]]|\\$'\n')#) ]]
   1175   REPLY=$(( end_pos + ${#match[1]} - 1 ))
   1176   reply=($list_highlights)
   1177   return $(( $#braces_stack > 0 ))
   1178 }
   1179 
   1180 # Check if $arg is variable assignment
   1181 _zsh_highlight_main_highlighter_check_assign()
   1182 {
   1183     setopt localoptions extended_glob
   1184     [[ $arg == [[:alpha:]_][[:alnum:]_]#(|\[*\])(|[+])=* ]] ||
   1185       [[ $arg == [0-9]##(|[+])=* ]]
   1186 }
   1187 
   1188 _zsh_highlight_main_highlighter_highlight_path_separators()
   1189 {
   1190   local pos style_pathsep
   1191   style_pathsep=$1_pathseparator
   1192   reply=()
   1193   [[ -z "$ZSH_HIGHLIGHT_STYLES[$style_pathsep]" || "$ZSH_HIGHLIGHT_STYLES[$1]" == "$ZSH_HIGHLIGHT_STYLES[$style_pathsep]" ]] && return 0
   1194   for (( pos = start_pos; $pos <= end_pos; pos++ )) ; do
   1195     if [[ $BUFFER[pos] == / ]]; then
   1196       reply+=($((pos - 1)) $pos $style_pathsep)
   1197     fi
   1198   done
   1199 }
   1200 
   1201 # Check if $1 is a path.
   1202 # If yes, return 0 and in $REPLY the style to use.
   1203 # Else, return non-zero (and the contents of $REPLY is undefined).
   1204 #
   1205 # $2 should be non-zero iff we're in command position.
   1206 _zsh_highlight_main_highlighter_check_path()
   1207 {
   1208   _zsh_highlight_main_highlighter_expand_path "$1"
   1209   local expanded_path="$REPLY" tmp_path
   1210   integer in_command_position=$2
   1211 
   1212   if [[ $zsyh_user_options[autocd] == on ]]; then
   1213     integer autocd=1
   1214   else
   1215     integer autocd=0
   1216   fi
   1217 
   1218   if (( in_command_position )); then
   1219     # ### Currently, this value is never returned: either it's overwritten
   1220     # ### below, or the return code is non-zero
   1221     REPLY=arg0
   1222   else
   1223     REPLY=path
   1224   fi
   1225 
   1226   if [[ ${1[1]} == '=' && $1 == ??* && ${1[2]} != $'\x28' && $zsyh_user_options[equals] == 'on' && $expanded_path[1] != '/' ]]; then
   1227     REPLY=unknown-token # will error out if executed
   1228     return 0
   1229   fi
   1230 
   1231   [[ -z $expanded_path ]] && return 1
   1232 
   1233   # Check if this is a blacklisted path
   1234   if [[ $expanded_path[1] == / ]]; then
   1235     tmp_path=$expanded_path
   1236   else
   1237     tmp_path=$PWD/$expanded_path
   1238   fi
   1239   tmp_path=$tmp_path:a
   1240 
   1241   while [[ $tmp_path != / ]]; do
   1242     [[ -n ${(M)ZSH_HIGHLIGHT_DIRS_BLACKLIST:#$tmp_path} ]] && return 1
   1243     tmp_path=$tmp_path:h
   1244   done
   1245 
   1246   if (( in_command_position )); then
   1247     if [[ -x $expanded_path ]]; then
   1248       if (( autocd )); then
   1249         if [[ -d $expanded_path ]]; then
   1250           REPLY=autodirectory
   1251         fi
   1252         return 0
   1253       elif [[ ! -d $expanded_path ]]; then
   1254         # ### This seems unreachable for the current callers
   1255         return 0
   1256       fi
   1257     fi
   1258   else
   1259     if [[ -L $expanded_path || -e $expanded_path ]]; then
   1260       return 0
   1261     fi
   1262   fi
   1263 
   1264   # Search the path in CDPATH
   1265   if [[ $expanded_path != /* ]] && (( autocd || ! in_command_position )); then
   1266     # TODO: When we've dropped support for pre-5.0.6 zsh, use the *(Y1) glob qualifier here.
   1267     local cdpath_dir
   1268     for cdpath_dir in $cdpath ; do
   1269       if [[ -d "$cdpath_dir/$expanded_path" && -x "$cdpath_dir/$expanded_path" ]]; then
   1270         if (( in_command_position && autocd )); then
   1271           REPLY=autodirectory
   1272         fi
   1273         return 0
   1274       fi
   1275     done
   1276   fi
   1277 
   1278   # If dirname($1) doesn't exist, neither does $1.
   1279   [[ ! -d ${expanded_path:h} ]] && return 1
   1280 
   1281   # If this word ends the buffer, check if it's the prefix of a valid path.
   1282   if (( has_end && (len == end_pos) )) &&
   1283      (( ! $#in_alias )) &&
   1284      [[ $WIDGET != zle-line-finish ]]; then
   1285     # TODO: When we've dropped support for pre-5.0.6 zsh, use the *(Y1) glob qualifier here.
   1286     local -a tmp
   1287     if (( in_command_position )); then
   1288       # We include directories even when autocd is enabled, because those
   1289       # directories might contain executable files: e.g., BUFFER="/bi" en route
   1290       # to typing "/bin/sh".
   1291       tmp=( ${expanded_path}*(N-*,N-/) )
   1292     else
   1293       tmp=( ${expanded_path}*(N) )
   1294     fi
   1295     (( ${+tmp[1]} )) && REPLY=path_prefix && return 0
   1296   fi
   1297 
   1298   # It's not a path.
   1299   return 1
   1300 }
   1301 
   1302 # Highlight an argument and possibly special chars in quotes starting at $1 in $arg
   1303 # This command will at least highlight $1 to end_pos with the default style
   1304 # If $2 is set to 0, the argument cannot be highlighted as an option.
   1305 #
   1306 # This function currently assumes it's never called for the command word.
   1307 _zsh_highlight_main_highlighter_highlight_argument()
   1308 {
   1309   local base_style=default i=$1 option_eligible=${2:-1} path_eligible=1 ret start style
   1310   local -a highlights
   1311 
   1312   local -a match mbegin mend
   1313   local MATCH; integer MBEGIN MEND
   1314 
   1315   case "$arg[i]" in
   1316     '%')
   1317       if [[ $arg[i+1] == '?' ]]; then
   1318         (( i += 2 ))
   1319       fi
   1320       ;;
   1321     '-')
   1322       if (( option_eligible )); then
   1323         if [[ $arg[i+1] == - ]]; then
   1324           base_style=double-hyphen-option
   1325         else
   1326           base_style=single-hyphen-option
   1327         fi
   1328         path_eligible=0
   1329       fi
   1330       ;;
   1331     '=')
   1332       if [[ $arg[i+1] == $'\x28' ]]; then
   1333         (( i += 2 ))
   1334         _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,-1]
   1335         ret=$?
   1336         (( i += REPLY ))
   1337         highlights+=(
   1338           $(( start_pos + $1 - 1 )) $(( start_pos + i )) process-substitution
   1339           $(( start_pos + $1 - 1 )) $(( start_pos + $1 + 1 )) process-substitution-delimiter
   1340           $reply
   1341         )
   1342         if (( ret == 0 )); then
   1343           highlights+=($(( start_pos + i - 1 )) $(( start_pos + i )) process-substitution-delimiter)
   1344         fi
   1345       fi
   1346   esac
   1347 
   1348   # This loop is a hot path.  Keep it fast!
   1349   (( --i ))
   1350   while (( ++i <= $#arg )); do
   1351     i=${arg[(ib.i.)[\\\'\"\`\$\<\>\*\?]]}
   1352     case "$arg[$i]" in
   1353       "") break;;
   1354       "\\") (( i += 1 )); continue;;
   1355       "'")
   1356         _zsh_highlight_main_highlighter_highlight_single_quote $i
   1357         (( i = REPLY ))
   1358         highlights+=($reply)
   1359         ;;
   1360       '"')
   1361         _zsh_highlight_main_highlighter_highlight_double_quote $i
   1362         (( i = REPLY ))
   1363         highlights+=($reply)
   1364         ;;
   1365       '`')
   1366         _zsh_highlight_main_highlighter_highlight_backtick $i
   1367         (( i = REPLY ))
   1368         highlights+=($reply)
   1369         ;;
   1370       '$')
   1371         if [[ $arg[i+1] != "'" ]]; then
   1372           path_eligible=0
   1373         fi
   1374         if [[ $arg[i+1] == "'" ]]; then
   1375           _zsh_highlight_main_highlighter_highlight_dollar_quote $i
   1376           (( i = REPLY ))
   1377           highlights+=($reply)
   1378           continue
   1379         elif [[ $arg[i+1] == $'\x28' ]]; then
   1380           if [[ $arg[i+2] == $'\x28' ]] && _zsh_highlight_main_highlighter_highlight_arithmetic $i; then
   1381             # Arithmetic expansion
   1382             (( i = REPLY ))
   1383             highlights+=($reply)
   1384             continue
   1385           fi
   1386           start=$i
   1387           (( i += 2 ))
   1388           _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,-1]
   1389           ret=$?
   1390           (( i += REPLY ))
   1391           highlights+=(
   1392             $(( start_pos + start - 1)) $(( start_pos + i )) command-substitution-unquoted
   1393             $(( start_pos + start - 1)) $(( start_pos + start + 1)) command-substitution-delimiter-unquoted
   1394             $reply
   1395           )
   1396           if (( ret == 0 )); then
   1397             highlights+=($(( start_pos + i - 1)) $(( start_pos + i )) command-substitution-delimiter-unquoted)
   1398           fi
   1399           continue
   1400         fi
   1401         while [[ $arg[i+1] == [=~#+'^'] ]]; do
   1402           (( i += 1 ))
   1403         done
   1404         if [[ $arg[i+1] == [*@#?$!-] ]]; then
   1405           (( i += 1 ))
   1406         fi;;
   1407       [\<\>])
   1408         if [[ $arg[i+1] == $'\x28' ]]; then # \x28 = open paren
   1409           start=$i
   1410           (( i += 2 ))
   1411           _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,-1]
   1412           ret=$?
   1413           (( i += REPLY ))
   1414           highlights+=(
   1415             $(( start_pos + start - 1)) $(( start_pos + i )) process-substitution
   1416             $(( start_pos + start - 1)) $(( start_pos + start + 1 )) process-substitution-delimiter
   1417             $reply
   1418           )
   1419           if (( ret == 0 )); then
   1420             highlights+=($(( start_pos + i - 1)) $(( start_pos + i )) process-substitution-delimiter)
   1421           fi
   1422           continue
   1423         fi
   1424         ;|
   1425       *)
   1426         if $highlight_glob &&
   1427            [[ $zsyh_user_options[multios] == on || $in_redirection -eq 0 ]] &&
   1428            [[ ${arg[$i]} =~ ^[*?] || ${arg:$i-1} =~ ^\<[0-9]*-[0-9]*\> ]]; then
   1429           highlights+=($(( start_pos + i - 1 )) $(( start_pos + i + $#MATCH - 1)) globbing)
   1430           (( i += $#MATCH - 1 ))
   1431           path_eligible=0
   1432         else
   1433           continue
   1434         fi
   1435         ;;
   1436     esac
   1437   done
   1438 
   1439   if (( path_eligible )); then
   1440     if (( in_redirection )) && [[ $last_arg == *['<>']['&'] && $arg[$1,-1] == (<0->|p|-) ]]; then
   1441       if [[ $arg[$1,-1] == (p|-) ]]; then
   1442         base_style=redirection
   1443       else
   1444         base_style=numeric-fd
   1445       fi
   1446     # This function is currently never called for the command word, so $2 is hard-coded as 0.
   1447     elif _zsh_highlight_main_highlighter_check_path $arg[$1,-1] 0; then
   1448       base_style=$REPLY
   1449       _zsh_highlight_main_highlighter_highlight_path_separators $base_style
   1450       highlights+=($reply)
   1451     fi
   1452   fi
   1453 
   1454   highlights=($(( start_pos + $1 - 1 )) $end_pos $base_style $highlights)
   1455   _zsh_highlight_main_add_many_region_highlights $highlights
   1456 }
   1457 
   1458 # Quote Helper Functions
   1459 #
   1460 # $arg is expected to be set to the current argument
   1461 # $start_pos is expected to be set to the start of $arg in $BUFFER
   1462 # $1 is the index in $arg which starts the quote
   1463 # $REPLY is returned as the end of quote index in $arg
   1464 # $reply is returned as an array of region_highlight additions
   1465 
   1466 # Highlight single-quoted strings
   1467 _zsh_highlight_main_highlighter_highlight_single_quote()
   1468 {
   1469   local arg1=$1 i q=\' style
   1470   i=$arg[(ib:arg1+1:)$q]
   1471   reply=()
   1472 
   1473   if [[ $zsyh_user_options[rcquotes] == on ]]; then
   1474     while [[ $arg[i+1] == "'" ]]; do
   1475       reply+=($(( start_pos + i - 1 )) $(( start_pos + i + 1 )) rc-quote)
   1476       (( i++ ))
   1477       i=$arg[(ib:i+1:)$q]
   1478     done
   1479   fi
   1480 
   1481   if [[ $arg[i] == "'" ]]; then
   1482     style=single-quoted-argument
   1483   else
   1484     # If unclosed, i points past the end
   1485     (( i-- ))
   1486     style=single-quoted-argument-unclosed
   1487   fi
   1488   reply=($(( start_pos + arg1 - 1 )) $(( start_pos + i )) $style $reply)
   1489   REPLY=$i
   1490 }
   1491 
   1492 # Highlight special chars inside double-quoted strings
   1493 _zsh_highlight_main_highlighter_highlight_double_quote()
   1494 {
   1495   local -a breaks match mbegin mend saved_reply
   1496   local MATCH; integer last_break=$(( start_pos + $1 - 1 )) MBEGIN MEND
   1497   local i j k ret style
   1498   reply=()
   1499 
   1500   for (( i = $1 + 1 ; i <= $#arg ; i += 1 )) ; do
   1501     (( j = i + start_pos - 1 ))
   1502     (( k = j + 1 ))
   1503     case "$arg[$i]" in
   1504       ('"') break;;
   1505       ('`') saved_reply=($reply)
   1506             _zsh_highlight_main_highlighter_highlight_backtick $i
   1507             (( i = REPLY ))
   1508             reply=($saved_reply $reply)
   1509             continue
   1510             ;;
   1511       ('$') style=dollar-double-quoted-argument
   1512             # Look for an alphanumeric parameter name.
   1513             if [[ ${arg:$i} =~ ^([A-Za-z_][A-Za-z0-9_]*|[0-9]+) ]] ; then
   1514               (( k += $#MATCH )) # highlight the parameter name
   1515               (( i += $#MATCH )) # skip past it
   1516             elif [[ ${arg:$i} =~ ^[{]([A-Za-z_][A-Za-z0-9_]*|[0-9]+)[}] ]] ; then
   1517               (( k += $#MATCH )) # highlight the parameter name and braces
   1518               (( i += $#MATCH )) # skip past it
   1519             elif [[ $arg[i+1] == '$' ]]; then
   1520               # $$ - pid
   1521               (( k += 1 )) # highlight both dollar signs
   1522               (( i += 1 )) # don't consider the second one as introducing another parameter expansion
   1523             elif [[ $arg[i+1] == [-#*@?] ]]; then
   1524               # $#, $*, $@, $?, $- - like $$ above
   1525               (( k += 1 )) # highlight both dollar signs
   1526               (( i += 1 )) # don't consider the second one as introducing another parameter expansion
   1527             elif [[ $arg[i+1] == $'\x28' ]]; then
   1528               saved_reply=($reply)
   1529               if [[ $arg[i+2] == $'\x28' ]] && _zsh_highlight_main_highlighter_highlight_arithmetic $i; then
   1530                 # Arithmetic expansion
   1531                 (( i = REPLY ))
   1532                 reply=($saved_reply $reply)
   1533                 continue
   1534               fi
   1535 
   1536               breaks+=( $last_break $(( start_pos + i - 1 )) )
   1537               (( i += 2 ))
   1538               _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,-1]
   1539               ret=$?
   1540               (( i += REPLY ))
   1541               last_break=$(( start_pos + i ))
   1542               reply=(
   1543                 $saved_reply
   1544                 $j $(( start_pos + i )) command-substitution-quoted
   1545                 $j $(( j + 2 )) command-substitution-delimiter-quoted
   1546                 $reply
   1547               )
   1548               if (( ret == 0 )); then
   1549                 reply+=($(( start_pos + i - 1 )) $(( start_pos + i )) command-substitution-delimiter-quoted)
   1550               fi
   1551               continue
   1552             else
   1553               continue
   1554             fi
   1555             ;;
   1556       "\\") style=back-double-quoted-argument
   1557             if [[ \\\`\"\$${histchars[1]} == *$arg[$i+1]* ]]; then
   1558               (( k += 1 )) # Color following char too.
   1559               (( i += 1 )) # Skip parsing the escaped char.
   1560             else
   1561               continue
   1562             fi
   1563             ;;
   1564       ($histchars[1]) # ! - may be a history expansion
   1565             if [[ $arg[i+1] != ('='|$'\x28'|$'\x7b'|[[:blank:]]) ]]; then
   1566               style=history-expansion
   1567             else
   1568               continue
   1569             fi
   1570             ;;
   1571       *) continue ;;
   1572 
   1573     esac
   1574     reply+=($j $k $style)
   1575   done
   1576 
   1577   if [[ $arg[i] == '"' ]]; then
   1578     style=double-quoted-argument
   1579   else
   1580     # If unclosed, i points past the end
   1581     (( i-- ))
   1582     style=double-quoted-argument-unclosed
   1583   fi
   1584   (( last_break != start_pos + i )) && breaks+=( $last_break $(( start_pos + i )) )
   1585   saved_reply=($reply)
   1586   reply=()
   1587   for 1 2 in $breaks; do
   1588     (( $1 != $2 )) && reply+=($1 $2 $style)
   1589   done
   1590   reply+=($saved_reply)
   1591   REPLY=$i
   1592 }
   1593 
   1594 # Highlight special chars inside dollar-quoted strings
   1595 _zsh_highlight_main_highlighter_highlight_dollar_quote()
   1596 {
   1597   local -a match mbegin mend
   1598   local MATCH; integer MBEGIN MEND
   1599   local i j k style
   1600   local AA
   1601   integer c
   1602   reply=()
   1603 
   1604   for (( i = $1 + 2 ; i <= $#arg ; i += 1 )) ; do
   1605     (( j = i + start_pos - 1 ))
   1606     (( k = j + 1 ))
   1607     case "$arg[$i]" in
   1608       "'") break;;
   1609       "\\") style=back-dollar-quoted-argument
   1610             for (( c = i + 1 ; c <= $#arg ; c += 1 )); do
   1611               [[ "$arg[$c]" != ([0-9xXuUa-fA-F]) ]] && break
   1612             done
   1613             AA=$arg[$i+1,$c-1]
   1614             # Matching for HEX and OCT values like \0xA6, \xA6 or \012
   1615             if [[    "$AA" =~ "^(x|X)[0-9a-fA-F]{1,2}"
   1616                   || "$AA" =~ "^[0-7]{1,3}"
   1617                   || "$AA" =~ "^u[0-9a-fA-F]{1,4}"
   1618                   || "$AA" =~ "^U[0-9a-fA-F]{1,8}"
   1619                ]]; then
   1620               (( k += $#MATCH ))
   1621               (( i += $#MATCH ))
   1622             else
   1623               if (( $#arg > $i+1 )) && [[ $arg[$i+1] == [xXuU] ]]; then
   1624                 # \x not followed by hex digits is probably an error
   1625                 style=unknown-token
   1626               fi
   1627               (( k += 1 )) # Color following char too.
   1628               (( i += 1 )) # Skip parsing the escaped char.
   1629             fi
   1630             ;;
   1631       *) continue ;;
   1632 
   1633     esac
   1634     reply+=($j $k $style)
   1635   done
   1636 
   1637   if [[ $arg[i] == "'" ]]; then
   1638     style=dollar-quoted-argument
   1639   else
   1640     # If unclosed, i points past the end
   1641     (( i-- ))
   1642     style=dollar-quoted-argument-unclosed
   1643   fi
   1644   reply=($(( start_pos + $1 - 1 )) $(( start_pos + i )) $style $reply)
   1645   REPLY=$i
   1646 }
   1647 
   1648 # Highlight backtick substitutions
   1649 _zsh_highlight_main_highlighter_highlight_backtick()
   1650 {
   1651   # buf is the contents of the backticks with a layer of backslashes removed.
   1652   # last is the index of arg for the start of the string to be copied into buf.
   1653   #     It is either one past the beginning backtick or one past the last backslash.
   1654   # offset is a count of consumed \ (the delta between buf and arg).
   1655   # offsets is an array indexed by buf offset of when the delta between buf and arg changes.
   1656   #     It is sparse, so search backwards to the last value
   1657   local buf highlight style=back-quoted-argument-unclosed style_end
   1658   local -i arg1=$1 end_ i=$1 last offset=0 start subshell_has_end=0
   1659   local -a highlight_zone highlights offsets
   1660   reply=()
   1661 
   1662   last=$(( arg1 + 1 ))
   1663   # Remove one layer of backslashes and find the end
   1664   while i=$arg[(ib:i+1:)[\\\\\`]]; do # find the next \ or `
   1665     if (( i > $#arg )); then
   1666       buf=$buf$arg[last,i]
   1667       offsets[i-arg1-offset]='' # So we never index past the end
   1668       (( i-- ))
   1669       subshell_has_end=$(( has_end && (start_pos + i == len) ))
   1670       break
   1671     fi
   1672 
   1673     if [[ $arg[i] == '\' ]]; then
   1674       (( i++ ))
   1675       # POSIX XCU 2.6.3
   1676       if [[ $arg[i] == ('$'|'`'|'\') ]]; then
   1677         buf=$buf$arg[last,i-2]
   1678         (( offset++ ))
   1679         # offsets is relative to buf, so adjust by -arg1
   1680         offsets[i-arg1-offset]=$offset
   1681       else
   1682         buf=$buf$arg[last,i-1]
   1683       fi
   1684     else # it's an unquoted ` and this is the end
   1685       style=back-quoted-argument
   1686       style_end=back-quoted-argument-delimiter
   1687       buf=$buf$arg[last,i-1]
   1688       offsets[i-arg1-offset]='' # So we never index past the end
   1689       break
   1690     fi
   1691     last=$i
   1692   done
   1693 
   1694   _zsh_highlight_main_highlighter_highlight_list 0 '' $subshell_has_end $buf
   1695 
   1696   # Munge the reply to account for removed backslashes
   1697   for start end_ highlight in $reply; do
   1698     start=$(( start_pos + arg1 + start + offsets[(Rb:start:)?*] ))
   1699     end_=$(( start_pos + arg1 + end_ + offsets[(Rb:end_:)?*] ))
   1700     highlights+=($start $end_ $highlight)
   1701     if [[ $highlight == back-quoted-argument-unclosed && $style == back-quoted-argument ]]; then
   1702       # An inner backtick command substitution is unclosed, but this level is closed
   1703       style_end=unknown-token
   1704     fi
   1705   done
   1706 
   1707   reply=(
   1708     $(( start_pos + arg1 - 1 )) $(( start_pos + i )) $style
   1709     $(( start_pos + arg1 - 1 )) $(( start_pos + arg1 )) back-quoted-argument-delimiter
   1710     $highlights
   1711   )
   1712   if (( $#style_end )); then
   1713     reply+=($(( start_pos + i - 1)) $(( start_pos + i )) $style_end)
   1714   fi
   1715   REPLY=$i
   1716 }
   1717 
   1718 # Highlight special chars inside arithmetic expansions
   1719 _zsh_highlight_main_highlighter_highlight_arithmetic()
   1720 {
   1721   local -a saved_reply
   1722   local style
   1723   integer i j k paren_depth ret
   1724   reply=()
   1725 
   1726   for (( i = $1 + 3 ; i <= end_pos - start_pos ; i += 1 )) ; do
   1727     (( j = i + start_pos - 1 ))
   1728     (( k = j + 1 ))
   1729     case "$arg[$i]" in
   1730       [\'\"\\@{}])
   1731         style=unknown-token
   1732         ;;
   1733       '(')
   1734         (( paren_depth++ ))
   1735         continue
   1736         ;;
   1737       ')')
   1738         if (( paren_depth )); then
   1739           (( paren_depth-- ))
   1740           continue
   1741         fi
   1742         [[ $arg[i+1] == ')' ]] && { (( i++ )); break; }
   1743         # Special case ) at the end of the buffer to avoid flashing command substitution for a character
   1744         (( has_end && (len == k) )) && break
   1745         # This is a single paren and there are no open parens, so this isn't an arithmetic expansion
   1746         return 1
   1747         ;;
   1748       '`')
   1749         saved_reply=($reply)
   1750         _zsh_highlight_main_highlighter_highlight_backtick $i
   1751         (( i = REPLY ))
   1752         reply=($saved_reply $reply)
   1753         continue
   1754         ;;
   1755       '$' )
   1756         if [[ $arg[i+1] == $'\x28' ]]; then
   1757           saved_reply=($reply)
   1758           if [[ $arg[i+2] == $'\x28' ]] && _zsh_highlight_main_highlighter_highlight_arithmetic $i; then
   1759             # Arithmetic expansion
   1760             (( i = REPLY ))
   1761             reply=($saved_reply $reply)
   1762             continue
   1763           fi
   1764 
   1765           (( i += 2 ))
   1766           _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,end_pos]
   1767           ret=$?
   1768           (( i += REPLY ))
   1769           reply=(
   1770             $saved_reply
   1771             $j $(( start_pos + i )) command-substitution-quoted
   1772             $j $(( j + 2 )) command-substitution-delimiter-quoted
   1773             $reply
   1774           )
   1775           if (( ret == 0 )); then
   1776             reply+=($(( start_pos + i - 1 )) $(( start_pos + i )) command-substitution-delimiter)
   1777           fi
   1778           continue
   1779         else
   1780           continue
   1781         fi
   1782         ;;
   1783       ($histchars[1]) # ! - may be a history expansion
   1784         if [[ $arg[i+1] != ('='|$'\x28'|$'\x7b'|[[:blank:]]) ]]; then
   1785           style=history-expansion
   1786         else
   1787           continue
   1788         fi
   1789         ;;
   1790       *)
   1791         continue
   1792         ;;
   1793 
   1794     esac
   1795     reply+=($j $k $style)
   1796   done
   1797 
   1798   if [[ $arg[i] != ')' ]]; then
   1799     # If unclosed, i points past the end
   1800     (( i-- ))
   1801   fi
   1802     style=arithmetic-expansion
   1803   reply=($(( start_pos + $1 - 1)) $(( start_pos + i )) arithmetic-expansion $reply)
   1804   REPLY=$i
   1805 }
   1806 
   1807 
   1808 # Called with a single positional argument.
   1809 # Perform filename expansion (tilde expansion) on the argument and set $REPLY to the expanded value.
   1810 #
   1811 # Does not perform filename generation (globbing).
   1812 _zsh_highlight_main_highlighter_expand_path()
   1813 {
   1814   (( $# == 1 )) || print -r -- >&2 "zsh-syntax-highlighting: BUG: _zsh_highlight_main_highlighter_expand_path: called without argument"
   1815 
   1816   # The $~1 syntax normally performs filename generation, but not when it's on the right-hand side of ${x:=y}.
   1817   setopt localoptions nonomatch
   1818   unset REPLY
   1819   : ${REPLY:=${(Q)${~1}}}
   1820 }
   1821 
   1822 # -------------------------------------------------------------------------------------------------
   1823 # Main highlighter initialization
   1824 # -------------------------------------------------------------------------------------------------
   1825 
   1826 _zsh_highlight_main__precmd_hook() {
   1827   # Unset the WARN_NESTED_VAR option, taking care not to error if the option
   1828   # doesn't exist (zsh older than zsh-5.3.1-test-2).
   1829   setopt localoptions
   1830   if eval '[[ -o warnnestedvar ]]' 2>/dev/null; then
   1831     unsetopt warnnestedvar
   1832   fi
   1833 
   1834   _zsh_highlight_main__command_type_cache=()
   1835 }
   1836 
   1837 autoload -Uz add-zsh-hook
   1838 if add-zsh-hook precmd _zsh_highlight_main__precmd_hook 2>/dev/null; then
   1839   # Initialize command type cache
   1840   typeset -gA _zsh_highlight_main__command_type_cache
   1841 else
   1842   print -r -- >&2 'zsh-syntax-highlighting: Failed to load add-zsh-hook. Some speed optimizations will not be used.'
   1843   # Make sure the cache is unset
   1844   unset _zsh_highlight_main__command_type_cache
   1845 fi
   1846 typeset -ga ZSH_HIGHLIGHT_DIRS_BLACKLIST