ratatui_core/text/
text.rs

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