use smol_str::SmolStr;
use crate::ast::{command_name, first_group_range, nth_group_text};
use crate::semantic::SemanticModel;
use crate::semantic::label::{CitationRef, 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: first_group_range(&command),
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,
});
}
} else if is_cite_command(&name)
&& let Some(arg) = nth_group_text(&command, 0)
{
if name == "nocite" && arg.trim() == "*" {
model.nocite_all = true;
} else {
for key in arg.split(',').map(str::trim).filter(|k| !k.is_empty()) {
model.citations.push(CitationRef {
name: SmolStr::from(key),
command: SmolStr::from(name.as_str()),
range: command.text_range(),
});
}
}
}
}
resolve(&mut model);
model
}
pub(crate) fn is_cite_command(name: &str) -> bool {
const EXTRA: &[&str] = &[
"parencite",
"Parencite",
"footcite",
"footcitetext",
"textcite",
"Textcite",
"smartcite",
"Smartcite",
"autocite",
"Autocite",
"supercite",
"fullcite",
"footfullcite",
"nocite",
"notecite",
"Notecite",
"pnotecite",
"fnotecite",
];
name.starts_with("cite") || name.starts_with("Cite") || EXTRA.contains(&name)
}
pub(crate) 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;
}
}