runtime(sh): allow "#" in special derefs

Commit: 
https://github.com/vim/vim/commit/10040bc9cde340c52b5093cacb1d60fd2e621883
Author: D. Ben Knoble <[email protected]>
Date:   Tue Apr 21 19:59:07 2026 +0000

    runtime(sh): allow "#" in special derefs
    
    Code like ${!#} flags the "#" as shDerefWordError [1]; the "!prefix"
    syntax region delegates to one of the shDerefSpecial handlers via
    @shDerefList, but it misses the "#" case as valid for ${##} and ${!#}.
    
    [1]: https://vi.stackexchange.com/q/48617/10604
    
    Correct that. Indirection is only valid in Bash in Ksh, so rearrange the
    "!" handling to be conditional.
    
    closes: #20016
    
    Helped-by: Christian Brabandt <[email protected]>
    Signed-off-by: D. Ben Knoble <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim
index 05eb488d5..d022059d7 100644
--- a/runtime/syntax/sh.vim
+++ b/runtime/syntax/sh.vim
@@ -24,6 +24,7 @@
 "              2026 Feb 15 improve comment handling #19414
 "              2026 Mar 23 improve matching of function definitions #19638
 "              2026 Apr 02 improve matching of function definitions #19849
+"              2026 Apr 19 improve detection of special variables #20016
 " }}}
 " Version:             208
 " Former URL:          http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH
@@ -751,13 +752,15 @@ endif
 if exists("b:is_bash")
     syn region shDeref matchgroup=PreProc start="\${!" end="\*\=}"     
contains=@shDerefList,shDerefOffset
     syn match  shDerefVar      contained       "{\@<=!\h\w*"           
nextgroup=@shDerefVarList
+    syn match  shDerefSpecial  contained       "\({!\)\@<=[[:alnum:]*#@_]\+"   
nextgroup=@shDerefVarList,shDerefOp
 endif
 if (exists("b:is_kornshell") && !exists("b:is_ksh88"))
     syn match  shDerefVar      contained       "{\@<=!\h\w*[[:alnum:]_.]*"     
nextgroup=@shDerefVarList
+    syn match  shDerefSpecial  contained       "\({!\)\@<=[[:alnum:]*#@_]\+"   
nextgroup=@shDerefVarList,shDerefOp
 endif
 
 syn match  shDerefSpecial      contained       "{\@<=[-*@?0]"          
nextgroup=shDerefOp,shDerefOffset,shDerefOpError
-syn match  shDerefSpecial      contained       "\({[#!]\)\@<=[[:alnum:]*@_]\+" 
nextgroup=@shDerefVarList,shDerefOp
+syn match  shDerefSpecial      contained       "\({[#]\)\@<=[[:alnum:]*@_]\+"  
nextgroup=@shDerefVarList,shDerefOp
 syn match  shDerefVar  contained       "{\@<=\h\w*"            
nextgroup=@shDerefVarList
 syn match  shDerefVar  contained       '\d'                            
nextgroup=@shDerefVarList
 if exists("b:is_kornshell") || exists("b:is_posix")
diff --git a/runtime/syntax/testdir/dumps/sh_03_01.dump 
b/runtime/syntax/testdir/dumps/sh_03_01.dump
index 3ac5b4bc7..81e2c87de 100644
--- a/runtime/syntax/testdir/dumps/sh_03_01.dump
+++ b/runtime/syntax/testdir/dumps/sh_03_01.dump
@@ -17,4 +17,4 @@
 |:+0#0000e05&| 
+0#0000000&|'+0#af5f00255&|$+0#e000002&|{|V|a|r|i|a|b|l|e|B|:|-|$|{|V|a|r|i|a|b|l|e|C|:|-|e|n|g|}@1|'+0#af5f00255&|
 +0#0000000&@39
 @75
 |#+0#0000e05&| |A|n|o|t|h|e|r| |t|e|s|t| +0#0000000&@60
-@57|1|8|,|0|-|1| @7|8|1|%| 
+@57|1|8|,|0|-|1| @7|6|5|%| 
diff --git a/runtime/syntax/testdir/dumps/sh_03_02.dump 
b/runtime/syntax/testdir/dumps/sh_03_02.dump
index 6d03697c7..f34582e49 100644
--- a/runtime/syntax/testdir/dumps/sh_03_02.dump
+++ b/runtime/syntax/testdir/dumps/sh_03_02.dump
@@ -1,7 +1,11 @@
 |#+0#0000e05#ffffff0| |A|n|o|t|h|e|r| |t|e|s|t| +0#0000000&@60
 
|V+0#00e0e07&|a|r|i|a|b|l|e|=+0#0000000&|$+0#e000e06&|{|V|a|r|i|a|b|l|e|B|:+0#af5f00255&|-|$+0#e000e06&|{|V|a|r|i|a|b|l|e|C|:+0#af5f00255&|-|$+0#e000e06&|{|V|a|r|i|a|b|l|e|D|:+0#af5f00255&|-|$+0#e000e06&|{|V|a|r|i|a|b|l|e|E|:+0#af5f00255&|=|e+0#0000000&|n|g|}+0#e000e06&@3|
 +0#0000000&@6
 @7|:+0#0000e05&| 
+0#0000000&@7|$+0#e000e06&|{|V|a|r|i|a|b|l|e|B|:+0#af5f00255&|=|$+0#e000e06&|{|V|a|r|i|a|b|l|e|C|:+0#af5f00255&|-|$+0#e000e06&|{|V|a|r|i|a|b|l|e|D|:+0#af5f00255&|-|$+0#e000e06&|{|V|a|r|i|a|b|l|e|E|:+0#af5f00255&|=|e+0#0000000&|n|g|}+0#e000e06&@3
-> +0#0000000&@74
+| +0#0000000&@74
+|#+0#0000e05&| |s|p|e|c|i|a|l| |v|a|r|i|a|b|l|e|s| +0#0000000&@55
+>e+0#af5f00255&|c|h|o| 
+0#e000002&|"+0#af5f00255&|$+0#e000e06&|{|!|#|}|"+0#af5f00255&| 
+0#e000002&@11|#+0#0000e05&| |l|a|s|t| |p|o|s|i|t|i|o|n|a|l| |a|r|g|u|m|e|n|t| 
+0#0000000&@24
+|e+0#af5f00255&|c|h|o| 
+0#e000002&|"+0#af5f00255&|$+0#e000e06&|{|!|@|}|"+0#af5f00255&| 
+0#e000002&@11|#+0#0000e05&| |d|e|r|e|f| +0#0000000&@43
+|e+0#af5f00255&|c|h|o| 
+0#e000002&|"+0#af5f00255&|$+0#e000e06&|{|#|P|A|T|H|}|"+0#af5f00255&| 
+0#e000002&@8|#+0#0000e05&| |l|e|n|g|t|h| +0#0000000&@42
 |~+0#4040ff13&| @73
 |~| @73
 |~| @73
@@ -13,8 +17,4 @@
 |~| @73
 |~| @73
 |~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-| +0#0000000&@56|3@1|,|0|-|1| @7|B|o|t| 
+| +0#0000000&@56|3|5|,|1| @9|B|o|t| 
diff --git a/runtime/syntax/testdir/dumps/sh_bash_00.dump 
b/runtime/syntax/testdir/dumps/sh_bash_00.dump
index 85966ebd7..15093831d 100644
--- a/runtime/syntax/testdir/dumps/sh_bash_00.dump
+++ b/runtime/syntax/testdir/dumps/sh_bash_00.dump
@@ -14,7 +14,7 @@
 |e+0#af5f00255&|c|h|o| +0#e000002&|$+0#e000e06&|{| |e+0#af5f00255&|c|h|o| 
+0#e000002&|'+0#af5f00255&|s+0#e000002&|e|v|e|n|'+0#af5f00255&| 
+0#e000002&@3|;+0#e000e06&|}| +0#0000000&@48
 |e+0#af5f00255&|c|h|o| +0#e000002&|$+0#e000e06&|{| |e+0#af5f00255&|c|h|o| 
+0#e000002&|'+0#af5f00255&|e+0#e000002&|i|g|h|t|'+0#af5f00255&|;+0#e000e06&| 
@2|}| +0#0000000&@49
 |t+0#af5f00255&|y|p|e|s|e|t| 
+0#e000e06&|n+0#00e0e07&|i|n|e|=+0#0000000&|$+0#e000e06&|{| 
|p+0#af5f00255&|w|d|;| +0#e000e06&|}| +0#0000000&@52
-|e+0#af5f00255&|c|h|o| +0#e000002&|$+0#e000e06&|{| |e+0#af5f00255&|c|h|o| 
+0#e000002&|'+0#af5f00255&|n+0#e000002&|i|n|e|'+0#af5f00255&| 
+0#e000002&|;+0#e000e06&| | +0#0000000&@52
+|e+0#af5f00255&|c|h|o| +0#e000002&|$+0#e000e06&|{| |e+0#af5f00255&|c|h|o| 
+0#e000002&|'+0#af5f00255&|n+0#e000002&|i|n|e|'+0#af5f00255&| 
+0#e000002&|;+0#e000e06&| +0#0000000&@53
 | +0#e000e06&|}| +0#0000000&@72
 @75
 |i|s|_|b|a|s|h|:| |1|,| @45|1|,|1| @10|T|o|p| 
diff --git a/runtime/syntax/testdir/dumps/sh_bash_01.dump 
b/runtime/syntax/testdir/dumps/sh_bash_01.dump
index 18961a798..1a65e23fc 100644
--- a/runtime/syntax/testdir/dumps/sh_bash_01.dump
+++ b/runtime/syntax/testdir/dumps/sh_bash_01.dump
@@ -1,7 +1,7 @@
 |e+0#af5f00255#ffffff0|c|h|o| +0#e000002&|$+0#e000e06&|{| 
|e+0#af5f00255&|c|h|o| 
+0#e000002&|'+0#af5f00255&|s+0#e000002&|e|v|e|n|'+0#af5f00255&| 
+0#e000002&@3|;+0#e000e06&|}| +0#0000000&@48
 |e+0#af5f00255&|c|h|o| +0#e000002&|$+0#e000e06&|{| |e+0#af5f00255&|c|h|o| 
+0#e000002&|'+0#af5f00255&|e+0#e000002&|i|g|h|t|'+0#af5f00255&|;+0#e000e06&| 
@2|}| +0#0000000&@49
 |t+0#af5f00255&|y|p|e|s|e|t| 
