stylish_stringlike/text/spans/
mod.rs

1mod search_tree;
2mod span;
3use super::{
4    BoundedWidth, Expandable, HasWidth, Joinable, Paintable, Pushable, RawText, Replaceable,
5    Sliceable, Width,
6};
7
8use regex::{Captures, Regex, Replacer};
9use search_tree::SearchTree;
10pub use span::Span;
11use std::borrow::{Borrow, Cow};
12use std::fmt;
13use std::iter::{once, repeat, FromIterator};
14use std::ops::{Deref, RangeBounds};
15/// A string with various styles applied to the span.
16/// Styles do not not cascade. Only the most recent style
17/// applies to the current character.
18#[derive(Clone, Debug)]
19pub struct Spans<T> {
20    content: String,
21    /// Byte-indexed map of spans
22    spans: SearchTree<T>,
23}
24
25impl<T> Default for Spans<T> {
26    fn default() -> Self {
27        Self {
28            content: String::new(),
29            spans: Default::default(),
30        }
31    }
32}
33
34impl<T: PartialEq> Eq for Spans<T> {}
35
36impl<T: PartialEq> PartialEq for Spans<T> {
37    fn eq(&self, other: &Spans<T>) -> bool {
38        self.content == other.content && self.spans == other.spans
39    }
40}
41
42impl<T> Spans<T> {
43    #[allow(clippy::type_complexity)]
44    fn segments(
45        &self,
46    ) -> Box<dyn Iterator<Item = ((&usize, Cow<'_, T>), Option<(&usize, Cow<'_, T>)>)> + '_>
47    where
48        T: Clone + Default,
49    {
50        if self.spans.contains_key(0) {
51            Box::new(
52                self.spans
53                    .iter()
54                    .map(|(key, val)| (key, Cow::Borrowed(val)))
55                    .zip(
56                        self.spans
57                            .iter()
58                            .map(|(key, val)| (key, Cow::Borrowed(val)))
59                            .map(Some)
60                            .skip(1)
61                            .chain(repeat(None)),
62                    ),
63            )
64        } else {
65            Box::new(
66                once((&0, Cow::Owned(Default::default())))
67                    .chain(
68                        self.spans
69                            .iter()
70                            .map(|(key, val)| (key, Cow::Borrowed(val))),
71                    )
72                    .zip(
73                        self.spans
74                            .iter()
75                            .map(|(key, val)| (key, Cow::Borrowed(val)))
76                            .map(Some)
77                            .chain(repeat(None)),
78                    ),
79            )
80        }
81    }
82    /// Returns the spans of text contained in this object.
83    pub fn spans(&self) -> impl Iterator<Item = Span<'_, T>>
84    where
85        T: Clone + Default,
86    {
87        self.segments()
88            .filter_map(move |((first_key, style), second)| {
89                let second_key = if let Some((second_key, _)) = second {
90                    *second_key
91                } else {
92                    self.content.len()
93                };
94                #[allow(clippy::manual_map)]
95                if let Some(ref s) = self.content.get(*first_key..second_key) {
96                    Some(Span::new(style, Cow::Borrowed(s)))
97                } else {
98                    // This represents an invalid state in the spans.
99                    // One of the spans is actually out of the range of the length of the string.
100                    None
101                }
102            })
103    }
104    fn trim(&mut self) {
105        self.spans.trim(self.content.len().saturating_sub(1));
106    }
107}
108
109impl<T: Clone + PartialEq> Pushable<Spans<T>> for Spans<T> {
110    fn push(&mut self, other: &Spans<T>) {
111        // copy_with_shift always succeeds because len is always positive so no
112        // risk converting
113        self.spans
114            .copy_with_shift(&other.spans, .., self.content.len())
115            .unwrap();
116        self.content.push_str(&other.content);
117        self.trim();
118    }
119}
120
121impl<'a, T: Clone + PartialEq> Pushable<Span<'a, T>> for Spans<T> {
122    fn push(&mut self, other: &Span<'a, T>) {
123        self.spans
124            .insert(self.content.len(), other.style().clone().into_owned());
125        self.content.push_str(other.raw_ref());
126        self.spans.dedup();
127        self.trim();
128    }
129}
130
131impl<T> Pushable<&str> for Spans<T> {
132    fn push(&mut self, other: &&str) {
133        self.content.push_str(other);
134    }
135}
136
137impl<T> Pushable<str> for Spans<T> {
138    fn push(&mut self, other: &str) {
139        self.content.push_str(other);
140    }
141}
142
143impl<T: Default + Clone + PartialEq> Expandable for Spans<T> {
144    fn expand(&self, capture: &Captures) -> Self {
145        let mut result: Spans<T> = Default::default();
146        for span in self.spans() {
147            let mut dst = String::new();
148            capture.expand(span.raw_ref(), &mut dst);
149            let new_span = Span::<T>::borrowed(&span.style(), &dst);
150            result.push(&new_span);
151        }
152        result
153    }
154}
155
156// Did a specific impl for this because I haven't figured out how to get
157// a blanket impl over string that works properly
158impl<'a, T: Clone + PartialEq> Replaceable<&'a str> for Spans<T> {
159    fn replace(&self, from: &str, replacer: &'a str) -> Self {
160        let mut result = Spans {
161            content: String::new(),
162            spans: SearchTree::new(),
163        };
164
165        let mut last_end = 0;
166        for (start, part) in self.content.match_indices(from) {
167            if let Some(spans) = self.slice(last_end..start) {
168                result.push(&spans);
169                if let Some(mut r) = self.slice(start..start + part.len()) {
170                    r.content = String::from(replacer);
171                    result.push(&r);
172                }
173            }
174            last_end = start + part.len();
175        }
176        if let Some(spans) = self.slice(last_end..) {
177            result.push(&spans);
178        }
179        result.trim();
180        result
181    }
182    fn replace_regex(&self, searcher: &Regex, replacer: &'a str) -> Self {
183        let mut last_end = 0;
184        let mut result = Spans {
185            content: String::new(),
186            spans: SearchTree::new(),
187        };
188        let captures = searcher.captures_iter(&self.content);
189        for capture in captures {
190            let mat = capture
191                .get(0)
192                .expect("Captures are always supposed to have one match");
193            if let Some(spans) = self.slice(last_end..mat.start()) {
194                result.push(&spans);
195                if let Some(mut r) = self.slice(mat.start()..mat.end()) {
196                    let mut new = String::new();
197                    String::from(replacer).replace_append(&capture, &mut new);
198                    r.content = new;
199                    result.push(&r);
200                }
201                last_end = mat.end();
202            }
203        }
204        if let Some(spans) = self.slice(last_end..) {
205            result.push(&spans);
206        }
207        result.trim();
208        result
209    }
210}
211
212impl<'a, T: Clone> Sliceable for Spans<T> {
213    fn slice<R>(&self, range: R) -> Option<Self>
214    where
215        R: RangeBounds<usize> + Clone,
216    {
217        let string = self.content.deref().slice(range.clone());
218        if self.spans.is_empty() {
219            if let Some(string) = string {
220                return Some(Spans {
221                    content: string.to_string(),
222                    spans: SearchTree::new(),
223                });
224            }
225        }
226        let spans = self.spans.slice(range);
227        if let (Some(string), Some(spans)) = (string, spans) {
228            Some(Spans {
229                content: string.to_string(),
230                spans,
231            })
232        } else {
233            None
234        }
235    }
236}
237
238impl<'a, T, U> FromIterator<U> for Spans<T>
239where
240    T: Clone + PartialEq + 'a,
241    U: Borrow<Spans<T>> + 'a,
242{
243    fn from_iter<I>(iter: I) -> Spans<T>
244    where
245        I: IntoIterator<Item = U>,
246    {
247        let mut result: Spans<T> = Default::default();
248        for span in iter {
249            result.push(span.borrow());
250        }
251        result.spans.dedup();
252        result
253    }
254}
255
256impl<'a, T> FromIterator<Span<'a, T>> for Spans<T>
257where
258    T: Clone + PartialEq + 'a,
259{
260    fn from_iter<I>(iter: I) -> Spans<T>
261    where
262        I: IntoIterator<Item = Span<'a, T>>,
263    {
264        let mut result: Spans<T> = Default::default();
265        for span in iter {
266            result.push(&span);
267        }
268        result.spans.dedup();
269        result
270    }
271}
272
273impl<T> RawText for Spans<T> {
274    fn raw(&self) -> String {
275        self.content.clone()
276    }
277    fn raw_ref<'a>(&self) -> &str {
278        &self.content
279    }
280}
281
282impl<T> From<&str> for Spans<T>
283where
284    T: Clone + Default + PartialEq,
285{
286    fn from(other: &str) -> Spans<T> {
287        let mut spans: SearchTree<_> = Default::default();
288        spans.insert(0, Default::default());
289        Spans {
290            content: String::from(other),
291            spans,
292        }
293    }
294}
295
296impl<'a, T: Paintable + Clone + Default> fmt::Display for Spans<T> {
297    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
298        T::paint_many(self.spans().map(|span| (span.style().clone(), span.raw()))).fmt(fmt)
299    }
300}
301
302impl<T> BoundedWidth for Spans<T> {
303    fn bounded_width(&self) -> usize {
304        self.content.bounded_width()
305    }
306}
307
308impl<T> HasWidth for Spans<T> {
309    fn width(&self) -> Width {
310        Width::Bounded(self.bounded_width())
311    }
312}
313
314impl<T: PartialEq + Clone> Joinable<Spans<T>> for Spans<T> {
315    type Output = Spans<T>;
316    fn join(&self, other: &Spans<T>) -> Self::Output {
317        let mut result: Spans<T> = Default::default();
318        result.push(self);
319        result.push(other);
320        result.trim();
321        result
322    }
323}
324
325impl<T: PartialEq + Clone> Joinable<Span<'_, T>> for Spans<T> {
326    type Output = Spans<T>;
327    fn join(&self, other: &Span<'_, T>) -> Self::Output {
328        let mut result: Spans<T> = Default::default();
329        result.push(self);
330        result.push(other);
331        result
332    }
333}
334
335#[cfg(test)]
336mod test {
337    use super::*;
338    use crate::text::{Sliceable, Split, Splitable, WidthSliceable};
339    use ansi_term::{ANSIString, ANSIStrings, Color, Style};
340    fn strings_to_spans(strings: &[ANSIString<'_>]) -> Spans<Style> {
341        strings.iter().map(Span::<Style>::from).collect()
342    }
343    fn string_to_spans(string: &ANSIString<'_>) -> Spans<Style> {
344        let span = Span::<Style>::from(string);
345        let mut spans: Spans<Style> = Default::default();
346        spans.push(&span);
347        spans
348    }
349    #[test]
350    fn test_slice_width_easy() {
351        let text = strings_to_spans(&[Color::Green.paint("foo")]);
352        let actual = text.slice_width(..2).unwrap();
353        let expected = strings_to_spans(&[Color::Green.paint("fo")]);
354        assert_eq!(expected, actual);
355    }
356    #[test]
357    fn test_slice_width_left_hard() {
358        let text = strings_to_spans(&[Color::Green.paint("👱👱👱")]);
359        let actual = text.slice_width(..3).unwrap();
360        let expected = strings_to_spans(&[Color::Green.paint("👱")]);
361        assert_eq!(expected, actual);
362        let actual = text.slice_width(..4).unwrap();
363        let expected = strings_to_spans(&[Color::Green.paint("👱👱")]);
364        assert_eq!(expected, actual);
365    }
366    #[test]
367    fn test_finite_width() {
368        let text = strings_to_spans(&[Color::Green.paint("foo")]);
369        let expected = 3;
370        let actual = text.bounded_width();
371        assert_eq!(expected, actual);
372    }
373    #[test]
374    fn build_span() {
375        let from = Color::Green.paint("foo");
376        let to = string_to_spans(&from);
377        let expected = format!("{}", from);
378        let actual = format!("{}", to);
379        assert_eq!(expected, actual);
380    }
381    #[test]
382    fn build_spans() {
383        let texts = [
384            Color::Red.paint("a"),
385            Color::Blue.paint("b"),
386            Color::Blue.paint("⛇"),
387        ];
388        let text = strings_to_spans(&texts);
389        let string = ANSIStrings(&texts);
390        let strings = text.spans().map(ANSIString::from).collect::<Vec<_>>();
391        let output = ANSIStrings(&strings);
392        let expected = format!("{}", string);
393        let actual = format!("{}", output);
394        assert_eq!(expected, actual);
395    }
396    #[test]
397    fn simple_replace() {
398        let text = strings_to_spans(&[Color::Red.paint("foo")]);
399        let actual = text.replace("foo", "bar");
400        let expected = strings_to_spans(&[Color::Red.paint("bar")]);
401        assert_eq!(expected, actual);
402    }
403    #[test]
404    fn replace_in_span() {
405        let text = strings_to_spans(&[Color::Red.paint("Bob "), Color::Blue.paint("Dylan")]);
406        let new_text = text.replace("Bob", "Robert");
407        let target_text =
408            strings_to_spans(&[Color::Red.paint("Robert "), Color::Blue.paint("Dylan")]);
409        assert_eq!(new_text, target_text);
410    }
411    #[test]
412    fn replace_chars() {
413        let text = strings_to_spans(&[
414            Color::Blue.paint("what"),
415            Color::Red.paint("//\\/;,!"),
416            Color::Blue.paint("the fudge"),
417        ]);
418        let new_text = text.replace("/", "/");
419        let target_text = strings_to_spans(&[
420            Color::Blue.paint("what"),
421            Color::Red.paint("//\\/;,!"),
422            Color::Blue.paint("the fudge"),
423        ]);
424        assert_eq!(new_text, target_text);
425    }
426    #[test]
427    fn replace_across_span_simple_2() {
428        let text = strings_to_spans(&[
429            Color::Red.paint("Here is some f"),
430            Color::Blue.paint("oo foo fo"),
431            Color::Green.paint("o"),
432        ]);
433        let new_text = text.replace("foo", "bar");
434        let target_text = strings_to_spans(&[
435            Color::Red.paint("Here is some b"),
436            Color::Blue.paint("ar bar ba"),
437            Color::Green.paint("r"),
438        ]);
439        assert_eq!(new_text, target_text);
440    }
441    #[test]
442    fn simple_regex_replace() {
443        let text = strings_to_spans(&[Color::Red.paint("foooo")]);
444        let new_text = text.replace_regex(&Regex::new("fo+").unwrap(), "bar");
445        let target_text = strings_to_spans(&[Color::Red.paint("bar")]);
446
447        assert_eq!(new_text, target_text);
448    }
449    #[test]
450    fn replace_regex_across_span_simple_trival() {
451        let text = strings_to_spans(&[Color::Red.paint("Here lies "), Color::Blue.paint("Beavis")]);
452        let new_text = text.replace_regex(
453            &Regex::new(r"(Here lies) Beavis").unwrap(),
454            "Here lies Butthead",
455        );
456        let target_text = strings_to_spans(&[
457            Color::Red.paint("Here lies "),
458            Color::Blue.paint("Butthead"),
459        ]);
460        assert_eq!(new_text, target_text);
461    }
462    #[test]
463    fn replace_regex_across_span_simple_backref() {
464        let text = strings_to_spans(&[Color::Red.paint("Here lies "), Color::Blue.paint("Beavis")]);
465        let new_text =
466            text.replace_regex(&Regex::new(r"(Here lies) Beavis").unwrap(), "$1 Butthead");
467        let target_text = strings_to_spans(&[
468            Color::Red.paint("Here lies "),
469            Color::Blue.paint("Butthead"),
470        ]);
471        assert_eq!(new_text, target_text);
472    }
473    #[test]
474    fn replace_regex_across_span_simple_2_backref() {
475        let text = strings_to_spans(&[
476            Color::Red.paint("Here is some f"),
477            Color::Blue.paint("ooo fuuu f"),
478            Color::Green.paint("aaa"),
479        ]);
480        let new_text = text.replace_regex(&Regex::new("f(([aeiou])+)").unwrap(), "b${2}r");
481        let target_text = strings_to_spans(&[
482            Color::Red.paint("Here is some b"),
483            Color::Blue.paint("or bur b"),
484            Color::Green.paint("ar"),
485        ]);
486        println!("expected: {}", target_text);
487        println!("actual:   {}", new_text);
488        assert_eq!(new_text, target_text);
489    }
490    #[test]
491    fn replace_regex_across_span_simple_2_trivial() {
492        let text = strings_to_spans(&[
493            Color::Red.paint("Here is some f"),
494            Color::Blue.paint("ooo fuuu f"),
495            Color::Green.paint("aaa"),
496        ]);
497        let new_text = text.replace_regex(&Regex::new("f(([aeiou])+)").unwrap(), "bar");
498        let target_text = strings_to_spans(&[
499            Color::Red.paint("Here is some b"),
500            Color::Blue.paint("ar bar b"),
501            Color::Green.paint("ar"),
502        ]);
503        println!("expected: {}", target_text);
504        println!("actual:   {}", new_text);
505        assert_eq!(new_text, target_text);
506    }
507    #[test]
508    fn replace_regex_empty() {
509        let text = strings_to_spans(&[
510            Color::Red.paint("Here is some f"),
511            Color::Blue.paint("ooo fuuu f"),
512            Color::Green.paint("aaa"),
513        ]);
514        let new_text = text.replace_regex(&Regex::new("quux").unwrap(), "bar");
515        assert_eq!(new_text, text);
516    }
517    #[test]
518    fn replace_regex_empty_fancy() {
519        let text = strings_to_spans(&[
520            Color::Red.paint("Here is some f"),
521            Color::Blue.paint("ooo fuuu f"),
522            Color::Green.paint("aaa"),
523        ]);
524        let new_text = text.replace_regex(&Regex::new("([zyx])").unwrap(), "missing $1 letters");
525        assert_eq!(new_text, text);
526    }
527    #[test]
528    fn replace_regex_styled_easy() {
529        let text = strings_to_spans(&[
530            Color::Red.paint("Foo"),
531            Color::Blue.paint("Bar"),
532            Color::Green.paint("Baz"),
533        ]);
534        let replacement = strings_to_spans(&[Color::Cyan.paint("Quux")]);
535        let actual = text.replace_regex(&Regex::new("Bar").unwrap(), &replacement);
536        let expected = strings_to_spans(&[
537            Color::Red.paint("Foo"),
538            Color::Cyan.paint("Quux"),
539            Color::Green.paint("Baz"),
540        ]);
541        assert_eq!(expected, actual);
542    }
543    #[test]
544    fn replace_regex_styled_complex() {
545        let text = strings_to_spans(&[
546            Color::Red.paint("555"),
547            Color::Black.paint("."),
548            Color::Blue.paint("444"),
549            Color::White.paint("."),
550            Color::Green.paint("3333"),
551        ]);
552        let replacement = strings_to_spans(&[
553            Color::Red.paint("$1"),
554            Color::Black.paint("-"),
555            Color::Green.paint("$2"),
556            Color::Black.paint("-"),
557            Color::Blue.paint("$3"),
558        ]);
559        let regex = Regex::new(r"(\d{3})[.-](\d{3})[.-](\d{4})").unwrap();
560        for name in regex.capture_names() {
561            println!("name: {:#?}", name);
562        }
563        println!("location: {:#?}", regex.capture_locations());
564        println!("len: {:#?}", regex.capture_locations().len());
565        let actual = text.replace_regex(&regex, &replacement);
566        let expected = strings_to_spans(&[
567            Color::Red.paint("555"),
568            Color::Black.paint("-"),
569            Color::Green.paint("444"),
570            Color::Black.paint("-"),
571            Color::Blue.paint("3333"),
572        ]);
573        println!("expected: {}", expected);
574        println!("actual: {}", actual);
575        assert_eq!(expected, actual);
576    }
577    #[test]
578    fn span() {
579        let texts = [
580            Color::Red.paint("Here is some f"),
581            Color::Blue.paint("ooo fuuu f"),
582            Color::Green.paint("aaa"),
583        ];
584        let text = strings_to_spans(&texts);
585        let span = text.spans().next().unwrap();
586        let expected = format!("{}", texts[0]);
587        let actual = format!("{}", span);
588        assert_eq!(expected, actual);
589    }
590    #[test]
591    fn raw() {
592        let text = strings_to_spans(&[
593            Color::Red.paint("Here is some f"),
594            Color::Blue.paint("ooo fuuu f"),
595            Color::Green.paint("aaa"),
596        ]);
597
598        let expected = String::from("Here is some fooo fuuu faaa");
599        let actual = text.raw();
600        assert_eq!(expected, actual);
601    }
602    #[test]
603    fn slice_start() {
604        let text = strings_to_spans(&[Color::Red.paint("01234"), Color::Blue.paint("56789")]);
605        let actual = text.slice(0..8).unwrap();
606        let expected = strings_to_spans(&[Color::Red.paint("01234"), Color::Blue.paint("567")]);
607
608        assert_eq!(expected, actual);
609    }
610    #[test]
611    fn slice_middle() {
612        let text = strings_to_spans(&[
613            Color::Red.paint("012"),
614            Color::Blue.paint("345"),
615            Color::Green.paint("678"),
616        ]);
617        let actual = text.slice(2..8).unwrap();
618        let expected = strings_to_spans(&[
619            Color::Red.paint("2"),
620            Color::Blue.paint("345"),
621            Color::Green.paint("67"),
622        ]);
623
624        assert_eq!(expected, actual);
625    }
626    #[test]
627    fn slice_end() {
628        let text = strings_to_spans(&[
629            Color::Red.paint("012"),
630            Color::Blue.paint("345"),
631            Color::Green.paint("678"),
632        ]);
633        let actual = text.slice(2..).unwrap();
634        let expected = strings_to_spans(&[
635            Color::Red.paint("2"),
636            Color::Blue.paint("345"),
637            Color::Green.paint("678"),
638        ]);
639
640        assert_eq!(expected, actual);
641    }
642    #[test]
643    fn slice_full() {
644        let text = strings_to_spans(&[
645            Color::Red.paint("012"),
646            Color::Blue.paint("345"),
647            Color::Green.paint("678"),
648        ]);
649        let actual = text.slice(..).unwrap();
650        let expected = strings_to_spans(&[
651            Color::Red.paint("012"),
652            Color::Blue.paint("345"),
653            Color::Green.paint("678"),
654        ]);
655
656        assert_eq!(expected, actual);
657    }
658    #[test]
659    fn split_outer() {
660        let texts = vec![
661            Color::Black.paint("::"),
662            Color::Red.paint("Some"),
663            Color::Blue.paint("::"),
664            Color::Green.paint("Random"),
665            Color::Cyan.paint("::"),
666            Color::White.paint("Place"),
667            Color::Yellow.paint("::"),
668        ];
669        let spans = strings_to_spans(&texts);
670        let actual = spans.split("::").collect::<Vec<_>>();
671        let expected = vec![
672            Split {
673                segment: None,
674                delim: Some(string_to_spans(&texts[0])),
675            },
676            Split {
677                segment: Some(string_to_spans(&texts[1])),
678                delim: Some(string_to_spans(&texts[2])),
679            },
680            Split {
681                segment: Some(string_to_spans(&texts[3])),
682                delim: Some(string_to_spans(&texts[4])),
683            },
684            Split {
685                segment: Some(string_to_spans(&texts[5])),
686                delim: Some(string_to_spans(&texts[6])),
687            },
688        ];
689        assert_eq!(expected, actual);
690    }
691    #[test]
692    fn split_inner() {
693        let texts = vec![
694            Color::Red.paint("Some"),
695            Color::Blue.paint("::"),
696            Color::Green.paint("Random"),
697            Color::Cyan.paint("::"),
698            Color::White.paint("Place"),
699        ];
700        let spans = strings_to_spans(&texts);
701        let actual = spans.split("::").collect::<Vec<_>>();
702        let expected = vec![
703            Split {
704                segment: Some(string_to_spans(&texts[0])),
705                delim: Some(string_to_spans(&texts[1])),
706            },
707            Split {
708                segment: Some(string_to_spans(&texts[2])),
709                delim: Some(string_to_spans(&texts[3])),
710            },
711            Split {
712                segment: Some(string_to_spans(&texts[4])),
713                delim: None,
714            },
715        ];
716        assert_eq!(expected, actual);
717    }
718}