use super::*;
pub fn compute_document_symbols(text: &str) -> Vec<DocumentSymbol> {
let root = parse(text).cst;
let model = SemanticModel::build(&root);
let bindings: HashMap<TextRange, SmolStr> = model
.bindings()
.iter()
.filter(|b| matches!(b.kind, BindingKind::Local | BindingKind::Implicit))
.map(|b| (b.def_range, b.name.clone()))
.collect();
let line_index = LineIndex::new(text);
let mut symbols = Vec::new();
collect_document_symbols(&root, &bindings, &line_index, &mut symbols);
symbols
}
pub(crate) fn collect_document_symbols(
node: &SyntaxNode,
bindings: &HashMap<TextRange, SmolStr>,
line_index: &LineIndex,
out: &mut Vec<DocumentSymbol>,
) {
for child in node.children() {
match document_symbol_for(&child, bindings, line_index) {
Some(symbol) => out.push(symbol),
None => collect_document_symbols(&child, bindings, line_index, out),
}
}
}
#[expect(deprecated, reason = "DocumentSymbol::deprecated is a required field")]
pub(crate) fn document_symbol_for(
node: &SyntaxNode,
bindings: &HashMap<TextRange, SmolStr>,
line_index: &LineIndex,
) -> Option<DocumentSymbol> {
let assign = AssignmentExpr::cast(node.clone())?;
let name_token = assign.target_name_token()?;
let name = bindings.get(&name_token.text_range())?;
let value = assign.value_element();
let is_function =
matches!(&value, Some(NodeOrToken::Node(n)) if FunctionExpr::can_cast(n.kind()));
let mut children = Vec::new();
if let Some(NodeOrToken::Node(value_node)) = &value {
collect_document_symbols(value_node, bindings, line_index, &mut children);
}
Some(DocumentSymbol {
name: name.to_string(),
detail: None,
kind: if is_function {
LspSymbolKind::FUNCTION
} else {
LspSymbolKind::VARIABLE
},
tags: None,
deprecated: None,
range: text_range_to_lsp_range(line_index, node.text_range()),
selection_range: text_range_to_lsp_range(line_index, name_token.text_range()),
children: (!children.is_empty()).then_some(children),
})
}