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