use std::iter::Peekable; use std::slice; use unicode_segmentation::{GraphemeIndices, Graphemes, UnicodeSegmentation}; use crate::Style; #[derive(Debug, Default, Clone)] pub struct Styled { text: String, /// List of `(style, until)` tuples. The style should be applied to all /// chars in the range `prev_until..until`. styles: Vec<(Style, usize)>, } impl Styled { pub fn new>(text: S, style: Style) -> Self { Self::default().then(text, style) } pub fn new_plain>(text: S) -> Self { Self::default().then_plain(text) } pub fn then>(mut self, text: S, style: Style) -> Self { let text = text.as_ref(); if !text.is_empty() { self.text.push_str(text); self.styles.push((style, self.text.len())); } self } pub fn then_plain>(self, text: S) -> Self { self.then(text, Style::new()) } pub fn and_then(mut self, mut other: Self) -> Self { let delta = self.text.len(); for (_, until) in &mut other.styles { *until += delta; } self.text.push_str(&other.text); self.styles.extend(other.styles); self } pub fn text(&self) -> &str { &self.text } pub fn split_at(self, mid: usize) -> (Self, Self) { let (left_text, right_text) = self.text.split_at(mid); let mut left_styles = vec![]; let mut right_styles = vec![]; let mut from = 0; for (style, until) in self.styles { if from < mid { left_styles.push((style, until.min(mid))); } if mid < until { right_styles.push((style, until.saturating_sub(mid))); } from = until; } let left = Self { text: left_text.to_string(), styles: left_styles, }; let right = Self { text: right_text.to_string(), styles: right_styles, }; (left, right) } pub fn split_at_indices(self, indices: &[usize]) -> Vec { let mut lines = vec![]; let mut rest = self; let mut offset = 0; for i in indices { let (left, right) = rest.split_at(i - offset); lines.push(left); rest = right; offset = *i; } lines.push(rest); lines } pub fn trim_end(&mut self) { self.text = self.text.trim_end().to_string(); let text_len = self.text.len(); let mut styles_len = 0; for (_, until) in &mut self.styles { styles_len += 1; if *until >= text_len { *until = text_len; break; } } while self.styles.len() > styles_len { self.styles.pop(); } } } ////////////////////////////// // Iterating over graphemes // ////////////////////////////// pub struct StyledGraphemeIndices<'a> { text: GraphemeIndices<'a>, styles: Peekable>, } impl<'a> Iterator for StyledGraphemeIndices<'a> { type Item = (usize, Style, &'a str); fn next(&mut self) -> Option { let (gi, grapheme) = self.text.next()?; let (mut style, mut until) = **self.styles.peek().expect("styles cover entire text"); while gi >= until { self.styles.next(); (style, until) = **self.styles.peek().expect("styles cover entire text"); } Some((gi, style, grapheme)) } } impl Styled { pub fn graphemes(&self) -> Graphemes<'_> { self.text.graphemes(true) } pub fn grapheme_indices(&self) -> GraphemeIndices<'_> { self.text.grapheme_indices(true) } pub fn styled_grapheme_indices(&self) -> StyledGraphemeIndices<'_> { StyledGraphemeIndices { text: self.grapheme_indices(), styles: self.styles.iter().peekable(), } } } ////////////////////////// // Converting to Styled // ////////////////////////// impl From<&str> for Styled { fn from(text: &str) -> Self { Self::new_plain(text) } } impl From for Styled { fn from(text: String) -> Self { Self::new_plain(text) } } impl> From<(S,)> for Styled { fn from((text,): (S,)) -> Self { Self::new_plain(text) } } impl> From<(S, Style)> for Styled { fn from((text, style): (S, Style)) -> Self { Self::new(text, style) } } impl> From<&[(S, Style)]> for Styled { fn from(segments: &[(S, Style)]) -> Self { let mut result = Self::default(); for (text, style) in segments { result = result.then(text, *style); } result } }