Skip to content

perf(highlighter): Cache decoration-provider hot paths - #1148

Open
seflue wants to merge 3 commits into
nvim-orgmode:masterfrom
seflue:fix/recompute-folds
Open

perf(highlighter): Cache decoration-provider hot paths#1148
seflue wants to merge 3 commits into
nvim-orgmode:masterfrom
seflue:fix/recompute-folds

Conversation

@seflue

@seflue seflue commented May 16, 2026

Copy link
Copy Markdown
Contributor

Typing in folded org-files is slow because nvim_set_decoration_provider's on_line does per-line work on every redraw.

Two commits add caching to foldtext: move vim.fn.foldclosed and vim.fn.col from on_line to on_win, and replace the changedtick wholesale-drop with an nvim_buf_attach listener that keeps the cache consistent under edits.

A third commit applies the same pattern to stars. Each buffer keeps a per-line cache of the (stars) node's end_col; on_bytes shifts and drops entries to stay consistent with edits, and on_changedtree re-queries the changed ranges after a reparse. Edits inside a headline that don't touch the leading stars keep their cache entry, since the stars themselves don't change.

I used the snacks profiler to identify the hot methods; it's not a controlled benchmark, but foldtext.on_line and stars.on_line no longer show up near the top after these changes.

@kristijanhusak

Copy link
Copy Markdown
Member

I just pushed a change to master to hide leading stars via tree-sitter queries (5ea6251), so we can rebase and simplify this a bit more.

@seflue
seflue force-pushed the fix/recompute-folds branch 2 times, most recently from 48f02ee to a2978c8 Compare May 18, 2026 07:23
@seflue

seflue commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

I removed the commit regarding the stars from my PR and force-pushed. You said, you have some simplifications in mind. Is it regarding the window map, holding a cache for each window? I was a bit undecided ... it's covering an edge case, when the user has the buffer open in two windows in parallel. You could argue, that this is in indeed a bit "edgy". On the other hand - especially when working with large files it is not completely uncommon to have the same buffer at different positions open in parallel.

In large folded org-files, foldtext.on_line is a hot
path: two vim.fn calls (foldclosed, col) run per
visible line per redraw, even on cache hits.

Move them into on_win, building a per-window map of
fold boundaries and line lengths for the visible range.
on_line becomes an O(1) lookup. nvim_buf_get_lines
replaces per-line col() queries; foldclosedend lets the
scan skip over folded contents.
Replace the changedtick check that dropped the entire
foldtext cache on every keystroke with an
nvim_buf_attach listener: on_bytes drops cache entries
inside the edited range and shifts later entries by the
row delta, on_reload clears the cache for full reloads.
Unedited closed folds keep their cached hl_group and
skip the treesitter lookup on subsequent redraws.
@seflue
seflue force-pushed the fix/recompute-folds branch from a2978c8 to c87da52 Compare September 9, 2026 06:40
Archiving or refiling a subtree left the surrounding virtual
indentation broken. The indentation of the removed lines piled up
on the headline that followed them, and once the deferred update
ran, the line above the subtree lost its indentation entirely.

Indentation marks of removed lines are now dropped as soon as the
lines go away, rather than only in the scheduled update. The
scheduled update in turn computes its sizes against the buffer it
writes to: archiving leaves a different buffer current, which made
every lookup come back empty.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants