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.

823 lines
32 KiB

  1. # -------------------------------------------------------------------------------------------------
  2. # Copyright (c) 2010-2016 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. typeset -gA __hsmw_highlight_main__command_type_cache
  30. # Define default styles.
  31. typeset -gA HSMW_HIGHLIGHT_STYLES
  32. : ${HSMW_HIGHLIGHT_STYLES[default]:=none}
  33. : ${HSMW_HIGHLIGHT_STYLES[unknown-token]:=fg=red,bold}
  34. : ${HSMW_HIGHLIGHT_STYLES[reserved-word]:=fg=yellow}
  35. : ${HSMW_HIGHLIGHT_STYLES[alias]:=fg=green}
  36. : ${HSMW_HIGHLIGHT_STYLES[suffix-alias]:=fg=green}
  37. : ${HSMW_HIGHLIGHT_STYLES[builtin]:=fg=green}
  38. : ${HSMW_HIGHLIGHT_STYLES[function]:=fg=green}
  39. : ${HSMW_HIGHLIGHT_STYLES[command]:=fg=green}
  40. : ${HSMW_HIGHLIGHT_STYLES[precommand]:=fg=green}
  41. : ${HSMW_HIGHLIGHT_STYLES[commandseparator]:=none}
  42. : ${HSMW_HIGHLIGHT_STYLES[hashed-command]:=fg=green}
  43. : ${HSMW_HIGHLIGHT_STYLES[path]:=fg=magenta}
  44. : ${HSMW_HIGHLIGHT_STYLES[path_pathseparator]:=}
  45. : ${HSMW_HIGHLIGHT_STYLES[path_prefix]:=fg=magenta}
  46. : ${HSMW_HIGHLIGHT_STYLES[path_prefix_pathseparator]:=}
  47. : ${HSMW_HIGHLIGHT_STYLES[globbing]:=fg=blue,bold}
  48. : ${HSMW_HIGHLIGHT_STYLES[history-expansion]:=fg=blue,bold}
  49. : ${HSMW_HIGHLIGHT_STYLES[single-hyphen-option]:=none}
  50. : ${HSMW_HIGHLIGHT_STYLES[double-hyphen-option]:=none}
  51. : ${HSMW_HIGHLIGHT_STYLES[back-quoted-argument]:=none}
  52. : ${HSMW_HIGHLIGHT_STYLES[single-quoted-argument]:=fg=yellow}
  53. : ${HSMW_HIGHLIGHT_STYLES[double-quoted-argument]:=fg=yellow}
  54. : ${HSMW_HIGHLIGHT_STYLES[dollar-quoted-argument]:=fg=yellow}
  55. : ${HSMW_HIGHLIGHT_STYLES[dollar-double-quoted-argument]:=fg=cyan}
  56. : ${HSMW_HIGHLIGHT_STYLES[back-double-quoted-argument]:=fg=cyan}
  57. : ${HSMW_HIGHLIGHT_STYLES[back-dollar-quoted-argument]:=fg=cyan}
  58. : ${HSMW_HIGHLIGHT_STYLES[assign]:=none}
  59. : ${HSMW_HIGHLIGHT_STYLES[redirection]:=none}
  60. : ${HSMW_HIGHLIGHT_STYLES[comment]:=fg=black,bold}
  61. # Get the type of a command.
  62. #
  63. # Uses the zsh/parameter module if available to avoid forks, and a
  64. # wrapper around 'type -w' as fallback.
  65. #
  66. # Takes a single argument.
  67. #
  68. # The result will be stored in REPLY.
  69. -hsmw-highlight-main-type() {
  70. if (( $+__hsmw_highlight_main__command_type_cache )); then
  71. REPLY=$__hsmw_highlight_main__command_type_cache[(e)$1]
  72. if [[ -n "$REPLY" ]]; then
  73. return
  74. fi
  75. fi
  76. if (( $#options_to_set )); then
  77. setopt localoptions $options_to_set;
  78. fi
  79. unset REPLY
  80. if zmodload -e zsh/parameter; then
  81. if (( $+aliases[(e)$1] )); then
  82. REPLY=alias
  83. elif (( $+saliases[(e)${1##*.}] )); then
  84. REPLY='suffix alias'
  85. elif (( $reswords[(Ie)$1] )); then
  86. REPLY=reserved
  87. elif (( $+functions[(e)$1] )); then
  88. REPLY=function
  89. elif (( $+builtins[(e)$1] )); then
  90. REPLY=builtin
  91. elif (( $+commands[(e)$1] )); then
  92. REPLY=command
  93. # zsh 5.2 and older have a bug whereby running 'type -w ./sudo' implicitly
  94. # runs 'hash ./sudo=/usr/local/bin/./sudo' (assuming /usr/local/bin/sudo
  95. # exists and is in $PATH). Avoid triggering the bug, at the expense of
  96. # falling through to the $(x) below, incurring a fork. (Issue #354.)
  97. #
  98. # The second disjunct mimics the isrelative() C call from the zsh bug.
  99. elif { [[ $1 != */* ]] || is-at-least 5.3 } &&
  100. ! builtin type -w -- $1 >/dev/null 2>&1; then
  101. REPLY=none
  102. fi
  103. fi
  104. if ! (( $+REPLY )); then
  105. REPLY="${$(LC_ALL=C builtin type -w -- $1 2>/dev/null)#*: }"
  106. fi
  107. if (( $+__hsmw_highlight_main__command_type_cache )); then
  108. __hsmw_highlight_main__command_type_cache[(e)$1]=$REPLY
  109. fi
  110. }
  111. # Check whether the first argument is a redirection operator token.
  112. # Report result via the exit code.
  113. -hsmw-highlight-is-redirection() {
  114. # A redirection operator token:
  115. # - starts with an optional single-digit number;
  116. # - then, has a '<' or '>' character;
  117. # - is not a process substitution [<(...) or >(...)].
  118. [[ $1 == (<0-9>|)(\<|\>)* ]] && [[ $1 != (\<|\>)$'\x28'* ]]
  119. }
  120. # Resolve alias.
  121. #
  122. # Takes a single argument.
  123. #
  124. # The result will be stored in REPLY.
  125. -hsmw-highlight-resolve-alias() {
  126. if zmodload -e zsh/parameter; then
  127. REPLY=${aliases[$arg]}
  128. else
  129. REPLY="${"$(alias -- $arg)"#*=}"
  130. fi
  131. }
  132. # Check that the top of $braces_stack has the expected value. If it does, set
  133. # the style according to $2; otherwise, set style=unknown-token.
  134. #
  135. # $1: character expected to be at the top of $braces_stack
  136. # $2: assignment to execute it if matches
  137. -hsmw-highlight-stack-pop() {
  138. if [[ $braces_stack[1] == $1 ]]; then
  139. braces_stack=${braces_stack:1}
  140. eval "$2"
  141. else
  142. style=unknown-token
  143. fi
  144. }
  145. # Main syntax highlighting function.
  146. -hsmw-highlight-process()
  147. {
  148. ## Before we even 'emulate -L', we must test a few options that would reset.
  149. if [[ -o interactive_comments ]]; then
  150. local interactive_comments= # set to empty
  151. fi
  152. if [[ -o ignore_braces ]] || eval '[[ -o ignore_close_braces ]] 2>/dev/null'; then
  153. local right_brace_is_recognised_everywhere=false
  154. else
  155. local right_brace_is_recognised_everywhere=true
  156. fi
  157. if [[ -o path_dirs ]]; then
  158. integer path_dirs_was_set=1
  159. else
  160. integer path_dirs_was_set=0
  161. fi
  162. if [[ -o multi_func_def ]]; then
  163. integer multi_func_def=1
  164. else
  165. integer multi_func_def=0
  166. fi
  167. emulate -L zsh
  168. setopt localoptions extendedglob bareglobqual
  169. ## Variable declarations and initializations
  170. local start_pos=0 end_pos highlight_glob=true arg style
  171. local in_array_assignment=false # true between 'a=(' and the matching ')'
  172. typeset -a __HSMW_HIGHLIGHT_TOKENS_COMMANDSEPARATOR
  173. typeset -a __HSMW_HIGHLIGHT_TOKENS_PRECOMMANDS
  174. typeset -a __HSMW_HIGHLIGHT_TOKENS_CONTROL_FLOW
  175. local -a options_to_set # used in callees
  176. local buf="$1"
  177. integer len="${#buf}"
  178. integer pure_buf_len=len # historical, was $#BUFFER, i.e. len without $PREBUFFER; used e.g. in *_check_path
  179. local braces_stack # "R" for round, "Q" for square, "Y" for curly
  180. if (( path_dirs_was_set )); then
  181. options_to_set+=( PATH_DIRS )
  182. fi
  183. unset path_dirs_was_set
  184. __HSMW_HIGHLIGHT_TOKENS_COMMANDSEPARATOR=(
  185. '|' '||' ';' '&' '&&'
  186. '|&'
  187. '&!' '&|'
  188. # ### 'case' syntax, but followed by a pattern, not by a command
  189. # ';;' ';&' ';|'
  190. )
  191. __HSMW_HIGHLIGHT_TOKENS_PRECOMMANDS=(
  192. 'builtin' 'command' 'exec' 'nocorrect' 'noglob'
  193. 'pkexec' # immune to #121 because it's usually not passed --option flags
  194. )
  195. # Tokens that, at (naively-determined) "command position", are followed by
  196. # a de jure command position. All of these are reserved words.
  197. __HSMW_HIGHLIGHT_TOKENS_CONTROL_FLOW=(
  198. $'\x7b' # block
  199. $'\x28' # subshell
  200. '()' # anonymous function
  201. 'while'
  202. 'until'
  203. 'if'
  204. 'then'
  205. 'elif'
  206. 'else'
  207. 'do'
  208. 'time'
  209. 'coproc'
  210. '!' # reserved word; unrelated to $histchars[1]
  211. )
  212. local -a match mbegin mend
  213. # State machine
  214. #
  215. # The states are:
  216. # - :start: Command word
  217. # - :sudo_opt: A leading-dash option to sudo (such as "-u" or "-i")
  218. # - :sudo_arg: The argument to a sudo leading-dash option that takes one,
  219. # when given as a separate word; i.e., "foo" in "-u foo" (two
  220. # words) but not in "-ufoo" (one word).
  221. # - :regular: "Not a command word", and command delimiters are permitted.
  222. # Mainly used to detect premature termination of commands.
  223. # - :always: The word 'always' in the «{ foo } always { bar }» syntax.
  224. #
  225. # When the kind of a word is not yet known, $this_word / $next_word may contain
  226. # multiple states. For example, after "sudo -i", the next word may be either
  227. # another --flag or a command name, hence the state would include both :start:
  228. # and :sudo_opt:.
  229. #
  230. # The tokens are always added with both leading and trailing colons to serve as
  231. # word delimiters (an improvised array); [[ $x == *:foo:* ]] and x=${x//:foo:/}
  232. # will DTRT regardless of how many elements or repetitions $x has..
  233. #
  234. # Handling of redirections: upon seeing a redirection token, we must stall
  235. # the current state --- that is, the value of $this_word --- for two iterations
  236. # (one for the redirection operator, one for the word following it representing
  237. # the redirection target). Therefore, we set $in_redirection to 2 upon seeing a
  238. # redirection operator, decrement it each iteration, and stall the current state
  239. # when it is non-zero. Thus, upon reaching the next word (the one that follows
  240. # the redirection operator and target), $this_word will still contain values
  241. # appropriate for the word immediately following the word that preceded the
  242. # redirection operator.
  243. #
  244. # The "the previous word was a redirection operator" state is not communicated
  245. # to the next iteration via $next_word/$this_word as usual, but via
  246. # $in_redirection. The value of $next_word from the iteration that processed
  247. # the operator is discarded.
  248. #
  249. local this_word=':start:' next_word
  250. integer in_redirection
  251. # Processing buffer
  252. local proc_buf="$buf"
  253. for arg in ${interactive_comments-${(z)buf}} \
  254. ${interactive_comments+${(zZ+c+)buf}}; do
  255. # Initialize $next_word.
  256. if (( in_redirection )); then
  257. (( --in_redirection ))
  258. fi
  259. if (( in_redirection == 0 )); then
  260. # Initialize $next_word to its default value.
  261. next_word=':regular:'
  262. else
  263. # Stall $next_word.
  264. fi
  265. # Initialize per-"simple command" [zshmisc(1)] variables:
  266. #
  267. # $already_added (see next paragraph)
  268. # $style how to highlight $arg
  269. # $in_array_assignment boolean flag for "between '(' and ')' of array assignment"
  270. # $highlight_glob boolean flag for "'noglob' is in effect"
  271. #
  272. # $already_added is set to 1 to disable adding an entry to region_highlight
  273. # for this iteration. Currently, that is done for "" and $'' strings,
  274. # which add the entry early so escape sequences within the string override
  275. # the string's color.
  276. integer already_added=0
  277. style=unknown-token
  278. if [[ $this_word == *':start:'* ]]; then
  279. in_array_assignment=false
  280. if [[ $arg == 'noglob' ]]; then
  281. highlight_glob=false
  282. fi
  283. fi
  284. # Compute the new $start_pos and $end_pos, skipping over whitespace in $buf.
  285. if [[ $arg == ';' ]] ; then
  286. # We're looking for either a semicolon or a newline, whichever comes
  287. # first. Both of these are rendered as a ";" (SEPER) by the ${(z)..}
  288. # flag.
  289. #
  290. # We can't use the (Z+n+) flag because that elides the end-of-command
  291. # token altogether, so 'echo foo\necho bar' (two commands) becomes
  292. # indistinguishable from 'echo foo echo bar' (one command with three
  293. # words for arguments).
  294. local needle=$'[;\n]'
  295. integer offset=$(( ${proc_buf[(i)$needle]} - 1 ))
  296. (( start_pos += offset ))
  297. (( end_pos = start_pos + $#arg ))
  298. else
  299. # The line was:
  300. #
  301. # integer offset=$(((len-start_pos)-${#${proc_buf##([[:space:]]|\\[[:space:]])#}}))
  302. #
  303. # - len-start_pos is length of current proc_buf; basically: initial length minus where
  304. # we are, and proc_buf is chopped to the "where we are" (compare the "previous value
  305. # of start_pos" below, and the len-(start_pos-offset) = len-start_pos+offset)
  306. # - what's after main minus sign is: length of proc_buf without spaces at the beginning
  307. # - so what the line actually did, was computing length of the spaces!
  308. # - this can be done via (#b) flag, like below
  309. if [[ "$proc_buf" = (#b)(#s)(([[:space:]]|\\[[:space:]])##)* ]]; then
  310. # The first, outer parenthesis
  311. integer offset="${#match[1]}"
  312. else
  313. integer offset=0
  314. fi
  315. ((start_pos+=offset))
  316. ((end_pos=$start_pos+${#arg}))
  317. fi
  318. # Compute the new $proc_buf. We advance it
  319. # (chop off characters from the beginning)
  320. # beyond what end_pos points to, by skipping
  321. # as many characters as end_pos was advanced.
  322. #
  323. # end_pos was advanced by $offset (via start_pos)
  324. # and by $#arg. Note the `start_pos=$end_pos`
  325. # below.
  326. #
  327. # As for the [,len]. We could use [,len-start_pos+offset]
  328. # here, but to make it easier on eyes, we use len and
  329. # rely on the fact that Zsh simply handles that. The
  330. # length of proc_buf is len-start_pos+offset because
  331. # we're chopping it to match current start_pos, so its
  332. # length matches the previous value of start_pos.
  333. #
  334. # Why [,-1] is slower than [,length] isn't clear.
  335. proc_buf="${proc_buf[offset + $#arg + 1,len]}"
  336. # Handle the INTERACTIVE_COMMENTS option.
  337. #
  338. # We use the (Z+c+) flag so the entire comment is presented as one token in $arg.
  339. if [[ -n ${interactive_comments+'set'} && $arg[1] == $histchars[3] ]]; then
  340. if [[ $this_word == *(':regular:'|':start:')* ]]; then
  341. style=comment
  342. else
  343. style=unknown-token # prematurely terminated
  344. fi
  345. -hsmw-add-highlight $start_pos $end_pos $style
  346. already_added=1
  347. continue
  348. fi
  349. # Analyse the current word.
  350. if -hsmw-highlight-is-redirection $arg ; then
  351. # A '<' or '>', possibly followed by a digit
  352. in_redirection=2
  353. fi
  354. # Special-case the first word after 'sudo'.
  355. if (( ! in_redirection )); then
  356. if [[ $this_word == *':sudo_opt:'* ]] && [[ $arg != -* ]]; then
  357. this_word=${this_word//:sudo_opt:/}
  358. fi
  359. fi
  360. # Parse the sudo command line
  361. if (( ! in_redirection )); then
  362. if [[ $this_word == *':sudo_opt:'* ]]; then
  363. case "$arg" in
  364. # Flag that requires an argument
  365. '-'[Cgprtu]) this_word=${this_word//:start:/};
  366. next_word=':sudo_arg:';;
  367. # This prevents misbehavior with sudo -u -otherargument
  368. '-'*) this_word=${this_word//:start:/};
  369. next_word+=':start:';
  370. next_word+=':sudo_opt:';;
  371. *) ;;
  372. esac
  373. elif [[ $this_word == *':sudo_arg:'* ]]; then
  374. next_word+=':sudo_opt:'
  375. next_word+=':start:'
  376. fi
  377. fi
  378. # The Great Fork: is this a command word? Is this a non-command word?
  379. if [[ $this_word == *':always:'* && $arg == 'always' ]]; then
  380. # try-always construct
  381. style=reserved-word # de facto a reserved word, although not de jure
  382. next_word=':start:'
  383. elif [[ $this_word == *':start:'* ]] && (( in_redirection == 0 )); then # $arg is the command word
  384. if [[ -n ${(M)__HSMW_HIGHLIGHT_TOKENS_PRECOMMANDS:#"$arg"} ]]; then
  385. style=precommand
  386. elif [[ "$arg" = "sudo" ]]; then
  387. style=precommand
  388. next_word=${next_word//:regular:/}
  389. next_word+=':sudo_opt:'
  390. next_word+=':start:'
  391. else
  392. -hsmw-highlight-expand-path $arg
  393. local expanded_arg="$REPLY"
  394. -hsmw-highlight-main-type ${expanded_arg}
  395. local res="$REPLY"
  396. () {
  397. # Special-case: command word is '$foo', like that, without braces or anything.
  398. #
  399. # That's not entirely correct --- if the parameter's value happens to be a reserved
  400. # word, the parameter expansion will be highlighted as a reserved word --- but that
  401. # incorrectness is outweighed by the usability improvement of permitting the use of
  402. # parameters that refer to commands, functions, and builtins.
  403. local -a match mbegin mend
  404. local MATCH; integer MBEGIN MEND
  405. if [[ $res == none ]] && (( ${+parameters} )) &&
  406. [[ ${arg[1]} == \$ ]] && [[ ${arg:1} = ([[:alpha:]_][[:alnum:]_]#|[[:digit:]]##) ]] &&
  407. (( ${+parameters[${MATCH}]} ))
  408. then
  409. -hsmw-highlight-main-type ${(P)MATCH}
  410. res=$REPLY
  411. fi
  412. }
  413. case $res in
  414. reserved) # reserved word
  415. style=reserved-word
  416. if [[ $arg == $'\x7b' ]]; then
  417. braces_stack='Y'"$braces_stack"
  418. elif [[ $arg == $'\x7d' ]]; then
  419. # We're at command word, so no need to check $right_brace_is_recognised_everywhere
  420. -hsmw-highlight-stack-pop 'Y' style=reserved-word
  421. if [[ $style == reserved-word ]]; then
  422. next_word+=':always:'
  423. fi
  424. fi
  425. ;;
  426. 'suffix alias') style=suffix-alias;;
  427. alias) () {
  428. integer insane_alias
  429. case $arg in
  430. # Issue #263: aliases with '=' on their LHS.
  431. #
  432. # There are three cases:
  433. #
  434. # - Unsupported, breaks 'alias -L' output, but invokable:
  435. ('='*) :;;
  436. # - Unsupported, not invokable:
  437. (*'='*) insane_alias=1;;
  438. # - The common case:
  439. (*) :;;
  440. esac
  441. if (( insane_alias )); then
  442. style=unknown-token
  443. else
  444. style=alias
  445. -hsmw-highlight-resolve-alias $arg
  446. local alias_target="$REPLY"
  447. [[ -n ${(M)__HSMW_HIGHLIGHT_TOKENS_PRECOMMANDS:#"$alias_target"} && -z ${(M)__HSMW_HIGHLIGHT_TOKENS_PRECOMMANDS:#"$arg"} ]] && __HSMW_HIGHLIGHT_TOKENS_PRECOMMANDS+=($arg)
  448. fi
  449. }
  450. ;;
  451. builtin) style=builtin;;
  452. function) style=function;;
  453. command) style=command;;
  454. hashed) style=hashed-command;;
  455. none) if -hsmw-highlight-check-assign; then
  456. style=assign
  457. if [[ $arg[-1] == '(' ]]; then
  458. in_array_assignment=true
  459. else
  460. # assignment to a scalar parameter.
  461. # (For array assignments, the command doesn't start until the ")" token.)
  462. next_word+=':start:'
  463. fi
  464. elif [[ $arg[0,1] = $histchars[0,1] ]] && (( $#arg[0,2] == 2 )); then
  465. style=history-expansion
  466. elif [[ $arg[0,1] == $histchars[2,2] ]]; then
  467. style=history-expansion
  468. elif [[ -n ${(M)__HSMW_HIGHLIGHT_TOKENS_COMMANDSEPARATOR:#"$arg"} ]]; then
  469. if [[ $this_word == *':regular:'* ]]; then
  470. # This highlights empty commands (semicolon follows nothing) as an error.
  471. # Zsh accepts them, though.
  472. style=commandseparator
  473. else
  474. style=unknown-token
  475. fi
  476. elif (( in_redirection == 2 )); then
  477. style=redirection
  478. elif [[ $arg[1,2] == '((' ]]; then
  479. # Arithmetic evaluation.
  480. #
  481. # Note: prior to zsh-5.1.1-52-g4bed2cf (workers/36669), the ${(z)...}
  482. # splitter would only output the '((' token if the matching '))' had
  483. # been typed. Therefore, under those versions of zsh, BUFFER="(( 42"
  484. # would be highlighted as an error until the matching "))" are typed.
  485. #
  486. # We highlight just the opening parentheses, as a reserved word; this
  487. # is how [[ ... ]] is highlighted, too.
  488. style=reserved-word
  489. -hsmw-add-highlight $start_pos $((start_pos + 2)) $style
  490. already_added=1
  491. if [[ $arg[-2,-1] == '))' ]]; then
  492. -hsmw-add-highlight $((end_pos - 2)) $end_pos $style
  493. already_added=1
  494. fi
  495. elif [[ $arg == '()' ]]; then
  496. # anonymous function
  497. style=reserved-word
  498. elif [[ $arg == $'\x28' ]]; then
  499. # subshell
  500. style=reserved-word
  501. braces_stack='R'"$braces_stack"
  502. else
  503. if -hsmw-highlight-check-path; then
  504. style=$REPLY
  505. else
  506. style=unknown-token
  507. fi
  508. fi
  509. ;;
  510. *) -hsmw-add-highlight $start_pos $end_pos commandtypefromthefuture-$res
  511. already_added=1
  512. ;;
  513. esac
  514. fi
  515. fi
  516. if (( ! already_added )) && [[ $style == unknown-token ]] && # not handled by the 'command word' codepath
  517. { (( in_redirection )) || [[ $this_word == *':regular:'* ]] || [[ $this_word == *':sudo_opt:'* ]] || [[ $this_word == *':sudo_arg:'* ]] }
  518. then # $arg is a non-command word
  519. case $arg in
  520. $'\x29') # subshell or end of array assignment
  521. if $in_array_assignment; then
  522. style=assign
  523. in_array_assignment=false
  524. next_word+=':start:'
  525. else
  526. -hsmw-highlight-stack-pop 'R' style=reserved-word
  527. fi;;
  528. $'\x28\x29') # possibly a function definition
  529. if (( multi_func_def )) || false # TODO: or if the previous word was a command word
  530. then
  531. next_word+=':start:'
  532. fi
  533. style=reserved-word
  534. ;;
  535. $'\x7d') # right brace
  536. #
  537. # Parsing rule: # {
  538. #
  539. # Additionally, `tt(})' is recognized in any position if neither the
  540. # tt(IGNORE_BRACES) option nor the tt(IGNORE_CLOSE_BRACES) option is set."""
  541. if $right_brace_is_recognised_everywhere; then
  542. -hsmw-highlight-stack-pop 'Y' style=reserved-word
  543. if [[ $style == reserved-word ]]; then
  544. next_word+=':always:'
  545. fi
  546. else
  547. # Fall through to the catchall case at the end.
  548. fi
  549. ;|
  550. '--'*) style=double-hyphen-option;;
  551. '-'*) style=single-hyphen-option;;
  552. "'"*) style=single-quoted-argument;;
  553. '"'*) style=double-quoted-argument
  554. -hsmw-add-highlight $start_pos $end_pos $style
  555. -hsmw-highlight-string
  556. already_added=1
  557. ;;
  558. \$\'*) style=dollar-quoted-argument
  559. -hsmw-add-highlight $start_pos $end_pos $style
  560. -hsmw-highlight-dollar-string
  561. already_added=1
  562. ;;
  563. '`'*) style=back-quoted-argument;;
  564. [*?]*|*[^\\][*?]*)
  565. $highlight_glob && style=globbing || style=default;;
  566. *) if false; then
  567. elif [[ $arg = $'\x7d' ]] && $right_brace_is_recognised_everywhere; then
  568. # was handled by the $'\x7d' case above
  569. elif [[ $arg[0,1] = $histchars[0,1] ]] && (( $#arg[0,2] == 2 )); then
  570. style=history-expansion
  571. elif [[ -n ${(M)__HSMW_HIGHLIGHT_TOKENS_COMMANDSEPARATOR:#"$arg"} ]]; then
  572. if [[ $this_word == *':regular:'* ]]; then
  573. style=commandseparator
  574. else
  575. style=unknown-token
  576. fi
  577. elif (( in_redirection == 2 )); then
  578. style=redirection
  579. else
  580. if -hsmw-highlight-check-path; then
  581. style=$REPLY
  582. else
  583. style=default
  584. fi
  585. fi
  586. ;;
  587. esac
  588. fi
  589. if ! (( already_added )); then
  590. -hsmw-add-highlight $start_pos $end_pos $style
  591. [[ $style == path || $style == path_prefix ]] && -hsmw-highlight-path-separators
  592. fi
  593. if [[ -n ${(M)__HSMW_HIGHLIGHT_TOKENS_COMMANDSEPARATOR:#"$arg"} ]]; then
  594. if [[ $arg == ';' ]] && $in_array_assignment; then
  595. # literal newline inside an array assignment
  596. next_word=':regular:'
  597. else
  598. next_word=':start:'
  599. highlight_glob=true
  600. fi
  601. elif
  602. [[ -n ${(M)__HSMW_HIGHLIGHT_TOKENS_CONTROL_FLOW:#"$arg"} && $this_word == *':start:'* ]] ||
  603. [[ -n ${(M)__HSMW_HIGHLIGHT_TOKENS_PRECOMMANDS:#"$arg"} && $this_word == *':start:'* ]]; then
  604. next_word=':start:'
  605. elif [[ $arg == "repeat" && $this_word == *':start:'* ]]; then
  606. # skip the repeat-count word
  607. in_redirection=2
  608. # The redirection mechanism assumes $this_word describes the word
  609. # following the redirection. Make it so.
  610. #
  611. # That word can be a command word with shortloops (`repeat 2 ls`)
  612. # or a command separator (`repeat 2; ls` or `repeat 2; do ls; done`).
  613. #
  614. # The repeat-count word will be handled like a redirection target.
  615. this_word=':start::regular:'
  616. fi
  617. start_pos=$end_pos
  618. if (( in_redirection == 0 )); then
  619. # This is the default/common codepath.
  620. this_word=$next_word
  621. else
  622. # Stall $this_word.
  623. fi
  624. done
  625. }
  626. # Check if $arg is variable assignment
  627. -hsmw-highlight-check-assign()
  628. {
  629. setopt localoptions extended_glob
  630. [[ $arg == [[:alpha:]_][[:alnum:]_]#(|\[*\])(|[+])=* ]] ||
  631. [[ $arg == [0-9]##(|[+])=* ]]
  632. }
  633. -hsmw-highlight-path-separators()
  634. {
  635. local pos style_pathsep
  636. style_pathsep=${style}_pathseparator
  637. [[ -z "$HSMW_HIGHLIGHT_STYLES[$style_pathsep]" || "$HSMW_HIGHLIGHT_STYLES[$style]" == "$HSMW_HIGHLIGHT_STYLES[$style_pathsep]" ]] && return 0
  638. for (( pos = start_pos; $pos <= end_pos; pos++ )) ; do
  639. if [[ ${buf[pos]} == "/" ]]; then
  640. -hsmw-add-highlight $((pos - 1)) $pos $style_pathsep
  641. fi
  642. done
  643. }
  644. # Check if $arg is a path.
  645. # If yes, return 0 and in $REPLY the style to use.
  646. # Else, return non-zero (and the contents of $REPLY is undefined).
  647. -hsmw-highlight-check-path()
  648. {
  649. -hsmw-highlight-expand-path $arg;
  650. local expanded_path="$REPLY"
  651. REPLY=path
  652. [[ -z $expanded_path ]] && return 1
  653. [[ -L $expanded_path ]] && return 0
  654. [[ -e $expanded_path ]] && return 0
  655. # Search the path in CDPATH
  656. local cdpath_dir
  657. for cdpath_dir in $cdpath ; do
  658. [[ -e "$cdpath_dir/$expanded_path" ]] && return 0
  659. done
  660. # If dirname($arg) doesn't exist, neither does $arg.
  661. [[ ! -d ${expanded_path:h} ]] && return 1
  662. # If this word ends the buffer, check if it's the prefix of a valid path.
  663. if [[ ${buf[1]} != "-" && $pure_buf_len == $end_pos ]]; then
  664. local -a tmp
  665. tmp=( ${expanded_path}*(N) )
  666. (( $#tmp > 0 )) && REPLY=path_prefix && return 0
  667. fi
  668. # It's not a path.
  669. return 1
  670. }
  671. # Highlight special chars inside double-quoted strings
  672. -hsmw-highlight-string()
  673. {
  674. setopt localoptions noksharrays
  675. local -a match mbegin mend
  676. local MATCH; integer MBEGIN MEND
  677. local i j k style
  678. # Starting quote is at 1, so start parsing at offset 2 in the string.
  679. for (( i = 2 ; i < end_pos - start_pos ; i += 1 )) ; do
  680. (( j = i + start_pos - 1 ))
  681. (( k = j + 1 ))
  682. case "$arg[$i]" in
  683. '$' ) style=dollar-double-quoted-argument
  684. # Look for an alphanumeric parameter name.
  685. if [[ ${arg:$i} = ([[:alpha:]_][[:alnum:]_]#|[[:digit:]]##) ]] ; then
  686. (( k += $#MATCH )) # highlight the parameter name
  687. (( i += $#MATCH )) # skip past it
  688. elif [[ ${arg:$i} = [{]([[:alpha:]_][[:alnum:]_]#|[[:digit:]]##)[}]* ]] ; then
  689. (( k += $#MATCH )) # highlight the parameter name and braces
  690. (( i += $#MATCH )) # skip past it
  691. else
  692. continue
  693. fi
  694. ;;
  695. "\\") style=back-double-quoted-argument
  696. if [[ \\\`\"\$ == *$arg[$i+1]* ]]; then
  697. (( k += 1 )) # Color following char too.
  698. (( i += 1 )) # Skip parsing the escaped char.
  699. else
  700. continue
  701. fi
  702. ;;
  703. *) continue ;;
  704. esac
  705. -hsmw-add-highlight $j $k $style
  706. done
  707. }
  708. # Highlight special chars inside dollar-quoted strings
  709. -hsmw-highlight-dollar-string()
  710. {
  711. setopt localoptions noksharrays
  712. local -a match mbegin mend
  713. local MATCH; integer MBEGIN MEND
  714. local i j k style
  715. local AA
  716. integer c
  717. # Starting dollar-quote is at 1:2, so start parsing at offset 3 in the string.
  718. for (( i = 3 ; i < end_pos - start_pos ; i += 1 )) ; do
  719. (( j = i + start_pos - 1 ))
  720. (( k = j + 1 ))
  721. case "$arg[$i]" in
  722. "\\") style=back-dollar-quoted-argument
  723. for (( c = i + 1 ; c <= end_pos - start_pos ; c += 1 )); do
  724. [[ "$arg[$c]" != ([0-9xXuUa-fA-F]) ]] && break
  725. done
  726. AA=$arg[$i+1,$c-1]
  727. # Matching for HEX and OCT values like \0xA6, \xA6 or \012
  728. if [[ "$AA" =~ "^(x|X)[0-9a-fA-F]{1,2}"
  729. || "$AA" =~ "^[0-7]{1,3}"
  730. || "$AA" =~ "^u[0-9a-fA-F]{1,4}"
  731. || "$AA" =~ "^U[0-9a-fA-F]{1,8}"
  732. ]]; then
  733. (( k += $#MATCH ))
  734. (( i += $#MATCH ))
  735. else
  736. if (( $#arg > $i+1 )) && [[ $arg[$i+1] == [xXuU] ]]; then
  737. # \x not followed by hex digits is probably an error
  738. style=unknown-token
  739. fi
  740. (( k += 1 )) # Color following char too.
  741. (( i += 1 )) # Skip parsing the escaped char.
  742. fi
  743. ;;
  744. *) continue ;;
  745. esac
  746. -hsmw-add-highlight $j $k $style
  747. done
  748. }
  749. # Called with a single positional argument.
  750. # Perform filename expansion (tilde expansion) on the argument and set $REPLY to the expanded value.
  751. # Does not perform filename generation (globbing).
  752. -hsmw-highlight-expand-path()
  753. {
  754. (( $# == 1 )) || print -r -- >&2 "hsmw-highlight: BUG: -hsmw-highlight-expand-path: called without argument"
  755. # The $~1 syntax normally performs filename generation, but not when it's on the right-hand side of ${x:=y}.
  756. setopt localoptions nonomatch
  757. unset REPLY
  758. : ${REPLY:=${(Q)~1}}
  759. }
  760. # -------------------------------------------------------------------------------------------------
  761. # Main highlighter initialization
  762. # -------------------------------------------------------------------------------------------------
  763. -hsmw-highlight-init() {
  764. __hsmw_highlight_main__command_type_cache=()
  765. }
  766. -hsmw-add-highlight()
  767. {
  768. local -i start end
  769. local highlight
  770. start=$1
  771. end=$2
  772. shift 2
  773. for highlight; do
  774. if (( $+HSMW_HIGHLIGHT_STYLES[$highlight] )); then
  775. reply+=("$start $end $HSMW_HIGHLIGHT_STYLES[$highlight]")
  776. break
  777. fi
  778. done
  779. }
  780. __HSMW_MH_SOURCED=1
  781. # vim:ft=zsh