vim-patch:b0691b4: runtime(sh): Fix various syntax highlighting problems in ksh93 scripts

- Fixed syntax highlighting for ksh93 namespace variables starting
  with '${.'
- Added support for the alarm, eloop, fds, mkservice, pids, poll and
  sha2sum builtins (which are indeed ksh93 builtins, albeit whether or
  not they are available depends on the ksh release and the compiled
  SHOPT options).
- Added support for the many Unix commands provided by ksh93's libcmd
  as builtin commands (since these are general commands, scripts for
  other shells like bash will also highlight these).
  - The dumps for the sh_0{2,5,6,8,9}.sh were recreated due to this
    change affecting commands those scripts call (e.g. 'wc').
- Enabled ${parameter/pattern/string} and friends for ksh syntax.
- Enabled case modification for ksh. See also:
  https://github.com/ksh93/ksh/commit/c1762e03
- Enabled ;;& support for ksh. See also:
  https://github.com/ksh93/ksh/commit/fc89d20a
- Added many special ksh variables using 93u+m's data/variables.c
  as a reference.

If vim can't figure out which ksh release is in play using e.g.
the hashbang path, in such a case a generic default that enables
everything and the kitchen sink will be used. Otherwise, features will
be disabled if it's absolutely known a certain feature will not be
present. Examples:
   - ERRNO is ksh88 specific, so that is locked to ksh88.
   - Only 93u+m (assumed for generic) has SRANDOM, and only 93u+m
     and 93v- have case modification support.
   - 93u+ and 93v- have VPATH and CSWIDTH variables (the latter
     is vestigal, but still present in the hardcoded variable table).
   - 93v- and ksh2020 have (buggy and near unusable) implementations
     of compgen and complete.
   - Only mksh provides function substitutions, i.e. ${|command;}.

This took the better part of my day to implement. It seems to work well
enough though. (Also had to regenerate the dumps again while testing
it, as now there are dup scripts with mere hashbang differences, used
solely for testing syntax highlighting differences.)

closes: vim/vim#17348

b0691b46bd

Co-authored-by: Johnothan King <johnothanking@protonmail.com>
This commit is contained in:
Christian Clason
2025-05-22 07:59:33 +02:00
committed by Christian Clason
parent 7077c59295
commit 172a90c245
2 changed files with 127 additions and 32 deletions

View File

@ -3066,6 +3066,10 @@ variables in your vimrc:
< sh: Bourne shell >
let g:is_sh = 1
Specific shell features are automatically enabled based on the shell detected
from the shebang line ("#! ..."). For KornShell Vim detects different shell
features for mksh, ksh88, ksh93, ksh93u, ksh93v, and ksh2020.
If there's no "#! ..." line, and the user hasn't availed themself of a default
sh.vim syntax setting as just shown, then syntax/sh.vim will assume the POSIX
shell syntax. No need to quote RFCs or market penetration statistics in error

View File

