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?

1
  • 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, 2015 at 18:55

3 Answers 3

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 ;-)

0
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

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.

1
  • 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, 2015 at 19:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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