Skip to content

Commit 9c0d057

Browse files
zzzyxwvutJohnoKingdkearns
authored andcommitted
runtime(sh): Improve the matching of function definitions
- Drop the remaining undefined "shFunctionStart" references (gone in v7.2b.000, c236c16). - Revise nestable contexts where function definitions are supported: * Stop looking for function definitions in arithmetic expressions. * Recognise function definitions enclosing other function definitions. - In addition to grouping commands "{}" and "()", also match other compound commands (e.g. "if"; see "shFunctionCmd*") whenever these commands are supported as complete function bodies. - Balance body delimiters "(" and ")" for "shFunctionFour" in Bash; match such function bodies whenever the use of the function parameter list "()" token is optional, i.e. when the "function" reserved word is present. - Enable the use of "shFunctionFour" definitions. - Do not claim optional leading whitespace characters before a matched function definition. - Prefer patterns with ASCII atoms (e.g. "\h") to equivalent collections (e.g. "[A-Za-z_]") for speed. - Accommodate word-boundary assertions in function name patterns to names that may start and/or end with supported non-word characters, e.g. "@test:". - Match more valid function names in Bash: non-ASCII names, non-word names. - Allow for function names with "do", "done", etc. prefixes; confine these name checks to "shDoError" and "shIfError". fixes: #19619 related: #19638 References: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_09_04 https://gitweb.git.savannah.gnu.org/gitweb/?p=bash.git;a=blob_plain;f=doc/bash.html;hb=637f5c8696a6adc9b4519f1cd74aa78492266b7f https://web.archive.org/web/20151105130220/http://www.research.att.com/sw/download/man/man1/ksh88.html https://web.archive.org/web/20151025145158/http://www2.research.att.com/sw/download/man/man1/ksh.html http://www.mirbsd.org/htman/i386/man1/mksh.htm Co-authored-by: Johnothan King <johnothanking@protonmail.com> Co-authored-by: Doug Kearns <dougkearns@gmail.com> Signed-off-by: Aliaksei Budavei <0x000c70@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent 4292eea commit 9c0d057

31 files changed

Lines changed: 633 additions & 35 deletions

