Vim’s code folding isn’t just about hiding code; it’s a dynamic lens that lets you control information density, revealing only the parts of your codebase that matter right now.

Let’s see it in action. Imagine you have a Python file like this:

# --- Module Imports ---
import os
import sys
import logging

# --- Configuration ---
class AppConfig:
    def __init__(self):
        self.debug = False
        self.log_level = logging.INFO
        self.database_url = "postgresql://user:pass@host:port/db"

    def load_settings(self, config_file="settings.json"):
        # ... logic to load from file ...
        pass

# --- Core Application Logic ---
class Application:
    def __init__(self):
        self.config = AppConfig()
        self.logger = logging.getLogger(__name__)
        self.data = []

    def initialize(self):
        self.logger.info("Initializing application...")
        # Load configuration
        self.config.load_settings()
        # Connect to database (placeholder)
        self.logger.info("Database connection established.")

    def process_data(self, raw_data):
        self.logger.debug("Processing raw data...")
        for item in raw_data:
            processed_item = self.transform(item)
            self.data.append(processed_item)
        self.logger.info(f"Processed {len(raw_data)} items.")

    def transform(self, item):
        # Complex transformation logic
        return item.upper() # Simplified for example

    def run(self):
        self.initialize()
        sample_data = ["apple", "banana", "cherry"]
        self.process_data(sample_data)
        self.logger.info("Application finished.")

# --- Main Execution Block ---
if __name__ == "__main__":
    app = Application()
    app.run()

By default, Vim sees this as a flat file. But with folding enabled, you can collapse entire blocks. Press zf followed by a motion (like } to fold to the end of the current block) or V to select lines and then zf.

# --- Module Imports ---
import os
import sys
import logging

# --- Configuration ---
class AppConfig:
    def __init__(self):
        self.debug = False
        self.log_level = logging.INFO
        self.database_url = "postgresql://user:pass@host:port/db"

    def load_settings(self, config_file="settings.json"):
        # ... logic to load from file ...
        pass

# --- Core Application Logic ---
class Application:
    def __init__(self):
        self.config = AppConfig()
        self.logger = logging.getLogger(__name__)
        self.data = []

    def initialize(self):
        self.logger.info("Initializing application...")
        # Load configuration
        self.config.load_settings()
        # Connect to database (placeholder)
        self.logger.info("Database connection established.")

    def process_data(self, raw_data):
        self.logger.debug("Processing raw data...")
        for item in raw_data:
            processed_item = self.transform(item)
            self.data.append(processed_item)
        self.logger.info(f"Processed {len(raw_data)} items.")

    def transform(self, item):
        # Complex transformation logic
        return item.upper() # Simplified for example

    def run(self):
        self.initialize()
        sample_data = ["apple", "banana", "cherry"]
        self.process_data(sample_data)
        self.logger.info("Application finished.")

# --- Main Execution Block ---
if __name__ == "__main__":
    app = Application()
    app.run()

See how the classes and the if __name__ == "__main__": block are now just single lines? This is a manual fold. Vim can also fold automatically based on syntax.

To enable automatic folding, you first need to set a fold method. The most common are:

  • manual: Folds you create yourself (zf, zo, zc, etc.).

  • indent: Folds based on indentation levels. Great for Python, YAML, etc.

  • syntax: Folds based on Vim’s understanding of the file’s syntax (e.g., recognizing blocks, functions, classes in C, Java, Python).

  • marker: Folds defined by special marker lines (e.g., {{{ and }}}).

To set the fold method for the current buffer, use :set foldmethod=syntax (or indent, manual, marker). For indent on Python, it’s often the most intuitive.

Once a fold method is set, you can navigate and manipulate folds:

  • zc: Close (fold) the current fold.
  • zo: Open (unfold) the current fold.
  • za: Toggle (open or close) the current fold.
  • zC: Close all folds under the cursor recursively.
  • zO: Open all folds under the cursor recursively.
  • zA: Toggle all folds under the cursor recursively.
  • zr: Reduce (open) all folds by one level.
  • zm: More (close) all folds by one level.
  • zR: Reduce (open) all folds completely.
  • zM: More (close) all folds completely.
  • zi: Toggle foldenable (enable/disable all folding).

For the Python example, setting :set foldmethod=indent would produce something like this:

# --- Module Imports ---
import os
import sys
import logging

# --- Configuration ---
class AppConfig: ...

# --- Core Application Logic ---
class Application: ...

# --- Main Execution Block ---
if __name__ == "__main__": ...

This allows you to quickly jump between major sections of your code. The ... indicates a folded section.

The foldlevel option, used with foldmethod=manual or foldmethod=marker, controls how many levels of folds are open by default. :set foldlevel=2 would open the first two levels of manual or marker folds. :set foldlevelstart=99 is a common trick to open everything by default.

When using foldmethod=syntax, Vim analyzes the file type and applies its built-in folding rules. For example, in C, it might fold based on curly braces {}. In Python, it uses indentation. The foldexpr option allows for custom folding logic, but it’s rarely needed for common languages due to the effectiveness of syntax and indent.

A subtlety often overlooked is how Vim calculates fold levels. When using foldmethod=indent, a new fold level is created for each increase in indentation. However, certain lines, like comments that start with # or // (depending on the language) or blank lines, can sometimes interrupt or alter the perceived fold structure if they are at the same indentation level as code that would otherwise define a new fold. Vim’s foldlevel is based on nesting depth, not just the number of lines. So, a deeply nested if statement inside a function inside a class will contribute to a higher foldlevel than a simple if statement at the top of the file.

To make folding persistent across Vim sessions, add :set foldmethod=syntax (or your preferred method) to your ~/.vimrc file.

The next step is often customizing what gets displayed when a fold is collapsed, using fillchar and foldtext.

Want structured learning?

Take the full Vim course →