Performance Guide¶
Tips for keeping Kodemirror editors responsive, especially with large documents.
How Virtualization Works¶
Kodemirror uses LazyColumn for virtualized rendering — only visible lines are composed. This means:
- Documents with millions of lines are feasible
- Memory usage scales with viewport size, not document size
- Scrolling performance depends on line complexity, not document length
Decoration Performance¶
Decorations (syntax highlighting, markers, search matches) are stored in RangeSet — a sorted, immutable tree structure. Performance tips:
- Keep decoration counts reasonable. Hundreds of decorations per viewport are fine. Tens of thousands may cause slowdowns.
- Use
RangeSetBuilderto create decorations in sorted order. Out-of-order insertions are rejected. - Reuse decoration instances. Create
Decoration.mark(...)once and add the same instance to multiple ranges. - Update incrementally. In
ViewPlugin.update, only rebuild decorations whenupdate.docChangedorupdate.viewportChanged.
override fun update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
decorations = buildDecorations(update.session)
}
}
Parser Performance¶
The syntax parser runs incrementally — it only parses what's needed for the visible viewport plus a buffer zone.
ensureSyntaxTreeparses up to a given position. Don't call this for the entire document unless necessary.forceParsingblocks until parsing completes up to a position. Avoid calling this from the main thread for large ranges.- Language nesting (e.g., JavaScript inside HTML) adds overhead proportional to the number of nested regions.
StateField vs ViewPlugin¶
| Concern | StateField | ViewPlugin |
|---|---|---|
| Runs on every transaction | Yes | Yes (update) |
| Has access to viewport | No | Yes |
| Can skip work when offscreen | No | Yes |
| Good for decorations | No (runs even when hidden) | Yes |
| Good for derived data | Yes | No |
Rule of thumb: If you need decorations that depend on the viewport, use ViewPlugin. If you need derived data from the document, use StateField.
Minimizing Recomposition¶
- Avoid reading
session.statein composables that don't need it — this triggers recomposition on every transaction. - Use
onChangeoronSelectionfor callbacks instead of observing state directly. @ImmutableonEditorThemeenables Compose to skip recomposition of theme-dependent composables when the theme hasn't changed.
Large Document Checklist¶
For documents over 100K lines:
- Use
basicSetupor a minimal extension set — avoid unnecessary extensions. - Ensure syntax highlighting uses an incremental parser (all built-in languages do).
- Avoid decorations that span the entire document.
- Use
collapseUnchangedin merge views to hide unchanged regions. - Test on target platforms — mobile devices have less memory and slower CPUs.