Folding is a way to hide a chunk of text, and thus unclutter the view. That comes handy when you need to focus on some other part of code, or if you want to get a quick overview of the whole file.

Vim folding in action

Note that the folded text is not modified anyhow, it's simply hidden from the sight, displayed as a single line.

You can copy, paste, and delete it as if it was a single line, while the operation is applied to the whole chunk of text. The navigation in that file with j / k also becomes faster.

Folding is multi-level. You can fold an already folded piece of text if it's a part of an even bigger fold. Think about blocks of code with multi-level nesting (a function inside a function).

How does Vim know which part is foldable and which is not?

That governs by the foldmethod option. Let's start with the simplest folding method - manual.

Manual Folding

With manual folding, Vim doesn't attempt to automatically figure out the foldable areas of text. Instead, it delegates this job to a user - you.

Set the fold method to manual :

set foldmethod=manual

Then you can use zf operator to mark a piece text as foldable. zf can be used

with text objects (i.e. zfap marks the entire paragraph) as well as

marks the entire paragraph) as well as in visual mode (for example, hit V , then hit j multiple times to select how many lines you want, then zf to make them foldable).

The zf operator not only creates a fold but also folds the text. To unfold, it press zo , and then zc closes the fold again. Mnemonics of this is also quite smart ( :help usr_28 ). z looks like a folded piece of paper, and then o stands for "open", and c for "close".

There is also zM and zR to close / open all the folds in the file, no matter how deep. That's super useful with big files when you need to get an overview of the whole file.

And of course, let's not forget about za , which works like a toggle. When the cursor is on an open fold, it will close it and the other way around. I'm using it so often that I have it mapped to :

nnoremap <space> za

Fold methods

Manually defining folds is not too fun, especially while dealing with a text which is textually or semantically structured.

Think about any programming language with C-like syntax. There are blocks of code defined with a help curly braces { / } . Those blocks usually have an inheriting (nesting) lexical scope, so it's only natural to declare every block within the curly braces as foldable.

In other languages like python, those blocks are loosely defined by an indent.

So we have several more fold methods to automatically define folds:

indent (bigger the indent is - larger the fold level; works quite well for many programming languages)

(bigger the indent is - larger the fold level; works quite well for many programming languages) syntax (folding is defined in the syntax files)

(folding is defined in the syntax files) marker (looks for markers in the text; everything within comments foldable block {{{ and }}} is a fold)

(looks for markers in the text; everything within comments and is a fold) expr (fold level is calculated for each line by providing a special function)

The indent option is the simplest thing that works well for many languages, and this is my default choice.

set foldmethod=indent " fold based on indent

Then we can override this for a particular file type with autocmd :

autocmd FileType vim setlocal foldmethod=marker

Fold expression

While I usually happily get away with using the "indent" fold method. There are sometimes exceptions.

JavaScript love imports. They usually go at the beginning of a file and may occupy quite a lot of space. It would be good to be able to collapse/hide that area from the view. The problem is that they go without any indenting, that's why foldmethod=indent doesn't help here.

How can we keep indent-based folding while also cover the case with imports?

We need to implement a custom fold function and use it as fold expression.

autocmd FileType javascript setlocal foldmethod=expr autocmd FileType javascript setlocal foldexpr=JSFolds()

Now let's see how that JSFolds function can be implemented.

That function is called for every line in the file, and for each line, it should return a positive number (indent level) or a special symbol (more about it later).

function! JSFolds() let thisline = getline(v:lnum) if thisline =~? '\v^\s*$' return '-1' endif if thisline =~ '^import.*$' return 1 else return indent(v:lnum) / &shiftwidth endif endfunction

Let's see what's happening there.

We get the current line as a string with getline operator, passing v:lnum as argument (the current line number - remember that function is being called for every line in the buffer).

If the line is empty ( \v^\s*$ - the regular expression for any number of spaces), we return magical string '-1'. That means "use the fold level of a line before or after this line, whichever is the lowest." (see :help fold-expr for other special strings).

If the line starts with "import" (regex ^import.*$ ) than always return 1.

Otherwise, behave as the "indent" fold method — we return the current indent level of that line.

As you might imagine, we've only scratch the surface here. A folding expression can be quite complex.

If you'd like to dive deeper, here's some inspiration:

Random folding tips

In the conclusion of this article, I'd like to share some random tips which you may or find useful.

On the very first gif in this article, you can see there's a special fold column on the left that indicates open and closed folds. I didn't know it exists until I started to write this article. You can turn it on with set foldcolumn=2

When you fold a piece of text, it's displayed as a single line. There's a matchgroup associated with it, so you can set how it should look like. While usually it's included with the colorscheme definitions, you might want to override the colors like this highlight Folded guifg=PeachPuff4 . Same goes for the fold column: highlight FoldColumn guibg=darkgrey guifg=white

There's a way to save your folds in a file. The command :mkview saves it along with other view/UI details, and the :loadview loads it back. And you can automate that.

Where to go next?