@@ -104,45 +104,30 @@ _zsh_highlight_main_add_many_region_highlights() {
104104 done
105105}
106106
107+ _zsh_highlight_main_calculate_styles () {
108+ local config=" ${(pj: \0 : )${(@ kv)ZSH_HIGHLIGHT_STYLES} } " .
109+ [[ $config == $_zsh_highlight_main__config ]] && return
110+ _zsh_highlight_main__config=$config
111+ typeset -gA _zsh_highlight_main__styles=(" ${(@ kv)ZSH_HIGHLIGHT_STYLES} " )
112+
113+ integer finished
114+ local key val
115+ while (( ! finished )) ; do
116+ finished=1
117+ for key val in ${(@ kv)_zsh_highlight_main__fallback_of} ; do
118+ [[ -n $_zsh_highlight_main__styles [$key ] ]] && continue
119+ if [[ -z $_zsh_highlight_main__styles [$key ] &&
120+ -n ${_zsh_highlight_main__styles[$key]::= ${_zsh_highlight_main__styles[$val]} } ]]; then
121+ finished=0
122+ fi
123+ done
124+ done
125+ }
126+
107127_zsh_highlight_main_calculate_fallback () {
108- local -A fallback_of; fallback_of=(
109- alias arg0
110- suffix-alias arg0
111- global-alias dollar-double-quoted-argument
112- builtin arg0
113- function arg0
114- command arg0
115- precommand arg0
116- hashed-command arg0
117- autodirectory arg0
118- arg0_\* arg0
119-
120- # TODO: Maybe these? —
121- # named-fd file-descriptor
122- # numeric-fd file-descriptor
123-
124- path_prefix path
125- # The path separator fallback won't ever be used, due to the optimisation
126- # in _zsh_highlight_main_highlighter_highlight_path_separators().
127- path_pathseparator path
128- path_prefix_pathseparator path_prefix
129-
130- single-quoted-argument{-unclosed,}
131- double-quoted-argument{-unclosed,}
132- dollar-quoted-argument{-unclosed,}
133- back-quoted-argument{-unclosed,}
134-
135- command-substitution{-quoted,,-unquoted,}
136- command-substitution-delimiter{-quoted,,-unquoted,}
137-
138- command-substitution{-delimiter,}
139- process-substitution{-delimiter,}
140- back-quoted-argument{-delimiter,}
141- )
142128 local needle=$1 value
143129 reply=($1 )
144- while [[ -n ${value::= $fallback_of [(k)$needle]} ]]; do
145- unset " fallback_of[$needle ]" # paranoia against infinite loops
130+ while [[ -n ${value::= $_zsh_highlight_main__fallback_of [(k)$needle]} ]]; do
146131 reply+=($value )
147132 needle=$value
148133 done
@@ -283,8 +268,8 @@ _zsh_highlight_main__resolve_alias() {
283268# Return true iff $1 is a global alias
284269_zsh_highlight_main__is_global_alias () {
285270 if zmodload -e zsh/parameter; then
286- (( ${+galiases[$arg ]} ))
287- elif [[ $arg == ' =' * ]]; then
271+ (( ${+galiases[$1 ]} ))
272+ elif [[ $1 == ' =' * ]]; then
288273 # avoid running into «alias -L '=foo'» erroring out with 'bad assignment'
289274 return 1
290275 else
@@ -325,8 +310,6 @@ _zsh_highlight_highlighter_main_paint()
325310 return
326311 fi
327312
328- typeset -a ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR
329- typeset -a ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW
330313 local -a options_to_set reply # used in callees
331314 local REPLY
332315
@@ -340,88 +323,23 @@ _zsh_highlight_highlighter_main_paint()
340323 # present, mean the precommand will not be acting as a precommand, i.e., will
341324 # not be followed by a :start: word.
342325 local flags_solo
343- # $precommand_options maps precommand name to values of $flags_with_argument,
344- # $flags_sans_argument, and flags_solo for that precommand, joined by a
345- # colon. (The value is NOT a getopt(3) spec, although it resembles one.)
326+ # $_zsh_highlight_main__precommand_options maps precommand name to values of
327+ # $flags_with_argument, $ flags_sans_argument, and flags_solo for that precommand,
328+ # joined by a colon. (The value is NOT a getopt(3) spec, although it resembles one.)
346329 #
347330 # Currently, setting $flags_sans_argument is only important for commands that
348331 # have a non-empty $flags_with_argument; see test-data/precommand4.zsh.
349- local -A precommand_options
350- precommand_options=(
351- # Precommand modifiers as of zsh 5.6.2 cf. zshmisc(1).
352- ' -' ' '
353- ' builtin' ' '
354- ' command' :pvV
355- ' exec' a:cl
356- ' noglob' ' '
357- # 'time' and 'nocorrect' shouldn't be added here; they're reserved words, not precommands.
358-
359- ' doas' aCu:Lns # as of OpenBSD's doas(1) dated September 4, 2016
360- ' nice' n: # as of current POSIX spec
361- ' pkexec' ' ' # doesn't take short options; immune to #121 because it's usually not passed --option flags
362- # Not listed: -h, which has two different meanings.
363- ' sudo' Cgprtu:AEHPSbilns:eKkVv # as of sudo 1.8.21p2
364- ' stdbuf' ioe:
365- ' eatmydata' ' '
366- ' catchsegv' ' '
367- ' nohup' ' '
368- ' setsid' :wc
369- ' env' u:i
370- ' ionice' cn:t:pPu # util-linux 2.33.1-0.1
371- ' strace' IbeaosXPpEuOS:ACdfhikqrtTvVxyDc # strace 4.26-0.2
372-
373- # As of OpenSSH 8.1p1
374- ' ssh-agent' aEPt:csDd:k
375- # suckless-tools v44
376- # Argumentless flags that can't be followed by a command: -v
377- ' tabbed' gnprtTuU:cdfhs
378-
379- # moreutils 0.62-1
380- ' chronic' :ev
381- ' ifne' :n
382-
383- )
384- # Commands that would need to skip one positional argument:
385- # flock
386- # ssh
387332
388- if [[ $zsyh_user_options [ignorebraces] == on || ${ zsyh_user_options[ignoreclosebraces]:- off} == on ]]; then
389- local right_brace_is_recognised_everywhere=false
333+ if [[ $zsyh_user_options [ignorebraces] == on || $zsyh_user_options [ignoreclosebraces] == on ]]; then
334+ local -i right_brace_is_recognised_everywhere=0
390335 else
391- local right_brace_is_recognised_everywhere=true
336+ local -i right_brace_is_recognised_everywhere=1
392337 fi
393338
394339 if [[ $zsyh_user_options [pathdirs] == on ]]; then
395340 options_to_set+=( PATH_DIRS )
396341 fi
397342
398- ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR=(
399- ' |' ' ||' ' ;' ' &' ' &&'
400- $' \n ' # ${(z)} returns ';' but we convert it to $'\n'
401- ' |&'
402- ' &!' ' &|'
403- # ### 'case' syntax, but followed by a pattern, not by a command
404- # ';;' ';&' ';|'
405- )
406-
407- # Tokens that, at (naively-determined) "command position", are followed by
408- # a de jure command position. All of these are reserved words.
409- ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW=(
410- $' \x7b ' # block
411- $' \x28 ' # subshell
412- ' ()' # anonymous function
413- ' while'
414- ' until'
415- ' if'
416- ' then'
417- ' elif'
418- ' else'
419- ' do'
420- ' time'
421- ' coproc'
422- ' !' # reserved word; unrelated to $histchars[1]
423- )
424-
425343 if (( $+ X_ZSH_HIGHLIGHT_DIRS_BLACKLIST )) ; then
426344 print >&2 ' zsh-syntax-highlighting: X_ZSH_HIGHLIGHT_DIRS_BLACKLIST is deprecated. Please use ZSH_HIGHLIGHT_DIRS_BLACKLIST.'
427345 ZSH_HIGHLIGHT_DIRS_BLACKLIST=($X_ZSH_HIGHLIGHT_DIRS_BLACKLIST )
@@ -431,13 +349,19 @@ _zsh_highlight_highlighter_main_paint()
431349 _zsh_highlight_main_highlighter_highlight_list -$# PREBUFFER ' ' 1 " $PREBUFFER$BUFFER "
432350
433351 # end is a reserved word
434- local start end_ style
352+ integer start end_
353+ local style
435354 for start end_ style in $reply ; do
436355 (( start >= end_ )) && { print -r -- >&2 " zsh-syntax-highlighting: BUG: _zsh_highlight_highlighter_main_paint: start($start ) >= end($end_ )" ; return }
437356 (( end_ <= 0 )) && continue
438357 (( start < 0 )) && start=0 # having start<0 is normal with e.g. multiline strings
439- _zsh_highlight_main_calculate_fallback $style
440- _zsh_highlight_add_highlight $start $end_ $reply
358+ if (( $+ _zsh_highlight_main__styles )) ; then
359+ style=$_zsh_highlight_main__styles [$style ]
360+ [[ -n $style ]] && region_highlight+=(" $start $end_ $style , memo=zsh-syntax-highlighting" )
361+ else
362+ _zsh_highlight_main_calculate_fallback $style
363+ _zsh_highlight_add_highlight $start $end_ $reply
364+ fi
441365 done
442366}
443367
@@ -545,8 +469,8 @@ _zsh_highlight_main_highlighter_highlight_list()
545469 # when given as a separate word; i.e., "foo" in "-u foo" (two
546470 # words) but not in "-ufoo" (one word).
547471 # Note: :sudo_opt: and :sudo_arg: are used for any precommand
548- # declared in ${precommand_options }, not just for sudo(8).
549- # The naming is historical.
472+ # declared in ${_zsh_highlight_main__precommand_options }, not just
473+ # for sudo(8). The naming is historical.
550474 # - :regular: "Not a command word", and command delimiters are permitted.
551475 # Mainly used to detect premature termination of commands.
552476 # - :always: The word 'always' in the «{ foo } always { bar }» syntax.
@@ -840,7 +764,7 @@ _zsh_highlight_main_highlighter_highlight_list()
840764 fi
841765
842766 # The Great Fork: is this a command word? Is this a non-command word?
843- if [[ -n ${(M)ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR :# " $arg " } ]] &&
767+ if [[ -n ${(M)_zsh_highlight_main__tokens_commandseparator :# " $arg " } ]] &&
844768 [[ $braces_stack != * T* || $arg != (' ||' | ' &&' ) ]]; then
845769
846770 # First, determine the style of the command separator itself.
@@ -899,10 +823,10 @@ _zsh_highlight_main_highlighter_highlight_list()
899823 saw_assignment=false
900824 next_word=' :start::start_of_pipeline:' # only left brace is allowed, apparently
901825 elif ! (( in_redirection)) && [[ $this_word == * ' :start:' * ]]; then # $arg is the command word
902- if (( ${+precommand_options [$arg]} )) && _zsh_highlight_main__is_runnable $arg ; then
826+ if (( ${+_zsh_highlight_main__precommand_options [$arg]} )) && _zsh_highlight_main__is_runnable $arg ; then
903827 style=precommand
904828 () {
905- set -- " ${(@ s.: .)precommand_options [$arg]} "
829+ set -- " ${(@ s.: .)_zsh_highlight_main__precommand_options [$arg]} "
906830 flags_with_argument=$1
907831 flags_sans_argument=$2
908832 flags_solo=$3
@@ -928,7 +852,7 @@ _zsh_highlight_main_highlighter_highlight_list()
928852 braces_stack=' Y' " $braces_stack "
929853 ;;
930854 ($' \x7d ' )
931- # We're at command word, so no need to check $ right_brace_is_recognised_everywhere
855+ # We're at command word, so no need to check right_brace_is_recognised_everywhere
932856 _zsh_highlight_main__stack_pop ' Y' reserved-word
933857 if [[ $style == reserved-word ]]; then
934858 next_word+=' :always:'
@@ -1087,7 +1011,7 @@ _zsh_highlight_main_highlighter_highlight_list()
10871011 ;;
10881012 esac
10891013 fi
1090- if [[ -n ${(M)ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW :# " $arg " } ]]; then
1014+ if [[ -n ${(M)_zsh_highlight_main__tokens_control_flow :# " $arg " } ]]; then
10911015 next_word=' :start::start_of_pipeline:'
10921016 fi
10931017 elif _zsh_highlight_main__is_global_alias " $arg " ; then # $arg is a global alias that isn't in command position
@@ -1126,7 +1050,7 @@ _zsh_highlight_main_highlighter_highlight_list()
11261050 fi
11271051 ;;
11281052 (* ) if false ; then
1129- elif [[ $arg = $' \x7d ' ]] && $ right_brace_is_recognised_everywhere; then
1053+ elif [[ $arg = $' \x7d ' ]] && (( right_brace_is_recognised_everywhere )) ; then
11301054 # Parsing rule: {
11311055 #
11321056 # Additionally, `tt(})' is recognized in any position if neither the
@@ -1313,7 +1237,7 @@ _zsh_highlight_main_highlighter_highlight_argument()
13131237 local base_style=default i=$1 option_eligible=${2:- 1} path_eligible=1 ret start style
13141238 local -a highlights
13151239
1316- local -a match mbegin mend
1240+ local -a match mbegin mend reply
13171241 local MATCH; integer MBEGIN MEND
13181242
13191243 case " $arg [i]" in
@@ -1837,6 +1761,10 @@ _zsh_highlight_main__precmd_hook() {
18371761
18381762 _zsh_highlight_main__command_type_cache=()
18391763 _zsh_highlight_main__path_cache=()
1764+
1765+ if [[ $ZSH_VERSION != (5.< 9-> * | < 6-> .* ) ]]; then
1766+ _zsh_highlight_main_calculate_styles
1767+ fi
18401768}
18411769
18421770autoload -Uz add-zsh-hook
@@ -1849,3 +1777,102 @@ else
18491777 unset _zsh_highlight_main__command_type_cache _zsh_highlight_main__path_cache
18501778fi
18511779typeset -g a ZSH_HIGHLIGHT_DIRS_BLACKLIST
1780+
1781+ typeset -g A _zsh_highlight_main__precommand_options= (
1782+ # Precommand modifiers as of zsh 5.6.2 cf. zshmisc(1).
1783+ ' -' ' '
1784+ ' builtin' ' '
1785+ ' command' :pvV
1786+ ' exec' a:cl
1787+ ' noglob' ' '
1788+ # 'time' and 'nocorrect' shouldn't be added here; they're reserved words, not precommands.
1789+
1790+ ' doas' aCu:Lns # as of OpenBSD's doas(1) dated September 4, 2016
1791+ ' nice' n: # as of current POSIX spec
1792+ ' pkexec' ' ' # doesn't take short options; immune to #121 because it's usually not passed --option flags
1793+ # Not listed: -h, which has two different meanings.
1794+ ' sudo' Cgprtu:AEHPSbilns:eKkVv # as of sudo 1.8.21p2
1795+ ' stdbuf' ioe:
1796+ ' eatmydata' ' '
1797+ ' catchsegv' ' '
1798+ ' nohup' ' '
1799+ ' setsid' :wc
1800+ ' env' u:i
1801+ ' ionice' cn:t:pPu # util-linux 2.33.1-0.1
1802+ ' strace' IbeaosXPpEuOS:ACdfhikqrtTvVxyDc # strace 4.26-0.2
1803+
1804+ # As of OpenSSH 8.1p1
1805+ ' ssh-agent' aEPt:csDd:k
1806+ # suckless-tools v44
1807+ # Argumentless flags that can't be followed by a command: -v
1808+ ' tabbed' gnprtTuU:cdfhs
1809+
1810+ # moreutils 0.62-1
1811+ ' chronic' :ev
1812+ ' ifne' :n
1813+ )
1814+ # Commands that would need to skip one positional argument:
1815+ # flock
1816+ # ssh
1817+
1818+ typeset -g a _zsh_highlight_main__tokens_commandseparator= (
1819+ ' |' ' ||' ' ;' ' &' ' &&'
1820+ $' \n ' # ${(z)} returns ';' but we convert it to $'\n'
1821+ ' |&'
1822+ ' &!' ' &|'
1823+ # ### 'case' syntax, but followed by a pattern, not by a command
1824+ # ';;' ';&' ';|'
1825+ )
1826+
1827+ # Tokens that, at (naively-determined) "command position", are followed by
1828+ # a de jure command position. All of these are reserved words.
1829+ typeset -g a _zsh_highlight_main__tokens_control_flow= (
1830+ $' \x7b ' # block
1831+ $' \x28 ' # subshell
1832+ ' ()' # anonymous function
1833+ ' while'
1834+ ' until'
1835+ ' if'
1836+ ' then'
1837+ ' elif'
1838+ ' else'
1839+ ' do'
1840+ ' time'
1841+ ' coproc'
1842+ ' !' # reserved word; unrelated to $histchars[1]
1843+ )
1844+
1845+ typeset -g A _zsh_highlight_main__fallback_of= (
1846+ alias arg0
1847+ suffix-alias arg0
1848+ global-alias dollar-double-quoted-argument
1849+ builtin arg0
1850+ function arg0
1851+ command arg0
1852+ precommand arg0
1853+ hashed-command arg0
1854+ autodirectory arg0
1855+ arg0_\* arg0
1856+
1857+ # TODO: Maybe these? —
1858+ # named-fd file-descriptor
1859+ # numeric-fd file-descriptor
1860+
1861+ path_prefix path
1862+ # The path separator fallback won't ever be used, due to the optimisation
1863+ # in _zsh_highlight_main_highlighter_highlight_path_separators().
1864+ path_pathseparator path
1865+ path_prefix_pathseparator path_prefix
1866+
1867+ single-quoted-argument{-unclosed,}
1868+ double-quoted-argument{-unclosed,}
1869+ dollar-quoted-argument{-unclosed,}
1870+ back-quoted-argument{-unclosed,}
1871+
1872+ command-substitution{-quoted,,-unquoted,}
1873+ command-substitution-delimiter{-quoted,,-unquoted,}
1874+
1875+ command-substitution{-delimiter,}
1876+ process-substitution{-delimiter,}
1877+ back-quoted-argument{-delimiter,}
1878+ )
0 commit comments