use std::{collections::BTreeMap, ops::Range};
use crate::{backend::Match, query::Query};
pub(crate) struct Injection<'a> {
pub language: &'a str,
pub ranges: Vec<Range<usize>>,
}
pub(crate) fn collect<'a, Q>(
query: &'a Query<Q>,
matches: &[Match],
source: &'a str,
ranges: &[Range<usize>],
language: &'a str,
parent: Option<&'a str>,
) -> Vec<Injection<'a>> {
let mut result = Vec::new();
let mut combined: BTreeMap<(usize, &str), Vec<Range<usize>>> = BTreeMap::new();
let mut included = Vec::new();
for matched in matches {
let pattern = &query.patterns[matched.pattern];
let captured_language = matched
.captures
.iter()
.find(|capture| query.capture_names[capture.index as usize] == "injection.language")
.map(|capture| source[capture.node.range.clone()].trim());
let name = captured_language
.or_else(|| pattern.value("injection.language"))
.or_else(|| pattern.property("injection.self").map(|_| language))
.or_else(|| pattern.property("injection.parent").and(parent));
let Some(name) = name else { continue };
let mut contents = Vec::new();
for capture in &matched.captures {
if query.capture_names[capture.index as usize] != "injection.content" {
continue;
}
let node = &capture.node;
included.clear();
if pattern.property("injection.include-children").is_some() {
included.push(node.range.clone());
} else {
let mut start = node.range.start;
for child in node.children.iter().filter(|child| child.named) {
if child.range.start >= node.range.end {
break;
}
if child.range.end <= start {
continue;
}
if child.range.start > start {
included.push(start..child.range.start);
}
start = child.range.end.min(node.range.end);
}
if start < node.range.end {
included.push(start..node.range.end);
}
}
for outer in ranges {
for inner in &included {
let range = outer.start.max(inner.start)..outer.end.min(inner.end);
if !range.is_empty() {
contents.push(range);
}
}
}
}
if contents.is_empty() {
continue;
}
if pattern.property("injection.combined").is_some() {
combined
.entry((matched.pattern, name))
.or_default()
.extend(contents);
} else {
result.push(Injection {
language: name,
ranges: normalize(contents),
});
}
}
result.extend(
combined
.into_iter()
.map(|((_, language), ranges)| Injection {
language,
ranges: normalize(ranges),
}),
);
result
}
fn normalize(mut ranges: Vec<Range<usize>>) -> Vec<Range<usize>> {
ranges.sort_by_key(|range| (range.start, range.end));
ranges.dedup_by(|range, previous| {
if range.start <= previous.end {
previous.end = previous.end.max(range.end);
true
} else {
false
}
});
ranges
}