[vim/vim] runtime(sh): allow "#" in special derefs (PR #20016)

6 views
Skip to first unread message

D. Ben Knoble

unread,
Apr 19, 2026, 9:54:04 AM (2 days ago) Apr 19
to vim/vim, Subscribed

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 ${!#}.

Correct that.


You can view, comment on, or merge this pull request online at:

  https://github.com/vim/vim/pull/20016

Commit Summary

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

File Changes

(1 file)

Patch Links:


Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/20016@github.com>

Christian Brabandt

unread,
Apr 19, 2026, 3:00:10 PM (2 days ago) Apr 19
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#20016)

thanks, let me add this to a syntax tests as well.


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/20016/c4276594341@github.com>

D. Ben Knoble

unread,
Apr 19, 2026, 3:16:27 PM (2 days ago) Apr 19
to vim/vim, Subscribed
benknoble left a comment (vim/vim#20016)

thanks, let me add this to a syntax tests as well.

Thanks! I wasn't totally sure what tests existed for this or how to deal with them. Happy to learn :)


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/20016/c4276624883@github.com>

Christian Brabandt

unread,
Apr 19, 2026, 3:31:11 PM (2 days ago) Apr 19
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#20016)

Hm, actually, this seems slightly wrong. It seems ${!...} is only valid for ksh and bash, so I would suggest the following change instead:

diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim
index 05eb488d5..23f8cc19b 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/input/sh_03.sh b/runtime/syntax/testdir/input/sh_03.sh
index 8dd6dab9c..28e80e172 100644
--- a/runtime/syntax/testdir/input/sh_03.sh
+++ b/runtime/syntax/testdir/input/sh_03.sh
@@ -12,6 +12,11 @@ echo ${VariableA##pat1}
 echo ${VariableB%pat1}
 echo ${VariableB%%pat1}

+# special variables
+echo "${!#}"      # last positional argument
+echo "${!@}"      # deref
+echo "${#PATH}"   # length
+
 # This gets marked as an error
 Variable=${VariableB:+${VariableC:=eng}}       # :+ seems to work for ksh as well as bash
 Variable=${VariableB:-${VariableC:-eng}}       # :- is ksh and bash

plus re-generate the sh_03* dump files


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/20016/c4276651094@github.com>

D. Ben Knoble

unread,
Apr 20, 2026, 5:39:53 PM (22 hours ago) Apr 20
to vim/vim, Push

@benknoble pushed 1 commit.

  • 61b1d98 runtime(sh): allow "#" in special derefs


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/20016/before/bde43e727b62eb0ee38508999ea8e1525830f248/after/61b1d98331c41a361a3a0abd05fbed8a37bd7bbc@github.com>

D. Ben Knoble

unread,
Apr 20, 2026, 5:39:55 PM (22 hours ago) Apr 20
to vim/vim, Subscribed
benknoble left a comment (vim/vim#20016)
range-diff: take Christian's patch and regenerate syntax files
1:  bde43e727 ! 1:  61b1d9833 runtime(sh): allow "#" in special derefs
    @@ Commit message
     
         [1]: https://vi.stackexchange.com/q/48617/10604
     
    -    Correct that.
    +    Correct that. Indirection is only valid in Bash in Ksh, so rearrange the
    +    "!" handling to be conditional.
     
    +    Helped-by: Christian Brabandt <c...@256bit.org>
         Signed-off-by: D. Ben Knoble <ben.knob...@gmail.com>
     
      ## runtime/syntax/sh.vim ##
    -@@ runtime/syntax/sh.vim: if (exists("b:is_kornshell") && !exists("b:is_ksh88"))
    +@@
    + "		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
    +@@ runtime/syntax/sh.vim: 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  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")
    +
    + ## runtime/syntax/testdir/dumps/sh_03_01.dump ##
    +@@
    + |:+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|%| 
    +
    + ## runtime/syntax/testdir/dumps/sh_03_02.dump ##
    +@@
    + |#+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
    +@@
    + |~| @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| 
    +
    + ## runtime/syntax/testdir/dumps/sh_bash_00.dump ##
    +@@
    + |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| 
    +
    + ## runtime/syntax/testdir/dumps/sh_bash_01.dump ##
    +@@
    + |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
    +@@
    + |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|%| 
    +
    + ## runtime/syntax/testdir/dumps/sh_bash_02.dump ##
    +@@
    + |$+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
    +@@
    + |~| @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| 
    +
    + ## runtime/syntax/testdir/input/sh_03.sh ##
    +@@
    + Variable=${VariableB:-${VariableC:-${VariableD:-${VariableE:=eng}}}}
    +        :        ${VariableB:=${VariableC:-${VariableD:-${VariableE:=eng}}}}
    + 
    ++# special variables
    ++echo "${!#}"		# last positional argument
    ++echo "${!@}"		# deref
    ++echo "${#PATH}"		# length
    +
    + ## runtime/syntax/testdir/input/sh_bash.bash ##
    +@@ runtime/syntax/testdir/input/sh_bash.bash: echo ${ echo 'six'
    + echo ${	echo 'seven'	;}
    + echo ${ echo 'eight';	}
    + typeset nine=${ pwd; }
    +-echo ${ echo 'nine' ; 
    ++echo ${ echo 'nine' ;
    +  }
    + 
    + valsubfunc() {
    +@@ runtime/syntax/testdir/input/sh_bash.bash: echo "${|valsubfunc eleven; }"
    + printf '%s\n' "${|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

A few notes:

  • I added the tests to both a Ksh test and a Bash test; the latter was the only one where I actually saw the highlight bug for ${!#}.
  • I added them at the bottom to make validating the dumps easier, though there were some changes that appeared to be spurious foreground changes?
  • I couldn't apply the patch posted in the comments because it had damaged whitespace (some tabs changed to spaces), so I had to make the edits by hand.


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/20016/c4284456234@github.com>

Reply all
Reply to author
Forward
0 new messages