#![warn(missing_docs)]
use alloc::borrow::{Cow, ToOwned};
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use unicode_width::UnicodeWidthStr;
use crate::buffer::Buffer;
use crate::layout::{Alignment, Rect};
use crate::style::{Style, Styled};
use crate::text::{Line, Span};
use crate::widgets::Widget;
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Text<'a> {
pub alignment: Option<Alignment>,
pub style: Style,
pub lines: Vec<Line<'a>>,
}
impl fmt::Debug for Text<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.lines.is_empty() {
f.write_str("Text::default()")?;
} else if self.lines.len() == 1 {
write!(f, "Text::from({:?})", self.lines[0])?;
} else {
f.write_str("Text::from_iter(")?;
f.debug_list().entries(self.lines.iter()).finish()?;
f.write_str(")")?;
}
self.style.fmt_stylize(f)?;
match self.alignment {
Some(Alignment::Left) => f.write_str(".left_aligned()")?,
Some(Alignment::Center) => f.write_str(".centered()")?,
Some(Alignment::Right) => f.write_str(".right_aligned()")?,
_ => (),
}
Ok(())
}
}
impl<'a> Text<'a> {
pub fn raw<T>(content: T) -> Self
where
T: Into<Cow<'a, str>>,
{
let lines: Vec<_> = match content.into() {
Cow::Borrowed("") => vec![Line::from("")],
Cow::Borrowed(s) => s.lines().map(Line::from).collect(),
Cow::Owned(s) if s.is_empty() => vec![Line::from("")],
Cow::Owned(s) => s.lines().map(|l| Line::from(l.to_owned())).collect(),
};
Self::from(lines)
}
pub fn styled<T, S>(content: T, style: S) -> Self
where
T: Into<Cow<'a, str>>,
S: Into<Style>,
{
Self::raw(content).patch_style(style)
}
pub fn width(&self) -> usize {
UnicodeWidthStr::width(self)
}
pub fn height(&self) -> usize {
self.lines.len()
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
self.style = style.into();
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
self.style = self.style.patch(style);
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn reset_style(self) -> Self {
self.patch_style(Style::reset())
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn alignment(self, alignment: Alignment) -> Self {
Self {
alignment: Some(alignment),
..self
}
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn left_aligned(self) -> Self {
self.alignment(Alignment::Left)
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn centered(self) -> Self {
self.alignment(Alignment::Center)
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn right_aligned(self) -> Self {
self.alignment(Alignment::Right)
}
pub fn iter(&self) -> core::slice::Iter<'_, Line<'a>> {
self.lines.iter()
}
pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Line<'a>> {
self.lines.iter_mut()
}
pub fn push_line<T: Into<Line<'a>>>(&mut self, line: T) {
self.lines.push(line.into());
}
pub fn push_span<T: Into<Span<'a>>>(&mut self, span: T) {
let span = span.into();
if let Some(last) = self.lines.last_mut() {
last.push_span(span);
} else {
self.lines.push(Line::from(span));
}
}
}
impl UnicodeWidthStr for Text<'_> {
fn width(&self) -> usize {
self.lines
.iter()
.map(UnicodeWidthStr::width)
.max()
.unwrap_or_default()
}
fn width_cjk(&self) -> usize {
self.lines
.iter()
.map(UnicodeWidthStr::width_cjk)
.max()
.unwrap_or_default()
}
}
impl<'a> IntoIterator for Text<'a> {
type Item = Line<'a>;
type IntoIter = alloc::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.lines.into_iter()
}
}
impl<'a> IntoIterator for &'a Text<'a> {
type Item = &'a Line<'a>;
type IntoIter = core::slice::Iter<'a, Line<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut Text<'a> {
type Item = &'a mut Line<'a>;
type IntoIter = core::slice::IterMut<'a, Line<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl From<String> for Text<'_> {
fn from(s: String) -> Self {
Self::raw(s)
}
}
impl<'a> From<&'a str> for Text<'a> {
fn from(s: &'a str) -> Self {
Self::raw(s)
}
}
impl<'a> From<Cow<'a, str>> for Text<'a> {
fn from(s: Cow<'a, str>) -> Self {
Self::raw(s)
}
}
impl<'a> From<Span<'a>> for Text<'a> {
fn from(span: Span<'a>) -> Self {
Self {
lines: vec![Line::from(span)],
..Default::default()
}
}
}
impl<'a> From<Line<'a>> for Text<'a> {
fn from(line: Line<'a>) -> Self {
Self {
lines: vec![line],
..Default::default()
}
}
}
impl<'a> From<Vec<Line<'a>>> for Text<'a> {
fn from(lines: Vec<Line<'a>>) -> Self {
Self {
lines,
..Default::default()
}
}
}
impl<'a, T> FromIterator<T> for Text<'a>
where
T: Into<Line<'a>>,
{
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let lines = iter.into_iter().map(Into::into).collect();
Self {
lines,
..Default::default()
}
}
}
impl<'a> core::ops::Add<Line<'a>> for Text<'a> {
type Output = Self;
fn add(mut self, line: Line<'a>) -> Self::Output {
self.push_line(line);
self
}
}
impl core::ops::Add<Self> for Text<'_> {
type Output = Self;
fn add(mut self, text: Self) -> Self::Output {
self.lines.extend(text.lines);
self
}
}
impl core::ops::AddAssign for Text<'_> {
fn add_assign(&mut self, rhs: Self) {
self.lines.extend(rhs.lines);
}
}
impl<'a> core::ops::AddAssign<Line<'a>> for Text<'a> {
fn add_assign(&mut self, line: Line<'a>) {
self.push_line(line);
}
}
impl<'a, T> Extend<T> for Text<'a>
where
T: Into<Line<'a>>,
{
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
let lines = iter.into_iter().map(Into::into);
self.lines.extend(lines);
}
}
pub trait ToText {
fn to_text(&self) -> Text<'_>;
}
impl<T: fmt::Display> ToText for T {
fn to_text(&self) -> Text<'_> {
Text::raw(self.to_string())
}
}
impl fmt::Display for Text<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some((last, rest)) = self.lines.split_last() {
for line in rest {
writeln!(f, "{line}")?;
}
write!(f, "{last}")?;
}
Ok(())
}
}
impl Widget for Text<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
Widget::render(&self, area, buf);
}
}
impl Widget for &Text<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
buf.set_style(area, self.style);
for (line, line_area) in self.iter().zip(area.rows()) {
line.render_with_alignment(line_area, buf, self.alignment);
}
}
}
impl Styled for Text<'_> {
type Item = Self;
fn style(&self) -> Style {
self.style
}
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
self.style(style)
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use core::iter;
use rstest::{fixture, rstest};
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[fixture]
fn small_buf() -> Buffer {
Buffer::empty(Rect::new(0, 0, 10, 1))
}
#[test]
fn raw() {
let text = Text::raw("The first line\nThe second line");
assert_eq!(
text.lines,
vec![Line::from("The first line"), Line::from("The second line")]
);
}
#[test]
fn styled() {
let style = Style::new().yellow().italic();
let styled_text = Text::styled("The first line\nThe second line", style);
let mut text = Text::raw("The first line\nThe second line");
text.style = style;
assert_eq!(styled_text, text);
}
#[test]
fn width() {
let text = Text::from("The first line\nThe second line");
assert_eq!(15, text.width());
}
#[test]
fn height() {
let text = Text::from("The first line\nThe second line");
assert_eq!(2, text.height());
}
#[test]
fn patch_style() {
let style = Style::new().yellow().italic();
let style2 = Style::new().red().underlined();
let text = Text::styled("The first line\nThe second line", style).patch_style(style2);
let expected_style = Style::new().red().italic().underlined();
let expected_text = Text::styled("The first line\nThe second line", expected_style);
assert_eq!(text, expected_text);
}
#[test]
fn reset_style() {
let style = Style::new().yellow().italic();
let text = Text::styled("The first line\nThe second line", style).reset_style();
assert_eq!(text.style, Style::reset());
}
#[test]
fn from_string() {
let text = Text::from(String::from("The first line\nThe second line"));
assert_eq!(
text.lines,
vec![Line::from("The first line"), Line::from("The second line")]
);
}
#[test]
fn from_str() {
let text = Text::from("The first line\nThe second line");
assert_eq!(
text.lines,
vec![Line::from("The first line"), Line::from("The second line")]
);
}
#[test]
fn from_cow() {
let text = Text::from(Cow::Borrowed("The first line\nThe second line"));
assert_eq!(
text.lines,
vec![Line::from("The first line"), Line::from("The second line")]
);
}
#[test]
fn from_span() {
let style = Style::new().yellow().italic();
let text = Text::from(Span::styled("The first line\nThe second line", style));
assert_eq!(
text.lines,
vec![Line::from(Span::styled(
"The first line\nThe second line",
style
))]
);
}
#[test]
fn from_line() {
let text = Text::from(Line::from("The first line"));
assert_eq!(text.lines, [Line::from("The first line")]);
}
#[rstest]
#[case(42, Text::from("42"))]
#[case("just\ntesting", Text::from("just\ntesting"))]
#[case(true, Text::from("true"))]
#[case(6.66, Text::from("6.66"))]
#[case('a', Text::from("a"))]
#[case(String::from("hello"), Text::from("hello"))]
#[case(-1, Text::from("-1"))]
#[case("line1\nline2", Text::from("line1\nline2"))]
#[case(
"first line\nsecond line\nthird line",
Text::from("first line\nsecond line\nthird line")
)]
#[case("trailing newline\n", Text::from("trailing newline\n"))]
fn to_text(#[case] value: impl fmt::Display, #[case] expected: Text) {
assert_eq!(value.to_text(), expected);
}
#[test]
fn from_vec_line() {
let text = Text::from(vec![
Line::from("The first line"),
Line::from("The second line"),
]);
assert_eq!(
text.lines,
vec![Line::from("The first line"), Line::from("The second line")]
);
}
#[test]
fn from_iterator() {
let text = Text::from_iter(vec!["The first line", "The second line"]);
assert_eq!(
text.lines,
vec![Line::from("The first line"), Line::from("The second line")]
);
}
#[test]
fn collect() {
let text: Text = iter::once("The first line")
.chain(iter::once("The second line"))
.collect();
assert_eq!(
text.lines,
vec![Line::from("The first line"), Line::from("The second line")]
);
}
#[test]
fn into_iter() {
let text = Text::from("The first line\nThe second line");
let mut iter = text.into_iter();
assert_eq!(iter.next(), Some(Line::from("The first line")));
assert_eq!(iter.next(), Some(Line::from("The second line")));
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_text() {
let mut text = Text::raw("Red").red();
text += Text::raw("Blue").blue();
assert_eq!(
text,
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");
text.extend(vec![
Line::from("The third line"),
Line::from("The fourth line"),
]);
assert_eq!(
text.lines,
vec![
Line::from("The first line"),
Line::from("The second line"),
Line::from("The third line"),
Line::from("The fourth line"),
]
);
}
#[test]
fn extend_from_iter() {
let mut text = Text::from("The first line\nThe second line");
text.extend(vec![
Line::from("The third line"),
Line::from("The fourth line"),
]);
assert_eq!(
text.lines,
vec![
Line::from("The first line"),
Line::from("The second line"),
Line::from("The third line"),
Line::from("The fourth line"),
]
);
}
#[test]
fn extend_from_iter_str() {
let mut text = Text::from("The first line\nThe second line");
text.extend(vec!["The third line", "The fourth line"]);
assert_eq!(
text.lines,
vec![
Line::from("The first line"),
Line::from("The second line"),
Line::from("The third line"),
Line::from("The fourth line"),
]
);
}
#[rstest]
#[case::one_line("The first line")]
#[case::multiple_lines("The first line\nThe second line")]
fn display_raw_text(#[case] value: &str) {
let text = Text::raw(value);
assert_eq!(format!("{text}"), value);
}
#[test]
fn display_styled_text() {
let styled_text = Text::styled(
"The first line\nThe second line",
Style::new().yellow().italic(),
);
assert_eq!(format!("{styled_text}"), "The first line\nThe second line");
}
#[test]
fn display_text_from_vec() {
let text_from_vec = Text::from(vec![
Line::from("The first line"),
Line::from("The second line"),
]);
assert_eq!(
format!("{text_from_vec}"),
"The first line\nThe second line"
);
}
#[test]
fn display_extended_text() {
let mut text = Text::from("The first line\nThe second line");
assert_eq!(format!("{text}"), "The first line\nThe second line");
text.extend(vec![
Line::from("The third line"),
Line::from("The fourth line"),
]);
assert_eq!(
format!("{text}"),
"The first line\nThe second line\nThe third line\nThe fourth line"
);
}
#[test]
fn stylize() {
assert_eq!(Text::default().green().style, Color::Green.into());
assert_eq!(
Text::default().on_green().style,
Style::new().bg(Color::Green)
);
assert_eq!(Text::default().italic().style, Modifier::ITALIC.into());
}
#[test]
fn left_aligned() {
let text = Text::from("Hello, world!").left_aligned();
assert_eq!(text.alignment, Some(Alignment::Left));
}
#[test]
fn centered() {
let text = Text::from("Hello, world!").centered();
assert_eq!(text.alignment, Some(Alignment::Center));
}
#[test]
fn right_aligned() {
let text = Text::from("Hello, world!").right_aligned();
assert_eq!(text.alignment, Some(Alignment::Right));
}
#[test]
fn push_line() {
let mut text = Text::from("A");
text.push_line(Line::from("B"));
text.push_line(Span::from("C"));
text.push_line("D");
assert_eq!(
text.lines,
vec![
Line::raw("A"),
Line::raw("B"),
Line::raw("C"),
Line::raw("D")
]
);
}
#[test]
fn push_line_empty() {
let mut text = Text::default();
text.push_line(Line::from("Hello, world!"));
assert_eq!(text.lines, [Line::from("Hello, world!")]);
}
#[test]
fn push_span() {
let mut text = Text::from("A");
text.push_span(Span::raw("B"));
text.push_span("C");
assert_eq!(
text.lines,
vec![Line::from(vec![
Span::raw("A"),
Span::raw("B"),
Span::raw("C")
])],
);
}
#[test]
fn push_span_empty() {
let mut text = Text::default();
text.push_span(Span::raw("Hello, world!"));
assert_eq!(text.lines, [Line::from(Span::raw("Hello, world!"))]);
}
mod widget {
use super::*;
#[test]
fn render() {
let text = Text::from("foo");
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["foo "]));
}
#[rstest]
fn render_out_of_bounds(mut small_buf: Buffer) {
let out_of_bounds_area = Rect::new(20, 20, 10, 1);
Text::from("Hello, world!").render(out_of_bounds_area, &mut small_buf);
assert_eq!(small_buf, Buffer::empty(small_buf.area));
}
#[test]
fn render_right_aligned() {
let text = Text::from("foo").alignment(Alignment::Right);
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" foo"]));
}
#[test]
fn render_centered_odd() {
let text = Text::from("foo").alignment(Alignment::Center);
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" foo "]));
}
#[test]
fn render_centered_even() {
let text = Text::from("foo").alignment(Alignment::Center);
let area = Rect::new(0, 0, 6, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" foo "]));
}
#[test]
fn render_right_aligned_with_truncation() {
let text = Text::from("123456789").alignment(Alignment::Right);
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["56789"]));
}
#[test]
fn render_centered_odd_with_truncation() {
let text = Text::from("123456789").alignment(Alignment::Center);
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["34567"]));
}
#[test]
fn render_centered_even_with_truncation() {
let text = Text::from("123456789").alignment(Alignment::Center);
let area = Rect::new(0, 0, 6, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["234567"]));
}
#[test]
fn render_one_line_right() {
let text = Text::from(vec![
"foo".into(),
Line::from("bar").alignment(Alignment::Center),
])
.alignment(Alignment::Right);
let area = Rect::new(0, 0, 5, 2);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" foo", " bar "]));
}
#[test]
fn render_only_styles_line_area() {
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
Text::from("foo".on_blue()).render(area, &mut buf);
let mut expected = Buffer::with_lines(["foo "]);
expected.set_style(Rect::new(0, 0, 3, 1), Style::new().bg(Color::Blue));
assert_eq!(buf, expected);
}
#[test]
fn render_truncates() {
let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
Text::from("foobar".on_blue()).render(Rect::new(0, 0, 3, 1), &mut buf);
let mut expected = Buffer::with_lines(["foo "]);
expected.set_style(Rect::new(0, 0, 3, 1), Style::new().bg(Color::Blue));
assert_eq!(buf, expected);
}
}
mod iterators {
use super::*;
#[fixture]
fn hello_world() -> Text<'static> {
Text::from(vec![
Line::styled("Hello ", Color::Blue),
Line::styled("world!", Color::Green),
])
}
#[rstest]
fn iter(hello_world: Text<'_>) {
let mut iter = hello_world.iter();
assert_eq!(iter.next(), Some(&Line::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(&Line::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[rstest]
fn iter_mut(mut hello_world: Text<'_>) {
let mut iter = hello_world.iter_mut();
assert_eq!(iter.next(), Some(&mut Line::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(&mut Line::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[rstest]
fn into_iter(hello_world: Text<'_>) {
let mut iter = hello_world.into_iter();
assert_eq!(iter.next(), Some(Line::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(Line::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[rstest]
fn into_iter_ref(hello_world: Text<'_>) {
let mut iter = (&hello_world).into_iter();
assert_eq!(iter.next(), Some(&Line::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(&Line::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[test]
fn into_iter_mut_ref() {
let mut hello_world = Text::from(vec![
Line::styled("Hello ", Color::Blue),
Line::styled("world!", Color::Green),
]);
let mut iter = (&mut hello_world).into_iter();
assert_eq!(iter.next(), Some(&mut Line::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(&mut Line::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[rstest]
fn for_loop_ref(hello_world: Text<'_>) {
let mut result = String::new();
for line in &hello_world {
result.push_str(line.to_string().as_ref());
}
assert_eq!(result, "Hello world!");
}
#[rstest]
fn for_loop_mut_ref() {
let mut hello_world = Text::from(vec![
Line::styled("Hello ", Color::Blue),
Line::styled("world!", Color::Green),
]);
let mut result = String::new();
for line in &mut hello_world {
result.push_str(line.to_string().as_ref());
}
assert_eq!(result, "Hello world!");
}
#[rstest]
fn for_loop_into(hello_world: Text<'_>) {
let mut result = String::new();
for line in hello_world {
result.push_str(line.to_string().as_ref());
}
assert_eq!(result, "Hello world!");
}
}
#[rstest]
#[case::default(Text::default(), "Text::default()")]
#[case::raw(
Text::raw("Hello, world!"),
r#"Text::from(Line::from("Hello, world!"))"#
)]
#[case::styled(
Text::styled("Hello, world!", Color::Yellow),
r#"Text::from(Line::from("Hello, world!")).yellow()"#
)]
#[case::complex_styled(
Text::from("Hello, world!").yellow().on_blue().bold().italic().not_dim().not_hidden(),
r#"Text::from(Line::from("Hello, world!")).yellow().on_blue().bold().italic().not_dim().not_hidden()"#
)]
#[case::alignment(
Text::from("Hello, world!").centered(),
r#"Text::from(Line::from("Hello, world!")).centered()"#
)]
#[case::styled_alignment(
Text::styled("Hello, world!", Color::Yellow).centered(),
r#"Text::from(Line::from("Hello, world!")).yellow().centered()"#
)]
#[case::multiple_lines(
Text::from(vec![
Line::from("Hello, world!"),
Line::from("How are you?")
]),
r#"Text::from_iter([Line::from("Hello, world!"), Line::from("How are you?")])"#
)]
fn debug(#[case] text: Text, #[case] expected: &str) {
assert_eq!(format!("{text:?}"), expected);
}
#[test]
fn debug_alternate() {
let text = Text::from_iter([
Line::from("Hello, world!"),
Line::from("How are you?").bold().left_aligned(),
Line::from_iter([
Span::from("I'm "),
Span::from("doing ").italic(),
Span::from("great!").bold(),
]),
])
.on_blue()
.italic()
.centered();
assert_eq!(
format!("{text:#?}"),
indoc::indoc! {r#"
Text::from_iter([
Line::from("Hello, world!"),
Line::from("How are you?").bold().left_aligned(),
Line::from_iter([
Span::from("I'm "),
Span::from("doing ").italic(),
Span::from("great!").bold(),
]),
]).on_blue().italic().centered()"#}
);
}
}