ratatui_core/text/
text.rs

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