stylish_stringlike/widget/
truncatable.rs1use crate::text::{BoundedWidth, HasWidth, Pushable, Width, WidthSliceable};
2
3pub trait Truncateable: HasWidth + WidthSliceable {}
5
6impl<'a, T> Truncateable for T where T: WidthSliceable + HasWidth {}
7
8pub trait TruncationStrategy<T>
10where
11 T: WidthSliceable + HasWidth,
12{
13 fn truncate(&self, target: &T, width: usize) -> Option<T::Output>;
15}
16
17#[derive(Debug, Clone)]
19pub enum TruncationStyle<T: BoundedWidth> {
20 #[allow(dead_code)]
22 Left(T),
23 #[allow(dead_code)]
25 Right(T),
26 #[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}