S1 follow-up — scope the per-edit highlight queries (typing perf, Q#S6)
Scrolling became fast after S1 but typing stayed slow: scrolling doesn't bump the CRDT generation, so the daemon's StyleGate caches and no query runs — but every keystroke bumps the generation and forced TWO whole-file tree-sitter passes on the daemon, which S1 deferred as Q#S6. With the GPU now O(visible), this was the remaining O(file) per-keystroke cost. 1. StyleSpans query scoped to the viewport. New `compute_highlight_spans_in_range` sets `QueryCursor::set_byte_range` so the capture walk is proportional to the visible range, not the whole tree; `scoped_style_spans` passes the declared viewport. The StyleGate still recomputes on the edit's generation bump (M11.7 resync), but that recompute is now O(visible). 2. FileStyleSummary (the minimap — inherently a whole-file pass) debounced to reparse-completion: skip the recompute while a reparse is in flight (`pending_edit_count() > 0`). During continuous typing the whole-file pass runs at reparse rate, not keystroke rate; when typing settles and the parse lands, it recomputes once. Together these drop the daemon's per-keystroke cost from two whole-file tree-sitter passes to one viewport-scoped pass (+ an amortized whole-file summary). Only the semantic (pmacs-gpu) path is affected; the grid/TUI path doesn't use this producer. Gates green: fmt; clippy --all-targets --workspace -D warnings (default + crdt); pmacs lib 1334; syntax 6; semantic_render 28; m11_5_semantic_acceptance 2; m4_acceptance 88. Awaiting visual confirmation: typing in a large file is now responsive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ecb0a862fc
commit
6bebd770e1
|
|
@ -356,6 +356,18 @@ impl SemanticRenderState {
|
|||
buffer_id: BufferId,
|
||||
generation: u64,
|
||||
) -> Option<InstanceMessage> {
|
||||
// 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<StyleSp
|
|||
}
|
||||
|
||||
let capture_names = query.capture_names();
|
||||
let highlights = crate::syntax::compute_highlight_spans(&query, &bundle);
|
||||
// Scope the tree-sitter capture walk to the visible byte range so
|
||||
// re-styling on each edit is O(visible), not O(file) — the typing
|
||||
// bottleneck on large files (framing Q#S6). Captures whose nodes
|
||||
// intersect the range are returned, then clipped exactly below.
|
||||
let highlights = crate::syntax::compute_highlight_spans_in_range(
|
||||
&query,
|
||||
&bundle,
|
||||
Some(vis_start as usize..vis_end as usize),
|
||||
);
|
||||
let mut out = Vec::new();
|
||||
for hs in highlights {
|
||||
let s = u64::from(hs.start_byte).max(vis_start);
|
||||
|
|
|
|||
|
|
@ -691,9 +691,27 @@ pub struct HighlightSpan {
|
|||
pub fn compute_highlight_spans(
|
||||
query: &tree_sitter::Query,
|
||||
bundle: &ParseTreeBundle,
|
||||
) -> Vec<HighlightSpan> {
|
||||
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<std::ops::Range<usize>>,
|
||||
) -> Vec<HighlightSpan> {
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue