stylish_stringlike/widget/
repeat.rs

1use crate::text::{BoundedWidth, HasWidth, Joinable, Width, WidthSliceable};
2use std::ops::{Bound, RangeBounds};
3
4use std::marker::PhantomData;
5
6/// A text widget that repeats its content arbitrarily many times.
7#[derive(Clone, Debug)]
8pub struct Repeat<'a, T> {
9    content: T,
10    _marker: PhantomData<&'a ()>,
11}
12
13impl<'a, T> Repeat<'a, T> {
14    pub fn new(content: T) -> Repeat<'a, T> {
15        Repeat {
16            content,
17            _marker: Default::default(),
18        }
19    }
20}
21
22impl<'a, T> HasWidth for Repeat<'a, T> {
23    fn width(&self) -> Width {
24        Width::Unbounded
25    }
26}
27
28impl<'a, T, U> WidthSliceable for Repeat<'a, T>
29where
30    T: BoundedWidth + WidthSliceable<Output = T> + Joinable<T, Output = U>,
31    U: Default + Joinable<U, Output = U> + Joinable<T, Output = U> + BoundedWidth + 'a,
32{
33    type Output = U;
34    fn slice_width<R>(&self, range: R) -> Option<Self::Output>
35    where
36        R: RangeBounds<usize>,
37    {
38        use std::ops::Bound::*;
39        fn shift_range<R: RangeBounds<usize>>(
40            range: &R,
41            shift: i32,
42        ) -> Option<(Bound<usize>, Bound<usize>)> {
43            fn ss(target: usize, shift: i32) -> usize {
44                if shift < 0 {
45                    target.saturating_sub(shift.abs() as usize)
46                } else {
47                    target + shift as usize
48                }
49            }
50            let start = match range.start_bound() {
51                Excluded(s) => Excluded(ss(*s, shift)),
52                Included(s) => Included(ss(*s, shift)),
53                Unbounded => Unbounded,
54            };
55            let end = match range.end_bound() {
56                Excluded(e) => Excluded(ss(*e, shift)),
57                Included(e) => {
58                    if *e as i32 + shift < 0 {
59                        Excluded(0)
60                    } else {
61                        Included(ss(*e, shift))
62                    }
63                }
64                Unbounded => return None,
65            };
66            Some((start, end))
67        }
68        let target_width = match (range.start_bound(), range.end_bound()) {
69            (_, Unbounded) => return None,
70            (Unbounded, Excluded(e)) => *e,
71            (Unbounded, Included(e)) => *e + 1,
72            (Included(s), Excluded(e)) => e.saturating_sub(*s),
73            (Included(s), Included(e)) => (*e + 1).saturating_sub(*s),
74            (Excluded(s), Excluded(e)) => e.saturating_sub(*s + 1),
75            (Excluded(s), Included(e)) => (*e + 1).saturating_sub(*s + 1),
76        };
77        if target_width == 0 {
78            return None;
79        }
80        let self_width = self.content.bounded_width();
81
82        if self_width == 0 {
83            return None;
84        }
85        let mut res: U = Default::default();
86
87        let mut segment = 0;
88        let mut started = false;
89        loop {
90            let shifted_range = shift_range(&range, -((segment * self_width) as i32));
91            if let Some(shifted_range) = shifted_range {
92                let sliced = self.content.slice_width(shifted_range);
93                if let Some(sliced) = sliced {
94                    started = true;
95                    res = res.join(&sliced)
96                } else if started {
97                    break;
98                }
99            } else {
100                return None;
101            }
102            segment += 1;
103            if segment > 10 {
104                return Some(res);
105            }
106        }
107
108        Some(res)
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use super::*;
115    use crate::text::*;
116    use ansi_term::{Color, Style};
117    use std::borrow::Cow;
118    #[test]
119    fn make_repeat_trivial_null() {
120        let span = Span::<Style>::new(
121            Cow::Owned(Color::Yellow.normal()),
122            Cow::Owned(String::from("")),
123        );
124        let repeat = Repeat::new(span);
125        let actual = repeat.slice_width(1..100);
126        let expected = None;
127        assert_eq!(expected, actual);
128    }
129    #[test]
130    fn make_repeat_trivial_empty() {
131        let span = Span::<Style>::new(
132            Cow::Owned(Color::Yellow.normal()),
133            Cow::Owned(String::from("0")),
134        );
135        let repeat = Repeat::new(span);
136        let actual = repeat.slice_width(0..0);
137        let expected = None;
138        assert_eq!(expected, actual);
139    }
140    #[test]
141    fn make_repeat_trivial_inclusive() {
142        let span = Span::<Style>::new(
143            Cow::Owned(Color::Yellow.normal()),
144            Cow::Owned(String::from("0")),
145        );
146        let repeat = Repeat::new(span);
147        let res = repeat.slice_width(..=0);
148        let actual = format!("{}", res.unwrap());
149        let expected = format!("{}", Color::Yellow.paint("0"));
150        assert_eq!(expected, actual);
151    }
152    #[test]
153    fn make_repeat_trivial_unbounded() {
154        let span = Span::<Style>::new(
155            Cow::Owned(Color::Yellow.normal()),
156            Cow::Owned(String::from("0")),
157        );
158        let repeat = Repeat::new(span);
159        let actual = repeat.slice_width(0..);
160        let expected = None;
161        assert_eq!(expected, actual);
162    }
163    #[test]
164    fn make_repeat_trivial_single() {
165        let span = Span::<Style>::new(
166            Cow::Owned(Color::Yellow.normal()),
167            Cow::Owned(String::from("0")),
168        );
169        let repeat = Repeat::new(span);
170        let res = repeat.slice_width(..1);
171        let actual = format!("{}", res.unwrap());
172        let expected = format!("{}", Color::Yellow.paint("0"));
173        assert_eq!(expected, actual);
174    }
175    #[test]
176    fn make_repeat_trivial_multiple() {
177        let span = Span::<Style>::new(
178            Cow::Owned(Color::Yellow.normal()),
179            Cow::Owned(String::from("0")),
180        );
181        let repeat = Repeat::new(span);
182        let res = repeat.slice_width(..2);
183        let actual = format!("{}", res.unwrap());
184        let expected = format!("{}", Color::Yellow.paint("00"));
185        assert_eq!(expected, actual);
186    }
187    #[test]
188    fn make_repeat_long() {
189        let span = Span::<Style>::new(
190            Cow::Owned(Color::Yellow.normal()),
191            Cow::Owned(String::from("01234")),
192        );
193        let repeat = Repeat::new(span);
194        let res = repeat.slice_width(1..14);
195        let actual = format!("{}", res.unwrap());
196        let expected = format!("{}", Color::Yellow.paint("1234012340123"));
197        assert_eq!(expected, actual);
198    }
199    #[test]
200    fn make_repeat_short() {
201        let span = Span::<Style>::new(
202            Cow::Owned(Color::Yellow.normal()),
203            Cow::Owned(String::from("01234")),
204        );
205        let repeat = Repeat::new(span);
206        let res = repeat.slice_width(..3);
207        let actual = format!("{}", res.unwrap());
208        let expected = format!("{}", Color::Yellow.paint("012"));
209        assert_eq!(expected, actual);
210    }
211    #[test]
212    fn make_repeat_mid_inclusive() {
213        let span = Span::<Style>::new(
214            Cow::Owned(Color::Yellow.normal()),
215            Cow::Owned(String::from("01234")),
216        );
217        let repeat = Repeat::new(span);
218        let res = repeat.slice_width(1..=3);
219        let actual = format!("{}", res.unwrap());
220        let expected = format!("{}", Color::Yellow.paint("123"));
221        assert_eq!(expected, actual);
222    }
223    #[test]
224    fn make_repeat_mid_ex_in() {
225        use std::ops::Bound::*;
226        let span = Span::<Style>::new(
227            Cow::Owned(Color::Yellow.normal()),
228            Cow::Owned(String::from("01234")),
229        );
230        let repeat = Repeat::new(span);
231        let res = repeat.slice_width((Excluded(1), Included(3)));
232        let actual = format!("{}", res.unwrap());
233        let expected = format!("{}", Color::Yellow.paint("23"));
234        assert_eq!(expected, actual);
235    }
236    #[test]
237    fn make_repeat_mid_ex_ex() {
238        use std::ops::Bound::*;
239        let span = Span::<Style>::new(
240            Cow::Owned(Color::Yellow.normal()),
241            Cow::Owned(String::from("01234")),
242        );
243        let repeat = Repeat::new(span);
244        let res = repeat.slice_width((Excluded(1), Excluded(3)));
245        let actual = format!("{}", res.unwrap());
246        let expected = format!("{}", Color::Yellow.paint("2"));
247        assert_eq!(expected, actual);
248    }
249    #[test]
250    fn make_repeat_left() {
251        let span = Span::<Style>::new(
252            Cow::Owned(Color::Yellow.normal()),
253            Cow::Owned(String::from("01234")),
254        );
255        let repeat = Repeat::new(span);
256        let res = repeat.slice_width(1..9);
257        let actual = format!("{}", res.unwrap());
258        let expected = format!("{}", Color::Yellow.paint("12340123"));
259        assert_eq!(expected, actual);
260    }
261    #[test]
262    fn make_repeat_shifted_long() {
263        let span = Span::<Style>::new(
264            Cow::Owned(Color::Yellow.normal()),
265            Cow::Owned(String::from("01234")),
266        );
267        let repeat = Repeat::new(span);
268        let res = repeat.slice_width(7..18);
269        let actual = format!("{}", res.unwrap());
270        let expected = format!("{}", Color::Yellow.paint("23401234012"));
271        assert_eq!(expected, actual);
272    }
273    #[test]
274    fn make_repeat_shifted_extra_long() {
275        let span = Span::<Style>::new(
276            Cow::Owned(Color::Yellow.normal()),
277            Cow::Owned(String::from("01234")),
278        );
279        let repeat = Repeat::new(span);
280        let res = repeat.slice_width(7..23);
281        let actual = format!("{}", res.unwrap());
282        let expected = format!("{}", Color::Yellow.paint("2340123401234012"));
283        assert_eq!(expected, actual);
284    }
285}