use crate::text::{BoundedWidth, HasWidth, Pushable, Width, WidthSliceable};
pub trait Truncateable: HasWidth + WidthSliceable {}
impl<'a, T> Truncateable for T where T: WidthSliceable + HasWidth {}
pub trait TruncationStrategy<T>
where
T: WidthSliceable + HasWidth,
{
fn truncate(&self, target: &T, width: usize) -> Option<T::Output>;
}
#[derive(Debug, Clone)]
pub enum TruncationStyle<T: BoundedWidth> {
#[allow(dead_code)]
Left(T),
#[allow(dead_code)]
Right(T),
#[allow(dead_code)]
Inner(T),
}
impl<T, S> TruncationStrategy<T> for TruncationStyle<S>
where
T: Truncateable,
S: BoundedWidth + WidthSliceable,
T::Output: Pushable<T::Output> + Pushable<S::Output> + Default + WidthSliceable,
{
fn truncate(&self, target: &T, width: usize) -> Option<T::Output> {
if width == 0 {
return None;
}
use TruncationStyle::*;
let mut result: T::Output = Default::default();
if let Width::Bounded(w) = target.width() {
if width >= w {
result.push(&target.slice_width(..));
return Some(result);
}
match self {
Left(ref sym) => {
result.push(&target.slice_width(..width.saturating_sub(sym.bounded_width())));
result.push(&sym.slice_width(..));
}
Right(ref sym) => {
result.push(&sym.slice_width(..));
result.push(&target.slice_width(
w.saturating_sub(width.saturating_sub(sym.bounded_width()))..,
));
}
Inner(ref sym) => {
let inner_width = sym.bounded_width();
let target_width = width.saturating_sub(inner_width);
let left_width = target_width / 2 + target_width % 2;
let right_width = target_width / 2;
let left_slice = target.slice_width(..left_width);
let right_slice = target.slice_width(w.saturating_sub(right_width)..);
result.push(&left_slice);
result.push(&sym.slice_width(..));
result.push(&right_slice);
}
}
} else {
match self {
Left(ref symbol) => {
result
.push(&target.slice_width(..width.saturating_sub(symbol.bounded_width())));
result.push(&symbol.slice_width(..));
}
Right(ref symbol) => {
result.push(&symbol.slice_width(..));
result
.push(&target.slice_width(..width.saturating_sub(symbol.bounded_width())));
}
Inner(s) => {
let inner_width = s.bounded_width();
let target_width = width.saturating_sub(inner_width);
let left_width = target_width / 2 + target_width % 2;
let right_width = target_width / 2;
let left_slice = target.slice_width(..left_width);
let right_slice = target.slice_width(..right_width);
result.push(&left_slice);
result.push(&s.slice_width(..));
result.push(&right_slice);
}
}
return Some(result);
}
Some(result)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::text::*;
use std::borrow::Cow;
#[test]
fn truncate_text() {
let fmt_1 = Tag::new("<1>", "</1>");
let fmt_2 = Tag::new("<2>", "</2>");
let fmt_3 = Tag::new("<3>", "</3>");
let mut spans: Spans<Tag> = Default::default();
spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("01234")));
spans.push(&Span::new(Cow::Borrowed(&fmt_3), Cow::Borrowed("56789")));
let truncator = {
let mut ellipsis = Spans::<Tag>::default();
ellipsis.push(&Span::new(Cow::Borrowed(&fmt_1), Cow::Borrowed("...")));
TruncationStyle::Left(ellipsis)
};
let actual = format!("{}", truncator.truncate(&spans, 9).unwrap());
let expected = String::from("<2>01234</2><3>5</3><1>...</1>");
assert_eq!(expected, actual);
}
#[test]
fn truncate_none() {
let fmt_2 = Tag::new("<2>", "</2>");
let fmt_3 = Tag::new("<3>", "</3>");
let mut spans: Spans<Tag> = Default::default();
spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("01234")));
spans.push(&Span::new(Cow::Borrowed(&fmt_3), Cow::Borrowed("56789")));
let truncator: TruncationStyle<Option<Spans<Tag>>> = TruncationStyle::Left(None);
let actual = format!("{}", truncator.truncate(&spans, 9).unwrap());
let expected = String::from("<2>01234</2><3>5678</3>");
assert_eq!(expected, actual);
}
#[test]
fn truncate_one() {
let fmt_1 = Tag::new("<1>", "</1>");
let fmt_2 = Tag::new("<2>", "</2>");
let mut spans: Spans<Tag> = Default::default();
spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("0")));
let truncator = {
let mut ellipsis = Spans::<Tag>::default();
ellipsis.push(&Span::new(Cow::Borrowed(&fmt_1), Cow::Borrowed("…")));
TruncationStyle::Left(ellipsis)
};
let actual = format!("{}", truncator.truncate(&spans, 1).unwrap());
let expected = String::from("<2>0</2>");
assert_eq!(expected, actual);
}
}