+0#e000e06&|n+0#00e0e07&|i|n|e|=+0#0000000&|$+0#e000e06&|{| 
|p+0#af5f00255&|w|d|;| +0#e000e06&|}| +0#0000000&@52
-|e+0#af5f00255&|c|h|o| +0#e000002&|$+0#e000e06&|{| |e+0#af5f00255&|c|h|o| 
+0#e000002&|'+0#af5f00255&|n+0#e000002&|i|n|e|'+0#af5f00255&| 
+0#e000002&|;+0#e000e06&| | +0#0000000&@52
+|e+0#af5f00255&|c|h|o| +0#e000002&|$+0#e000e06&|{| |e+0#af5f00255&|c|h|o| 
+0#e000002&|'+0#af5f00255&|n+0#e000002&|i|n|e|'+0#af5f00255&| 
+0#e000002&|;+0#e000e06&| +0#0000000&@53
 | +0#e000e06&|}| +0#0000000&@72
 > @74
 |v+0#e000e06&|a|l|s|u|b|f|u|n|c|(|)| |{| +0#0000000&@60
@@ -12,9 +12,9 @@
 |p+0#af5f00255&|r|i|n|t|f| 
+0#e000e06&|'+0#af5f00255&|%+0#e000002&|s|\|n|'+0#af5f00255&| 
+0#e000e06&|"+0#af5f00255&|$+0#e000e06&|{|||v|a|l|s|u|b|f|u|n|c| |t|w|e|l|v|e| 
@4|;|}|"+0#af5f00255&| +0#0000000&@31
 
