main: Recurse into backtick command substitutions

This commit is contained in:
Matthew Martin 2018-03-12 19:29:09 -05:00
parent c40c72c386
commit 9db393309e
2 changed files with 67 additions and 12 deletions

View file

@ -1084,21 +1084,67 @@ _zsh_highlight_main_highlighter_highlight_dollar_quote()
REPLY=$i
}
# Highlight backtick subshells
# Highlight backtick substitutions
_zsh_highlight_main_highlighter_highlight_backtick()
{
local arg1=$1 i=$1 q=\` style
# buf is the contents of the backticks with a layer of backslashes removed.
# last is the index of arg for the start of the string to be copied into buf.
# It is either one past the beginning backtick or one past the last backslash.
# offset is a count of consumed \ (the delta between buf and arg).
# offsets is an array indexed by buf offset of when the delta between buf and arg changes.
# It is sparse, so search backwards to the last value
# unclosed is an array of one highlight to append to reply if this back-quoted-argument
# is closed and there is an unclosed back-quoted-argument in buf.
local buf highlight style=back-quoted-argument-unclosed
local -i arg1=$1 end_ i=$1 last offset=0 start subshell_has_end=0
local -a highlight_zone highlights offsets unclosed
reply=()
while i=$arg[(ib:i+1:)$q]; [[ $arg[i-1] == '\' && $i -lt $(( end_pos - start_pos )) ]]; do done
if [[ $arg[i] == '`' ]]; then
style=back-quoted-argument
else
# If unclosed, i points past the end
(( i-- ))
style=back-quoted-argument-unclosed
fi
reply=($(( start_pos + arg1 - 1 )) $(( start_pos + i )) $style)
last=$(( arg1 + 1 ))
# Remove one layer of backslashes and find the end
while i=$arg[(ib:i+1:)[\\\\\`]]; do # find the next \ or `
if (( i > end_pos - start_pos )); then
buf=$buf$arg[last,i]
offsets[i-arg1-offset]='' # So we never index past the end
(( i-- ))
subshell_has_end=$(( has_end && (start_pos + i == len) ))
break
fi
if [[ $arg[i] == '\' ]]; then
(( i++ ))
# POSIX XCU 2.6.3
if [[ $arg[i] == ('$'|'`'|'\') ]]; then
buf=$buf$arg[last,i-2]
(( offset++ ))
# offsets is relative to buf, so adjust by -arg1
offsets[i-arg1-offset]=$offset
else
buf=$buf$arg[last,i-1]
fi
else # it's an unquoted ` and this is the end
style=back-quoted-argument
buf=$buf$arg[last,i-1]
offsets[i-arg1-offset]='' # So we never index past the end
break
fi
last=$i
done
_zsh_highlight_main_highlighter_highlight_list 0 '' $subshell_has_end $buf
# Munge the reply to account for removed backslashes
for start end_ highlight in $reply; do
start=$(( start_pos + arg1 + start + offsets[(Rb:start:)?*] ))
end_=$(( start_pos + arg1 + end_ + offsets[(Rb:end_:)?*] ))
highlights+=($start $end_ $highlight)
if [[ $highlight == back-quoted-argument-unclosed && $style == back-quoted-argument ]]; then
# An inner backtick command substitution is unclosed, but this level is closed
unclosed=($(( start_pos + i - 1)) $(( start_pos + i )) unknown-token)
fi
done
reply=($(( start_pos + arg1 - 1 )) $(( start_pos + i )) $style $highlights $unclosed)
REPLY=$i
}

View file

@ -27,16 +27,25 @@
# vim: ft=zsh sw=2 ts=2 et
# -------------------------------------------------------------------------------------------------
# 42 is in the command position in a nested subshell.
BUFFER='echo `echo \`42\`` "is `echo equal` to" `echo 6 times 9'
expected_region_highlight=(
"1 4 builtin" # echo
"6 18 default" # `echo \`42\``
"6 18 back-quoted-argument" # `echo \`42\``
"7 10 builtin" # echo
"12 17 default" # \`42\`
"12 17 back-quoted-argument" # \`42\`
"14 15 unknown-token" # 42
"20 39 default" # "is `echo equal` to"
"20 39 double-quoted-argument" # "is `echo equal` to"
"24 35 back-quoted-argument" # `echo equal`
"25 28 builtin" # echo
"30 34 default" # equal
"41 55 default" # `echo 6 times 9
"41 55 back-quoted-argument-unclosed" # `echo 6 times 9
"42 45 builtin" # echo
"47 47 default" # 6
"49 53 default" # times
"55 55 default" # 9
)