You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1843 lines
68 KiB

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