fix: hide diff scope labels for visible declarations

The visible declaration already provides scope context. Cache the actual
identifier point separately from the declaration extent/decorators and hide
labels when it is visible in that side of the projected viewport.

Refill reclaimed rows and anchor carets against the resulting layout.
Retain sticky context when the symbol scrolls or wraps out of view.

Verified: 951 tests passed, cargo build, and real PTY smoke for declaration
visibility/scroll restoration, wrapping, horizontal clipping, unified view,
and short horizontal-pane caret reflow.
This commit is contained in:
Valdemar Lorenzen
2026-10-15 14:47:51 +02:00
parent 39fd3a669b
commit a93afc7d16
5 changed files with 228 additions and 99 deletions
+24 -9
View File
@@ -46,12 +46,14 @@ pub struct SyntaxSpan {
pub(crate) struct ScopeContext<'a> {
pub start_row: usize,
pub end_row: usize,
pub name: Point,
pub label: &'a str,
}
struct CachedScope {
start_row: usize,
end_row: usize,
name: Point,
label: String,
}
@@ -334,6 +336,7 @@ impl SyntaxCache {
Some(ScopeContext {
start_row: scope.start_row,
end_row: scope.end_row,
name: scope.name,
label: &scope.label,
})
}
@@ -581,8 +584,8 @@ fn collect_scopes(
{
ancestors.pop();
}
if let Some((extent, name)) =
scope_declaration(node, language, source).filter(|(extent, _)| {
if let Some((extent, name, label)) =
scope_declaration(node, language, source).filter(|(extent, _, _)| {
ranges.is_empty()
|| ranges.iter().any(|range| {
range.start_byte <= extent.start_byte()
@@ -594,9 +597,9 @@ fn collect_scopes(
.last()
.map_or("", |(index, _)| scopes[*index].label.as_str());
let label = if parent.is_empty() {
name
label
} else {
format!("{parent} › {name}")
format!("{parent} › {label}")
};
let start_row = extent.start_position().row;
let end = extent.end_position();
@@ -605,6 +608,7 @@ fn collect_scopes(
scopes.push(CachedScope {
start_row,
end_row,
name,
label,
});
let end_row = end_row.min(rows.len());
@@ -633,7 +637,7 @@ fn scope_declaration<'a>(
node: Node<'a>,
language: &str,
source: &str,
) -> Option<(Node<'a>, String)> {
) -> Option<(Node<'a>, Point, String)> {
if !node.is_named() || node.is_missing() {
return None;
}
@@ -646,7 +650,7 @@ fn scope_declaration<'a>(
Some(trait_name) => scope_label(&["impl", trait_name, "for", target]),
None => scope_label(&["impl", target]),
};
return Some((node, label));
return Some((node, field("type")?.start_position(), label));
}
(
"rust",
@@ -686,8 +690,12 @@ fn scope_declaration<'a>(
.named_child(0)?
.child_by_field_name("type")?;
let receiver = scope_text(receiver, source)?;
let name = scope_text(field("name")?, source)?;
return Some((node, scope_label(&[receiver, "›", name])));
let name = field("name")?;
return Some((
node,
name.start_position(),
scope_label(&[receiver, "›", scope_text(name, source)?]),
));
}
("go", "type_spec")
if field("type")
@@ -764,7 +772,11 @@ fn scope_declaration<'a>(
{
extent = parent;
}
Some((extent, scope_label(&[scope_text(name, source)?])))
Some((
extent,
name.start_position(),
scope_label(&[scope_text(name, source)?]),
))
}
fn scope_label(parts: &[&str]) -> String {
@@ -1567,6 +1579,7 @@ mod tests {
Some(ScopeContext {
start_row: 1,
end_row: 4,
name: Point::new(1, 7),
label: "impl State › old",
})
);
@@ -1578,6 +1591,7 @@ mod tests {
Some(ScopeContext {
start_row: 6,
end_row: 9,
name: Point::new(6, 3),
label: "next",
})
);
@@ -1605,6 +1619,7 @@ mod tests {
Some(ScopeContext {
start_row: 1,
end_row: 4,
name: Point::new(2, 8),
label: "State › run",
})
);