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: Togglefoldenable(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.