runtime/syntax/sh.vim

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
" 2026 Jan 15 highlight command switches that contain a digit
2323
" 2026 Feb 11 improve support for KornShell function names and variables
2424
" 2026 Feb 15 improve comment handling #19414
25+
" 2026 Mar 23 improve matching of function definitions #19638
2526
" }}}
2627
" Version: 208
2728
" Former URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH
@@ -261,7 +262,7 @@ syn cluster shErrorList contains=shDoError,shIfError,shInError,shCaseError,shEsa
261262
if exists("b:is_kornshell") || exists("b:is_bash")
262263
syn cluster ErrorList add=shDTestError
263264
endif
264-
syn cluster shArithParenList contains=shArithmetic,shArithParen,shCaseEsac,shComment,shDeref,shDerefVarArray,shDo,shDerefSimple,shEcho,shEscape,shExpr,shNumber,shOperator,shPosnParm,shExSingleQuote,shExDoubleQuote,shHereString,shRedir,shSingleQuote,shDoubleQuote,shStatement,shVariable,shAlias,shTest,shCtrlSeq,shSpecial,shParen,bashSpecialVariables,bashStatement,shIf,shFor,shFunctionKey,shFunctionOne,shFunctionTwo,shNamespaceOne
265+
syn cluster shArithParenList contains=shArithmetic,shArithParen,shCaseEsac,shComment,shDeref,shDerefVarArray,shDo,shDerefSimple,shEcho,shEscape,shExpr,shNumber,shOperator,shPosnParm,shExSingleQuote,shExDoubleQuote,shHereString,shRedir,shSingleQuote,shDoubleQuote,shStatement,shVariable,shAlias,shTest,shCtrlSeq,shSpecial,shParen,bashSpecialVariables,bashStatement,shIf,shFor
265266
syn cluster shArithList contains=@shArithParenList,shParenError
266267
syn cluster shBracketExprList contains=shCharClassOther,shCharClass,shCollSymb,shEqClass
267268
syn cluster shCaseEsacList contains=shCaseStart,shCaseLabel,shCase,shCaseBar,shCaseIn,shComment,shDeref,shDerefSimple,shCaseCommandSub,shCaseExSingleQuote,shCaseSingleQuote,shCaseDoubleQuote,shCtrlSeq,@shErrorList,shStringSpecial,shCaseRange
@@ -278,7 +279,16 @@ syn cluster shDerefVarList contains=shDerefOffset,shDerefOp,shDerefVarArray,shDe
278279
syn cluster shEchoList contains=shArithmetic,shBracketExpr,shCommandSub,shCommandSubBQ,shDerefVarArray,shSubshare,shValsub,shDeref,shDerefSimple,shEscape,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shCtrlSeq,shEchoQuote
279280
syn cluster shExprList1 contains=shBracketExpr,shNumber,shOperator,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shExpr,shDblBrace,shDeref,shDerefSimple,shCtrlSeq
280281
syn cluster shExprList2 contains=@shExprList1,@shCaseList,shTest
281-
syn cluster shFunctionList contains=shBracketExpr,@shCommandSubList,shCaseEsac,shColon,shComment,shDo,shEcho,shExpr,shFor,shHereDoc,shIf,shOption,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shOperator,shCtrlSeq
282+
syn cluster shFunctionCmds contains=shFor,shCaseEsac,shIf,shRepeat,shDblBrace,shDblParen
283+
if exists("b:is_ksh88") || exists("b:is_mksh")
284+
" Offer "shFunctionCmds" as is.
285+
elseif exists("b:is_kornshell") || exists("b:is_bash")
286+
syn cluster shFunctionCmds add=shForPP
287+
else
288+
syn cluster shFunctionCmds remove=shDblBrace,shDblParen
289+
endif
290+
syn cluster shFunctionDefList contains=shDoError,shIfError,shFunctionKey,shFunctionOne,shFunctionThree,shFunctionCmdOne
291+
syn cluster shFunctionList contains=shBracketExpr,@shCommandSubList,shCaseEsac,shColon,shComment,shDo,shEcho,shExpr,shFor,shHereDoc,shIf,shOption,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shOperator,shCtrlSeq,@shFunctionDefList
282292
if exists("b:is_kornshell") || exists("b:is_bash")
283293
syn cluster shFunctionList add=shRepeat,shDblBrace,shDblParen,shForPP
284294
syn cluster shDerefList add=shCommandSubList,shEchoDeref
@@ -287,7 +297,7 @@ syn cluster shHereBeginList contains=@shCommandSubList
287297
syn cluster shHereList contains=shBeginHere,shHerePayload
288298
syn cluster shHereListDQ contains=shBeginHere,@shDblQuoteList,shHerePayload
289299
syn cluster shIdList contains=shArithmetic,shCommandSub,shCommandSubBQ,shDerefVarArray,shSubshare,shValsub,shWrapLineOperator,shSetOption,shComment,shDeref,shDerefSimple,shHereString,shNumber,shOperator,shRedir,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shExpr,shCtrlSeq,shStringSpecial,shAtExpr
290-
syn cluster shIfList contains=@shLoopList,shDblBrace,shDblParen,shFunctionKey,shFunctionOne,shFunctionTwo,shNamespaceOne
300+
syn cluster shIfList contains=@shLoopList,shDblBrace,shDblParen,@shFunctionDefList
291301
syn cluster shLoopList contains=@shCaseList,@shErrorList,shCaseEsac,shConditional,shDblBrace,shExpr,shFor,shIf,shOption,shSet,shTest,shTestOpr,shTouch
292302
if exists("b:is_kornshell") || exists("b:is_bash")
293303
syn cluster shLoopList add=shForPP,shDblParen
@@ -647,39 +657,49 @@ endif
647657

648658
" Functions: {{{1
649659
if !exists("b:is_posix")
650-
syn keyword shFunctionKey function skipwhite skipnl nextgroup=shFunctionTwo
660+
syn keyword shFunctionKey function skipwhite skipnl nextgroup=shDoError,shIfError,shFunctionTwo,shFunctionFour,shFunctionCmdTwo
651661
endif
652662

