diff --git a/src/semantic_render.rs b/src/semantic_render.rs index 7b065d5..66d5b8b 100644 --- a/src/semantic_render.rs +++ b/src/semantic_render.rs @@ -356,6 +356,18 @@ impl SemanticRenderState { buffer_id: BufferId, generation: u64, ) -> Option { + // The summary is a *whole-file* tree-sitter pass (the minimap + // needs every line). Recomputing it on every edit's generation + // bump was a per-keystroke O(file) cost — a major part of the + // typing slowness. For a grammar-backed buffer, debounce it to + // reparse-completion: skip while a reparse is in flight + // (`current_fresh` is `None`), so during continuous typing the + // whole-file pass runs at reparse rate, not keystroke rate. + if let Some(handle) = state.syntax_registry.view(buffer_id) + && handle.pending_edit_count() > 0 + { + return None; + } if self.last_summary.get(&buffer_id).copied() == Some(generation) { return None; } @@ -871,7 +883,15 @@ fn scoped_style_spans(state: &EditorState, vp: &DeclaredViewport) -> Vec Vec { + compute_highlight_spans_in_range(query, bundle, None) +} + +/// Like [`compute_highlight_spans`], but restricts the query to nodes +/// intersecting `byte_range` when `Some`. tree-sitter's +/// `QueryCursor::set_byte_range` makes the capture walk proportional to +/// the range, not the whole tree — the semantic producer passes the +/// declared viewport so styling a screenful of a huge file is +/// O(visible), not O(file) (the per-edit typing cost; framing Q#S6). +#[must_use] +pub fn compute_highlight_spans_in_range( + query: &tree_sitter::Query, + bundle: &ParseTreeBundle, + byte_range: Option>, ) -> Vec { let mut spans = Vec::new(); let mut cursor = tree_sitter::QueryCursor::new(); + if let Some(range) = byte_range { + cursor.set_byte_range(range); + } let source: &[u8] = bundle.source.as_ref(); let root = bundle.tree.root_node(); let mut iter = cursor.captures(query, root, source);