|u+0#00e0e07&|n|l|u|c|k|y|=+0#0000000&|$+0#e000e06&|{||+0#af5f00255&|v+0#e000e06&|a|l|s|u|b|f|u|n|c|
 |t|h|i|r|t|e@1|n| +0#0000000&@44
 |}+0#e000e06&| +0#0000000&@73
-|t+0#af5f00255&|y|p|e|s|e|t| 
+0#e000e06&|n+0#00e0e07&|o|t|a|f|l|o|a|t|=+0#0000000&|$+0#e000e06&|{||+0#af5f00255&|v+0#e000e06&|a|l|s|u|b|f|u|n|c|
 |n|o|t|a|n|u|m|b|e|r| @5|;+0#af5f00255&| +0#e000e06&| +0#0000000&@24
+|t+0#af5f00255&|y|p|e|s|e|t| 
+0#e000e06&|n+0#00e0e07&|o|t|a|f|l|o|a|t|=+0#0000000&|$+0#e000e06&|{||+0#af5f00255&|v+0#e000e06&|a|l|s|u|b|f|u|n|c|
 |n|o|t|a|n|u|m|b|e|r| @5|;+0#af5f00255&| +0#0000000&@25
 | +0#e000e06&|}| +0#0000000&@72
 |e+0#af5f00255&|c|h|o| +0#e000002&|$+0#e000e06&|u|n|l|u|c|k|y| 
+0#e000002&|$+0#e000e06&|n|o|t|a|n|u|m|b|e|r| +0#0000000&@49
 |$+0#e000e06&|{||+0#af5f00255&|e|c|h|o| 
