cursorOnlyUpdate method
void
cursorOnlyUpdate()
Notifies listeners for a cursor/selection-only change that does NOT mutate the document structure or text. Crucially it does NOT invalidate the content-derived caches (node index, caret-stop rail), so repeated arrow navigation reuses them instead of rebuilding O(n) on every press.
Listeners that are expensive on cursor-only changes can read cursorOnlyChange (true for the duration of this synchronous call) and skip unnecessary work.
Implementation
void cursorOnlyUpdate() {
final sw = Stopwatch()..start();
_cursorOnlyChange = true;
// Pre-compute expensive lookups once before notifying 20+ widgets.
// Each widget previously called findLogicalContainerId + isNodeSelected
// + getSelectionRangeForNode independently — O(visible × n).
// Now it's O(n) once, then O(1) per widget.
_cachedCursorContainerId = cursor.focusId.isNotEmpty
? findLogicalContainerId(cursor.focusId)
: null;
_cachedSelectedNodeIds.clear();
_cachedSelectionRanges.clear();
if (selectionManager.hasSelection) {
for (final node in _content.nodes) {
final nodeId = node.id;
if (selectionManager.isNodeSelected(nodeId)) {
_cachedSelectedNodeIds.add(nodeId);
final range = getSelectionRangeForNode(nodeId);
if (range != null) {
_cachedSelectionRanges[nodeId] = range;
}
}
}
}
cursor.notifyListeners();
sw.stop();
print('[CURSOR_ONLY] cursor.notifyListeners=${sw.elapsedMicroseconds}μs');
sw.reset();
sw.start();
notifyListeners();
sw.stop();
print('[CURSOR_ONLY] doc.notifyListeners=${sw.elapsedMicroseconds}μs');
_cursorOnlyChange = false;
}