use crate::SymbolKind;
use mun_syntax::{
ast::{self, NameOwner},
match_ast, AstNode, SourceFile, SyntaxNode, TextRange, WalkEvent,
};
#[derive(Debug, Clone)]
pub struct StructureNode {
pub parent: Option<usize>,
pub label: String,
pub navigation_range: TextRange,
pub node_range: TextRange,
pub kind: SymbolKind,
pub detail: Option<String>,
}
pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
let mut result = Vec::new();
let mut stack = Vec::new();
for event in file.syntax().preorder() {
match event {
WalkEvent::Enter(node) => {
if let Some(mut symbol) = try_convert_to_structure_node(&node) {
symbol.parent = stack.last().copied();
stack.push(result.len());
result.push(symbol);
}
}
WalkEvent::Leave(node) => {
if try_convert_to_structure_node(&node).is_some() {
stack.pop().unwrap();
}
}
}
}
result
}
fn try_convert_to_structure_node(node: &SyntaxNode) -> Option<StructureNode> {
fn decl<N: NameOwner>(node: N, kind: SymbolKind) -> Option<StructureNode> {
decl_with_detail(&node, None, kind)
}
fn decl_with_detail<N: NameOwner>(
node: &N,
detail: Option<String>,
kind: SymbolKind,
) -> Option<StructureNode> {
let name = node.name()?;
Some(StructureNode {
parent: None,
label: name.text().to_string(),
navigation_range: name.syntax().text_range(),
node_range: node.syntax().text_range(),
kind,
detail,
})
}
fn collapse_whitespaces(node: &SyntaxNode, output: &mut String) {
let mut can_insert_ws = false;
node.text().for_each_chunk(|chunk| {
for line in chunk.lines() {
let line = line.trim();
if line.is_empty() {
if can_insert_ws {
output.push(' ');
can_insert_ws = false;
}
} else {
output.push_str(line);
can_insert_ws = true;
}
}
})
}
fn decl_with_type_ref<N: NameOwner>(
node: &N,
type_ref: Option<ast::TypeRef>,
kind: SymbolKind,
) -> Option<StructureNode> {
let detail = type_ref.map(|type_ref| {
let mut detail = String::new();
collapse_whitespaces(type_ref.syntax(), &mut detail);
detail
});
decl_with_detail(node, detail, kind)
}
match_ast! {
match node {
ast::FunctionDef(it) => {
let mut detail = String::from("fn");
if let Some(param_list) = it.param_list() {
collapse_whitespaces(param_list.syntax(), &mut detail);
}
if let Some(ret_type) = it.ret_type() {
detail.push(' ');
collapse_whitespaces(ret_type.syntax(), &mut detail);
}
decl_with_detail(&it, Some(detail), SymbolKind::Function)
},
ast::StructDef(it) => decl(it, SymbolKind::Struct),
ast::TypeAliasDef(it) => decl_with_type_ref(&it, it.type_ref(), SymbolKind::TypeAlias),
_ => None
}
}
}