use smol_str::SmolStr;
use crate::ast::{command_name, nth_group_text};
use crate::semantic::SemanticModel;
use crate::semantic::label::{LabelDef, LabelRef, RefCommand};
use crate::syntax::{SyntaxKind, SyntaxNode};
pub fn build(root: &SyntaxNode) -> SemanticModel {
let mut model = SemanticModel::default();
for command in root
.descendants()
.filter(|node| node.kind() == SyntaxKind::COMMAND)
{
let Some(name) = command_name(&command) else {
continue;
};
if name == "label" {
if let Some(key) = nth_group_text(&command, 0) {
let key = key.trim();
if !key.is_empty() {
model.labels.push(LabelDef {
name: SmolStr::from(key),
range: command.text_range(),
referenced: false,
});
}
}
} else if let Some(kind) = ref_command(&name)
&& let Some(arg) = nth_group_text(&command, 0)
{
for key in split_keys(&arg, kind) {
model.refs.push(LabelRef {
name: SmolStr::from(key),
command: kind,
range: command.text_range(),
resolved: false,
});
}
}
}
resolve(&mut model);
model
}
fn ref_command(name: &str) -> Option<RefCommand> {
Some(match name {
"ref" => RefCommand::Ref,
"pageref" => RefCommand::PageRef,
"eqref" => RefCommand::EqRef,
"autoref" => RefCommand::AutoRef,
"nameref" => RefCommand::NameRef,
"cref" => RefCommand::Cref,
"Cref" => RefCommand::CrefUpper,
"vref" => RefCommand::Vref,
"Vref" => RefCommand::VrefUpper,
"cpageref" => RefCommand::CpageRef,
_ => return None,
})
}
fn split_keys(arg: &str, kind: RefCommand) -> Vec<&str> {
if kind.is_key_list() {
arg.split(',')
.map(str::trim)
.filter(|key| !key.is_empty())
.collect()
} else {
let key = arg.trim();
if key.is_empty() {
Vec::new()
} else {
vec![key]
}
}
}
fn resolve(model: &mut SemanticModel) {
for ref_idx in 0..model.refs.len() {
let name = model.refs[ref_idx].name.clone();
let mut hit = false;
for label in &mut model.labels {
if label.name == name {
label.referenced = true;
hit = true;
}
}
model.refs[ref_idx].resolved = hit;
}
}