653663
if exists("b:is_bash")
654664
syn keyword shFunctionKey coproc
655-
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*[A-Za-z_0-9:][-a-zA-Z_0-9:]*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
656-
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(do\)\@!\&\<[A-Za-z_0-9:][-a-zA-Z_0-9:]*\>\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
657-
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*[A-Za-z_0-9:][-a-zA-Z_0-9:]*\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
658-
ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\%(do\)\@!\&\<[A-Za-z_0-9:][-a-zA-Z_0-9:]*\>\s*\%(()\)\=\_s*)" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
665+
syn match shFunctionCmdOne "^\s*\zs\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*()\ze\_s*\%(\%(for\|case\|select\|if\|while\|until\)\>\|\[\[\s\|((\)" skipwhite skipnl nextgroup=@shFunctionCmds
666+
syn match shFunctionCmdTwo "\%(\<\k\+\>\|[^()<>|&$;\t ]\+\)\+\ze\s*\%(()\ze\)\=\_s*\%(\<\%(for\|case\|select\|if\|while\|until\)\>\|\[\[\s\|((\)" contained skipwhite skipnl nextgroup=@shFunctionCmds
667+
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\zs\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
668+
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment
669+
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\zs\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*()\_s*((\@!" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
670+
ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*\%(\%(()\)\=\)\@>\_s*((\@!" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment
659671
elseif exists("b:is_ksh88")
660672
" AT&T ksh88
661-
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*[A-Za-z_][A-Za-z_0-9]*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
662-
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(do\)\@!\&\<[A-Za-z_][A-Za-z_0-9]*\>\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
663-
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*[A-Za-z_][A-Za-z_0-9]*\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
664-
ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\%(do\)\@!\&\<[A-Za-z_][A-Za-z_0-9]*\>\_s*(" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
673+
syn match shFunctionCmdOne "^\s*\zs\h\w*\s*()\ze\_s*\%(\%(for\|case\|select\|if\|while\|until\)\>\|\[\[\s\|((\)" skipwhite skipnl nextgroup=@shFunctionCmds
674+
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\zs\h\w*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
675+
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\<\h\w*\>\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment
676+
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\zs\h\w*\s*()\_s*((\@!" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
665677
elseif exists("b:is_mksh")
666678
" MirBSD ksh is the wild west of absurd and abstruse function names...
667-
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*[-A-Za-z_@!+.%,0-9:]*[-A-Za-z_.%,0-9:]\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
668-
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(do\)\@!\&\<[-A-Za-z_@!+.%,0-9:]*[-A-Za-z_.%,0-9:]\>\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
669-
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*[-A-Za-z_@!+.%,0-9:]*[-A-Za-z_.%,0-9:]\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
670-
ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\%(do\)\@!\&\<[-A-Za-z_@!+.%,0-9:]*[-A-Za-z_.%,0-9:]\>\s*\%(()\)\=\_s*(" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
679+
syn match shFunctionCmdOne "^\s*\zs[-A-Za-z_@!+.%,0-9:]*[-A-Za-z_.%,0-9:]\s*()\ze\_s*\%(\%(for\|case\|select\|if\|while\|until\)\>\|\[\[\s\|((\)" skipwhite skipnl nextgroup=@shFunctionCmds
680+
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\zs[-A-Za-z_@!+.%,0-9:]*[-A-Za-z_.%,0-9:]\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
681+
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%([@!+.%,:-]\+\|\<\w\+\)*[-A-Za-z_.%,0-9:]\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment
682+
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\zs[-A-Za-z_@!+.%,0-9:]*[-A-Za-z_.%,0-9:]\s*()\_s*((\@!" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
671683
elseif exists("b:is_kornshell")
672684
" ksh93
673-
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*[A-Za-z_.][A-Za-z_.0-9]*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
674-
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(do\)\@!\&\<[A-Za-z_.][A-Za-z_.0-9]*\>\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
675-
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*[A-Za-z_.][A-Za-z_.0-9]*\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
676-
ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\%(do\)\@!\&\<[A-Za-z_.][A-Za-z_.0-9]*\>\_s*(" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
677-
ShFoldFunctions syn region shNamespaceOne matchgroup=shFunction start="\%(do\)\@!\&\<\h\w*\>\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
685+
syn match shFunctionCmdOne "^\s*\zs[A-Za-z_.][A-Za-z_.0-9]*\s*()\ze\_s*\%(\%(for\|case\|select\|if\|while\|until\)\>\|\[\[\s\|((\)" skipwhite skipnl nextgroup=@shFunctionCmds
686+
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\zs[A-Za-z_.][A-Za-z_.0-9]*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
687+
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(\.\|\<\h\+\)[A-Za-z_.0-9]*\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment
688+
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\zs[A-Za-z_.][A-Za-z_.0-9]*\s*()\_s*((\@!" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
689+
ShFoldFunctions syn region shNamespaceOne matchgroup=shFunction start="\<\h\w*\>\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment
678690
else
679-
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\h\w*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
680-
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(do\)\@!\&\<\h\w*\>\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
681-
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\h\w*\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
682-
ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\%(do\)\@!\&\<\h\w*\>\s*\%(()\)\=\_s*(" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment
691+
syn match shFunctionCmdOne "^\s*\zs\h\w*\s*()\ze\_s*\%(for\|case\|if\|while\|until\)\>" skipwhite skipnl nextgroup=@shFunctionCmds
692+
syn match shFunctionCmdTwo "\<\h\w*\s*()\ze\_s*\%(for\|case\|if\|while\|until\)\>" contained skipwhite skipnl nextgroup=@shFunctionCmds
693+
ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\zs\h\w*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
694+
ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\<\h\w*\>\s*()\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment
695+
ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\zs\h\w*\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment
696+
ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\<\h\w*\>\s*()\_s*(" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment
697+
endif
698+
699+
if !exists("g:sh_no_error")
700+
syn match shDoError "\<do\%(ne\)\=\s*()"
701+
syn match shIfError "\<then\s*()"
702+
syn match shIfError "\<else\s*()"
683703
endif
684704

685705
" Parameter Dereferencing: {{{1
@@ -883,6 +903,8 @@ if !exists("skip_sh_syntax_inits")
883903
hi def link shEchoQuote shString
884904
hi def link shForPP shLoop
885905
hi def link shFunction Function
906+
hi def link shFunctionCmdOne shFunction
907+
hi def link shFunctionCmdTwo shFunction
886908
hi def link shEmbeddedEcho shString
887909
hi def link shEscape shCommandSub
888910
hi def link shExDoubleQuote shDoubleQuote

runtime/syntax/testdir/dumps/sh_functions_bash_00.dump

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runtime/syntax/testdir/dumps/sh_functions_bash_01.dump

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)