stylish_stringlike/widget/
truncatable.rs

1use crate::text::{BoundedWidth, HasWidth, Pushable, Width, WidthSliceable};
2
3/// Objects that have width and are sliceable on width are truncateable.
4pub trait Truncateable: HasWidth + WidthSliceable {}
5
6impl<'a, T> Truncateable for T where T: WidthSliceable + HasWidth {}
7
8/// Functionality for truncating objects using some strategy.
9pub trait TruncationStrategy<T>
10where
11    T: WidthSliceable + HasWidth,
12{
13    /// Truncates target to width. Output should have a width equal to width.
14    fn truncate(&self, target: &T, width: usize) -> Option<T::Output>;
15}
16
17/// Styles for simple truncation.
18#[derive(Debug, Clone)]
19pub enum TruncationStyle<T: BoundedWidth> {
20    /// Keeps the left text, truncates text on the right. Optional symbol added when truncation occurs.
21    #[allow(dead_code)]
22    Left(T),
23    /// Keeps the right text, truncates text on the left. Optional symbol added when truncation occurs.
24    #[allow(dead_code)]
25    Right(T),
26    /// Keeps the outside text, truncates text on the inside. Optional symbol added when truncation occurs.
27    #[allow(dead_code)]
28    Inner(T),
29}
30
31impl<T, S> TruncationStrategy<T> for TruncationStyle<S>
32where
33    T: Truncateable,
34    S: BoundedWidth + WidthSliceable,
35    T::Output: Pushable<T::Output> + Pushable<S::Output> + Default + WidthSliceable,
36{
37    fn truncate(&self, target: &T, width: usize) -> Option<T::Output> {
38        if width == 0 {
39            return None;
40        }
41        use TruncationStyle::*;
42        let mut result: T::Output = Default::default();
43        if let Width::Bounded(w) = target.width() {
44            if width >= w {
45                result.push(&target.slice_width(..));
46                return Some(result);
47            }
48            match self {
49                Left(ref sym) => {
50                    result.push(&target.slice_width(..width.saturating_sub(sym.bounded_width())));
51                    result.push(&sym.slice_width(..));
52                }
53                Right(ref sym) => {
54                    result.push(&sym.slice_width(..));
55                    result.push(&target.slice_width(
56                        w.saturating_sub(width.saturating_sub(sym.bounded_width()))..,
57                    ));
58                }
59                Inner(ref sym) => {
60                    let inner_width = sym.bounded_width();
61                    let target_width = width.saturating_sub(inner_width);
62                    let left_width = target_width / 2 + target_width % 2;
63                    let right_width = target_width / 2;
64                    let left_slice = target.slice_width(..left_width);
65                    let right_slice = target.slice_width(w.saturating_sub(right_width)..);
66                    result.push(&left_slice);
67                    result.push(&sym.slice_width(..));
68                    result.push(&right_slice);
69                }
70            }
71        } else {
72            match self {
73                Left(ref symbol) => {
74                    result
75                        .push(&target.slice_width(..width.saturating_sub(symbol.bounded_width())));
76                    result.push(&symbol.slice_width(..));
77                }
78                Right(ref symbol) => {
79                    result.push(&symbol.slice_width(..));
80                    result
81                        .push(&target.slice_width(..width.saturating_sub(symbol.bounded_width())));
82                }
83                Inner(s) => {
84                    let inner_width = s.bounded_width();
85                    let target_width = width.saturating_sub(inner_width);
86                    let left_width = target_width / 2 + target_width % 2;
87                    let right_width = target_width / 2;
88                    let left_slice = target.slice_width(..left_width);
89                    let right_slice = target.slice_width(..right_width);
90                    result.push(&left_slice);
91                    result.push(&s.slice_width(..));
92                    result.push(&right_slice);
93                }
94            }
95            return Some(result);
96        }
97
98        Some(result)
99    }
100}
101
102#[cfg(test)]
103mod test {
104    use super::*;
105    use crate::text::*;
106    use std::borrow::Cow;
107    #[test]
108    fn truncate_text() {
109        let fmt_1 = Tag::new("<1>", "</1>");
110        let fmt_2 = Tag::new("<2>", "</2>");
111        let fmt_3 = Tag::new("<3>", "</3>");
112        let mut spans: Spans<Tag> = Default::default();
113        spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("01234")));
114        spans.push(&Span::new(Cow::Borrowed(&fmt_3), Cow::Borrowed("56789")));
115        let truncator = {
116            let mut ellipsis = Spans::<Tag>::default();
117            ellipsis.push(&Span::new(Cow::Borrowed(&fmt_1), Cow::Borrowed("...")));
118            TruncationStyle::Left(ellipsis)
119        };
120        let actual = format!("{}", truncator.truncate(&spans, 9).unwrap());
121        let expected = String::from("<2>01234</2><3>5</3><1>...</1>");
122        assert_eq!(expected, actual);
123    }
124    #[test]
125    fn truncate_none() {
126        let fmt_2 = Tag::new("<2>", "</2>");
127        let fmt_3 = Tag::new("<3>", "</3>");
128        let mut spans: Spans<Tag> = Default::default();
129        spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("01234")));
130        spans.push(&Span::new(Cow::Borrowed(&fmt_3), Cow::Borrowed("56789")));
131        let truncator: TruncationStyle<Option<Spans<Tag>>> = TruncationStyle::Left(None);
132        let actual = format!("{}", truncator.truncate(&spans, 9).unwrap());
133        let expected = String::from("<2>01234</2><3>5678</3>");
134        assert_eq!(expected, actual);
135    }
136    #[test]
137    fn truncate_one() {
138        let fmt_1 = Tag::new("<1>", "</1>");
139        let fmt_2 = Tag::new("<2>", "</2>");
140        let mut spans: Spans<Tag> = Default::default();
141        spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("0")));
142        let truncator = {
143            let mut ellipsis = Spans::<Tag>::default();
144            ellipsis.push(&Span::new(Cow::Borrowed(&fmt_1), Cow::Borrowed("…")));
145            TruncationStyle::Left(ellipsis)
146        };
147        let actual = format!("{}", truncator.truncate(&spans, 1).unwrap());
148        let expected = String::from("<2>0</2>");
149        assert_eq!(expected, actual);
150    }
151}