use std::{collections::BTreeSet, ops::Range};
use crate::{HighlightConfig, Highlighted, Line, Token};
pub(crate) struct Span<'a> {
pub node: usize,
pub range: Range<usize>,
pub highlight: Option<&'a str>,
pub rank: (usize, i32, std::cmp::Reverse<usize>, usize),
}
pub(crate) struct Rainbow {
pub range: Range<usize>,
pub depth: usize,
pub layer: usize,
}
pub(crate) fn lines<'a>(
source: &'a str,
spans: Vec<Span<'a>>,
rainbows: Vec<Rainbow>,
config: &HighlightConfig,
) -> Highlighted<'a> {
let mut events = Vec::with_capacity(2 + 2 * spans.len() + 2 * rainbows.len());
events.push((0, false, 0, usize::MAX));
events.push((source.len(), false, 0, usize::MAX));
for (index, span) in spans.iter().enumerate() {
events.push((span.range.start, true, 0, index));
events.push((span.range.end, false, 0, index));
}
for (index, rainbow) in rainbows.iter().enumerate() {
events.push((rainbow.range.start, true, 1, index));
events.push((rainbow.range.end, false, 1, index));
}
events.sort_unstable();
let mut result = Highlighted {
lines: vec![Line {
number: 1,
highlighted: false,
tokens: Vec::new(),
}],
};
let mut styles = BTreeSet::<(_, usize)>::new();
let mut colors = BTreeSet::<(_, usize)>::new();
let mut position = 0;
for (offset, start, kind, index) in events {
if offset > position {
let highlight = styles.last().and_then(|&(_, index)| spans[index].highlight);
let rainbow = colors.last().map(|&(_, index)| rainbows[index].depth);
push(&mut result, &source[position..offset], highlight, rainbow);
position = offset;
}
if index == usize::MAX {
continue;
}
if kind == 0 {
let key = (spans[index].rank, index);
if start {
styles.insert(key);
} else {
styles.remove(&key);
}
} else {
let rainbow = &rainbows[index];
let key = (
(rainbow.layer, std::cmp::Reverse(rainbow.range.len())),
index,
);
if start {
colors.insert(key);
} else {
colors.remove(&key);
}
}
}
let mut selected = config.highlighted_lines.clone();
selected.sort_unstable();
let mut selected = selected.into_iter().peekable();
for line in &mut result.lines {
while selected.peek().is_some_and(|number| *number < line.number) {
selected.next();
}
line.highlighted = selected.peek() == Some(&line.number);
}
result
}
fn push<'a>(
result: &mut Highlighted<'a>,
text: &'a str,
highlight: Option<&'a str>,
rainbow: Option<usize>,
) {
for (index, text) in text.split('\n').enumerate() {
if index > 0 {
let number = result.lines.len() + 1;
result.lines.push(Line {
number,
highlighted: false,
tokens: Vec::new(),
});
}
if !text.is_empty() {
let tokens = &mut result.lines.last_mut().unwrap().tokens;
tokens.push(Token {
text,
highlight,
rainbow,
});
}
}
}