diff --git a/cursive-core/src/utils/markup/cursup.rs b/cursive-core/src/utils/markup/cursup.rs index 7b0cfead..7cd318d0 100644 --- a/cursive-core/src/utils/markup/cursup.rs +++ b/cursive-core/src/utils/markup/cursup.rs @@ -33,14 +33,36 @@ struct Candidate { brace: usize, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] enum Event { + StartSkip, Start(Style), End, - StartSkip, Resume, } +impl PartialOrd for Event { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Event { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self, other) { + (Event::StartSkip, Event::Start(_) | Event::End | Event::Resume) + | (Event::Start(_), Event::End | Event::Resume) + | (Event::End, Event::Resume) => std::cmp::Ordering::Less, + + (Event::Start(_) | Event::End | Event::Resume, Event::StartSkip) + | (Event::End | Event::Resume, Event::Start(_)) + | (Event::Resume, Event::End) => std::cmp::Ordering::Greater, + + _ => std::cmp::Ordering::Equal, + } + } +} + /// Parse spans for the given text. pub fn parse_spans(input: &str) -> Vec { let mut candidates = Vec::::new(); @@ -63,6 +85,8 @@ pub fn parse_spans(input: &str) -> Vec { events.push((candidate.brace + 1, Event::Start(style))); events.push((i, Event::End)); events.push((i + 1, Event::Resume)); + + state = State::Plain; } (State::Plain, _) => (), @@ -89,7 +113,7 @@ pub fn parse_spans(input: &str) -> Vec { } } - events.sort_by_key(|(i, _)| *i); + events.sort(); let mut spans = Vec::new(); let mut style_stack = vec![Style::default()]; @@ -255,6 +279,18 @@ mod tests { ); } + #[test] + fn simple() { + let parsed = parse("/{simple}").canonical(); + assert_eq!(parsed, StyledString::plain("simple")); + } + + #[test] + fn escape() { + let parsed = parse("/{/}red{foo}").canonical(); + assert_eq!(parsed, StyledString::plain("/red{foo}")); + } + #[test] fn hex_color() { let parsed = parse("/#ff0000{red}"); diff --git a/cursive-core/src/utils/span.rs b/cursive-core/src/utils/span.rs index 72204816..1e621d8e 100644 --- a/cursive-core/src/utils/span.rs +++ b/cursive-core/src/utils/span.rs @@ -229,7 +229,34 @@ impl SpannedString { SpannedString { source, spans } } + /// Compacts and simplifies this string, resulting in a canonical form. + /// + /// If two styled strings represent the same styled text, they should have equal canonical + /// forms. + /// + /// (The PartialEq implementation for StyledStrings requires both the source and spans to be + /// equals, so non-visible changes such as text in the source between spans could cause + /// StyledStrings to evaluate as non-equal.) + pub fn canonicalize(&mut self) + where + T: PartialEq, + { + self.compact(); + self.simplify(); + } + + /// Returns the canonical form of this styled string. + pub fn canonical(mut self) -> Self + where + T: PartialEq, + { + self.canonicalize(); + self + } + /// Compacts the source to only include the spans content. + /// + /// This does not change the number of spans, but changes the source. pub fn compact(&mut self) { // Prepare the new source let mut source = String::new(); @@ -247,6 +274,36 @@ impl SpannedString { self.source = source; } + /// Attemps to reduce the number of spans by merging consecutive similar ones. + pub fn simplify(&mut self) + where + T: PartialEq, + { + // Now, merge consecutive similar spans. + let mut i = 0; + while i + 1 < self.spans.len() { + let left = &self.spans[i]; + let right = &self.spans[i + 1]; + if left.attr != right.attr { + i += 1; + continue; + } + + let (_, left_end) = left.content.as_borrowed().unwrap(); + let (right_start, right_end) = right.content.as_borrowed().unwrap(); + let right_width = right.width; + + if left_end != right_start { + i += 1; + continue; + } + + *self.spans[i].content.as_borrowed_mut().unwrap().1 = right_end; + self.spans[i].width += right_width; + self.spans.remove(i + 1); + } + } + /// Shrink the source to discard any unused suffix. pub fn trim_end(&mut self) { if let Some(max) = self @@ -558,6 +615,19 @@ impl IndexedCow { } } + /// Return the `(start, end)` indexes if `self` is `IndexedCow::Borrowed`. + pub fn as_borrowed_mut(&mut self) -> Option<(&mut usize, &mut usize)> { + if let IndexedCow::Borrowed { + ref mut start, + ref mut end, + } = *self + { + Some((start, end)) + } else { + None + } + } + /// Returns the embedded text content if `self` is `IndexedCow::Owned`. pub fn as_owned(&self) -> Option<&str> { if let IndexedCow::Owned(ref content) = *self {