//! Work with spans of text. //! //! 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. /// /// Each span has an associated attribute `T`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SpannedString { source: String, 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, T: Default, { fn from(value: S) -> Self { Self::single_span(value.into(), T::default()) } } 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 with_spans(source: S, spans: Vec>) -> Self where S: Into, { let source = source.into(); // Make sure the spans are within bounds. // This should disapear when compiled in release mode. for span in &spans { if let IndexedCow::Borrowed { end, .. } = span.content { assert!(end <= source.len()); } } SpannedString { source, spans } } /// Returns a new SpannedString with a single span. pub fn single_span(source: S, attr: T) -> Self where S: Into, { let source = source.into(); let spans = vec![ IndexedSpan { content: IndexedCow::Borrowed { start: 0, end: source.len(), }, attr, }, ]; Self::with_spans(source, spans) } /// Appends the given `StyledString` to `self`. pub fn append(&mut self, other: S) where S: Into, { let other = other.into(); self.append_raw(&other.source, other.spans); } /// Appends `content` and its corresponding spans to the end. /// /// It is not recommended to use this directly; /// instead, look at the `append` method. pub fn append_raw(&mut self, source: &str, spans: Vec>) { let offset = self.source.len(); let mut spans = spans; for span in &mut spans { span.content.offset(offset); } self.source.push_str(source); self.spans.append(&mut spans); } /// Gives access to the parsed styled spans. #[cfg_attr(feature = "cargo-clippy", allow(needless_lifetimes))] pub fn spans<'a>(&'a self) -> Vec> { self.spans .iter() .map(|span| span.resolve(&self.source)) .collect() } /// Returns a reference to the indexed spans. pub fn spans_raw(&self) -> &[IndexedSpan] { &self.spans } /// Returns a reference to the source string. /// /// This is the non-parsed string. pub fn source(&self) -> &str { &self.source } /// Returns `true` if self is empty. 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. #[derive(Debug, Clone, PartialEq, Eq)] pub struct IndexedSpan { /// Content of the span. pub content: IndexedCow, /// Attribute applied to the span. pub attr: T, } /// A resolved span borrowing its source string. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Span<'a, T: 'a> { /// Content of this span. pub content: &'a str, /// Attribute associated to this span. pub attr: &'a T, } impl IndexedSpan { /// Resolve the span to a string slice and an attribute. pub fn resolve<'a>(&'a self, source: &'a str) -> Span<'a, T> where T: 'a, { Span { content: self.content.resolve(source), attr: &self.attr, } } /// Returns `true` if `self` is an empty span. 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. #[derive(Debug, Clone, PartialEq, Eq)] pub enum IndexedCow { /// Indexes content in a separate string. Borrowed { /// Byte offset of the beginning of the span (inclusive) start: usize, /// Byte offset of the end of the span (exclusive) end: usize, }, /// Owns its content. Owned(String), } impl IndexedCow { /// Resolve the span to a string slice. pub fn resolve<'a>(&'a self, source: &'a str) -> &'a str { match *self { IndexedCow::Borrowed { start, end } => &source[start..end], IndexedCow::Owned(ref content) => content, } } /// Returns an indexed view of the given item. /// /// **Note**: it is assumed `cow`, if borrowed, is a substring of `source`. pub fn from_cow(cow: Cow, source: &str) -> Self { match cow { Cow::Owned(value) => IndexedCow::Owned(value), Cow::Borrowed(value) => { let source_pos = source.as_ptr() as usize; let value_pos = value.as_ptr() as usize; // Make sure `value` is indeed a substring of `source` assert!(value_pos >= source_pos); assert!(value_pos + value.len() <= source_pos + source.len()); let start = value_pos - source_pos; let end = start + value.len(); IndexedCow::Borrowed { start, end } } } } /// Returns `ŧrue` if this represents an empty span. pub fn is_empty(&self) -> bool { match *self { IndexedCow::Borrowed { start, end } => start == end, IndexedCow::Owned(ref content) => content.is_empty(), } } /// If `self` is borrowed, offset its indices by the given value. /// /// Useful to update spans when concatenating sources. pub fn offset(&mut self, offset: usize) { if let IndexedCow::Borrowed { ref mut start, ref mut end, } = *self { *start += offset; *end += offset; } } }