+0#e000002&|f|o|u|r|t|e@1|n|;+0#af5f00255&|}+0#e000e06&| +0#0000000&@56
 |$+0#e000e06&|{||+0#af5f00255&|e|c|h|o| +0#e000002&|f|i|f|t|e@1|n| 
+0#0000000&@59
-@57|1|9|,|0|-|1| @7|9|2|%| 
+@57|1|9|,|0|-|1| @7|7|2|%| 
diff --git a/runtime/syntax/testdir/dumps/sh_bash_02.dump 
b/runtime/syntax/testdir/dumps/sh_bash_02.dump
index 677a1ab12..f7ea1a300 100644
--- a/runtime/syntax/testdir/dumps/sh_bash_02.dump
+++ b/runtime/syntax/testdir/dumps/sh_bash_02.dump
@@ -1,5 +1,9 @@
 |$+0#e000e06#ffffff0|{||+0#af5f00255&|e|c|h|o| +0#e000002&|f|i|f|t|e@1|n| 
+0#0000000&@59
->}+0#e000e06&| +0#0000000&@73
+|}+0#e000e06&| +0#0000000&@73
+@75
+|e+0#af5f00255&|c|h|o| 
+0#e000002&|"+0#af5f00255&|$+0#e000e06&|{|!|#|}|"+0#af5f00255&| 
+0#e000002&@11|#+0#0000e05&| |l|a|s|t| |p|o|s|i|t|i|o|n|a|l| |a|r|g|u|m|e|n|t| 
+0#0000000&@24
+|e+0#af5f00255&|c|h|o| 
+0#e000002&|"+0#af5f00255&|$+0#e000e06&|{|!|@|}|"+0#af5f00255&| 
+0#e000002&@11|#+0#0000e05&| |d|e|r|e|f| +0#0000000&@43
+>e+0#af5f00255&|c|h|o| 
+0#e000002&|"+0#af5f00255&|$+0#e000e06&|{|#|P|A|T|H|}|"+0#af5f00255&| 
+0#e000002&@8|#+0#0000e05&| |l|e|n|g|t|h| +0#0000000&@42
 |~+0#4040ff13&| @73
 |~| @73
 |~| @73
@@ -13,8 +17,4 @@
 |~| @73
 |~| @73
 |~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-| +0#0000000&@56|3@1|,|1| @9|B|o|t| 
+| +0#0000000&@56|3|7|,|1| @9|B|o|t| 
diff --git a/runtime/syntax/testdir/input/sh_03.sh 
b/runtime/syntax/testdir/input/sh_03.sh
index 8dd6dab9c..d2ac8f0a4 100644
--- a/runtime/syntax/testdir/input/sh_03.sh
+++ b/runtime/syntax/testdir/input/sh_03.sh
@@ -31,3 +31,7 @@ Variable="${VariableB:-${VariableC:-eng}}"  # :- is ksh and 
bash
 Variable=${VariableB:-${VariableC:-${VariableD:-${VariableE:=eng}}}}
        :        ${VariableB:=${VariableC:-${VariableD:-${VariableE:=eng}}}}
 
+# special variables
+echo "${!#}"           # last positional argument
+echo "${!@}"           # deref
+echo "${#PATH}"                # length
diff --git a/runtime/syntax/testdir/input/sh_bash.bash 
b/runtime/syntax/testdir/input/sh_bash.bash
index 35b536c4e..798ce44eb 100644
--- a/runtime/syntax/testdir/input/sh_bash.bash
+++ b/runtime/syntax/testdir/input/sh_bash.bash
@@ -14,7 +14,7 @@ echo ${ echo 'six'
 echo ${        echo 'seven'    ;}
 echo ${ echo 'eight';  }
 typeset nine=${ pwd; }
-echo ${ echo 'nine' ; 
+echo ${ echo 'nine' ;
  }
 
 valsubfunc() {
@@ -25,9 +25,13 @@ echo "${|valsubfunc eleven; }"
 printf '%s
' "${|valsubfunc twelve ;}"
 unlucky=${|valsubfunc thirteen
 }
-typeset notafloat=${|valsubfunc notanumber     ; 
+typeset notafloat=${|valsubfunc notanumber     ;
  }
 echo $unlucky $notanumber
 ${|echo fourteen;}
 ${|echo fifteen
 }
+
+echo "${!#}"           # last positional argument
+echo "${!@}"           # deref
+echo "${#PATH}"                # length

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wFHUu-00EIjj-Kr%40256bit.org.

Raspunde prin e-mail lui