@ -14,6 +14,7 @@
" 2025 May 06 improve single-quote string matching in parameter expansions
" 2025 May 06 match KornShell compound arrays
" 2025 May 10 improve wildcard character class lists
" 2025 May 21 improve supported KornShell features
" Version: 208
" Former URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH
" For options and settings, please use: :help ft-sh-syntax
@ -31,7 +32,32 @@ let b:is_sh = 1
let s:shebang = getline(1)
if s:shebang =~ '^#!.\{-2,}\<ksh\>'
" The binary is too ambiguous (i.e. '/bin/ksh' or some such).
let b:is_kornshell = 1
let b:generic_korn = 1
elseif s:shebang =~ '^#!.\{-2,}\<ksh93u\+\>'
" ksh93u+ (or 93u-) release (still much too common to encounter)
let b:is_kornshell = 1
let b:is_ksh93u = 1
elseif s:shebang =~ '^#!.\{-2,}\<ksh93v\>'
" ksh93v- alpha or beta
let b:is_kornshell = 1
let b:is_ksh93v = 1
elseif s:shebang =~ '^#!.\{-2,}\<ksh93\>'
" Could be any ksh93 release
let b:is_kornshell = 1
let b:is_ksh93 = 1
elseif s:shebang =~ '^#!.\{-2,}\<ksh2020\>'
let b:is_kornshell = 1
let b:is_ksh2020 = 1
elseif s:shebang =~ '^#!.\{-2,}\<ksh88\>'
" The actual AT&T ksh88 and its feature set is assumed.
let b:is_kornshell = 1
let b:is_ksh88 = 1
elseif s:shebang =~ '^#!.\{-2,}\<mksh\>'
" MirBSD Korn Shell
let b:is_kornshell = 1
let b:is_mksh = 1
elseif s:shebang =~ '^#!.\{-2,}\<bash\>'
let b:is_bash = 1
elseif s:shebang =~ '^#!.\{-2,}\<dash\>'
@ -46,6 +72,7 @@ elseif s:shebang =~ '^#!.\{-2,}\<dash\>'
elseif !exists("b:is_kornshell") && !exists("b:is_bash") && !exists("b:is_posix") && !exists("b:is_dash")
if exists("g:is_kornshell")
let b:is_kornshell= 1
let b:generic_korn = 1
elseif exists("g:is_bash")
let b:is_bash= 1
elseif exists("g:is_dash")
@ -64,8 +91,33 @@ elseif !exists("b:is_kornshell") && !exists("b:is_bash") && !exists("b:is_posix"
elseif executable("/usr/bin/sh")
let s:shell = resolve("/usr/bin/sh")
endif
if s:shell =~ '\<ksh\>'
let b:is_kornshell= 1
if s:shell =~ '\<ksh\>'
" The binary is too ambiguous (i.e. '/bin/ksh' or some such).
let b:is_kornshell = 1
let b:generic_korn = 1
elseif s:shell =~ '\<ksh93u\+\>'
" ksh93u+ (or 93u-) release (still much too common to encounter)
let b:is_kornshell = 1
let b:is_ksh93u = 1
elseif s:shell =~ '\<ksh93v\>'
" ksh93v- alpha or beta
let b:is_kornshell = 1
let b:is_ksh93v = 1
elseif s:shell =~ '\<ksh93\>'
" Could be any ksh93 release
let b:is_kornshell = 1
let b:is_ksh93 = 1
elseif s:shebang =~ '\<ksh2020\>'
let b:is_kornshell = 1
let b:is_ksh2020 = 1
elseif s:shell =~ '\<ksh88\>'
" The actual AT&T ksh88 and its feature set is assumed.
let b:is_kornshell = 1
let b:is_ksh88 = 1
elseif s:shell =~ '\<mksh\>'
" MirBSD Korn Shell
let b:is_kornshell = 1
let b:is_mksh = 1
elseif s:shell =~ '\<bash\>'
let b:is_bash = 1
elseif s:shell =~ '\<dash\>'
@ -234,7 +286,7 @@ syn cluster shForList contains=shTestOpr,shNumber,shDerefSimple,shDeref,shComman
" Echo: {{{1
" ====
" This one is needed INSIDE a CommandSub, so that `echo bla` be correct
if exists("b:is_kornshell")
if (exists("b:is_kornshell") && !exists("b:is_ksh88"))
syn region shEcho matchgroup=shStatement start="\<echo\>" skip="\\$" matchgroup=shEchoDelim end="$" matchgroup=NONE end="[<>;&|()`}]"me=e-1 end="\d[<>]"me=e-2 end="#"me=e-1 end="\ze[ \t\n;]}" contains=@shEchoList skipwhite nextgroup=shQuickComment
syn region shEcho matchgroup=shStatement start="\<print\>" skip="\\$" matchgroup=shEchoDelim end="$" matchgroup=NONE end="[<>;&|()`}]"me=e-1 end="\d[<>]"me=e-2 end="#"me=e-1 end="\ze[ \t\n;]}" contains=@shEchoList skipwhite nextgroup=shQuickComment
else
@ -359,7 +411,7 @@ syn match shComma contained ","
syn match shCaseBar contained skipwhite "\(^\|[^\\]\)\(\\\\\)*\zs|" nextgroup=shCase,shCaseStart,shCaseBar,shComment,shCaseExSingleQuote,shCaseSingleQuote,shCaseDoubleQuote
syn match shCaseStart contained skipwhite skipnl "(" nextgroup=shCase,shCaseBar
syn match shCaseLabel contained skipwhite "\%(\\.\|[-a-zA-Z0-9_*.]\)\+" contains=shBracketExpr
if exists("b:is_bash")
if exists("b:is_bash") || (exists("b:is_kornshell") && !exists("b:is_ksh88") && !exists("b:is_ksh93u") && !exists("b:is_ksh93v") && !exists("b:is_ksh2020"))
ShFoldIfDoFor syn region shCase contained skipwhite skipnl matchgroup=shSnglCase start="\%(\\.\|[^#$()'" \t]\)\{-}\zs)" end=";;" end=";&" end=";;&" end="esac"me=s-1 contains=@shCaseList nextgroup=shCaseStart,shCase,shComment
elseif exists("b:is_kornshell")
ShFoldIfDoFor syn region shCase contained skipwhite skipnl matchgroup=shSnglCase start="\%(\\.\|[^#$()'" \t]\)\{-}\zs)" end=";;" end=";&" end="esac"me=s-1 contains=@shCaseList nextgroup=shCaseStart,shCase,shComment
@ -369,7 +421,7 @@ endif
ShFoldIfDoFor syn region shCaseEsac matchgroup=shConditional start="\<case\>" end="\<esac\>" contains=@shCaseEsacList
syn keyword shCaseIn contained skipwhite skipnl in nextgroup=shCase,shCaseStart,shCaseBar,shComment,shCaseExSingleQuote,shCaseSingleQuote,shCaseDoubleQuote
if exists("b:is_bash") || exists("b:is_kornshell")
if exists("b:is_bash") || (exists("b:is_kornshell") && !exists("b:is_ksh88"))
syn region shCaseExSingleQuote matchgroup=shQuote start=+\$'+ skip=+\\\\\|\\.+ end=+'+ contains=shStringSpecial,shSpecial skipwhite skipnl nextgroup=shCaseBar contained
elseif !exists("g:sh_no_error")
syn region shCaseExSingleQuote matchgroup=Error start=+\$'+ skip=+\\\\\|\\.+ end=+'+ contains=shStringSpecial skipwhite skipnl nextgroup=shCaseBar contained
@ -403,8 +455,16 @@ syn match shEscape contained '\%(^\)\@!\%(\\\\\)*\\.' nextgroup=shComment
if exists("b:is_kornshell") || exists("b:is_bash") || exists("b:is_posix")
syn region shCommandSub matchgroup=shCmdSubRegion start="\$((\@!" skip='\\\\\|\\.' end=")" contains=@shCommandSubList
if exists("b:is_kornshell")
syn region shSubshare matchgroup=shCmdSubRegion start="\${\ze[ \t\n<]" skip='\\\\\|\\.' end="\zs[ \t\n;]}" contains=@shCommandSubList
syn region shValsub matchgroup=shCmdSubRegion start="\${|" skip='\\\\\|\\.' end="}" contains=@shCommandSubList
if !exists("b:is_ksh88")
if exists("b:is_mksh")
syn region shSubshare matchgroup=shCmdSubRegion start="\${\ze[ \t\n]" skip='\\\\\|\\.' end="\zs[ \t\n;]}" contains=@shCommandSubList
else
syn region shSubshare matchgroup=shCmdSubRegion start="\${\ze[ \t\n<]" skip='\\\\\|\\.' end="\zs[ \t\n;]}" contains=@shCommandSubList
endif
endif
if exists("b:is_mksh") || exists("b:generic_korn")
syn region shValsub matchgroup=shCmdSubRegion start="\${|" skip='\\\\\|\\.' end="}" contains=@shCommandSubList
endif
endif
syn region shArithmetic matchgroup=shArithRegion start="\$((" skip='\\\\\|\\.' end="))" contains=@shArithList
syn region shArithmetic matchgroup=shArithRegion start="\$\[" skip='\\\\\|\\.' end="\]" contains=@shArithList
@ -419,17 +479,39 @@ if exists("b:is_bash")
syn cluster shCommandSubList add=bashSpecialVariables,bashStatement
syn cluster shCaseList add=bashAdminStatement,bashStatement
syn keyword bashSpecialVariables contained auto_resume BASH BASH_ALIASES BASH_ARGC BASH_ARGV BASH_CMDS BASH_COMMAND BASH_ENV BASH_EXECUTION_STRING BASH_LINENO BASHOPTS BASHPID BASH_REMATCH BASH_SOURCE BASH_SUBSHELL BASH_VERSINFO BASH_VERSION BASH_XTRACEFD CDPATH COLUMNS COMP_CWORD COMP_KEY COMP_LINE COMP_POINT COMPREPLY COMP_TYPE COMP_WORDBREAKS COMP_WORDS COPROC COPROC_PID DIRSTACK EMACS ENV EUID FCEDIT FIGNORE FUNCNAME FUNCNEST GLOBIGNORE GROUPS histchars HISTCMD HISTCONTROL HISTFILE HISTFILESIZE HISTIGNORE HISTSIZE HISTTIMEFORMAT HOME HOSTFILE HOSTNAME HOSTTYPE IFS IGNOREEOF INPUTRC LANG LC_ALL LC_COLLATE LC_CTYPE LC_MESSAGES LC_NUMERIC LINENO LINES MACHTYPE MAIL MAILCHECK MAILPATH MAPFILE OLDPWD OPTARG OPTERR OPTIND OSTYPE PATH PIPESTATUS POSIXLY_CORRECT PPID PROMPT_COMMAND PS0 PS1 PS2 PS3 PS4 PWD RANDOM READLINE_LINE READLINE_POINT REPLY SECONDS SHELL SHELLOPTS SHLVL TIMEFORMAT TIMEOUT TMPDIR UID
syn keyword bashStatement chmod clear complete du egrep expr fgrep find gnufind gnugrep grep head less ls mkdir mv rm rmdir rpm sed sleep sort strip tail
syn keyword bashStatement basename cat chgrp chmod chown cksum clear cmp comm command compgen complete cp cut date dirname du egrep expr fgrep find fmt fold getconf gnufind gnugrep grep head iconv id join less ln logname ls md5sum mkdir mkfifo mknod mktemp mv od paste pathchk readlink realpath rev rm rmdir rpm sed sha1sum sha224sum sha256sum sha384sum sha512sum sleep sort strip stty sum sync tail tee tr tty uname uniq wc which xargs xgrep
syn keyword bashAdminStatement daemon killall killproc nice reload restart start status stop
syn keyword bashStatement command compgen
endif
if exists("b:is_kornshell") || exists("b:is_posix")
syn cluster shCommandSubList add=kshSpecialVariables,kshStatement
syn cluster shCaseList add=kshStatement
syn keyword kshSpecialVariables contained CDPATH COLUMNS EDITOR ENV ERRNO FCEDIT FPATH HISTFILE HISTSIZE HOME IFS LINENO LINES MAIL MAILCHECK MAILPATH OLDPWD OPTARG OPTIND PATH PPID PS1 PS2 PS3 PS4 PWD RANDOM REPLY SECONDS SHELL TMOUT VISUAL
syn keyword kshStatement cat chmod clear cp du egrep expr fgrep find grep head killall less ls mkdir mv nice printenv rm rmdir sed sort strip stty tail tput
syn keyword kshStatement command setgroups setsenv
syn keyword kshSpecialVariables contained CDPATH COLUMNS EDITOR ENV FCEDIT FIGNORE FPATH HISTCMD HISTEDIT HISTFILE HISTSIZE HOME IFS JOBMAX KSH_VERSION LANG LC_ALL LC_COLLATE LC_CTYPE LC_MESSAGES LC_NUMERIC LC_TIME LINENO LINES MAIL MAILCHECK MAILPATH OLDPWD OPTARG OPTIND PATH PPID PS1 PS2 PS3 PS4 PWD RANDOM REPLY SECONDS SHELL SHLVL TMOUT VISUAL
syn keyword kshStatement basename cat chgrp chmod chown cksum clear cmp comm command cp cut date dirname du egrep expr fgrep find fmt fold grep head iconv id join killall less ln logname ls md5sum mkdir mknod mkfifo mktemp mv nice od paste pathchk printenv readlink realpath rev rm rmdir sed setgroups setsenv sha1sum sha224sum sha256sum sha384sum sha512sum sort strip stty sum sync tail tee tput tr tty uname uniq wc which xargs xgrep
if exists("b:is_ksh88")
syn keyword kshSpecialVariables contained ERRNO
elseif exists("b:is_mksh")
syn keyword kshSpecialVariables contained BASHPID EPOCHREALTIME EXECSHELL KSHEGID KSHGID KSHUID KSH_MATCH PATHSEP PGRP PIPESTATUS TMPDIR USER_ID
syn keyword kshStatement bind rename
elseif exists("b:is_ksh93v") || exists("b:is_ksh2020")
syn keyword kshSpecialVariables contained SH_OPTIONS COMP_CWORD COMP_LINE COMP_POINT COMP_WORDS COMP_KEY COMPREPLY COMP_WORDBREAKS COMP_TYPE
syn keyword kshStatement compgen complete
if exists("b:is_ksh93v")
syn keyword kshSpecialVariables contained VPATH CSWIDTH
syn keyword kshStatement vmstate alarm fds pids poll sha2sum
endif
elseif exists("b:is_ksh93u")
" ksh93u+
syn keyword kshSpecialVariables contained VPATH CSWIDTH
syn keyword kshStatement alarm fds pids vmstate
else
" 'ksh' is ambiguous; include everything
syn keyword kshSpecialVariables contained BASHPID EPOCHREALTIME EXECSHELL KSHEGID KSHGID KSHUID KSH_MATCH PATHSEP PGRP PIPESTATUS TMPDIR USER_ID SH_OPTIONS COMP_CWORD COMP_LINE COMP_POINT COMP_WORDS COMP_KEY COMPREPLY COMP_WORDBREAKS COMP_TYPE VPATH SRANDOM CSWIDTH
if !exists("b:is_ksh93")
syn keyword kshSpecialVariables contained ERRNO
endif
syn keyword kshStatement vmstate alarm fds pids poll sha2sum alarm eloop fds mkservice pids compgen complete bind rename
endif
endif
syn match shSource "^\.\s"
@ -504,8 +586,8 @@ ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc16 start="<<-\s*\\\_$\_s*
" Here Strings: {{{1
" =============
" available for: bash; ksh (really should be ksh93 only) but not if its a posix
if exists("b:is_bash") || (exists("b:is_kornshell") && !exists("b:is_posix"))
" available for: bash and ksh (except ksh88) but not if its a posix
if exists("b:is_bash") || ((exists("b:is_kornshell") && !exists("b:is_ksh88")) && !exists("b:is_posix"))
syn match shHereString "<<<" skipwhite nextgroup=shCmdParenRegion
endif
@ -518,7 +600,7 @@ if exists("b:is_bash")
syn region shArrayValue contained start="\[\%(..\{-}\]=\)\@=" end="\]=\@=" contains=@shArrayValueList nextgroup=shVarAssign
syn cluster shArrayValueList contains=shArithmetic,shArithParen,shCommandSub,shDeref,shDerefSimple,shExpr,shNumber,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shSpecial,shParen,bashSpecialVariables,shParenError
syn region shArrayRegion contained matchgroup=shShellVariables start="(" skip='\\\\\|\\.' end=")" contains=@shArrayValueList,shArrayValue,shComment
elseif exists("b:is_kornshell")
elseif (exists("b:is_kornshell") && !exists("b:is_ksh88"))
" The subscript form for array values, e.g. "foo=([2]=10 [4]=100)".
syn region shArrayValue contained start="\[\%(..\{-}\]=\)\@=" end="\]=\@=" contains=@shArrayValueList nextgroup=shVarAssign
syn cluster shArrayValueList contains=shArithmetic,shArithParen,shCommandSub,shDeref,shDerefSimple,shExpr,shNumber,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shSpecial,shParen,kshSpecialVariables,shParenError
@ -548,7 +630,7 @@ else
endif
" KornShell namespace: {{{1
if exists("b:is_kornshell")
if exists("b:is_kornshell") && !exists("b:is_ksh88") && !exists("b:is_mksh")
syn keyword shFunctionKey namespace skipwhite skipnl nextgroup=shFunctionTwo
endif
@ -578,8 +660,14 @@ if !exists("g:sh_no_error")
syn match shDerefWordError "[^}$[~]" contained
endif
syn match shDerefSimple "\$\%(\h\w*\|\d\)" nextgroup=@shNoZSList
if exists("b:is_kornshell")
syn region shDeref matchgroup=PreProc start="\${\ze[^ \t\n<|]" end="}" contains=@shDerefList,shDerefVarArray nextgroup=shSpecialStart
if exists("b:is_kornshell") && !exists("b:is_ksh88")
if exists("b:is_mksh")
syn region shDeref matchgroup=PreProc start="\${\ze[^ \t\n|]" end="}" contains=@shDerefList,shDerefVarArray nextgroup=shSpecialStart
elseif exists("b:generic_korn")
syn region shDeref matchgroup=PreProc start="\${\ze[^ \t\n<|]" end="}" contains=@shDerefList,shDerefVarArray nextgroup=shSpecialStart
else
syn region shDeref matchgroup=PreProc start="\${\ze[^ \t\n<]" end="}" contains=@shDerefList,shDerefVarArray nextgroup=shSpecialStart
endif
else
syn region shDeref matchgroup=PreProc start="\${" end="}" contains=@shDerefList,shDerefVarArray nextgroup=shSpecialStart
endif
@ -593,13 +681,14 @@ endif
" ksh: ${.sh.*} variables: {{{1
" ========================================
if exists("b:is_kornshell")
if exists("b:is_kornshell") && !exists("b:is_ksh88") && !exists("b:is_mksh")
syn match shDerefVar contained "\.\+" nextgroup=@shDerefVarList
syn region shDeref matchgroup=PreProc start="\${\ze[\.]" end="}" contains=@shDerefVarList,shDerefPSR,shDerefPPS
endif
" ksh: ${!var[*]} array index list syntax: {{{1
" ========================================
if exists("b:is_kornshell") || exists("b:is_posix")
if (exists("b:is_kornshell") && !exists("b:is_ksh88")) || exists("b:is_posix")
syn region shDeref matchgroup=PreProc start="\${!" end="}" contains=@shDerefVarArray
endif
@ -609,7 +698,7 @@ if exists("b:is_bash")
syn region shDeref matchgroup=PreProc start="\${!" end="\*\=}" contains=@shDerefList,shDerefOffset
syn match shDerefVar contained "{\@<=!\h\w*" nextgroup=@shDerefVarList
endif
if exists("b:is_kornshell")
if (exists("b:is_kornshell") && !exists("b:is_ksh88"))
syn match shDerefVar contained "{\@<=!\h\w*[[:alnum:]_.]*" nextgroup=@shDerefVarList
endif
@ -633,10 +722,10 @@ syn region shDerefVarArray contained matchgroup=shDeref start="\[" end="]" co
" ksh bash : ${parameter##pattern} remove large left pattern
" ksh bash : ${parameter%pattern} remove small right pattern
" ksh bash : ${parameter%%pattern} remove large right pattern
" bash : ${parameter^pattern} Case modification
" bash : ${parameter^^pattern} Case modification
" bash : ${parameter,pattern} Case modification
" bash : ${parameter,,pattern} Case modification
" ksh bash : ${parameter^pattern} Case modification
" ksh bash : ${parameter^^pattern} Case modification
" ksh bash : ${parameter,pattern} Case modification
" ksh bash : ${parameter,,pattern} Case modification
" bash : ${@:start:qty} display command line arguments from start to start+qty-1 (inferred)
" bash : ${parameter@operator} transforms parameter (operator∈[uULqEPARa])
syn cluster shDerefPatternList contains=shDerefPattern,shDerefString
@ -655,15 +744,17 @@ if exists("b:is_bash") || exists("b:is_kornshell") || exists("b:is_posix")
call s:GenerateBracketExpressionItems({'itemGroup': 'shDerefPattern', 'bracketGroup': 'shBracketExprDelim', 'extraArgs': 'contained nextgroup=shDerefPattern'})
syn match shDerefEscape contained '\%(\\\\\)*\\.'
endif
if exists("b:is_bash")
if exists("b:is_bash") || (exists("b:is_kornshell") && !exists("b:is_ksh88") && !exists("b:is_mksh") && !exists("b:is_ksh93u") && !exists("b:is_ksh2020"))
syn match shDerefOp contained "[,^]\{1,2}" nextgroup=@shDerefPatternList
endif
if exists("b:is_bash")
syn match shDerefOp contained "@[uULQEPAKa]"
endif
syn region shDerefString contained matchgroup=shDerefDelim start=+\%(\\\)\@<!'+ end=+'+
syn region shDerefString contained matchgroup=shDerefDelim start=+\%(\\\)\@<!"+ skip=+\\"+ end=+"+ contains=@shDblQuoteList,shStringSpecial
syn match shDerefString contained "\\["']" nextgroup=shDerefPattern
if exists("b:is_bash") || exists("b:is_kornshell") || exists("b:is_posix")
if exists("b:is_bash") || (exists("b:is_kornshell") && !exists("b:is_ksh88")) || exists("b:is_posix")
" bash ksh posix : ${parameter:offset}
" bash ksh posix : ${parameter:offset:length}
syn region shDerefOffset contained start=':[^-=?+]' end='\ze:' end='\ze}' contains=shDeref,shDerefSimple,shDerefEscape nextgroup=shDerefLen,shDeref,shDerefSimple
@ -671,15 +762,15 @@ if exists("b:is_bash") || exists("b:is_kornshell") || exists("b:is_posix")
syn match shDerefLen contained ":[^}]\+" contains=shDeref,shDerefSimple,shArithmetic
endif
if exists("b:is_bash")
" bash : ${parameter/pattern/string}
" bash : ${parameter//pattern/string}
if exists("b:is_bash") || (exists("b:is_kornshell") && !exists("b:is_ksh88"))
" bash ksh : ${parameter/pattern/string}
" bash ksh : ${parameter//pattern/string}
syn match shDerefPPS contained '/\{1,2}' nextgroup=shDerefPPSleft
syn region shDerefPPSleft contained start='.' skip=@\%(\\\\\)*\\/@ matchgroup=shDerefOp end='/' end='\ze}' end='"' nextgroup=shDerefPPSright contains=@shPPSLeftList
syn region shDerefPPSright contained start='.' skip=@\%(\\\\\)\+@ end='\ze}' contains=@shPPSRightList
" bash : ${parameter/#pattern/string}
" bash : ${parameter/%pattern/string}
" bash ksh : ${parameter/#pattern/string}
" bash ksh : ${parameter/%pattern/string}
syn match shDerefPSR contained '/[#%]' nextgroup=shDerefPSRleft,shDoubleQuote,shSingleQuote
syn region shDerefPSRleft contained start='[^"']' skip=@\%(\\\\\)*\\/@ matchgroup=shDerefOp end='/' end='\ze}' nextgroup=shDerefPSRright contains=shBracketExpr
syn region shDerefPSRright contained start='.' skip=@\%(\\\\\)\+@ end='\ze}'