cursive_core/utils/
span.rs

1//! Work with spans of text.
2//!
3//! This module defines various structs describing a span of text from a
4//! larger string.
5use std::borrow::Cow;
6use std::iter::FromIterator;
7use unicode_width::UnicodeWidthStr;
8
9/// A string with associated spans.
10///
11/// Each span has an associated attribute `T`.
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct SpannedString<T> {
14    source: String,
15    spans: Vec<IndexedSpan<T>>,
16}
17
18/// The immutable, borrowed equivalent of `SpannedString`.
19#[derive(Debug, PartialEq, Eq, Hash)]
20pub struct SpannedStr<'a, T> {
21    source: &'a str,
22    spans: &'a [IndexedSpan<T>],
23}
24
25// What we don't have: (&str, Vec<IndexedSpan<T>>)
26// To style an existing text.
27// Maybe replace `String` in `SpannedString` with `<S>`?
28
29/// Describes an object that appears like a `SpannedStr`.
30pub trait SpannedText {
31    /// Type of span returned by `SpannedText::spans()`.
32    ///
33    /// Most of the time it'll be `IndexedSpan`.
34    type S: AsRef<IndexedCow>;
35
36    /// Returns the source text.
37    fn source(&self) -> &str;
38
39    /// Returns the spans for this text.
40    fn spans(&self) -> &[Self::S];
41
42    /// Returns a `SpannedText` by reference.
43    fn as_ref(&self) -> SpannedTextRef<'_, Self> {
44        SpannedTextRef { r: self }
45    }
46}
47
48/// A reference to another `SpannedText`.
49pub struct SpannedTextRef<'a, C>
50where
51    C: SpannedText + ?Sized,
52{
53    r: &'a C,
54}
55
56impl<T> Default for SpannedString<T> {
57    fn default() -> Self {
58        SpannedString::new()
59    }
60}
61
62impl<'a, T> SpannedText for &'a SpannedString<T> {
63    type S = IndexedSpan<T>;
64
65    fn source(&self) -> &str {
66        &self.source
67    }
68
69    fn spans(&self) -> &[IndexedSpan<T>] {
70        &self.spans
71    }
72}
73
74impl<'a, C> SpannedText for SpannedTextRef<'a, C>
75where
76    C: 'a + SpannedText + ?Sized,
77{
78    type S = C::S;
79
80    fn source(&self) -> &str {
81        self.r.source()
82    }
83
84    fn spans(&self) -> &[C::S] {
85        self.r.spans()
86    }
87}
88
89impl<'a, T> SpannedText for SpannedStr<'a, T>
90where
91    T: 'a,
92{
93    type S = IndexedSpan<T>;
94
95    fn source(&self) -> &str {
96        self.source
97    }
98
99    fn spans(&self) -> &[IndexedSpan<T>] {
100        self.spans
101    }
102}
103
104impl<S, T> From<S> for SpannedString<T>
105where
106    S: Into<String>,
107    T: Default,
108{
109    fn from(value: S) -> Self {
110        Self::single_span(value.into(), T::default())
111    }
112}
113
114impl<'a, T> SpannedStr<'a, T>
115where
116    T: 'a,
117{
118    /// Creates a new `SpannedStr` from the given references.
119    pub const fn new(source: &'a str, spans: &'a [IndexedSpan<T>]) -> Self {
120        SpannedStr { source, spans }
121    }
122
123    /// Creates a new empty `SpannedStr`.
124    pub const fn empty() -> Self {
125        Self::new("", &[])
126    }
127
128    /// Gives access to the parsed styled spans.
129    pub fn spans<'b>(
130        &'b self,
131    ) -> impl DoubleEndedIterator<Item = Span<'a, T>> + ExactSizeIterator<Item = Span<'a, T>> + 'b
132    where
133        'a: 'b,
134    {
135        let source = self.source;
136        self.spans.iter().map(move |span| span.resolve(source))
137    }
138
139    /// Returns a reference to the indexed spans.
140    pub const fn spans_raw(&self) -> &'a [IndexedSpan<T>] {
141        self.spans
142    }
143
144    /// Returns a reference to the source (non-parsed) string.
145    pub const fn source(&self) -> &'a str {
146        self.source
147    }
148
149    /// Returns `true` if `self` is empty.
150    ///
151    /// Can be caused by an empty source, or no span.
152    pub const fn is_empty(&self) -> bool {
153        self.source.is_empty() || self.spans.is_empty()
154    }
155
156    /// Returns the width taken by this string.
157    ///
158    /// This is the sum of the width of each span.
159    pub fn width(&self) -> usize {
160        self.spans().map(|s| s.width).sum()
161    }
162
163    /// Create a new `SpannedStr` by borrowing from a `SpannedText`.
164    pub fn from_spanned_text<'b, S>(text: &'b S) -> Self
165    where
166        S: SpannedText<S = IndexedSpan<T>>,
167        'b: 'a,
168    {
169        Self {
170            source: text.source(),
171            spans: text.spans(),
172        }
173    }
174}
175
176impl<'a, T> Clone for SpannedStr<'a, T> {
177    fn clone(&self) -> Self {
178        SpannedStr {
179            source: self.source,
180            spans: self.spans,
181        }
182    }
183}
184
185impl SpannedString<()> {
186    /// Returns a simple spanned string without any attribute.
187    pub fn plain<S>(content: S) -> Self
188    where
189        S: Into<String>,
190    {
191        Self::single_span(content, ())
192    }
193}
194
195impl<T> SpannedString<T> {
196    /// Returns an empty `SpannedString`.
197    pub fn new() -> Self {
198        Self::with_spans(String::new(), Vec::new())
199    }
200
201    /// Concatenates all styled strings.
202    ///
203    /// Same as `spans.into_iter().collect()`.
204    pub fn concatenate<I>(spans: I) -> Self
205    where
206        I: IntoIterator<Item = Self>,
207    {
208        spans.into_iter().collect()
209    }
210
211    /// Creates a new `SpannedString` manually.
212    ///
213    /// It is not recommended to use this directly.
214    /// Instead, look for methods like `Markdown::parse`.
215    pub fn with_spans<S>(source: S, spans: Vec<IndexedSpan<T>>) -> Self
216    where
217        S: Into<String>,
218    {
219        let source = source.into();
220
221        // Make sure the spans are within bounds.
222        // This should disappear when compiled in release mode.
223        for span in &spans {
224            if let IndexedCow::Borrowed { end, .. } = span.content {
225                assert!(end <= source.len());
226            }
227        }
228
229        SpannedString { source, spans }
230    }
231
232    /// Compacts and simplifies this string, resulting in a canonical form.
233    ///
234    /// If two styled strings represent the same styled text, they should have equal canonical
235    /// forms.
236    ///
237    /// (The PartialEq implementation for StyledStrings requires both the source and spans to be
238    /// equals, so non-visible changes such as text in the source between spans could cause
239    /// StyledStrings to evaluate as non-equal.)
240    pub fn canonicalize(&mut self)
241    where
242        T: PartialEq,
243    {
244        self.compact();
245        self.simplify();
246    }
247
248    /// Returns the canonical form of this styled string.
249    pub fn canonical(mut self) -> Self
250    where
251        T: PartialEq,
252    {
253        self.canonicalize();
254        self
255    }
256
257    /// Compacts the source to only include the spans content.
258    ///
259    /// This does not change the number of spans, but changes the source.
260    pub fn compact(&mut self) {
261        // Prepare the new source
262        let mut source = String::new();
263
264        for span in &mut self.spans {
265            // Only include what we need.
266            let start = source.len();
267            source.push_str(span.content.resolve(&self.source));
268            let end = source.len();
269
270            // All spans now borrow the source.
271            span.content = IndexedCow::Borrowed { start, end };
272        }
273
274        self.source = source;
275    }
276
277    /// Attemps to reduce the number of spans by merging consecutive similar ones.
278    pub fn simplify(&mut self)
279    where
280        T: PartialEq,
281    {
282        // Now, merge consecutive similar spans.
283        let mut i = 0;
284        while i + 1 < self.spans.len() {
285            let left = &self.spans[i];
286            let right = &self.spans[i + 1];
287            if left.attr != right.attr {
288                i += 1;
289                continue;
290            }
291
292            let (_, left_end) = left.content.as_borrowed().unwrap();
293            let (right_start, right_end) = right.content.as_borrowed().unwrap();
294            let right_width = right.width;
295
296            if left_end != right_start {
297                i += 1;
298                continue;
299            }
300
301            *self.spans[i].content.as_borrowed_mut().unwrap().1 = right_end;
302            self.spans[i].width += right_width;
303            self.spans.remove(i + 1);
304        }
305    }
306
307    /// Shrink the source to discard any unused suffix.
308    pub fn trim_end(&mut self) {
309        if let Some(max) = self
310            .spans
311            .iter()
312            .filter_map(|s| s.content.as_borrowed())
313            .map(|(_start, end)| end)
314            .max()
315        {
316            self.source.truncate(max);
317        }
318    }
319
320    /// Shrink the source to discard any unused prefix.
321    pub fn trim_start(&mut self) {
322        if let Some(min) = self
323            .spans
324            .iter()
325            .filter_map(|s| s.content.as_borrowed())
326            .map(|(start, _end)| start)
327            .min()
328        {
329            self.source.drain(..min);
330            for span in &mut self.spans {
331                span.content.rev_offset(min);
332            }
333        }
334    }
335
336    /// Shrink the source to discard any unused prefix or suffix.
337    pub fn trim(&mut self) {
338        self.trim_end();
339        self.trim_start();
340    }
341
342    /// Returns a new SpannedString with a single span.
343    pub fn single_span<S>(source: S, attr: T) -> Self
344    where
345        S: Into<String>,
346    {
347        let source = source.into();
348
349        let spans = vec![IndexedSpan::simple_borrowed(&source, attr)];
350
351        Self::with_spans(source, spans)
352    }
353
354    /// Appends the given `StyledString` to `self`.
355    pub fn append<S>(&mut self, other: S)
356    where
357        S: Into<Self>,
358    {
359        let other = other.into();
360        self.append_raw(&other.source, other.spans);
361    }
362
363    /// Appends `content` and its corresponding spans to the end.
364    ///
365    /// It is not recommended to use this directly;
366    /// instead, look at the `append` method.
367    pub fn append_raw(&mut self, source: &str, spans: Vec<IndexedSpan<T>>) {
368        let offset = self.source.len();
369        let mut spans = spans;
370
371        for span in &mut spans {
372            span.content.offset(offset);
373        }
374
375        self.source.push_str(source);
376        self.spans.append(&mut spans);
377    }
378
379    /// Remove the given range of spans from the styled string.
380    ///
381    /// You may want to follow this with either `compact()`,
382    /// `trim_start()` or `trim_end()`.
383    pub fn remove_spans<R>(&mut self, range: R)
384    where
385        R: std::ops::RangeBounds<usize>,
386    {
387        self.spans.drain(range);
388    }
389
390    /// Iterates on the resolved spans.
391    pub fn spans(
392        &self,
393    ) -> impl DoubleEndedIterator<Item = Span<'_, T>> + ExactSizeIterator<Item = Span<'_, T>> {
394        let source = &self.source;
395        self.spans.iter().map(move |span| span.resolve(source))
396    }
397
398    /// Iterates on the resolved spans, with mutable access to the attributes.
399    pub fn spans_attr_mut(&mut self) -> impl Iterator<Item = SpanMut<'_, T>> {
400        let source = &self.source;
401        self.spans
402            .iter_mut()
403            .map(move |span| span.resolve_mut(source))
404    }
405
406    /// Returns a reference to the indexed spans.
407    pub fn spans_raw(&self) -> &[IndexedSpan<T>] {
408        &self.spans
409    }
410
411    /// Returns a mutable iterator on the spans of this string.
412    ///
413    /// This can be used to modify the style of each span.
414    pub fn spans_raw_attr_mut(
415        &mut self,
416    ) -> impl DoubleEndedIterator<Item = IndexedSpanRefMut<'_, T>>
417           + ExactSizeIterator<Item = IndexedSpanRefMut<'_, T>> {
418        self.spans.iter_mut().map(IndexedSpan::as_ref_mut)
419    }
420
421    /// Returns a reference to the source string.
422    ///
423    /// This is the non-parsed string.
424    pub fn source(&self) -> &str {
425        &self.source
426    }
427
428    /// Get the source, consuming this `StyledString`.
429    pub fn into_source(self) -> String {
430        self.source
431    }
432
433    /// Returns `true` if self is empty.
434    pub fn is_empty(&self) -> bool {
435        self.source.is_empty() || self.spans.is_empty()
436    }
437
438    /// Returns the width taken by this string.
439    ///
440    /// This is the sum of the width of each span.
441    pub fn width(&self) -> usize {
442        self.spans().map(|s| s.width).sum()
443    }
444}
445
446impl<T> FromIterator<SpannedString<T>> for SpannedString<T> {
447    fn from_iter<I: IntoIterator<Item = SpannedString<T>>>(iter: I) -> SpannedString<T> {
448        // It would look simpler to always fold(), starting with an empty string.
449        // But this here lets us re-use the allocation from the first string, which is a small win.
450        let mut iter = iter.into_iter();
451        if let Some(first) = iter.next() {
452            iter.fold(first, |mut acc, s| {
453                acc.append(s);
454                acc
455            })
456        } else {
457            SpannedString::new()
458        }
459    }
460}
461
462impl<'a, T> From<&'a SpannedString<T>> for SpannedStr<'a, T> {
463    fn from(other: &'a SpannedString<T>) -> Self {
464        SpannedStr::new(&other.source, &other.spans)
465    }
466}
467
468/// A reference to an IndexedSpan allowing modification of the attribute.
469#[derive(Debug, PartialEq, Eq, Hash)]
470pub struct IndexedSpanRefMut<'a, T> {
471    /// Points to the content of the span.
472    pub content: &'a IndexedCow,
473
474    /// Mutable reference to the attribute of the span.
475    pub attr: &'a mut T,
476
477    /// Width of the span.
478    pub width: usize,
479}
480
481/// An indexed span with an associated attribute.
482#[derive(Debug, Clone, PartialEq, Eq, Hash)]
483pub struct IndexedSpan<T> {
484    /// Content of the span.
485    pub content: IndexedCow,
486
487    /// Attribute applied to the span.
488    pub attr: T,
489
490    /// Width of the text for this span.
491    pub width: usize,
492}
493
494impl<T> AsRef<IndexedCow> for IndexedSpan<T> {
495    fn as_ref(&self) -> &IndexedCow {
496        &self.content
497    }
498}
499
500/// A resolved span borrowing its source string, with mutable access to the
501/// attribute.
502#[derive(Debug, PartialEq, Eq, Hash)]
503pub struct SpanMut<'a, T> {
504    /// Content of this span.
505    pub content: &'a str,
506
507    /// Attribute associated to this span.
508    pub attr: &'a mut T,
509
510    /// Width of the text for this span.
511    pub width: usize,
512}
513
514/// A resolved span borrowing its source string.
515#[derive(Debug, Clone, PartialEq, Eq, Hash)]
516pub struct Span<'a, T> {
517    /// Content of this span.
518    pub content: &'a str,
519
520    /// Attribute associated to this span.
521    pub attr: &'a T,
522
523    /// Width of the text for this span.
524    pub width: usize,
525}
526
527impl<T> IndexedSpan<T> {
528    /// Resolve the span to a string slice and an attribute.
529    pub fn resolve<'a>(&'a self, source: &'a str) -> Span<'a, T>
530    where
531        T: 'a,
532    {
533        Span {
534            content: self.content.resolve(source),
535            attr: &self.attr,
536            width: self.width,
537        }
538    }
539
540    /// Resolve the span to a string slice and a mutable attribute.
541    pub fn resolve_mut<'a>(&'a mut self, source: &'a str) -> SpanMut<'a, T>
542    where
543        T: 'a,
544    {
545        SpanMut {
546            content: self.content.resolve(source),
547            attr: &mut self.attr,
548            width: self.width,
549        }
550    }
551
552    /// Returns a reference struct to only access mutation of the attribute.
553    pub fn as_ref_mut(&mut self) -> IndexedSpanRefMut<'_, T> {
554        IndexedSpanRefMut {
555            content: &self.content,
556            attr: &mut self.attr,
557            width: self.width,
558        }
559    }
560
561    /// Returns `true` if `self` is an empty span.
562    pub fn is_empty(&self) -> bool {
563        self.content.is_empty()
564    }
565
566    /// Returns a single indexed span around the entire text.
567    pub fn simple_borrowed(content: &str, attr: T) -> Self {
568        IndexedSpan {
569            content: IndexedCow::Borrowed {
570                start: 0,
571                end: content.len(),
572            },
573            attr,
574            width: content.width(),
575        }
576    }
577
578    /// Returns a single owned indexed span around the entire text.
579    pub fn simple_owned(content: String, attr: T) -> Self {
580        let width = content.width();
581        IndexedSpan {
582            content: IndexedCow::Owned(content),
583            attr,
584            width,
585        }
586    }
587}
588
589/// A span of text that can be either owned, or indexed in another String.
590#[derive(Debug, Clone, PartialEq, Eq, Hash)]
591pub enum IndexedCow {
592    /// Indexes content in a separate string.
593    Borrowed {
594        /// Byte offset of the beginning of the span (inclusive)
595        start: usize,
596
597        /// Byte offset of the end of the span (exclusive)
598        end: usize,
599    },
600
601    /// Owns its content.
602    Owned(String),
603}
604
605impl IndexedCow {
606    /// Resolve the span to a string slice.
607    pub fn resolve<'a>(&'a self, source: &'a str) -> &'a str {
608        match *self {
609            IndexedCow::Borrowed { start, end } => &source[start..end],
610            IndexedCow::Owned(ref content) => content,
611        }
612    }
613
614    /// Gets a new `IndexedCow` for the given range.
615    ///
616    /// The given range is relative to this span.
617    pub fn subcow(&self, range: std::ops::Range<usize>) -> Self {
618        match *self {
619            IndexedCow::Borrowed { start, end } => {
620                if start + range.end > end {
621                    panic!("Attempting to get a subcow larger than itself!");
622                }
623                IndexedCow::Borrowed {
624                    start: start + range.start,
625                    end: start + range.end,
626                }
627            }
628            IndexedCow::Owned(ref content) => IndexedCow::Owned(content[range].into()),
629        }
630    }
631
632    /// Return the `(start, end)` indexes if `self` is `IndexedCow::Borrowed`.
633    pub fn as_borrowed(&self) -> Option<(usize, usize)> {
634        if let IndexedCow::Borrowed { start, end } = *self {
635            Some((start, end))
636        } else {
637            None
638        }
639    }
640
641    /// Return the `(start, end)` indexes if `self` is `IndexedCow::Borrowed`.
642    pub fn as_borrowed_mut(&mut self) -> Option<(&mut usize, &mut usize)> {
643        if let IndexedCow::Borrowed {
644            ref mut start,
645            ref mut end,
646        } = *self
647        {
648            Some((start, end))
649        } else {
650            None
651        }
652    }
653
654    /// Returns the embedded text content if `self` is `IndexedCow::Owned`.
655    pub fn as_owned(&self) -> Option<&str> {
656        if let IndexedCow::Owned(ref content) = *self {
657            Some(content)
658        } else {
659            None
660        }
661    }
662
663    /// Returns an indexed view of the given string.
664    ///
665    /// **Note**: it is assumed `cow`, if borrowed, is a substring of `source`.
666    pub fn from_str(value: &str, source: &str) -> Self {
667        let source_pos = source.as_ptr() as usize;
668        let value_pos = value.as_ptr() as usize;
669
670        // Make sure `value` is indeed a substring of `source`
671        assert!(value_pos >= source_pos);
672        assert!(value_pos + value.len() <= source_pos + source.len());
673
674        let start = value_pos - source_pos;
675        let end = start + value.len();
676
677        IndexedCow::Borrowed { start, end }
678    }
679
680    /// Returns an indexed view of the given item.
681    ///
682    /// **Note**: it is assumed `cow`, if borrowed, is a substring of `source`.
683    pub fn from_cow(cow: Cow<'_, str>, source: &str) -> Self {
684        match cow {
685            Cow::Owned(value) => IndexedCow::Owned(value),
686            Cow::Borrowed(value) => IndexedCow::from_str(value, source),
687        }
688    }
689
690    /// Returns `true` if this represents an empty span.
691    pub fn is_empty(&self) -> bool {
692        match *self {
693            IndexedCow::Borrowed { start, end } => start == end,
694            IndexedCow::Owned(ref content) => content.is_empty(),
695        }
696    }
697
698    /// If `self` is borrowed, offset its indices by the given value.
699    ///
700    /// Useful to update spans when concatenating sources. This span will now
701    /// point to text `offset` further in the source.
702    pub fn offset(&mut self, offset: usize) {
703        if let IndexedCow::Borrowed {
704            ref mut start,
705            ref mut end,
706        } = *self
707        {
708            *start += offset;
709            *end += offset;
710        }
711    }
712
713    /// If `self` is borrowed, offset its indices back by the given value.
714    ///
715    /// Useful to update spans when removing a prefix from the source.
716    /// This span will now point to text `offset` closer to the start of the source.
717    ///
718    /// This span may become empty as a result.
719    pub fn rev_offset(&mut self, offset: usize) {
720        if let IndexedCow::Borrowed {
721            ref mut start,
722            ref mut end,
723        } = *self
724        {
725            *start = start.saturating_sub(offset);
726            *end = end.saturating_sub(offset);
727        }
728    }
729}
730
731#[cfg(test)]
732mod tests {
733    use super::*;
734    use crate::style::Style;
735
736    #[test]
737    fn test_spanned_str_width() {
738        let spans = vec![
739            IndexedSpan {
740                content: IndexedCow::Borrowed { start: 0, end: 5 },
741                attr: Style::default(),
742                width: 5,
743            },
744            IndexedSpan {
745                content: IndexedCow::Borrowed { start: 6, end: 11 },
746                attr: Style::default(),
747                width: 5,
748            },
749        ];
750        let spanned_str = SpannedStr::new("Hello World", &spans);
751        assert_eq!(spanned_str.width(), 10);
752    }
753}