use super::{EmphSpan, TokenKind, TokenSpan};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AttributedSegment {
pub start: usize,
pub end: usize,
pub kind: Option<TokenKind>,
pub emphasized: bool,
}
pub fn attributed_segments(
text: &str,
tokens: &[TokenSpan],
emphasis: &[EmphSpan],
) -> Vec<AttributedSegment> {
let len = text.len();
let valid = |start: usize, end: usize| {
start < end && end <= len && text.is_char_boundary(start) && text.is_char_boundary(end)
};
let toks: Vec<&TokenSpan> = tokens.iter().filter(|t| valid(t.start, t.end)).collect();
let emph: Vec<&EmphSpan> = emphasis.iter().filter(|e| valid(e.start, e.end)).collect();
let mut points: Vec<usize> = Vec::with_capacity(2 + 2 * (toks.len() + emph.len()));
points.push(0);
points.push(len);
for t in &toks {
points.push(t.start);
points.push(t.end);
}
for e in &emph {
points.push(e.start);
points.push(e.end);
}
points.sort_unstable();
points.dedup();
let mut out = Vec::with_capacity(points.len().saturating_sub(1));
for window in points.windows(2) {
let (a, b) = (window[0], window[1]);
if a >= b {
continue;
}
let kind = toks
.iter()
.find(|t| t.start <= a && a < t.end)
.map(|t| t.kind);
let emphasized = emph.iter().any(|e| e.start <= a && a < e.end);
out.push(AttributedSegment {
start: a,
end: b,
kind,
emphasized,
});
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::highlight::{EmphSpan, TokenKind, TokenSpan};
fn tok(start: usize, end: usize, kind: TokenKind) -> TokenSpan {
TokenSpan { start, end, kind }
}
fn emph(start: usize, end: usize) -> EmphSpan {
EmphSpan { start, end }
}
#[test]
fn plain_text_is_one_unattributed_segment() {
let segs = attributed_segments("hello", &[], &[]);
assert_eq!(
segs,
vec![AttributedSegment {
start: 0,
end: 5,
kind: None,
emphasized: false
}]
);
}
#[test]
fn splits_at_the_union_of_token_and_emphasis_boundaries() {
let segs =
attributed_segments("let x = 1", &[tok(0, 3, TokenKind::Keyword)], &[emph(4, 5)]);
assert!(segs.iter().any(|s| s.start == 0
&& s.end == 3
&& s.kind == Some(TokenKind::Keyword)
&& !s.emphasized));
assert!(
segs.iter()
.any(|s| s.start == 4 && s.end == 5 && s.kind.is_none() && s.emphasized)
);
assert_eq!(segs.first().unwrap().start, 0);
assert_eq!(segs.last().unwrap().end, "let x = 1".len());
for w in segs.windows(2) {
assert_eq!(w[0].end, w[1].start);
}
}
#[test]
fn overlapping_token_and_emphasis_produce_a_segment_carrying_both() {
let segs = attributed_segments("abcde", &[tok(0, 5, TokenKind::String)], &[emph(1, 3)]);
assert!(segs.iter().any(|s| s.start == 1
&& s.end == 3
&& s.kind == Some(TokenKind::String)
&& s.emphasized));
}
#[test]
fn drops_a_malformed_channel_without_panicking() {
let text = "héllo"; let bad_token = tok(0, 999, TokenKind::Keyword); let segs = attributed_segments(text, &[bad_token], &[]);
assert_eq!(
segs,
vec![AttributedSegment {
start: 0,
end: text.len(),
kind: None,
emphasized: false
}]
);
}
}