From cedafc86df236c7ad31ffd71394c3b252d9137ab Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 25 Mar 2026 07:12:08 +0800 Subject: [PATCH] vim-patch:9c0d057: runtime(sh): Improve the matching of function definitions - Drop the remaining undefined "shFunctionStart" references (gone in v7.2b.000, c236c16d0). - 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: vim/vim#19619 related: vim/vim#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 https://github.com/vim/vim/commit/9c0d057e3d5a456b2f2879860e5e57bcdf130017 Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com> Co-authored-by: Johnothan King Co-authored-by: Doug Kearns --- runtime/syntax/sh.vim | 72 ++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim index 6b13ddacce..efe0b22c2a 100644 --- a/runtime/syntax/sh.vim +++ b/runtime/syntax/sh.vim @@ -22,6 +22,7 @@ " 2026 Jan 15 highlight command switches that contain a digit " 2026 Feb 11 improve support for KornShell function names and variables " 2026 Feb 15 improve comment handling #19414 +" 2026 Mar 23 improve matching of function definitions #19638 " }}} " Version: 208 " 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 if exists("b:is_kornshell") || exists("b:is_bash") syn cluster ErrorList add=shDTestError endif -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 +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 syn cluster shArithList contains=@shArithParenList,shParenError syn cluster shBracketExprList contains=shCharClassOther,shCharClass,shCollSymb,shEqClass 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 syn cluster shEchoList contains=shArithmetic,shBracketExpr,shCommandSub,shCommandSubBQ,shDerefVarArray,shSubshare,shValsub,shDeref,shDerefSimple,shEscape,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shCtrlSeq,shEchoQuote syn cluster shExprList1 contains=shBracketExpr,shNumber,shOperator,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shExpr,shDblBrace,shDeref,shDerefSimple,shCtrlSeq syn cluster shExprList2 contains=@shExprList1,@shCaseList,shTest -syn cluster shFunctionList contains=shBracketExpr,@shCommandSubList,shCaseEsac,shColon,shComment,shDo,shEcho,shExpr,shFor,shHereDoc,shIf,shOption,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shOperator,shCtrlSeq +syn cluster shFunctionCmds contains=shFor,shCaseEsac,shIf,shRepeat,shDblBrace,shDblParen +if exists("b:is_ksh88") || exists("b:is_mksh") + " Offer "shFunctionCmds" as is. +elseif exists("b:is_kornshell") || exists("b:is_bash") + syn cluster shFunctionCmds add=shForPP +else + syn cluster shFunctionCmds remove=shDblBrace,shDblParen +endif +syn cluster shFunctionDefList contains=shDoError,shIfError,shFunctionKey,shFunctionOne,shFunctionThree,shFunctionCmdOne +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 if exists("b:is_kornshell") || exists("b:is_bash") syn cluster shFunctionList add=shRepeat,shDblBrace,shDblParen,shForPP syn cluster shDerefList add=shCommandSubList,shEchoDeref @@ -287,7 +297,7 @@ syn cluster shHereBeginList contains=@shCommandSubList syn cluster shHereList contains=shBeginHere,shHerePayload syn cluster shHereListDQ contains=shBeginHere,@shDblQuoteList,shHerePayload 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 -syn cluster shIfList contains=@shLoopList,shDblBrace,shDblParen,shFunctionKey,shFunctionOne,shFunctionTwo,shNamespaceOne +syn cluster shIfList contains=@shLoopList,shDblBrace,shDblParen,@shFunctionDefList syn cluster shLoopList contains=@shCaseList,@shErrorList,shCaseEsac,shConditional,shDblBrace,shExpr,shFor,shIf,shOption,shSet,shTest,shTestOpr,shTouch if exists("b:is_kornshell") || exists("b:is_bash") syn cluster shLoopList add=shForPP,shDblParen @@ -647,39 +657,49 @@ endif " Functions: {{{1 if !exists("b:is_posix") - syn keyword shFunctionKey function skipwhite skipnl nextgroup=shFunctionTwo + syn keyword shFunctionKey function skipwhite skipnl nextgroup=shDoError,shIfError,shFunctionTwo,shFunctionFour,shFunctionCmdTwo endif if exists("b:is_bash") syn keyword shFunctionKey coproc - 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 - 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 - 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 - 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 + syn match shFunctionCmdOne "^\s*\zs\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*()\ze\_s*\%(\%(for\|case\|select\|if\|while\|until\)\>\|\[\[\s\|((\)" skipwhite skipnl nextgroup=@shFunctionCmds + syn match shFunctionCmdTwo "\%(\<\k\+\>\|[^()<>|&$;\t ]\+\)\+\ze\s*\%(()\ze\)\=\_s*\%(\<\%(for\|case\|select\|if\|while\|until\)\>\|\[\[\s\|((\)" contained skipwhite skipnl nextgroup=@shFunctionCmds + ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\zs\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment + ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment + ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\zs\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*()\_s*((\@!" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment + ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\%(\<\k\+\|[^()<>|&$;\t ]\+\)\+\s*\%(\%(()\)\=\)\@>\_s*((\@!" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment elseif exists("b:is_ksh88") " AT&T ksh88 - 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 - 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 - 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 - 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 + syn match shFunctionCmdOne "^\s*\zs\h\w*\s*()\ze\_s*\%(\%(for\|case\|select\|if\|while\|until\)\>\|\[\[\s\|((\)" skipwhite skipnl nextgroup=@shFunctionCmds + ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\zs\h\w*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment + ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\<\h\w*\>\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment + ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\zs\h\w*\s*()\_s*((\@!" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment elseif exists("b:is_mksh") " MirBSD ksh is the wild west of absurd and abstruse function names... - 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 - 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 - 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 - 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 + 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 + 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 + ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%([@!+.%,:-]\+\|\<\w\+\)*[-A-Za-z_.%,0-9:]\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment + 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 elseif exists("b:is_kornshell") " ksh93 - 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 - 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 - 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 - 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 - ShFoldFunctions syn region shNamespaceOne matchgroup=shFunction start="\%(do\)\@!\&\<\h\w*\>\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment + 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 + 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 + ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(\.\|\<\h\+\)[A-Za-z_.0-9]*\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment + 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 + ShFoldFunctions syn region shNamespaceOne matchgroup=shFunction start="\<\h\w*\>\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment else - ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\h\w*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment - ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(do\)\@!\&\<\h\w*\>\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment - ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\h\w*\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment - ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\%(do\)\@!\&\<\h\w*\>\s*\%(()\)\=\_s*(" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment + syn match shFunctionCmdOne "^\s*\zs\h\w*\s*()\ze\_s*\%(for\|case\|if\|while\|until\)\>" skipwhite skipnl nextgroup=@shFunctionCmds + syn match shFunctionCmdTwo "\<\h\w*\s*()\ze\_s*\%(for\|case\|if\|while\|until\)\>" contained skipwhite skipnl nextgroup=@shFunctionCmds + ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*\zs\h\w*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment + ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\<\h\w*\>\s*()\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment + ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*\zs\h\w*\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shQuickComment + ShFoldFunctions syn region shFunctionFour matchgroup=shFunction start="\<\h\w*\>\s*()\_s*(" end=")" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shQuickComment +endif + +if !exists("g:sh_no_error") + syn match shDoError "\