I got that to work by using the following set of rules:
syn region IfBlock start="then" end="end if" keepend extend transparent contains=Then,Else
syn region Then start="then" end="^\s*else"me=s-1 end="end if" extend transparent contained fold
syn region Else start="else" end="end if" extend transparent contained fold
But it was very tricky to find the right combination of settings that would work and some of the attempts that looked pretty similar to me (such as merging the Then and Else regions into one) didn't work as expected. So I'm not sure I'm fully able to explain why and how this works.
The idea, though, is that you have an external block that matches the full block all the way to the
end if and that block nests since it has both
contains= to enable matching the Then and Else regions inside it, and those regions match all the way to the
end if as well. They need to use
extend apparently (though I'm not fully sure why.)
In the Then block, I'm also ending it when finding an
else, but I'm matching it from the start of the line, with the whitespace, and using
me=s-1, so it actually matches the end of the line preceding the line with the
else, which makes the Then fold end on that line. Whenever an
end if is matched, it's included in the fold of the Then or Else that matched it.
It turns out this set of rules also works for
elseif <condition> then ... syntax, since it will match the Then block in that case which will properly create a fold there. (It seems this depends on
transparent, and it means it actually creates a Then inside an Else here, with the Else matching the
While testing, keeping it synchronized also seemed to be an issue, so you might need
syn sync fromstart or otherwise pass
syn sync a large
minlines= argument to ensure it's synchronizing lines properly.
If you're working on a similarly complex syntax definition, or if you want to tweak this one, I recommend that you load tpope's scriptease plug-in, which includes a
zS command to inspect the syntax groups defined at any specific location of your buffer. That's very handy to be able to "see" what Vim is matching, especially when transparent groups are involved.
You specifically mentioned that indentation was optional and in a comment to another answer you said that you didn't want to rely on that specifically... But using indentation here makes this matching much easier and also much more reliable, especially while you're editing the text and have blocks that might not be properly closed just yet.
When approaching this question, I first looked at the syntax files shipped with Vim runtimes and looked for one that would have
fold on the same rule, and I found this one, with the following rule:
syn region baanCondFold matchgroup=baanConditional start="^\z(\s*\)\(if\>\|else\>\)" end="^\z1endif\>" end="^\z1else\>"me=s-1 transparent fold keepend extend
As you can see, it uses
^ anchors to match the patterns at the beginning of lines, then
\z(\s*\) to match arbitrary whitespace in the
start pattern but save it in a match group and then later
\z1 to match the exact same amount of whitespace in the
That way, Vim will have an easy time matching nested blocks, since indentation will ensure an end pattern will only end the block at the same indentation level, so it's much easier to get everything right that way.
I'd probably recommend going that route of matching indentation for the fold syntax rules, possibly pairing that with including an "indent" plug-in that would define a function to use in
indentexpr that would help keep the indentation correct as you write code, or as you reformat it, for example using the