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 keepend
and extend
.
It uses 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 elseif
.)
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 else
and 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 end
groups.
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 =
operator.