diff --git a/crates/ide/src/ide/symbol_hierarchy.rs b/crates/ide/src/ide/symbol_hierarchy.rs index 438c8e1..c7db99a 100644 --- a/crates/ide/src/ide/symbol_hierarchy.rs +++ b/crates/ide/src/ide/symbol_hierarchy.rs @@ -1,12 +1,15 @@ -use crate::def::{BindingValue, Expr, ExprId}; +use crate::def::{AstPtr, NameId}; use crate::{DefDatabase, FileId, Module, ModuleSourceMap, NameKind}; use smol_str::SmolStr; use syntax::ast::{self, AstNode}; +use syntax::rowan::WalkEvent; use syntax::{SyntaxNode, TextRange}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SymbolTree { pub name: SmolStr, + // TODO: Avoid saving `NameId` in the public API. + name_id: NameId, pub full_range: TextRange, pub focus_range: TextRange, pub kind: NameKind, @@ -17,81 +20,119 @@ pub(crate) fn symbol_hierarchy(db: &dyn DefDatabase, file: FileId) -> Vec { +struct Collector<'a, 'b> { module: &'a Module, source_map: &'a ModuleSourceMap, - root_node: SyntaxNode, - symbols: Vec, + symbols: &'b mut Vec, } -impl Collector<'_> { - fn collect_expr(&mut self, expr_id: ExprId) { - let e = &self.module[expr_id]; - let (bindings, body) = match e { - Expr::LetIn(bindings, body) => (bindings, Some(*body)), - Expr::Attrset(bindings) | Expr::LetAttrset(bindings) | Expr::RecAttrset(bindings) => { - (bindings, None) - } - _ => return e.walk_child_exprs(|child| self.collect_expr(child)), - }; +impl Collector<'_, '_> { + fn push_symbol(&mut self, name_id: NameId, attr: &ast::Attr, full_range: TextRange) { + self.symbols.push(SymbolTree { + name: self.module[name_id].text.clone(), + name_id, + full_range, + focus_range: attr.syntax().text_range(), + kind: self.module[name_id].kind, + children: Vec::new(), + }); + } - for &(name, rhs) in bindings.statics.iter() { - let prev_len = self.symbols.len(); - match rhs { - BindingValue::InheritFrom(_) => {} - BindingValue::Inherit(child) | BindingValue::Expr(child) => { - self.collect_expr(child); + // TODO: Rewrite this in non-recursive form? + // Nested mutable borrowing is hard to impl without recursion yet. + fn collect_node(&mut self, n: &SyntaxNode) { + let mut iter = n.preorder(); + let mut last_is_path_value = false; + while let Some(event) = iter.next() { + let n = match event { + WalkEvent::Enter(n) => n, + WalkEvent::Leave(n) => { + last_is_path_value = ast::AttrpathValue::can_cast(n.kind()); + continue; + } + }; + let Some(binding) = ast::Binding::cast(n) else { + continue; + }; + match binding { + ast::Binding::Inherit(i) => { + for attr in i.attrs() { + let ptr = AstPtr::new(attr.syntax()); + if let Some(name_id) = self.source_map.name_for_node(ptr) { + self.push_symbol(name_id, &attr, i.syntax().text_range()); + } + } + // Continue traversing the from-expr. Attrs should be skipped automatically. + } + ast::Binding::AttrpathValue(path_value) => { + let Some(path) = path_value.attrpath() else { + continue; + }; + iter.skip_subtree(); + self.collect_path_value(path_value, path.attrs(), last_is_path_value); } } - (|| { - let text = self.module[name].text.clone(); - let kind = self.module[name].kind; - let name_node = self.source_map.nodes_for_name(name).next()?; - let focus_range = name_node.text_range(); - let full_range = name_node - .to_node(&self.root_node) - .ancestors() - .find_map(ast::Binding::cast)? - .syntax() - .text_range(); - let mut children = self.symbols.split_off(prev_len); - sort_symbols(&mut children); - self.symbols.push(SymbolTree { - name: text, - full_range, - focus_range, - kind, - children, - }); - Some(()) - })(); } - - bindings - .dynamics - .iter() - .flat_map(|&(lhs, rhs)| [lhs, rhs]) - .chain(bindings.inherit_froms.iter().copied()) - .chain(body) - .for_each(|e| self.collect_expr(e)); } -} -fn sort_symbols(syms: &mut [SymbolTree]) { - syms.sort_by_key(|sym| sym.full_range.start()); + fn collect_path_value( + &mut self, + binding: ast::AttrpathValue, + mut attrs: ast::AstChildren, + allow_merge_to_last: bool, + ) { + let Some(attr) = attrs.next() else { + if let Some(n) = binding.value() { + self.collect_node(n.syntax()); + } + return; + }; + + let ptr = AstPtr::new(attr.syntax()); + if let Some(name_id) = self.source_map.name_for_node(ptr) { + let binding_end_pos = binding.syntax().text_range().end(); + + // Merge adjacent bindings to the same tree node. + // Eg. `{ a.b = 1; a.c = 2; }` + // ^-------a-------^ + let current_sym = if allow_merge_to_last + && self.symbols.last().map(|tree| tree.name_id) == Some(name_id) + { + let last_sym = self.symbols.last_mut().unwrap(); + last_sym.full_range = last_sym.full_range.cover_offset(binding_end_pos); + last_sym + } else { + // `{ foo.bar = 1; }` + // --- focus + // ======== full + let full_range = attr.syntax().text_range().cover_offset(binding_end_pos); + self.push_symbol(name_id, &attr, full_range); + self.symbols.last_mut().unwrap() + }; + + Collector { + module: self.module, + source_map: self.source_map, + symbols: &mut current_sym.children, + } + .collect_path_value(binding, attrs, allow_merge_to_last); + } else { + // Incomplete or dynamic attributes. + self.collect_node(attr.syntax()); + self.collect_path_value(binding, attrs, false); + } + } } #[cfg(test)] @@ -139,7 +180,7 @@ mod tests { } #[test] - fn attrset() { + fn attrset_simple() { check( "{ a = 1; b = { c = 1; }; inherit d; inherit ({ e = 1; }) e; }", expect![[r#" @@ -152,4 +193,32 @@ mod tests { "#]], ); } + + #[test] + fn attrset_merge() { + check( + "{ + a.x.y = 1; + # comment + a.y.z = 2; + another = 42; + a.z = 3; + inherit inhrit; + a.w = 4; + }", + expect![[r#" + a: PlainAttrset + x: PlainAttrset + y: PlainAttrset + y: PlainAttrset + z: PlainAttrset + another: PlainAttrset + a: PlainAttrset + z: PlainAttrset + inhrit: PlainAttrset + a: PlainAttrset + w: PlainAttrset + "#]], + ); + } }