Vim Folding
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.
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 - in visual mode (for example, hit
V
, then hitj
multiple times to select how many lines you want, thenzf
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 Space:
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)syntax
(folding is defined in the syntax files)marker
(looks for markers in the text; everything within commentsfoldable block {{{
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, passingv: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?
- As already mentioned the user manual
:help usr_28
is a good place to start