main-highlighter.zsh 37 KB

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