stylish_stringlike/widget/
mod.rs

1//! Provides some widgets for displaying text objects in the [`crate::text`] module.
2mod hbox;
3mod repeat;
4mod text_widget;
5mod truncatable;
6pub use hbox::*;
7pub use repeat::*;
8pub use text_widget::*;
9pub use truncatable::*;
10
11#[cfg(test)]
12mod test {
13    use super::*;
14    use crate::text::{Pushable, Span, Spans, WidthSliceable};
15    use crate::widget::truncatable::TruncationStyle;
16    use ansi_term::{ANSIStrings, Color, Style};
17    use std::borrow::Cow;
18    fn make_spans(style: &Style, text: &str) -> Spans<Style> {
19        let ansistring = style.paint(text);
20        let span: Span<Style> = ansistring.into();
21        let mut spans: Spans<Style> = Default::default();
22        spans.push(&span);
23        spans
24    }
25
26    #[test]
27    fn truncate_trivial_left() {
28        let style = Color::Red.normal();
29        let content = "0123456";
30        let text = make_spans(&style, content);
31        let ellipsis_style = Color::Blue.normal();
32        let ellipsis = "…";
33        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
34        let truncation = TruncationStyle::Left(Some(ellipsis_span));
35        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
36        let mut hbox = HBox::new();
37        hbox.push(Box::new(widget));
38        let actual = format!("{}", hbox.truncate(4));
39        let expected = format!(
40            "{}",
41            ANSIStrings(&[style.paint("012"), ellipsis_style.paint(ellipsis)])
42        );
43        assert_eq!(expected, actual);
44    }
45
46    #[test]
47    fn truncate_compound_span_left() {
48        let style0 = Color::Red.normal();
49        let style1 = Color::Green.normal();
50        let content0 = "0123456";
51        let content1 = "789";
52        let text = {
53            let mut text = make_spans(&style0, content0);
54            text.push(&make_spans(&style1, content1));
55            text
56        };
57        let ellipsis_style = Color::Blue.normal();
58        let ellipsis = "…";
59        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
60        let truncation = TruncationStyle::Left(Some(ellipsis_span));
61        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
62        let mut hbox = HBox::new();
63        hbox.push(Box::new(widget));
64        let actual = format!("{}", hbox.truncate(4));
65        let expected = format!(
66            "{}",
67            ANSIStrings(&[style0.paint("012"), ellipsis_style.paint(ellipsis)])
68        );
69        assert_eq!(expected, actual);
70    }
71
72    #[test]
73    fn truncate_compound_span_2_left() {
74        let style0 = Color::Red.normal();
75        let style1 = Color::Green.normal();
76        let content0 = "0123456";
77        let content1 = "789";
78        let text = {
79            let mut text = make_spans(&style0, content0);
80            text.push(&make_spans(&style1, content1));
81            text
82        };
83        let ellipsis_style = Color::Blue.normal();
84        let ellipsis = "…";
85        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
86        let truncation = TruncationStyle::Left(Some(ellipsis_span));
87        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
88        let mut hbox = HBox::new();
89        hbox.push(Box::new(widget));
90        let actual = format!("{}", hbox.truncate(9));
91        let expected = format!(
92            "{}",
93            ANSIStrings(&[
94                style0.paint(content0),
95                style1.paint("7"),
96                ellipsis_style.paint(ellipsis),
97            ]),
98        );
99        assert_eq!(expected, actual);
100    }
101
102    #[test]
103    fn truncate_two_widgets_first_left() {
104        let style0 = Color::Red.normal();
105        let style1 = Color::Green.normal();
106        let content0 = "01234";
107        let content1 = "56789";
108        let first_span = make_spans(&style0, content0);
109        let second_span = make_spans(&style1, content1);
110        let ellipsis_style = Color::Blue.normal();
111        let ellipsis = "…";
112        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
113        let truncation = TruncationStyle::Left(Some(ellipsis_span));
114        let hbox = vec![&first_span, &second_span]
115            .iter()
116            .map(|s| {
117                let foo: Box<dyn Fitable<_>> =
118                    Box::new(TextWidget::<Spans<_>, TruncationStyle<_>>::new(
119                        Cow::Borrowed(s),
120                        Cow::Borrowed(&truncation),
121                    ));
122                foo
123            })
124            .collect::<HBox<Spans<_>>>();
125        let actual = format!("{}", hbox.truncate(8));
126        let expected = format!(
127            "{}",
128            ANSIStrings(&[
129                style0.paint("012"),
130                ellipsis_style.paint(ellipsis),
131                style1.paint("567"),
132                ellipsis_style.paint(ellipsis),
133            ])
134        );
135        assert_eq!(expected, actual);
136    }
137
138    #[test]
139    fn truncate_trivial_left_noop() {
140        let style = Color::Red.normal();
141        let content = "0123456";
142        let string = style.paint(content);
143        let text = make_spans(&style, content);
144        let ellipsis_style = Color::Blue.normal();
145        let ellipsis = "…";
146        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
147        let truncation = TruncationStyle::Left(Some(ellipsis_span));
148        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
149        let mut hbox = HBox::new();
150        hbox.push(Box::new(widget));
151        let actual = format!("{}", hbox.truncate(7));
152        let expected = format!("{}", ANSIStrings(&[string]));
153        assert_eq!(expected, actual);
154    }
155
156    #[test]
157    fn trunctate_infinite_left() {
158        let span = Span::<Style>::new(
159            Cow::Owned(Color::Blue.normal()),
160            Cow::Owned("=".to_string()),
161        );
162        use std::ops::Bound;
163        span.slice_width((Bound::Unbounded, Bound::Included(5)));
164        let repeat_widget = Repeat::new(span);
165        let truncator_style = Color::Black.normal();
166        let truncator_text = ".";
167        let truncator_span = make_spans(&truncator_style, truncator_text);
168        let truncation = TruncationStyle::Left(Some(truncator_span));
169        let repeat_text_widget =
170            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
171        let mut hbox = HBox::new();
172        hbox.push(Box::new(repeat_text_widget));
173        let actual = format!("{}", hbox.truncate(5));
174        let expected = format!(
175            "{}",
176            ANSIStrings(&[
177                Color::Blue.normal().paint("===="),
178                truncator_style.paint(truncator_text),
179            ])
180        );
181        assert_eq!(expected, actual);
182    }
183
184    #[test]
185    fn truncate_trivial_right() {
186        let style = Color::Red.normal();
187        let content = "0123456";
188        let text = make_spans(&style, content);
189        let ellipsis_style = Color::Blue.normal();
190        let ellipsis = "…";
191        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
192        let truncation = TruncationStyle::Right(Some(ellipsis_span));
193        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
194        let mut hbox = HBox::new();
195        hbox.push(Box::new(widget));
196        let actual = format!("{}", hbox.truncate(4));
197        let expected = format!(
198            "{}",
199            ANSIStrings(&[ellipsis_style.paint(ellipsis), style.paint("456")])
200        );
201        assert_eq!(expected, actual);
202    }
203
204    #[test]
205    fn truncate_compound_span_right() {
206        let style0 = Color::Red.normal();
207        let style1 = Color::Green.normal();
208        let content0 = "0123456";
209        let content1 = "789";
210        let text = {
211            let mut text = make_spans(&style0, content0);
212            text.push(&make_spans(&style1, content1));
213            text
214        };
215        let ellipsis_style = Color::Blue.normal();
216        let ellipsis = "…";
217        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
218        let truncation = TruncationStyle::Right(Some(ellipsis_span));
219        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
220        let mut hbox = HBox::new();
221        hbox.push(Box::new(widget));
222        let actual = format!("{}", hbox.truncate(6));
223        let expected = format!(
224            "{}",
225            ANSIStrings(&[
226                ellipsis_style.paint(ellipsis),
227                style0.paint("56"),
228                style1.paint("789"),
229            ])
230        );
231        assert_eq!(expected, actual);
232    }
233
234    #[test]
235    fn truncate_compound_span_2_right() {
236        let style0 = Color::Red.normal();
237        let style1 = Color::Green.normal();
238        let content0 = "0123456";
239        let content1 = "789";
240        let text = {
241            let mut text = make_spans(&style0, content0);
242            text.push(&make_spans(&style1, content1));
243            text
244        };
245        let ellipsis_style = Color::Blue.normal();
246        let ellipsis = "…";
247        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
248        let truncation = TruncationStyle::Right(Some(ellipsis_span));
249        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
250        let mut hbox = HBox::new();
251        hbox.push(Box::new(widget));
252        let actual = format!("{}", hbox.truncate(9));
253        let expected = format!(
254            "{}",
255            ANSIStrings(&[
256                ellipsis_style.paint(ellipsis),
257                style0.paint("23456"),
258                style1.paint(content1),
259            ])
260        );
261        assert_eq!(expected, actual);
262    }
263
264    #[test]
265    fn truncate_two_widgets_first_right() {
266        let style0 = Color::Red.normal();
267        let style1 = Color::Green.normal();
268        let content0 = "01234";
269        let content1 = "56789";
270        let first_span = make_spans(&style0, content0);
271        let second_span = make_spans(&style1, content1);
272        let ellipsis_style = Color::Blue.normal();
273        let ellipsis = "…";
274        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
275        let truncation = TruncationStyle::Right(Some(ellipsis_span));
276        let hbox = vec![&first_span, &second_span]
277            .iter()
278            .map(|s| {
279                let b: Box<dyn Fitable<_>> =
280                    Box::new(TextWidget::<Spans<_>, TruncationStyle<_>>::new(
281                        Cow::Borrowed(s),
282                        Cow::Borrowed(&truncation),
283                    ));
284                b
285            })
286            .collect::<HBox<_>>();
287        let actual = format!("{}", hbox.truncate(8));
288        let expected = format!(
289            "{}",
290            ANSIStrings(&[
291                ellipsis_style.paint(ellipsis),
292                style0.paint("234"),
293                ellipsis_style.paint(ellipsis),
294                style1.paint("789"),
295            ])
296        );
297        assert_eq!(expected, actual);
298    }
299
300    #[test]
301    fn truncate_trivial_right_noop() {
302        let style = Color::Red.normal();
303        let content = "0123456";
304        let string = style.paint(content);
305        let text = make_spans(&style, content);
306        let ellipsis_style = Color::Blue.normal();
307        let ellipsis = "…";
308        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
309        let truncation = TruncationStyle::Right(Some(ellipsis_span));
310        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
311        let mut hbox = HBox::new();
312        hbox.push(Box::new(widget));
313        let actual = format!("{}", hbox.truncate(7));
314        let expected = format!("{}", ANSIStrings(&[string]));
315        assert_eq!(expected, actual);
316    }
317
318    #[test]
319    fn trunctate_infinite_right() {
320        let repeat_widget = Repeat::new(Span::<Style>::new(
321            Cow::Owned(Color::Blue.normal()),
322            Cow::Owned("=".to_string()),
323        ));
324        let truncator_style = Color::Black.normal();
325        let truncator_str = ".";
326        let truncator_span = make_spans(&truncator_style, truncator_str);
327        let truncation = TruncationStyle::Right(Some(truncator_span));
328        let repeat_text_widget =
329            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
330        let mut hbox = HBox::new();
331        hbox.push(Box::new(repeat_text_widget));
332        let actual = format!("{}", hbox.truncate(5));
333        let expected = format!(
334            "{}",
335            ANSIStrings(&[
336                truncator_style.paint(truncator_str),
337                Color::Blue.normal().paint("===="),
338            ])
339        );
340        assert_eq!(expected, actual);
341    }
342
343    #[test]
344    fn truncate_trivial_inner() {
345        let style = Color::Red.normal();
346        let content = "0123456";
347        let text = make_spans(&style, content);
348        let ellipsis_style = Color::Blue.normal();
349        let ellipsis = "…";
350        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
351        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
352        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
353        let mut hbox = HBox::new();
354        hbox.push(Box::new(widget));
355        let actual = format!("{}", hbox.truncate(4));
356        let expected = format!(
357            "{}",
358            ANSIStrings(&[
359                style.paint("01"),
360                ellipsis_style.paint(ellipsis),
361                style.paint("6"),
362            ])
363        );
364        assert_eq!(expected, actual);
365    }
366
367    #[test]
368    fn truncate_compound_span_inner() {
369        let style0 = Color::Red.normal();
370        let style1 = Color::Green.normal();
371        let content0 = "01234";
372        let content1 = "56789";
373        let text = {
374            let mut text = make_spans(&style0, content0);
375            text.push(&make_spans(&style1, content1));
376            text
377        };
378        let ellipsis_style = Color::Blue.normal();
379        let ellipsis = "…";
380        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
381        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
382        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
383        let mut hbox = HBox::new();
384        hbox.push(Box::new(widget));
385        let actual = format!("{}", hbox.truncate(6));
386        let expected = format!(
387            "{}",
388            ANSIStrings(&[
389                style0.paint("012"),
390                ellipsis_style.paint(ellipsis),
391                style1.paint("89"),
392            ])
393        );
394        assert_eq!(expected, actual);
395    }
396
397    #[test]
398    fn truncate_compound_span_2_inner() {
399        let style0 = Color::Red.normal();
400        let style1 = Color::Green.normal();
401        let content0 = "0123456";
402        let content1 = "789";
403        let text = {
404            let mut text = make_spans(&style0, content0);
405            text.push(&make_spans(&style1, content1));
406            text
407        };
408        let ellipsis_style = Color::Blue.normal();
409        let ellipsis = "…";
410        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
411        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
412        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
413        let mut hbox = HBox::new();
414        hbox.push(Box::new(widget));
415        let actual = format!("{}", hbox.truncate(9));
416        let expected = format!(
417            "{}",
418            ANSIStrings(&[
419                style0.paint("0123"),
420                ellipsis_style.paint(ellipsis),
421                style0.paint("6"),
422                style1.paint(content1),
423            ])
424        );
425        eprintln!("expected: {}", expected);
426        eprintln!("actual:   {}", actual);
427        assert_eq!(expected, actual);
428    }
429
430    #[test]
431    fn truncate_two_widgets_first_inner() {
432        let style0 = Color::Red.normal();
433        let style1 = Color::Green.normal();
434        let content0 = "01234";
435        let content1 = "56789";
436        let first_span = make_spans(&style0, content0);
437        let second_span = make_spans(&style1, content1);
438        let ellipsis_style = Color::Blue.normal();
439        let ellipsis = "…";
440        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
441        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
442        let hbox = vec![&first_span, &second_span]
443            .iter()
444            .map(|s| {
445                let b: Box<dyn Fitable<_>> =
446                    Box::new(TextWidget::<Spans<_>, TruncationStyle<_>>::new(
447                        Cow::Borrowed(s),
448                        Cow::Borrowed(&truncation),
449                    ));
450                b
451            })
452            .collect::<HBox<_>>();
453
454        let actual = format!("{}", hbox.truncate(8));
455        let expected = format!(
456            "{}",
457            ANSIStrings(&[
458                style0.paint("01"),
459                ellipsis_style.paint(ellipsis),
460                style0.paint("4"),
461                style1.paint("56"),
462                ellipsis_style.paint(ellipsis),
463                style1.paint("9"),
464            ])
465        );
466        assert_eq!(expected, actual);
467    }
468
469    #[test]
470    fn truncate_trivial_inner_noop() {
471        let style = Color::Red.normal();
472        let content = "0123456";
473        let text = make_spans(&style, content);
474        let ellipsis_style = Color::Blue.normal();
475        let ellipsis = "…";
476        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
477        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
478        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
479        let mut hbox = HBox::new();
480        hbox.push(Box::new(widget));
481        let actual = format!("{}", hbox.truncate(7));
482        let expected = format!("{}", ANSIStrings(&[style.paint(content)]));
483        assert_eq!(expected, actual);
484    }
485
486    #[test]
487    fn trunctate_infinite_inner() {
488        let repeat_widget = Repeat::new(Span::<Style>::new(
489            Cow::Owned(Color::Blue.normal()),
490            Cow::Owned("=".to_string()),
491        ));
492        let truncator_style = Color::Black.normal();
493        let truncator_text = ".";
494        let truncator = truncator_style.paint(truncator_text);
495        let truncator_span = make_spans(&truncator_style, truncator_text);
496        let truncation = TruncationStyle::Inner(Some(truncator_span));
497        let repeat_text_widget =
498            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
499        let mut hbox = HBox::new();
500        hbox.push(Box::new(repeat_text_widget));
501        let actual = format!("{}", hbox.truncate(5));
502        let expected = format!(
503            "{}",
504            ANSIStrings(&[
505                Color::Blue.normal().paint("=="),
506                truncator.clone(),
507                Color::Blue.normal().paint("=="),
508            ])
509        );
510        assert_eq!(expected, actual);
511    }
512
513    #[test]
514    fn trunctate_infinite_none_left() {
515        let span = Span::<Style>::new(
516            Cow::Owned(Color::Blue.normal()),
517            Cow::Owned("=".to_string()),
518        );
519        use std::ops::Bound;
520        span.slice_width((Bound::Unbounded, Bound::Included(5)));
521        let repeat_widget = Repeat::new(span);
522        let truncator_style = Color::Black.normal();
523        let truncator_text = ".";
524        let truncator_span = make_spans(&truncator_style, truncator_text);
525        let truncation = TruncationStyle::Left(Some(truncator_span));
526        let repeat_text_widget =
527            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
528        let mut hbox = HBox::new();
529        hbox.push(Box::new(repeat_text_widget));
530        let actual = format!("{}", hbox.truncate(0));
531        let expected = String::new();
532        assert_eq!(expected, actual);
533    }
534
535    #[test]
536    fn trunctate_infinite_none_right() {
537        let span = Span::<Style>::new(
538            Cow::Owned(Color::Blue.normal()),
539            Cow::Owned("=".to_string()),
540        );
541        use std::ops::Bound;
542        span.slice_width((Bound::Unbounded, Bound::Included(5)));
543        let repeat_widget = Repeat::new(span);
544        let truncator_style = Color::Black.normal();
545        let truncator_text = ".";
546        let truncator_span = make_spans(&truncator_style, truncator_text);
547        let truncation = TruncationStyle::Right(Some(truncator_span));
548        let repeat_text_widget =
549            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
550        let mut hbox = HBox::new();
551        hbox.push(Box::new(repeat_text_widget));
552        let actual = format!("{}", hbox.truncate(0));
553        let expected = String::new();
554        assert_eq!(expected, actual);
555    }
556
557    #[test]
558    fn trunctate_infinite_none_inner() {
559        let span = Span::<Style>::new(
560            Cow::Owned(Color::Blue.normal()),
561            Cow::Owned("=".to_string()),
562        );
563        use std::ops::Bound;
564        span.slice_width((Bound::Unbounded, Bound::Included(5)));
565        let repeat_widget = Repeat::new(span);
566        let truncator_style = Color::Black.normal();
567        let truncator_text = ".";
568        let truncator_span = make_spans(&truncator_style, truncator_text);
569        let truncation = TruncationStyle::Inner(Some(truncator_span));
570        let repeat_text_widget =
571            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
572        let mut hbox = HBox::new();
573        hbox.push(Box::new(repeat_text_widget));
574        let actual = format!("{}", hbox.truncate(0));
575        let expected = String::new();
576        assert_eq!(expected, actual);
577    }
578
579    #[test]
580    fn trunctate_infinite_only_symbol() {
581        let span = Span::<Style>::new(
582            Cow::Owned(Color::Blue.normal()),
583            Cow::Owned("=".to_string()),
584        );
585        use std::ops::Bound;
586        span.slice_width((Bound::Unbounded, Bound::Included(5)));
587        let repeat_widget = Repeat::new(span);
588        let truncator_style = Color::Black.normal();
589        let truncator_text = ".";
590        let truncator_span = make_spans(&truncator_style, truncator_text);
591        let truncation = TruncationStyle::Inner(Some(truncator_span));
592        let repeat_text_widget =
593            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
594        let mut hbox = HBox::new();
595        hbox.push(Box::new(repeat_text_widget));
596        let actual = format!("{}", hbox.truncate(1));
597        let expected = format!("{}", truncator_style.paint(truncator_text));
598        assert_eq!(expected, actual);
599    }
600}