From 3725262ca384d28a46e03013023a19677b5a35fe Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Fri, 26 Jul 2024 01:37:49 -0700 Subject: [PATCH] feat(text): Add `Add` and `AddAssign` implementations for `Line`, `Span`, and `Text` (#1236) This enables: ```rust let line = Span::raw("Red").red() + Span::raw("blue").blue(); let line = Line::raw("Red").red() + Span::raw("blue").blue(); let line = Line::raw("Red").red() + Line::raw("Blue").blue(); let text = Line::raw("Red").red() + Line::raw("Blue").blue(); let text = Text::raw("Red").red() + Line::raw("Blue").blue(); let mut line = Line::raw("Red").red(); line += Span::raw("Blue").blue(); let mut text = Text::raw("Red").red(); text += Line::raw("Blue").blue(); line.extend(vec![Span::raw("1"), Span::raw("2"), Span::raw("3")]); ``` --- src/text/line.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ src/text/span.rs | 31 +++++++++++++++++ src/text/text.rs | 65 ++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) diff --git a/src/text/line.rs b/src/text/line.rs index 5e893c98..41a9fc60 100644 --- a/src/text/line.rs +++ b/src/text/line.rs @@ -548,6 +548,37 @@ where } } +/// Adds a `Span` to a `Line`, returning a new `Line` with the `Span` added. +impl<'a> std::ops::Add> for Line<'a> { + type Output = Self; + + fn add(mut self, rhs: Span<'a>) -> Self::Output { + self.spans.push(rhs); + self + } +} + +/// Adds two `Line`s together, returning a new `Text` with the contents of the two `Line`s. +impl<'a> std::ops::Add for Line<'a> { + type Output = Text<'a>; + + fn add(self, rhs: Self) -> Self::Output { + Text::from(vec![self, rhs]) + } +} + +impl<'a> std::ops::AddAssign> for Line<'a> { + fn add_assign(&mut self, rhs: Span<'a>) { + self.spans.push(rhs); + } +} + +impl<'a> Extend> for Line<'a> { + fn extend>>(&mut self, iter: T) { + self.spans.extend(iter); + } +} + impl Widget for Line<'_> { fn render(self, area: Rect, buf: &mut Buffer) { self.render_ref(area, buf); @@ -896,6 +927,62 @@ mod tests { assert_eq!(line.spans, vec![span],); } + #[test] + fn add_span() { + assert_eq!( + Line::raw("Red").red() + Span::raw("blue").blue(), + Line { + spans: vec![Span::raw("Red"), Span::raw("blue").blue()], + style: Style::new().red(), + alignment: None, + }, + ); + } + + #[test] + fn add_line() { + assert_eq!( + Line::raw("Red").red() + Line::raw("Blue").blue(), + Text { + lines: vec![Line::raw("Red").red(), Line::raw("Blue").blue()], + style: Style::default(), + alignment: None, + } + ); + } + + #[test] + fn add_assign_span() { + let mut line = Line::raw("Red").red(); + line += Span::raw("Blue").blue(); + assert_eq!( + line, + Line { + spans: vec![Span::raw("Red"), Span::raw("Blue").blue()], + style: Style::new().red(), + alignment: None, + }, + ); + } + + #[test] + fn extend() { + let mut line = Line::from("Hello, "); + line.extend(vec![Span::raw("world!")]); + assert_eq!(line.spans, vec![Span::raw("Hello, "), Span::raw("world!")]); + + let mut line = Line::from("Hello, "); + line.extend(vec![Span::raw("world! "), Span::raw("How are you?")]); + assert_eq!( + line.spans, + vec![ + Span::raw("Hello, "), + Span::raw("world! "), + Span::raw("How are you?") + ] + ); + } + #[test] fn into_string() { let line = Line::from(vec![ diff --git a/src/text/span.rs b/src/text/span.rs index 3b7ebfc0..ec64c400 100644 --- a/src/text/span.rs +++ b/src/text/span.rs @@ -342,6 +342,14 @@ where } } +impl<'a> std::ops::Add for Span<'a> { + type Output = Line<'a>; + + fn add(self, rhs: Self) -> Self::Output { + Line::from_iter([self, rhs]) + } +} + impl<'a> Styled for Span<'a> { type Item = Self; @@ -773,4 +781,27 @@ mod tests { ] ); } + + #[test] + fn add() { + assert_eq!( + Span::default() + Span::default(), + Line::from(vec![Span::default(), Span::default()]) + ); + + assert_eq!( + Span::default() + Span::raw("test"), + Line::from(vec![Span::default(), Span::raw("test")]) + ); + + assert_eq!( + Span::raw("test") + Span::default(), + Line::from(vec![Span::raw("test"), Span::default()]) + ); + + assert_eq!( + Span::raw("test") + Span::raw("content"), + Line::from(vec![Span::raw("test"), Span::raw("content")]) + ); + } } diff --git a/src/text/text.rs b/src/text/text.rs index 1bbaeb1d..a87afcda 100644 --- a/src/text/text.rs +++ b/src/text/text.rs @@ -570,6 +570,33 @@ where } } +impl<'a> std::ops::Add> for Text<'a> { + type Output = Self; + + fn add(mut self, line: Line<'a>) -> Self::Output { + self.push_line(line); + self + } +} + +/// Adds two `Text` together. +/// +/// This ignores the style and alignment of the second `Text`. +impl<'a> std::ops::Add for Text<'a> { + type Output = Self; + + fn add(mut self, text: Self) -> Self::Output { + self.lines.extend(text.lines); + self + } +} + +impl<'a> std::ops::AddAssign> for Text<'a> { + fn add_assign(&mut self, line: Line<'a>) { + self.push_line(line); + } +} + impl<'a, T> Extend for Text<'a> where T: Into>, @@ -829,6 +856,44 @@ mod tests { assert_eq!(iter.next(), None); } + #[test] + fn add_line() { + assert_eq!( + Text::raw("Red").red() + Line::raw("Blue").blue(), + Text { + lines: vec![Line::raw("Red"), Line::raw("Blue").blue()], + style: Style::new().red(), + alignment: None, + } + ); + } + + #[test] + fn add_text() { + assert_eq!( + Text::raw("Red").red() + Text::raw("Blue").blue(), + Text { + lines: vec![Line::raw("Red"), Line::raw("Blue")], + style: Style::new().red(), + alignment: None, + } + ); + } + + #[test] + fn add_assign_line() { + let mut text = Text::raw("Red").red(); + text += Line::raw("Blue").blue(); + assert_eq!( + text, + Text { + lines: vec![Line::raw("Red"), Line::raw("Blue").blue()], + style: Style::new().red(), + alignment: None, + } + ); + } + #[test] fn extend() { let mut text = Text::from("The first line\nThe second line");