Mastering Neovim Folds

Closely related to the previous chapter's topic of navigating vertically in a buffer, Neovim's fold feature takes a different approach to simplifying vertical navigation, while also providing some additional benefits. Rather than define how the cursor navigates over buffer content, folds temporarily collapse groups of lines in buffer down to a single line so that they can be navigated over, yanked, deleted, etc. This not only simplifies navigation, many users leverage folds to create a more distraction-free editing environment by hiding content that is not their present focus of editing.

As a conceptual example of how folds can simplify navigation, one might configure Neovim to open Markdown documents so that only the section headings are visible, while the section bodies are all folded By doing so, the user can quickly navigate to a specific section with just a few keystrokes, "unfold" the content of that section, then begin editing.

Background

Before we get into how to work with folds, let's quickly get a high-level review of how folds work. When Neovim opens a buffer it applies the fold-method that is defined in your configuration. Fold-methods define how a particular document should be folded, and there are a variety of fold-methods available, but we will get into that a bit later.

Each fold-method defines one or more fold-levels, which are a numerical indication of "how much" folding should occur. Fold-levels start from 0, meaning fully-folded, then increment as portions of the content are unfolded, up to the number of fold-levels defined for the current fold-method.

Going back to our conceptual Markdown example, when the document was opened only the top-level H1 headings were visible and the content of each section was folded. This represents the "deepest" fold state, since all but the top-level content is contained within folds. Now, suppose we navigate to a section then unfold it one level by pressing zr. At this point, the direct content of the H1 sections including any H2 headers might appear, while pressing it again might display the direct content of the H2 sections, plus any H3 headers.

Opening and Closing Folds

Fold-related commands start with z, which is appropriate since it looks a bit like a folded piece of paper. This z is followed by a second letter that defines the fold action to be taken. In almost all cases the second letter can be specified in two ways, lower-case and upper-case, where specifying the lower-case option asserts the specified action a single time, while the upper-case option asserts the specified action recursively.

We think of Neovim's fold commands as being grouped into two scopes, where "global" fold commands apply to the entire buffer, while "local" fold commands apply to the section that currently contains the cursor. Let's look at both separately.

Global Folding and Unfolding

Global folds apply to to an entire buffer, which can be very useful when opening a new document to "get a feel" for the structure, locate certain sections, etc. The global fold commands are:

CommandAction
zrDecrease folding by one level (reduce folding)
zmIncrease folding by one level (more folding)
zROpen (Remove) all folds
zMClose all folds (More folding)

To get a quick feel for these commands, open a file then hit zR to make sure all folds are open, then hit zM to quickly close all folds. Now, hit zr a few times to decrease the level of folding one step at a time. This may or may not do much, depending on the file type and how you currently have folds configured, but this should give most readers a quick feel for global folding.

Local Folding and Unfolding

Next, lets take a look at the fold commands that operate on only the portion of the document that contains the cursor.

CommandAction
zoopen fold
zOopen folds recursively
zcclose a fold
zCclose folds recursively
zaopen a closed fold, close an open fold
zAopen a closed fold or close an open fold recursively
zvopen enough folds to view the cursor line

To get a quick feel for how these commands differ from the global commands discussed previously, go back to the file you opened previously, then hit zM to close all of the folds in the document, as you did before. Now move the cursor over to some folded content, hit zo to open only the fold that contains the cursor, then zc to close it again. Toggling fold state is quite common, so there this can also be achieved by hitting za once to open the current fold, then hitting it again to toggle it closed again.

Fold-Methods

Set your fold-method in your Lua configuration file like this:

vim.opt.foldmethod = "fold-method"

where "fold-method" is the name of the method that you would like to use.

Neovim supports each of the legacy Vim fold-methods:

manual
Folds are manually opened and closed by executing the previously-discussed commands
indent
Folds are automatically created based upon indentation level
syntax
Folds are automatically created based on the current syntax highlighting
marker
Folds are automatically created based upon markers in the text
diff
Folds are automatically created for changed/unchanged text, when operating in a diff window.
expr
Folds are automatically created based upon the specified expression

We find the manual method to be useful in many cases, but otherwise we find that Neovim's built-in Treesitter support provides much more accurate, language-specific folding that are superior to the legacy fold methods.

Tree-sitter based folds leverage the expr fold-method, and use nvim-treesitter's foldexpr function to define the folds for each supported language. Setup instructions are included at the nvim-treesitter website, but in most cases is simple:

vim.wo.foldmethod = 'expr'
vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'

Before we leave the topic of folds, we should also mention that Neovim provides convenient function that allow users to quickly navigate buffers quickly by jumping between the folds similar to how we might do for marks and text objects:

CommandAction
]zmove to end of open fold
[zmove to start of open fold
zjmove to the start of the next fold
zkmove to the end of the previous fold