12

I have read the help page about fold-expr (:h fold-expr) but it didn't explain what's the syntax used in the expression.

There were there four examples:

  1. :set foldexpr=getline(v:lnum)[0]==\"\\t\"
  2. :set foldexpr=MyFoldLevel(v:lnum)
  3. :set foldexpr=getline(v:lnum)=~'^\\s*$'&&getline(v:lnum+1)=~'\\S'?'<1':1
  4. :set foldexpr=getline(v:lnum-1)=~'^\\s*$'&&getline(v:lnum)=~'\\S'?'>1':1

I understood that v:lnum is the line that needs an indention level, and that expression two is a call to a function.

what about expressions 1,3 and 4? Can someone please explain them to me?

elyashiv
  • 2,429
  • 2
  • 18
  • 23
  • My understanding is that the expression should return a number, and that number will be used to determine at which level the given line will be folded. 0 is not folded, 1 is the outermost fold, 2 is a fold nested inside a level 1 fold, and so on – tommcdo Feb 24 '15 at 18:55

3 Answers3

17

From :help 'foldexpr':

It is evaluated for each line to obtain its fold level

The foldexpr is evaluated, so it needs to be VimL code; there is no mention of "special syntax" or the like. The result of this evaluation controls what Vim considers a fold or not.

Possible values are

  0                     the line is not in a fold
  1, 2, ..              the line is in a fold with this level
  "<1", "<2", ..        a fold with this level ends at this line
  ">1", ">2", ..        a fold with this level starts at this line

This is not the full list; just the ones used in the examples in your question. See :help fold-expr for the full list.


First

The first one is fairly simple once we add some spaces and remove the backslashes we need to get this working in a :set command:

getline(v:lnum)[0] == "\t"
  1. getline(v:lnum) gets the entire line.
  2. [0] gets the first character of that
  3. and == "\t" checks if that is a tab character.
  4. VimL doesn't have "true" or "false", it just uses "0" for false, and "1" for true. So if this line starts with a tab, it's folded at foldlevel 1. If it doesn't, it's not in a fold (0).

If you would expand this to count the number of tabs you would have indentation-based folding (at least, when expandtab isn't enabled).


Third

The third one is really not that much more complicated as the first one; as with the first example, we first want to make it more readable:

getline(v:lnum) =~ '^\s*$' && getline(v:lnum + 1) =~ '\S' ? '<1' : 1
  1. We get the entire line with getline(v:lnum)
  2. We match that as a regexp with =~ to '^\s*$'; ^ anchors to the start, \s means any whitespace character, * means repeat the previous zero or more times, and $ anchors to the end. So this regexp matches (returns true) for blank lines or lines with only whitespace.
  3. getline(v:lnum + 1) gets the next line.
  4. We match this to \S, which matches any non-whitespace character anywhere on this line.
  5. If these 2 conditions are true, we evaluate to <1, otherwise, 1. This is done with the "ternary" if known from C and some other languages: condition ? return_if_true : return_if_false.
  6. <1 means a fold ends on this line, and 1 means foldlevel one.

So, If we end a fold if the line is blank and the next line is not blank. Otherwise, we're at foldlevel 1. Or, as :h fold-expr says it:

This will make a fold out of paragraphs separated by blank lines


Fourth

The fourth behaves the same as the third one, but does it in a slightly different way. Expanded, it's:

getline(v:lnum - 1) =~ '^\s*$' && getline(v:lnum) =~ '\S' ? '>1' : 1

If the previous line is a blank line, and the current line is a non-blank line, we start a fold on this line (>1), if not, we're setting the foldlevel to 1.


Afterword

So the logic on all 3 examples is really quite simple. Most of the difficulty comes in the lack of spaces and some of the backslash usage.

I suspect that calling a function has some overhead, and since this is evaluated for every line you want to have a decent performance. I don't know how great the difference is on modern machines though, and would recommend that you use a function (as in the 2nd example) unless you have performance problems. Remember The Knuth: "premature optimisation is the root of all evil".

This question is also on StackOverflow, which has a slightly different answer. But mine is of course better ;-)

Martin Tournoij
  • 62,054
  • 25
  • 192
  • 271
3

You're essentially asking what the other elements in these expressions are, which can be found by calling :help for any of them in turn:

v:lnum: the line being evaluated
getline(): get the line of text for a line number
==: equals
=~: matches
<cond>?<if-true>:<if-false>: evaluates to <if-true> if <cond> is true, else to <if-false>

I've broken down these expressions by their parts below to help illustrate their meaning:

1 Will return 1 for all lines starting with a tab and 0 for other lines:

v:lnum                      the current line number
getline(v:lnum)             the text of the current line
getline(v:lnum)[0]          the first character of the current line
getline(v:lnum)[0]==\"\\t\" the first char of the current line is 'tab'

3 Ends folds on blank lines after paragraphs:

 getline(v:lnum)=~'^\\s*$'                                       current line is only spaces
                              getline(v:lnum+1)=~'\\S'           next line has non-space
(getline(v:lnum)=~'^\\s*$' && getline(v:lnum+1)=~'\\S') ? '<1'   if both of these: <1
                                                              :1 otherwise: 1
(getline(v:lnum)=~'^\\s*$' && getline(v:lnum+1)=~'\\S') ? '<1':1

4 Starts folds on blank lines beginning paragraphs:

(getline(v:lnum-1)=~'^\\s*$'                                     previous line only spaces
                                getline(v:lnum)=~'\\S'           this line has non-space
(getline(v:lnum-1)=~'^\\s*$' && getline(v:lnum)=~'\\S') ? '>1'   if both of these: >1
                                                              :1 otherwise: 1
(getline(v:lnum-1)=~'^\\s*$' && getline(v:lnum)=~'\\S') ? '>1':1 

The meanings of <1, >1, etc. are right below these expressions in :help fold-expr

Martin Tournoij
  • 62,054
  • 25
  • 192
  • 271
Matt Boehm
  • 2,938
  • 16
  • 14
1

Accidentally posted my answer as a comment and submitted it early. Darn mobile.

My understanding is that the expression should return a number, and that number will be used to determine at which level the given line will be folded. 0 is not folded, 1 is the outermost fold, 2 is a fold nested inside a level 1 fold, and so on.

The expressions in the examples look like they'd evaluate to true or false. VimScript doesn't have a proper Boolean type, so this will really be 1 or 0, which are valid fold levels.

You could write your own expression using VimScript that is as simple as returning 1 or 0, or more complicated, allowing for nested folds.

tommcdo
  • 7,710
  • 1
  • 32
  • 36
  • Using just numbers will work, but it's worth noting that foldexpr's can evaluate to other special values, like =, a1, s1, >1, <1, -1 – Matt Boehm Feb 24 '15 at 19:03