2025, Oct 22 00:00
Fixing Neovim Treesitter Folding in Python: Auto-apply folds after parser initialization with BufReadPost
Python files open unfolded with Treesitter folding in Neovim? Learn why the parser isn't ready on load and how to auto-apply folds using BufReadPost, then zx.
Python buffers open, but folds aren’t applied until you manually hit zx. The same setup works with indent-based folding or other filetypes like Lua. If you’re using Treesitter-backed expression folding in Neovim and seeing this only in Python files, here’s what’s happening and how to fix it.
Minimal setup that shows the issue
The following configuration enables expression folding via Treesitter. With this in place, Python files load fully unfolded unless you trigger folding manually.
local o = vim.opt
o.foldcolumn = "1"
o.foldlevel = 1
o.foldtext = ""
o.foldmethod = "expr"
o.foldexpr = "nvim_treesitter#foldexpr()"
What’s going on
The folding expression nvim_treesitter#foldexpr() depends on a Treesitter syntax tree. When a Python buffer first opens, the Treesitter parser and its tree may not be ready yet. Because the folding expression runs before the tree exists, there’s nothing to fold and the buffer appears unfolded. Pressing zx later works because the tree has been created by then.
This behavior is specific to the timing of Treesitter initialization. The parser is installed and healthy, there are no interfering Python-specific autocommands, and there are no extra Python plugins in play. The difference you see between Python and, say, Lua or indent-based folding stems from when the syntax tree is available.
Fix: apply folds after Treesitter has initialized
Defer the fold application until Neovim’s event loop is idle, after the buffer has been read and plugins have initialized. Scheduling the fold refresh ensures the syntax tree exists when the fold expression runs.
-- group ensures a single, replaceable set of rules
local grp = vim.api.nvim_create_augroup("ts_fold_apply_py", { clear = true })
vim.api.nvim_create_autocmd("BufReadPost", {
  pattern = "*.py",
  desc = "Reapply folds for Python after Treesitter is ready (scheduled)",
  callback = function()
    vim.schedule(function()
      vim.cmd("normal! zx")
    end)
  end,
  group = grp,
})
Why this matters
Reliable initial folding keeps the on-open view consistent with your settings. You avoid the extra keystroke and ensure expression-based folding reflects the Treesitter structure right away, including in larger Python files where you expect meaningful folds out of the box.
Takeaways
If Treesitter expression folding doesn’t apply automatically in Python buffers, delay the fold refresh until after the parser finishes. Use BufReadPost and vim.schedule to run zx once the event loop is idle. With this in place, your configured folds will be applied as soon as the syntax tree exists.
The article is based on a question from StackOverflow by Kyle F. Hartzenberg and an answer by Kyle F. Hartzenberg.