diff --git a/src/utils/span.rs b/src/utils/span.rs index 0f61976..b148c8d 100644 --- a/src/utils/span.rs +++ b/src/utils/span.rs @@ -3,6 +3,7 @@ //! This module defines various structs describing a span of text from a //! larger string. use std::borrow::Cow; +use std::marker::PhantomData; /// A string with associated spans. /// @@ -13,6 +14,78 @@ pub struct SpannedString { spans: Vec>, } +/// The immutable, borrowed equivalent of `SpannedString`. +#[derive(Debug, PartialEq, Eq)] +pub struct SpannedStr<'a, T> +where + T: 'a, +{ + source: &'a str, + spans: &'a [IndexedSpan], +} + +/// Describes an object that appears like a `SpannedStr`. +pub trait SpannedText { + /// Returns the source text. + fn source(&self) -> &str; + + /// Returns the spans for this text. + fn spans(&self) -> &[IndexedSpan]; + + /// Returns a `SpannedText` by reference. + fn as_ref<'a>(&'a self) -> SpannedTextRef<'a, T, Self> { + SpannedTextRef { + r: self, + _phantom: PhantomData, + } + } +} + +/// A reference to another `SpannedText`. +pub struct SpannedTextRef<'a, T, C> +where + C: 'a + SpannedText + ?Sized, +{ + r: &'a C, + _phantom: PhantomData, +} + +impl<'a, T> SpannedText for &'a SpannedString { + fn source(&self) -> &str { + &self.source + } + + fn spans(&self) -> &[IndexedSpan] { + &self.spans + } +} + +impl<'a, T, C> SpannedText for SpannedTextRef<'a, T, C> +where + C: 'a + SpannedText + ?Sized, +{ + fn source(&self) -> &str { + self.r.source() + } + + fn spans(&self) -> &[IndexedSpan] { + self.r.spans() + } +} + +impl<'a, T> SpannedText for SpannedStr<'a, T> +where + T: 'a, +{ + fn source(&self) -> &str { + self.source + } + + fn spans(&self) -> &[IndexedSpan] { + self.spans + } +} + impl From for SpannedString where S: Into, @@ -23,12 +96,72 @@ where } } +impl<'a, T> SpannedStr<'a, T> +where + T: 'a, +{ + /// Creates a new `SpannedStr` from the given references. + pub fn new(source: &'a str, spans: &'a [IndexedSpan]) -> Self { + SpannedStr { source, spans } + } + + /// Gives access to the parsed styled spans. + #[cfg_attr(feature = "cargo-clippy", allow(needless_lifetimes))] + pub fn spans<'b>(&self) -> Vec> { + self.spans + .iter() + .map(|span| span.resolve(self.source)) + .collect() + } + + /// Returns a reference to the indexed spans. + pub fn spans_raw(&self) -> &'a [IndexedSpan] { + self.spans + } + + /// Returns a reference to the source (non-parsed) string. + pub fn source(&self) -> &'a str { + self.source + } + + /// Returns `true` if `self` is empty. + /// + /// Can be caused by an empty source, or no span. + pub fn is_empty(&self) -> bool { + self.source.is_empty() || self.spans.is_empty() + } +} + +impl<'a, T> Clone for SpannedStr<'a, T> { + fn clone(&self) -> Self { + SpannedStr { + source: self.source, + spans: self.spans, + } + } +} + +impl SpannedString<()> { + /// Returns a simple spanned string without any attribute. + pub fn plain(content: S) -> Self + where + S: Into, + { + Self::single_span(content, ()) + } +} + impl SpannedString { + /// Returns an empty `SpannedString`. + pub fn new() -> Self { + Self::with_spans(String::new(), Vec::new()) + } + /// Creates a new `SpannedString` manually. /// /// It is not recommended to use this directly. /// Instead, look for methods like `Markdown::parse`. - pub fn new(source: S, spans: Vec>) -> Self + pub fn with_spans(source: S, spans: Vec>) -> Self where S: Into, { @@ -46,7 +179,12 @@ impl SpannedString { } /// Returns a new SpannedString with a single span. - pub fn single_span(source: String, attr: T) -> Self { + pub fn single_span(source: S, attr: T) -> Self + where + S: Into, + { + let source = source.into(); + let spans = vec![ IndexedSpan { content: IndexedCow::Borrowed { @@ -57,7 +195,7 @@ impl SpannedString { }, ]; - Self::new(source, spans) + Self::with_spans(source, spans) } /// Appends the given `StyledString` to `self`. @@ -110,6 +248,11 @@ impl SpannedString { pub fn is_empty(&self) -> bool { self.source.is_empty() || self.spans.is_empty() } + + /// Returns a `SpannedStr` referencing `self`. + pub fn as_spanned_str<'a>(&'a self) -> SpannedStr<'a, T> { + SpannedStr::new(&self.source, &self.spans) + } } /// An indexed span with an associated attribute. @@ -148,6 +291,17 @@ impl IndexedSpan { pub fn is_empty(&self) -> bool { self.content.is_empty() } + + /// Returns a single span around the entire text. + pub fn simple(content: &str, attr: T) -> Self { + IndexedSpan { + content: IndexedCow::Borrowed { + start: 0, + end: content.len(), + }, + attr, + } + } } /// A span of text that can be either owned, or indexed in another String.