Skip to main content

ratatui_core/text/
text.rs

1#![warn(missing_docs)]
2use alloc::borrow::{Cow, ToOwned};
3use alloc::string::{String, ToString};
4use alloc::vec;
5use alloc::vec::Vec;
6use core::fmt;
7
8use unicode_width::UnicodeWidthStr;
9
10use crate::buffer::Buffer;
11use crate::layout::{Alignment, Rect};
12use crate::style::{Style, Styled};
13use crate::text::{Line, Span};
14use crate::widgets::Widget;
15
16/// A string split over one or more lines.
17///
18/// [`Text`] is used wherever text is displayed in the terminal and represents one or more [`Line`]s
19/// of text. When a [`Text`] is rendered, each line is rendered as a single line of text from top to
20/// bottom of the area. The text can be styled and aligned.
21///
22/// # Constructor Methods
23///
24/// - [`Text::raw`] creates a `Text` (potentially multiple lines) with no style.
25/// - [`Text::styled`] creates a `Text` (potentially multiple lines) with a style.
26/// - [`Text::default`] creates a `Text` with empty content and the default style.
27///
28/// # Conversion Methods
29///
30/// - [`Text::from`] creates a `Text` from a `String`.
31/// - [`Text::from`] creates a `Text` from a `&str`.
32/// - [`Text::from`] creates a `Text` from a `Cow<str>`.
33/// - [`Text::from`] creates a `Text` from a [`Span`].
34/// - [`Text::from`] creates a `Text` from a [`Line`].
35/// - [`Text::from`] creates a `Text` from a `Vec<Line>`.
36/// - [`Text::from`] creates a `Text` from a `&[Into<Line>]`.
37/// - [`Text::from_iter`] creates a `Text` from an iterator of items that can be converted into
38///   `Line`.
39///
40/// # Setter Methods
41///
42/// These methods are fluent setters. They return a `Text` with the property set.
43///
44/// - [`Text::style`] sets the style of this `Text`.
45/// - [`Text::alignment`] sets the alignment for this `Text`.
46/// - [`Text::left_aligned`] sets the alignment to [`Alignment::Left`].
47/// - [`Text::centered`] sets the alignment to [`Alignment::Center`].
48/// - [`Text::right_aligned`] sets the alignment to [`Alignment::Right`].
49///
50/// # Iteration Methods
51///
52/// - [`Text::iter`] returns an iterator over the lines of the text.
53/// - [`Text::iter_mut`] returns an iterator that allows modifying each line.
54/// - [`Text::into_iter`] returns an iterator over the lines of the text.
55///
56/// # Other Methods
57///
58/// - [`Text::width`] returns the max width of all the lines.
59/// - [`Text::height`] returns the height.
60/// - [`Text::patch_style`] patches the style of this `Text`, adding modifiers from the given style.
61/// - [`Text::reset_style`] resets the style of the `Text`.
62/// - [`Text::push_line`] adds a line to the text.
63/// - [`Text::push_span`] adds a span to the last line of the text.
64///
65/// # Examples
66///
67/// ## Creating Text
68///
69/// A [`Text`], like a [`Line`], can be constructed using one of the many `From` implementations or
70/// via the [`Text::raw`] and [`Text::styled`] methods. Helpfully, [`Text`] also implements
71/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
72///
73/// ```rust
74/// use std::borrow::Cow;
75/// use std::iter;
76///
77/// use ratatui_core::style::{Color, Modifier, Style, Stylize};
78/// use ratatui_core::text::{Line, Span, Text};
79///
80/// let style = Style::new().yellow().italic();
81/// let text = Text::raw("The first line\nThe second line").style(style);
82/// let text = Text::styled("The first line\nThe second line", style);
83/// let text = Text::styled(
84///     "The first line\nThe second line",
85///     (Color::Yellow, Modifier::ITALIC),
86/// );
87///
88/// let text = Text::from("The first line\nThe second line");
89/// let text = Text::from(String::from("The first line\nThe second line"));
90/// let text = Text::from(Cow::Borrowed("The first line\nThe second line"));
91/// let text = Text::from(Span::styled("The first line\nThe second line", style));
92/// let text = Text::from(Line::from("The first line"));
93/// let text = Text::from(vec![
94///     Line::from("The first line"),
95///     Line::from("The second line"),
96/// ]);
97/// let text = Text::from_iter(iter::once("The first line").chain(iter::once("The second line")));
98///
99/// let mut text = Text::default();
100/// text.extend(vec![
101///     Line::from("The first line"),
102///     Line::from("The second line"),
103/// ]);
104/// text.extend(Text::from("The third line\nThe fourth line"));
105/// ```
106///
107/// ## Styling Text
108///
109/// The text's [`Style`] is used by the rendering widget to determine how to style the text. Each
110/// [`Line`] in the text will be styled with the [`Style`] of the text, and then with its own
111/// [`Style`]. `Text` also implements [`Styled`] which means you can use the methods of the
112/// [`Stylize`] trait.
113///
114/// ```rust
115/// use ratatui_core::style::{Color, Modifier, Style, Stylize};
116/// use ratatui_core::text::{Line, Text};
117///
118/// let text = Text::from("The first line\nThe second line").style(Style::new().yellow().italic());
119/// let text = Text::from("The first line\nThe second line")
120///     .yellow()
121///     .italic();
122/// let text = Text::from(vec![
123///     Line::from("The first line").yellow(),
124///     Line::from("The second line").yellow(),
125/// ])
126/// .italic();
127/// ```
128///
129/// ## Aligning Text
130/// The text's [`Alignment`] can be set using [`Text::alignment`] or the related helper methods.
131/// Lines composing the text can also be individually aligned with [`Line::alignment`].
132///
133/// ```rust
134/// use ratatui_core::layout::Alignment;
135/// use ratatui_core::text::{Line, Text};
136///
137/// let text = Text::from("The first line\nThe second line").alignment(Alignment::Right);
138/// let text = Text::from("The first line\nThe second line").right_aligned();
139/// let text = Text::from(vec![
140///     Line::from("The first line").left_aligned(),
141///     Line::from("The second line").right_aligned(),
142///     Line::from("The third line"),
143/// ])
144/// .centered();
145/// ```
146///
147/// ## Rendering Text
148/// `Text` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`] or to a
149/// `Frame`.
150///
151/// ```rust
152/// # use ratatui_core::{buffer::Buffer, layout::Rect};
153/// use ratatui_core::text::Text;
154/// use ratatui_core::widgets::Widget;
155///
156/// // within another widget's `render` method:
157/// # fn render(area: Rect, buf: &mut Buffer) {
158/// let text = Text::from("The first line\nThe second line");
159/// text.render(area, buf);
160/// # }
161/// ```
162///
163/// Or you can use the `render_widget` method on a `Frame` within a `Terminal::draw` closure.
164///
165/// ```rust,ignore
166/// # use ratatui::{Frame, layout::Rect, text::Text};
167/// # fn draw(frame: &mut Frame, area: Rect) {
168/// let text = Text::from("The first line\nThe second line");
169/// frame.render_widget(text, area);
170/// # }
171/// ```
172///
173/// ## Rendering Text with a Paragraph Widget
174///
175/// Usually apps will use the `Paragraph` widget instead of rendering a `Text` directly as it
176/// provides more functionality.
177///
178/// ```rust,ignore
179/// use ratatui::{
180///     buffer::Buffer,
181///     layout::Rect,
182///     text::Text,
183///     widgets::{Paragraph, Widget, Wrap},
184/// };
185///
186/// # fn render(area: Rect, buf: &mut Buffer) {
187/// let text = Text::from("The first line\nThe second line");
188/// let paragraph = Paragraph::new(text)
189///     .wrap(Wrap { trim: true })
190///     .scroll((1, 1))
191///     .render(area, buf);
192/// # }
193/// ```
194///
195/// [`Stylize`]: crate::style::Stylize
196#[derive(Default, Clone, Eq, PartialEq, Hash)]
197pub struct Text<'a> {
198    /// The alignment of this text.
199    pub alignment: Option<Alignment>,
200    /// The style of this text.
201    pub style: Style,
202    /// The lines that make up this piece of text.
203    pub lines: Vec<Line<'a>>,
204}
205
206impl fmt::Debug for Text<'_> {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        if self.lines.is_empty() {
209            f.write_str("Text::default()")?;
210        } else if self.lines.len() == 1 {
211            write!(f, "Text::from({:?})", self.lines[0])?;
212        } else {
213            f.write_str("Text::from_iter(")?;
214            f.debug_list().entries(self.lines.iter()).finish()?;
215            f.write_str(")")?;
216        }
217        self.style.fmt_stylize(f)?;
218        match self.alignment {
219            Some(Alignment::Left) => f.write_str(".left_aligned()")?,
220            Some(Alignment::Center) => f.write_str(".centered()")?,
221            Some(Alignment::Right) => f.write_str(".right_aligned()")?,
222            _ => (),
223        }
224        Ok(())
225    }
226}
227
228impl<'a> Text<'a> {
229    /// Create some text (potentially multiple lines) with no style.
230    ///
231    /// # Examples
232    ///
233    /// ```rust
234    /// use ratatui_core::text::Text;
235    ///
236    /// Text::raw("The first line\nThe second line");
237    /// Text::raw(String::from("The first line\nThe second line"));
238    /// ```
239    pub fn raw<T>(content: T) -> Self
240    where
241        T: Into<Cow<'a, str>>,
242    {
243        let lines: Vec<_> = match content.into() {
244            Cow::Borrowed("") => vec![Line::from("")],
245            Cow::Borrowed(s) => s.lines().map(Line::from).collect(),
246            Cow::Owned(s) if s.is_empty() => vec![Line::from("")],
247            Cow::Owned(s) => s.lines().map(|l| Line::from(l.to_owned())).collect(),
248        };
249        Self::from(lines)
250    }
251
252    /// Create some text (potentially multiple lines) with a style.
253    ///
254    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
255    /// your own type that implements [`Into<Style>`]).
256    ///
257    /// # Examples
258    ///
259    /// ```rust
260    /// use ratatui_core::style::{Color, Modifier, Style};
261    /// use ratatui_core::text::Text;
262    ///
263    /// let style = Style::default()
264    ///     .fg(Color::Yellow)
265    ///     .add_modifier(Modifier::ITALIC);
266    /// Text::styled("The first line\nThe second line", style);
267    /// Text::styled(String::from("The first line\nThe second line"), style);
268    /// ```
269    ///
270    /// [`Color`]: crate::style::Color
271    pub fn styled<T, S>(content: T, style: S) -> Self
272    where
273        T: Into<Cow<'a, str>>,
274        S: Into<Style>,
275    {
276        Self::raw(content).patch_style(style)
277    }
278
279    /// Returns the max width of all the lines.
280    ///
281    /// # Examples
282    ///
283    /// ```rust
284    /// use ratatui_core::text::Text;
285    ///
286    /// let text = Text::from("The first line\nThe second line");
287    /// assert_eq!(15, text.width());
288    /// ```
289    pub fn width(&self) -> usize {
290        UnicodeWidthStr::width(self)
291    }
292
293    /// Returns the height.
294    ///
295    /// # Examples
296    ///
297    /// ```rust
298    /// use ratatui_core::text::Text;
299    ///
300    /// let text = Text::from("The first line\nThe second line");
301    /// assert_eq!(2, text.height());
302    /// ```
303    pub const fn height(&self) -> usize {
304        self.lines.len()
305    }
306
307    /// Sets the style of this text.
308    ///
309    /// Defaults to [`Style::default()`].
310    ///
311    /// Note: This field was added in v0.26.0. Prior to that, the style of a text was determined
312    /// only by the style of each [`Line`] contained in the line. For this reason, this field may
313    /// not be supported by all widgets (outside of the `ratatui` crate itself).
314    ///
315    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
316    /// your own type that implements [`Into<Style>`]).
317    ///
318    /// # Examples
319    /// ```rust
320    /// use ratatui_core::style::{Style, Stylize};
321    /// use ratatui_core::text::Text;
322    ///
323    /// let mut line = Text::from("foo").style(Style::new().red());
324    /// ```
325    ///
326    /// [`Color`]: crate::style::Color
327    #[must_use = "method moves the value of self and returns the modified value"]
328    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
329        self.style = style.into();
330        self
331    }
332
333    /// Patches the style of this Text, adding modifiers from the given style.
334    ///
335    /// This is useful for when you want to apply a style to a text that already has some styling.
336    /// In contrast to [`Text::style`], this method will not overwrite the existing style, but
337    /// instead will add the given style's modifiers to this text's style.
338    ///
339    /// `Text` also implements [`Styled`] which means you can use the methods of the [`Stylize`]
340    /// trait.
341    ///
342    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
343    /// your own type that implements [`Into<Style>`]).
344    ///
345    /// This is a fluent setter method which must be chained or used as it consumes self
346    ///
347    /// # Examples
348    ///
349    /// ```rust
350    /// use ratatui_core::style::{Color, Modifier};
351    /// use ratatui_core::text::Text;
352    ///
353    /// let raw_text = Text::styled("The first line\nThe second line", Modifier::ITALIC);
354    /// let styled_text = Text::styled(
355    ///     String::from("The first line\nThe second line"),
356    ///     (Color::Yellow, Modifier::ITALIC),
357    /// );
358    /// assert_ne!(raw_text, styled_text);
359    ///
360    /// let raw_text = raw_text.patch_style(Color::Yellow);
361    /// assert_eq!(raw_text, styled_text);
362    /// ```
363    ///
364    /// [`Color`]: crate::style::Color
365    /// [`Stylize`]: crate::style::Stylize
366    #[must_use = "method moves the value of self and returns the modified value"]
367    pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
368        self.style = self.style.patch(style);
369        self
370    }
371
372    /// Resets the style of the Text.
373    ///
374    /// Equivalent to calling [`patch_style(Style::reset())`](Text::patch_style).
375    ///
376    /// This is a fluent setter method which must be chained or used as it consumes self
377    ///
378    /// # Examples
379    ///
380    /// ```rust
381    /// use ratatui_core::style::{Color, Modifier, Style};
382    /// use ratatui_core::text::Text;
383    ///
384    /// let text = Text::styled(
385    ///     "The first line\nThe second line",
386    ///     (Color::Yellow, Modifier::ITALIC),
387    /// );
388    ///
389    /// let text = text.reset_style();
390    /// assert_eq!(Style::reset(), text.style);
391    /// ```
392    #[must_use = "method moves the value of self and returns the modified value"]
393    pub fn reset_style(self) -> Self {
394        self.patch_style(Style::reset())
395    }
396
397    /// Sets the alignment for this text.
398    ///
399    /// Defaults to: [`None`], meaning the alignment is determined by the rendering widget.
400    /// Setting the alignment of a Text generally overrides the alignment of its
401    /// parent Widget.
402    ///
403    /// Alignment can be set individually on each line to override this text's alignment.
404    ///
405    /// # Examples
406    ///
407    /// Set alignment to the whole text.
408    ///
409    /// ```rust
410    /// use ratatui_core::layout::Alignment;
411    /// use ratatui_core::text::Text;
412    ///
413    /// let mut text = Text::from("Hi, what's up?");
414    /// assert_eq!(None, text.alignment);
415    /// assert_eq!(
416    ///     Some(Alignment::Right),
417    ///     text.alignment(Alignment::Right).alignment
418    /// )
419    /// ```
420    ///
421    /// Set a default alignment and override it on a per line basis.
422    ///
423    /// ```rust
424    /// use ratatui_core::layout::Alignment;
425    /// use ratatui_core::text::{Line, Text};
426    ///
427    /// let text = Text::from(vec![
428    ///     Line::from("left").alignment(Alignment::Left),
429    ///     Line::from("default"),
430    ///     Line::from("default"),
431    ///     Line::from("right").alignment(Alignment::Right),
432    /// ])
433    /// .alignment(Alignment::Center);
434    /// ```
435    ///
436    /// Will render the following
437    ///
438    /// ```plain
439    /// left
440    ///   default
441    ///   default
442    ///       right
443    /// ```
444    #[must_use = "method moves the value of self and returns the modified value"]
445    pub fn alignment(self, alignment: Alignment) -> Self {
446        Self {
447            alignment: Some(alignment),
448            ..self
449        }
450    }
451
452    /// Left-aligns the whole text.
453    ///
454    /// Convenience shortcut for `Text::alignment(Alignment::Left)`.
455    /// Setting the alignment of a Text generally overrides the alignment of its
456    /// parent Widget, with the default alignment being inherited from the parent.
457    ///
458    /// Alignment can be set individually on each line to override this text's alignment.
459    ///
460    /// # Examples
461    ///
462    /// ```rust
463    /// use ratatui_core::text::Text;
464    ///
465    /// let text = Text::from("Hi, what's up?").left_aligned();
466    /// ```
467    #[must_use = "method moves the value of self and returns the modified value"]
468    pub fn left_aligned(self) -> Self {
469        self.alignment(Alignment::Left)
470    }
471
472    /// Center-aligns the whole text.
473    ///
474    /// Convenience shortcut for `Text::alignment(Alignment::Center)`.
475    /// Setting the alignment of a Text generally overrides the alignment of its
476    /// parent Widget, with the default alignment being inherited from the parent.
477    ///
478    /// Alignment can be set individually on each line to override this text's alignment.
479    ///
480    /// # Examples
481    ///
482    /// ```rust
483    /// use ratatui_core::text::Text;
484    ///
485    /// let text = Text::from("Hi, what's up?").centered();
486    /// ```
487    #[must_use = "method moves the value of self and returns the modified value"]
488    pub fn centered(self) -> Self {
489        self.alignment(Alignment::Center)
490    }
491
492    /// Right-aligns the whole text.
493    ///
494    /// Convenience shortcut for `Text::alignment(Alignment::Right)`.
495    /// Setting the alignment of a Text generally overrides the alignment of its
496    /// parent Widget, with the default alignment being inherited from the parent.
497    ///
498    /// Alignment can be set individually on each line to override this text's alignment.
499    ///
500    /// # Examples
501    ///
502    /// ```rust
503    /// use ratatui_core::text::Text;
504    ///
505    /// let text = Text::from("Hi, what's up?").right_aligned();
506    /// ```
507    #[must_use = "method moves the value of self and returns the modified value"]
508    pub fn right_aligned(self) -> Self {
509        self.alignment(Alignment::Right)
510    }
511
512    /// Returns an iterator over the lines of the text.
513    pub fn iter(&self) -> core::slice::Iter<'_, Line<'a>> {
514        self.lines.iter()
515    }
516
517    /// Returns an iterator that allows modifying each line.
518    pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Line<'a>> {
519        self.lines.iter_mut()
520    }
521
522    /// Adds a line to the text.
523    ///
524    /// `line` can be any type that can be converted into a `Line`. For example, you can pass a
525    /// `&str`, a `String`, a `Span`, or a `Line`.
526    ///
527    /// # Examples
528    ///
529    /// ```rust
530    /// use ratatui_core::text::{Line, Span, Text};
531    ///
532    /// let mut text = Text::from("Hello, world!");
533    /// text.push_line(Line::from("How are you?"));
534    /// text.push_line(Span::from("How are you?"));
535    /// text.push_line("How are you?");
536    /// ```
537    pub fn push_line<T: Into<Line<'a>>>(&mut self, line: T) {
538        self.lines.push(line.into());
539    }
540
541    /// Adds a span to the last line of the text.
542    ///
543    /// `span` can be any type that is convertible into a `Span`. For example, you can pass a
544    /// `&str`, a `String`, or a `Span`.
545    ///
546    /// # Examples
547    ///
548    /// ```rust
549    /// use ratatui_core::text::{Span, Text};
550    ///
551    /// let mut text = Text::from("Hello, world!");
552    /// text.push_span(Span::from("How are you?"));
553    /// text.push_span("How are you?");
554    /// ```
555    pub fn push_span<T: Into<Span<'a>>>(&mut self, span: T) {
556        let span = span.into();
557        if let Some(last) = self.lines.last_mut() {
558            last.push_span(span);
559        } else {
560            self.lines.push(Line::from(span));
561        }
562    }
563}
564
565impl UnicodeWidthStr for Text<'_> {
566    /// Returns the max width of all the lines.
567    fn width(&self) -> usize {
568        self.lines
569            .iter()
570            .map(UnicodeWidthStr::width)
571            .max()
572            .unwrap_or_default()
573    }
574
575    fn width_cjk(&self) -> usize {
576        self.lines
577            .iter()
578            .map(UnicodeWidthStr::width_cjk)
579            .max()
580            .unwrap_or_default()
581    }
582}
583
584impl<'a> IntoIterator for Text<'a> {
585    type Item = Line<'a>;
586    type IntoIter = alloc::vec::IntoIter<Self::Item>;
587
588    fn into_iter(self) -> Self::IntoIter {
589        self.lines.into_iter()
590    }
591}
592
593impl<'a> IntoIterator for &'a Text<'a> {
594    type Item = &'a Line<'a>;
595    type IntoIter = core::slice::Iter<'a, Line<'a>>;
596
597    fn into_iter(self) -> Self::IntoIter {
598        self.iter()
599    }
600}
601
602impl<'a> IntoIterator for &'a mut Text<'a> {
603    type Item = &'a mut Line<'a>;
604    type IntoIter = core::slice::IterMut<'a, Line<'a>>;
605
606    fn into_iter(self) -> Self::IntoIter {
607        self.iter_mut()
608    }
609}
610
611impl From<String> for Text<'_> {
612    fn from(s: String) -> Self {
613        Self::raw(s)
614    }
615}
616
617impl<'a> From<&'a str> for Text<'a> {
618    fn from(s: &'a str) -> Self {
619        Self::raw(s)
620    }
621}
622
623impl<'a> From<Cow<'a, str>> for Text<'a> {
624    fn from(s: Cow<'a, str>) -> Self {
625        Self::raw(s)
626    }
627}
628
629impl<'a> From<Span<'a>> for Text<'a> {
630    fn from(span: Span<'a>) -> Self {
631        Self {
632            lines: vec![Line::from(span)],
633            ..Default::default()
634        }
635    }
636}
637
638impl<'a> From<Line<'a>> for Text<'a> {
639    fn from(line: Line<'a>) -> Self {
640        Self {
641            lines: vec![line],
642            ..Default::default()
643        }
644    }
645}
646
647impl<'a> From<Vec<Line<'a>>> for Text<'a> {
648    fn from(lines: Vec<Line<'a>>) -> Self {
649        Self {
650            lines,
651            ..Default::default()
652        }
653    }
654}
655
656impl<'a, 'b, T> From<&'b [T]> for Text<'a>
657where
658    T: Into<Line<'a>> + Clone,
659{
660    fn from(value: &'b [T]) -> Self {
661        Self {
662            lines: value.iter().cloned().map(Into::into).collect(),
663            ..Default::default()
664        }
665    }
666}
667
668impl<'a, T> FromIterator<T> for Text<'a>
669where
670    T: Into<Line<'a>>,
671{
672    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
673        let lines = iter.into_iter().map(Into::into).collect();
674        Self {
675            lines,
676            ..Default::default()
677        }
678    }
679}
680
681impl<'a> core::ops::Add<Line<'a>> for Text<'a> {
682    type Output = Self;
683
684    fn add(mut self, line: Line<'a>) -> Self::Output {
685        self.push_line(line);
686        self
687    }
688}
689
690/// Adds two `Text` together.
691///
692/// This ignores the style and alignment of the second `Text`.
693impl core::ops::Add<Self> for Text<'_> {
694    type Output = Self;
695
696    fn add(mut self, text: Self) -> Self::Output {
697        self.lines.extend(text.lines);
698        self
699    }
700}
701
702/// Adds two `Text` together.
703///
704/// This ignores the style and alignment of the second `Text`.
705impl core::ops::AddAssign for Text<'_> {
706    fn add_assign(&mut self, rhs: Self) {
707        self.lines.extend(rhs.lines);
708    }
709}
710
711impl<'a> core::ops::AddAssign<Line<'a>> for Text<'a> {
712    fn add_assign(&mut self, line: Line<'a>) {
713        self.push_line(line);
714    }
715}
716
717impl<'a, T> Extend<T> for Text<'a>
718where
719    T: Into<Line<'a>>,
720{
721    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
722        let lines = iter.into_iter().map(Into::into);
723        self.lines.extend(lines);
724    }
725}
726
727/// A trait for converting a value to a [`Text`].
728///
729/// This trait is automatically implemented for any type that implements the [`Display`] trait. As
730/// such, `ToText` shouldn't be implemented directly: [`Display`] should be implemented instead, and
731/// you get the `ToText` implementation for free.
732///
733/// [`Display`]: std::fmt::Display
734pub trait ToText {
735    /// Converts the value to a [`Text`].
736    fn to_text(&self) -> Text<'_>;
737}
738
739/// # Panics
740///
741/// In this implementation, the `to_text` method panics if the `Display` implementation returns an
742/// error. This indicates an incorrect `Display` implementation since `fmt::Write for String` never
743/// returns an error itself.
744impl<T: fmt::Display> ToText for T {
745    fn to_text(&self) -> Text<'_> {
746        Text::raw(self.to_string())
747    }
748}
749
750impl fmt::Display for Text<'_> {
751    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752        if let Some((last, rest)) = self.lines.split_last() {
753            for line in rest {
754                writeln!(f, "{line}")?;
755            }
756            write!(f, "{last}")?;
757        }
758        Ok(())
759    }
760}
761
762impl Widget for Text<'_> {
763    fn render(self, area: Rect, buf: &mut Buffer) {
764        Widget::render(&self, area, buf);
765    }
766}
767
768impl Widget for &Text<'_> {
769    fn render(self, area: Rect, buf: &mut Buffer) {
770        let area = area.intersection(buf.area);
771        buf.set_style(area, self.style);
772        for (line, line_area) in self.iter().zip(area.rows()) {
773            line.render_with_alignment(line_area, buf, self.alignment);
774        }
775    }
776}
777
778impl Styled for Text<'_> {
779    type Item = Self;
780
781    fn style(&self) -> Style {
782        self.style
783    }
784
785    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
786        self.style(style)
787    }
788}
789
790#[cfg(test)]
791mod tests {
792    use alloc::format;
793    use core::iter;
794
795    use rstest::{fixture, rstest};
796
797    use super::*;
798    use crate::style::{Color, Modifier, Stylize};
799
800    #[fixture]
801    fn small_buf() -> Buffer {
802        Buffer::empty(Rect::new(0, 0, 10, 1))
803    }
804
805    #[test]
806    fn raw() {
807        let text = Text::raw("The first line\nThe second line");
808        assert_eq!(
809            text.lines,
810            vec![Line::from("The first line"), Line::from("The second line")]
811        );
812    }
813
814    #[test]
815    fn styled() {
816        let style = Style::new().yellow().italic();
817        let styled_text = Text::styled("The first line\nThe second line", style);
818
819        let mut text = Text::raw("The first line\nThe second line");
820        text.style = style;
821
822        assert_eq!(styled_text, text);
823    }
824
825    #[test]
826    fn width() {
827        let text = Text::from("The first line\nThe second line");
828        assert_eq!(15, text.width());
829    }
830
831    #[test]
832    fn height() {
833        let text = Text::from("The first line\nThe second line");
834        assert_eq!(2, text.height());
835    }
836
837    #[test]
838    fn patch_style() {
839        let style = Style::new().yellow().italic();
840        let style2 = Style::new().red().underlined();
841        let text = Text::styled("The first line\nThe second line", style).patch_style(style2);
842
843        let expected_style = Style::new().red().italic().underlined();
844        let expected_text = Text::styled("The first line\nThe second line", expected_style);
845
846        assert_eq!(text, expected_text);
847    }
848
849    #[test]
850    fn reset_style() {
851        let style = Style::new().yellow().italic();
852        let text = Text::styled("The first line\nThe second line", style).reset_style();
853
854        assert_eq!(text.style, Style::reset());
855    }
856
857    #[test]
858    fn from_string() {
859        let text = Text::from(String::from("The first line\nThe second line"));
860        assert_eq!(
861            text.lines,
862            vec![Line::from("The first line"), Line::from("The second line")]
863        );
864    }
865
866    #[test]
867    fn from_str() {
868        let text = Text::from("The first line\nThe second line");
869        assert_eq!(
870            text.lines,
871            vec![Line::from("The first line"), Line::from("The second line")]
872        );
873    }
874
875    #[test]
876    fn from_cow() {
877        let text = Text::from(Cow::Borrowed("The first line\nThe second line"));
878        assert_eq!(
879            text.lines,
880            vec![Line::from("The first line"), Line::from("The second line")]
881        );
882    }
883
884    #[test]
885    fn from_span() {
886        let style = Style::new().yellow().italic();
887        let text = Text::from(Span::styled("The first line\nThe second line", style));
888        assert_eq!(
889            text.lines,
890            vec![Line::from(Span::styled(
891                "The first line\nThe second line",
892                style
893            ))]
894        );
895    }
896
897    #[test]
898    fn from_line() {
899        let text = Text::from(Line::from("The first line"));
900        assert_eq!(text.lines, [Line::from("The first line")]);
901    }
902
903    #[test]
904    fn from_slice_of_spans() {
905        let slice = [
906            Span::from("Hello"),
907            Span::from("world!"),
908            Span::from("Extra"),
909        ];
910        let text = Text::from(&slice[0..1]);
911        let text2 = Text::from(&slice[1..]);
912        assert_eq!(text.lines, [Line::from("Hello")]);
913        assert_eq!(text2.lines, [Line::from("world!"), Line::from("Extra")]);
914    }
915
916    #[test]
917    fn from_slice_of_lines() {
918        let slice = [
919            Line::from("Hello"),
920            Line::from("world!"),
921            Line::from("Extra"),
922        ];
923        let text = Text::from(&slice[0..1]);
924        let text2 = Text::from(&slice[1..]);
925        assert_eq!(text.lines, [Line::from("Hello")]);
926        assert_eq!(text2.lines, [Line::from("world!"), Line::from("Extra")]);
927    }
928
929    #[test]
930    fn from_slice_of_strs() {
931        let slice = ["Hello", "world!", "Extra"];
932        let text = Text::from(&slice[0..1]);
933        let text2 = Text::from(&slice[1..]);
934        assert_eq!(text.lines, [Line::from("Hello")]);
935        assert_eq!(text2.lines, [Line::from("world!"), Line::from("Extra")]);
936    }
937
938    #[test]
939    fn from_slice_of_strings() {
940        let slice = [
941            "Hello".to_string(),
942            "world!".to_string(),
943            "Extra".to_string(),
944        ];
945        let text = Text::from(&slice[0..1]);
946        let text2 = Text::from(&slice[1..]);
947        assert_eq!(text.lines, [Line::from("Hello")]);
948        assert_eq!(text2.lines, [Line::from("world!"), Line::from("Extra")]);
949    }
950
951    #[rstest]
952    #[case(42, Text::from("42"))]
953    #[case("just\ntesting", Text::from("just\ntesting"))]
954    #[case(true, Text::from("true"))]
955    #[case(6.66, Text::from("6.66"))]
956    #[case('a', Text::from("a"))]
957    #[case(String::from("hello"), Text::from("hello"))]
958    #[case(-1, Text::from("-1"))]
959    #[case("line1\nline2", Text::from("line1\nline2"))]
960    #[case(
961        "first line\nsecond line\nthird line",
962        Text::from("first line\nsecond line\nthird line")
963    )]
964    #[case("trailing newline\n", Text::from("trailing newline\n"))]
965    fn to_text(#[case] value: impl fmt::Display, #[case] expected: Text) {
966        assert_eq!(value.to_text(), expected);
967    }
968
969    #[test]
970    fn from_vec_line() {
971        let text = Text::from(vec![
972            Line::from("The first line"),
973            Line::from("The second line"),
974        ]);
975        assert_eq!(
976            text.lines,
977            vec![Line::from("The first line"), Line::from("The second line")]
978        );
979    }
980
981    #[test]
982    fn from_iterator() {
983        let text = Text::from_iter(vec!["The first line", "The second line"]);
984        assert_eq!(
985            text.lines,
986            vec![Line::from("The first line"), Line::from("The second line")]
987        );
988    }
989
990    #[test]
991    fn collect() {
992        let text: Text = iter::once("The first line")
993            .chain(iter::once("The second line"))
994            .collect();
995        assert_eq!(
996            text.lines,
997            vec![Line::from("The first line"), Line::from("The second line")]
998        );
999    }
1000
1001    #[test]
1002    fn into_iter() {
1003        let text = Text::from("The first line\nThe second line");
1004        let mut iter = text.into_iter();
1005        assert_eq!(iter.next(), Some(Line::from("The first line")));
1006        assert_eq!(iter.next(), Some(Line::from("The second line")));
1007        assert_eq!(iter.next(), None);
1008    }
1009
1010    #[test]
1011    fn add_line() {
1012        assert_eq!(
1013            Text::raw("Red").red() + Line::raw("Blue").blue(),
1014            Text {
1015                lines: vec![Line::raw("Red"), Line::raw("Blue").blue()],
1016                style: Style::new().red(),
1017                alignment: None,
1018            }
1019        );
1020    }
1021
1022    #[test]
1023    fn add_text() {
1024        assert_eq!(
1025            Text::raw("Red").red() + Text::raw("Blue").blue(),
1026            Text {
1027                lines: vec![Line::raw("Red"), Line::raw("Blue")],
1028                style: Style::new().red(),
1029                alignment: None,
1030            }
1031        );
1032    }
1033
1034    #[test]
1035    fn add_assign_text() {
1036        let mut text = Text::raw("Red").red();
1037        text += Text::raw("Blue").blue();
1038        assert_eq!(
1039            text,
1040            Text {
1041                lines: vec![Line::raw("Red"), Line::raw("Blue")],
1042                style: Style::new().red(),
1043                alignment: None,
1044            }
1045        );
1046    }
1047
1048    #[test]
1049    fn add_assign_line() {
1050        let mut text = Text::raw("Red").red();
1051        text += Line::raw("Blue").blue();
1052        assert_eq!(
1053            text,
1054            Text {
1055                lines: vec![Line::raw("Red"), Line::raw("Blue").blue()],
1056                style: Style::new().red(),
1057                alignment: None,
1058            }
1059        );
1060    }
1061
1062    #[test]
1063    fn extend() {
1064        let mut text = Text::from("The first line\nThe second line");
1065        text.extend(vec![
1066            Line::from("The third line"),
1067            Line::from("The fourth line"),
1068        ]);
1069        assert_eq!(
1070            text.lines,
1071            vec![
1072                Line::from("The first line"),
1073                Line::from("The second line"),
1074                Line::from("The third line"),
1075                Line::from("The fourth line"),
1076            ]
1077        );
1078    }
1079
1080    #[test]
1081    fn extend_from_iter() {
1082        let mut text = Text::from("The first line\nThe second line");
1083        text.extend(vec![
1084            Line::from("The third line"),
1085            Line::from("The fourth line"),
1086        ]);
1087        assert_eq!(
1088            text.lines,
1089            vec![
1090                Line::from("The first line"),
1091                Line::from("The second line"),
1092                Line::from("The third line"),
1093                Line::from("The fourth line"),
1094            ]
1095        );
1096    }
1097
1098    #[test]
1099    fn extend_from_iter_str() {
1100        let mut text = Text::from("The first line\nThe second line");
1101        text.extend(vec!["The third line", "The fourth line"]);
1102        assert_eq!(
1103            text.lines,
1104            vec![
1105                Line::from("The first line"),
1106                Line::from("The second line"),
1107                Line::from("The third line"),
1108                Line::from("The fourth line"),
1109            ]
1110        );
1111    }
1112
1113    #[rstest]
1114    #[case::one_line("The first line")]
1115    #[case::multiple_lines("The first line\nThe second line")]
1116    fn display_raw_text(#[case] value: &str) {
1117        let text = Text::raw(value);
1118        assert_eq!(format!("{text}"), value);
1119    }
1120
1121    #[test]
1122    fn display_styled_text() {
1123        let styled_text = Text::styled(
1124            "The first line\nThe second line",
1125            Style::new().yellow().italic(),
1126        );
1127
1128        assert_eq!(format!("{styled_text}"), "The first line\nThe second line");
1129    }
1130
1131    #[test]
1132    fn display_text_from_vec() {
1133        let text_from_vec = Text::from(vec![
1134            Line::from("The first line"),
1135            Line::from("The second line"),
1136        ]);
1137
1138        assert_eq!(
1139            format!("{text_from_vec}"),
1140            "The first line\nThe second line"
1141        );
1142    }
1143
1144    #[test]
1145    fn display_extended_text() {
1146        let mut text = Text::from("The first line\nThe second line");
1147
1148        assert_eq!(format!("{text}"), "The first line\nThe second line");
1149
1150        text.extend(vec![
1151            Line::from("The third line"),
1152            Line::from("The fourth line"),
1153        ]);
1154
1155        assert_eq!(
1156            format!("{text}"),
1157            "The first line\nThe second line\nThe third line\nThe fourth line"
1158        );
1159    }
1160
1161    #[test]
1162    fn stylize() {
1163        assert_eq!(Text::default().green().style, Color::Green.into());
1164        assert_eq!(
1165            Text::default().on_green().style,
1166            Style::new().bg(Color::Green)
1167        );
1168        assert_eq!(Text::default().italic().style, Modifier::ITALIC.into());
1169    }
1170
1171    #[test]
1172    fn left_aligned() {
1173        let text = Text::from("Hello, world!").left_aligned();
1174        assert_eq!(text.alignment, Some(Alignment::Left));
1175    }
1176
1177    #[test]
1178    fn centered() {
1179        let text = Text::from("Hello, world!").centered();
1180        assert_eq!(text.alignment, Some(Alignment::Center));
1181    }
1182
1183    #[test]
1184    fn right_aligned() {
1185        let text = Text::from("Hello, world!").right_aligned();
1186        assert_eq!(text.alignment, Some(Alignment::Right));
1187    }
1188
1189    #[test]
1190    fn push_line() {
1191        let mut text = Text::from("A");
1192        text.push_line(Line::from("B"));
1193        text.push_line(Span::from("C"));
1194        text.push_line("D");
1195        assert_eq!(
1196            text.lines,
1197            vec![
1198                Line::raw("A"),
1199                Line::raw("B"),
1200                Line::raw("C"),
1201                Line::raw("D")
1202            ]
1203        );
1204    }
1205
1206    #[test]
1207    fn push_line_empty() {
1208        let mut text = Text::default();
1209        text.push_line(Line::from("Hello, world!"));
1210        assert_eq!(text.lines, [Line::from("Hello, world!")]);
1211    }
1212
1213    #[test]
1214    fn push_span() {
1215        let mut text = Text::from("A");
1216        text.push_span(Span::raw("B"));
1217        text.push_span("C");
1218        assert_eq!(
1219            text.lines,
1220            vec![Line::from(vec![
1221                Span::raw("A"),
1222                Span::raw("B"),
1223                Span::raw("C")
1224            ])],
1225        );
1226    }
1227
1228    #[test]
1229    fn push_span_empty() {
1230        let mut text = Text::default();
1231        text.push_span(Span::raw("Hello, world!"));
1232        assert_eq!(text.lines, [Line::from(Span::raw("Hello, world!"))]);
1233    }
1234
1235    mod widget {
1236        use super::*;
1237
1238        #[test]
1239        fn render() {
1240            let text = Text::from("foo");
1241            let area = Rect::new(0, 0, 5, 1);
1242            let mut buf = Buffer::empty(area);
1243            text.render(area, &mut buf);
1244            assert_eq!(buf, Buffer::with_lines(["foo  "]));
1245        }
1246
1247        #[rstest]
1248        fn render_out_of_bounds(mut small_buf: Buffer) {
1249            let out_of_bounds_area = Rect::new(20, 20, 10, 1);
1250            Text::from("Hello, world!").render(out_of_bounds_area, &mut small_buf);
1251            assert_eq!(small_buf, Buffer::empty(small_buf.area));
1252        }
1253
1254        #[test]
1255        fn render_right_aligned() {
1256            let text = Text::from("foo").alignment(Alignment::Right);
1257            let area = Rect::new(0, 0, 5, 1);
1258            let mut buf = Buffer::empty(area);
1259            text.render(area, &mut buf);
1260            assert_eq!(buf, Buffer::with_lines(["  foo"]));
1261        }
1262
1263        #[test]
1264        fn render_centered_odd() {
1265            let text = Text::from("foo").alignment(Alignment::Center);
1266            let area = Rect::new(0, 0, 5, 1);
1267            let mut buf = Buffer::empty(area);
1268            text.render(area, &mut buf);
1269            assert_eq!(buf, Buffer::with_lines([" foo "]));
1270        }
1271
1272        #[test]
1273        fn render_centered_even() {
1274            let text = Text::from("foo").alignment(Alignment::Center);
1275            let area = Rect::new(0, 0, 6, 1);
1276            let mut buf = Buffer::empty(area);
1277            text.render(area, &mut buf);
1278            assert_eq!(buf, Buffer::with_lines([" foo  "]));
1279        }
1280
1281        #[test]
1282        fn render_right_aligned_with_truncation() {
1283            let text = Text::from("123456789").alignment(Alignment::Right);
1284            let area = Rect::new(0, 0, 5, 1);
1285            let mut buf = Buffer::empty(area);
1286            text.render(area, &mut buf);
1287            assert_eq!(buf, Buffer::with_lines(["56789"]));
1288        }
1289
1290        #[test]
1291        fn render_centered_odd_with_truncation() {
1292            let text = Text::from("123456789").alignment(Alignment::Center);
1293            let area = Rect::new(0, 0, 5, 1);
1294            let mut buf = Buffer::empty(area);
1295            text.render(area, &mut buf);
1296            assert_eq!(buf, Buffer::with_lines(["34567"]));
1297        }
1298
1299        #[test]
1300        fn render_centered_even_with_truncation() {
1301            let text = Text::from("123456789").alignment(Alignment::Center);
1302            let area = Rect::new(0, 0, 6, 1);
1303            let mut buf = Buffer::empty(area);
1304            text.render(area, &mut buf);
1305            assert_eq!(buf, Buffer::with_lines(["234567"]));
1306        }
1307
1308        #[test]
1309        fn render_one_line_right() {
1310            let text = Text::from(vec![
1311                "foo".into(),
1312                Line::from("bar").alignment(Alignment::Center),
1313            ])
1314            .alignment(Alignment::Right);
1315            let area = Rect::new(0, 0, 5, 2);
1316            let mut buf = Buffer::empty(area);
1317            text.render(area, &mut buf);
1318            assert_eq!(buf, Buffer::with_lines(["  foo", " bar "]));
1319        }
1320
1321        #[test]
1322        fn render_only_styles_line_area() {
1323            let area = Rect::new(0, 0, 5, 1);
1324            let mut buf = Buffer::empty(area);
1325            Text::from("foo".on_blue()).render(area, &mut buf);
1326
1327            let mut expected = Buffer::with_lines(["foo  "]);
1328            expected.set_style(Rect::new(0, 0, 3, 1), Style::new().bg(Color::Blue));
1329            assert_eq!(buf, expected);
1330        }
1331
1332        #[test]
1333        fn render_truncates() {
1334            let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
1335            Text::from("foobar".on_blue()).render(Rect::new(0, 0, 3, 1), &mut buf);
1336
1337            let mut expected = Buffer::with_lines(["foo   "]);
1338            expected.set_style(Rect::new(0, 0, 3, 1), Style::new().bg(Color::Blue));
1339            assert_eq!(buf, expected);
1340        }
1341    }
1342
1343    mod iterators {
1344        use super::*;
1345
1346        /// a fixture used in the tests below to avoid repeating the same setup
1347        #[fixture]
1348        fn hello_world() -> Text<'static> {
1349            Text::from(vec![
1350                Line::styled("Hello ", Color::Blue),
1351                Line::styled("world!", Color::Green),
1352            ])
1353        }
1354
1355        #[rstest]
1356        fn iter(hello_world: Text<'_>) {
1357            let mut iter = hello_world.iter();
1358            assert_eq!(iter.next(), Some(&Line::styled("Hello ", Color::Blue)));
1359            assert_eq!(iter.next(), Some(&Line::styled("world!", Color::Green)));
1360            assert_eq!(iter.next(), None);
1361        }
1362
1363        #[rstest]
1364        fn iter_mut(mut hello_world: Text<'_>) {
1365            let mut iter = hello_world.iter_mut();
1366            assert_eq!(iter.next(), Some(&mut Line::styled("Hello ", Color::Blue)));
1367            assert_eq!(iter.next(), Some(&mut Line::styled("world!", Color::Green)));
1368            assert_eq!(iter.next(), None);
1369        }
1370
1371        #[rstest]
1372        fn into_iter(hello_world: Text<'_>) {
1373            let mut iter = hello_world.into_iter();
1374            assert_eq!(iter.next(), Some(Line::styled("Hello ", Color::Blue)));
1375            assert_eq!(iter.next(), Some(Line::styled("world!", Color::Green)));
1376            assert_eq!(iter.next(), None);
1377        }
1378
1379        #[rstest]
1380        fn into_iter_ref(hello_world: Text<'_>) {
1381            let mut iter = (&hello_world).into_iter();
1382            assert_eq!(iter.next(), Some(&Line::styled("Hello ", Color::Blue)));
1383            assert_eq!(iter.next(), Some(&Line::styled("world!", Color::Green)));
1384            assert_eq!(iter.next(), None);
1385        }
1386
1387        #[test]
1388        fn into_iter_mut_ref() {
1389            let mut hello_world = Text::from(vec![
1390                Line::styled("Hello ", Color::Blue),
1391                Line::styled("world!", Color::Green),
1392            ]);
1393            let mut iter = (&mut hello_world).into_iter();
1394            assert_eq!(iter.next(), Some(&mut Line::styled("Hello ", Color::Blue)));
1395            assert_eq!(iter.next(), Some(&mut Line::styled("world!", Color::Green)));
1396            assert_eq!(iter.next(), None);
1397        }
1398
1399        #[rstest]
1400        fn for_loop_ref(hello_world: Text<'_>) {
1401            let mut result = String::new();
1402            for line in &hello_world {
1403                result.push_str(line.to_string().as_ref());
1404            }
1405            assert_eq!(result, "Hello world!");
1406        }
1407
1408        #[rstest]
1409        fn for_loop_mut_ref() {
1410            let mut hello_world = Text::from(vec![
1411                Line::styled("Hello ", Color::Blue),
1412                Line::styled("world!", Color::Green),
1413            ]);
1414            let mut result = String::new();
1415            for line in &mut hello_world {
1416                result.push_str(line.to_string().as_ref());
1417            }
1418            assert_eq!(result, "Hello world!");
1419        }
1420
1421        #[rstest]
1422        fn for_loop_into(hello_world: Text<'_>) {
1423            let mut result = String::new();
1424            for line in hello_world {
1425                result.push_str(line.to_string().as_ref());
1426            }
1427            assert_eq!(result, "Hello world!");
1428        }
1429    }
1430
1431    #[rstest]
1432    #[case::default(Text::default(), "Text::default()")]
1433    // TODO jm: these could be improved to inspect the line / span if there's only one. e.g.
1434    // Text::from("Hello, world!") and Text::from("Hello, world!".blue()) but the current
1435    // implementation is good enough for now.
1436    #[case::raw(
1437        Text::raw("Hello, world!"),
1438        r#"Text::from(Line::from("Hello, world!"))"#
1439    )]
1440    #[case::styled(
1441        Text::styled("Hello, world!", Color::Yellow),
1442        r#"Text::from(Line::from("Hello, world!")).yellow()"#
1443    )]
1444    #[case::complex_styled(
1445        Text::from("Hello, world!").yellow().on_blue().bold().italic().not_dim().not_hidden(),
1446        r#"Text::from(Line::from("Hello, world!")).yellow().on_blue().bold().italic().not_dim().not_hidden()"#
1447    )]
1448    #[case::alignment(
1449        Text::from("Hello, world!").centered(),
1450        r#"Text::from(Line::from("Hello, world!")).centered()"#
1451    )]
1452    #[case::styled_alignment(
1453        Text::styled("Hello, world!", Color::Yellow).centered(),
1454        r#"Text::from(Line::from("Hello, world!")).yellow().centered()"#
1455    )]
1456    #[case::multiple_lines(
1457        Text::from(vec![
1458            Line::from("Hello, world!"),
1459            Line::from("How are you?")
1460        ]),
1461        r#"Text::from_iter([Line::from("Hello, world!"), Line::from("How are you?")])"#
1462    )]
1463    fn debug(#[case] text: Text, #[case] expected: &str) {
1464        assert_eq!(format!("{text:?}"), expected);
1465    }
1466
1467    #[test]
1468    fn debug_alternate() {
1469        let text = Text::from_iter([
1470            Line::from("Hello, world!"),
1471            Line::from("How are you?").bold().left_aligned(),
1472            Line::from_iter([
1473                Span::from("I'm "),
1474                Span::from("doing ").italic(),
1475                Span::from("great!").bold(),
1476            ]),
1477        ])
1478        .on_blue()
1479        .italic()
1480        .centered();
1481        assert_eq!(
1482            format!("{text:#?}"),
1483            indoc::indoc! {r#"
1484            Text::from_iter([
1485                Line::from("Hello, world!"),
1486                Line::from("How are you?").bold().left_aligned(),
1487                Line::from_iter([
1488                    Span::from("I'm "),
1489                    Span::from("doing ").italic(),
1490                    Span::from("great!").bold(),
1491                ]),
1492            ]).on_blue().italic().centered()"#}
1493        );
1494    }
1495}