I try to write a vim syntax file for folding if-else statements with the syntax:

if <condition> then <block to fold> [else <block to fold>] end if

Current approach

syn region Then start="then" end="end if" end="\(\s*else\)\@=" transparent fold
syn region Else start="else" end="end if"                      transparent fold

Test file:

if a>b then
  echo "ab"
  echo "ba"
end if
if a>c then
  if c>d then
    echo "cd"
  else  ' error: stops the outer if too
    echo "dc"
  end if
end if


  • The else-block is optional.
  • Keywords start on a new line.
  • Blocks can contain if-else statements again recursively.
  • Blocks optionally use indentation (leading space to be considered).
  • Challenge: the "else" stops the previous block and starts a new one.
  • Problem: in the test file above the commented else statement stops the inner and the outer then block.

So the question: how can it be done?

Outlook: once working, it shall be extended by optional elseif blocks:

if ... then ... [elseif <condition> then <block to fold>]* [else...] end if

This answer is more of a hack than a real solution to your question.

From the text you show, it seems you may rely on indentation for your folding. If so, you can use set foldmethod=indent.

A little function I use in such case:

function! FoldAll()                                                                                                     
  set foldcolumn=1                                                                                                      
  set foldmethod=indent                                                                                                 
  set foldmethod=manual                                                                                                 
command! FoldAll call FoldAll()
| improve this answer | |
  • Thanks for the hack, but not what I'm searching for. I just replaced "typically" by "optionally" in the note regarding indentation. If present, leading space might be considered for not avoiding proper folding, but folding must be based on keywords. – Philipp Nov 12 '19 at 13:14

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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.