ratatui_core/text/
line.rs

1#![deny(missing_docs)]
2#![warn(clippy::pedantic, clippy::nursery, clippy::arithmetic_side_effects)]
3use std::{borrow::Cow, fmt};
4
5use unicode_truncate::UnicodeTruncateStr;
6
7use crate::{
8    buffer::Buffer,
9    layout::{Alignment, Rect},
10    style::{Style, Styled},
11    text::{Span, StyledGrapheme, Text},
12    widgets::Widget,
13};
14
15/// A line of text, consisting of one or more [`Span`]s.
16///
17/// [`Line`]s are used wherever text is displayed in the terminal and represent a single line of
18/// text. When a [`Line`] is rendered, it is rendered as a single line of text, with each [`Span`]
19/// being rendered in order (left to right).
20///
21/// Any newlines in the content are removed when creating a [`Line`] using the constructor or
22/// conversion methods.
23///
24/// # Constructor Methods
25///
26/// - [`Line::default`] creates a line with empty content and the default style.
27/// - [`Line::raw`] creates a line with the given content and the default style.
28/// - [`Line::styled`] creates a line with the given content and style.
29///
30/// # Conversion Methods
31///
32/// - [`Line::from`] creates a `Line` from a [`String`].
33/// - [`Line::from`] creates a `Line` from a [`&str`].
34/// - [`Line::from`] creates a `Line` from a [`Vec`] of [`Span`]s.
35/// - [`Line::from`] creates a `Line` from single [`Span`].
36/// - [`String::from`] converts a line into a [`String`].
37/// - [`Line::from_iter`] creates a line from an iterator of items that are convertible to [`Span`].
38///
39/// # Setter Methods
40///
41/// These methods are fluent setters. They return a `Line` with the property set.
42///
43/// - [`Line::spans`] sets the content of the line.
44/// - [`Line::style`] sets the style of the line.
45/// - [`Line::alignment`] sets the alignment of the line.
46/// - [`Line::left_aligned`] sets the alignment of the line to [`Alignment::Left`].
47/// - [`Line::centered`] sets the alignment of the line to [`Alignment::Center`].
48/// - [`Line::right_aligned`] sets the alignment of the line to [`Alignment::Right`].
49///
50/// # Iteration Methods
51///
52/// - [`Line::iter`] returns an iterator over the spans of this line.
53/// - [`Line::iter_mut`] returns a mutable iterator over the spans of this line.
54/// - [`Line::into_iter`] returns an iterator over the spans of this line.
55///
56/// # Other Methods
57///
58/// - [`Line::patch_style`] patches the style of the line, adding modifiers from the given style.
59/// - [`Line::reset_style`] resets the style of the line.
60/// - [`Line::width`] returns the unicode width of the content held by this line.
61/// - [`Line::styled_graphemes`] returns an iterator over the graphemes held by this line.
62/// - [`Line::push_span`] adds a span to the line.
63///
64/// # Compatibility Notes
65///
66/// Before v0.26.0, [`Line`] did not have a `style` field and instead relied on only the styles that
67/// were set on each [`Span`] contained in the `spans` field. The [`Line::patch_style`] method was
68/// the only way to set the overall style for individual lines. For this reason, this field may not
69/// be supported yet by all widgets (outside of the `ratatui` crate itself).
70///
71/// # Examples
72///
73/// ## Creating Lines
74/// [`Line`]s can be created from [`Span`]s, [`String`]s, and [`&str`]s. They can be styled with a
75/// [`Style`].
76///
77/// ```rust
78/// use ratatui_core::{
79///     style::{Color, Modifier, Style, Stylize},
80///     text::{Line, Span},
81/// };
82///
83/// let style = Style::new().yellow();
84/// let line = Line::raw("Hello, world!").style(style);
85/// let line = Line::styled("Hello, world!", style);
86/// let line = Line::styled("Hello, world!", (Color::Yellow, Modifier::BOLD));
87///
88/// let line = Line::from("Hello, world!");
89/// let line = Line::from(String::from("Hello, world!"));
90/// let line = Line::from(vec![
91///     Span::styled("Hello", Style::new().blue()),
92///     Span::raw(" world!"),
93/// ]);
94/// ```
95///
96/// ## Styling Lines
97///
98/// The line's [`Style`] is used by the rendering widget to determine how to style the line. Each
99/// [`Span`] in the line will be styled with the [`Style`] of the line, and then with its own
100/// [`Style`]. If the line is longer than the available space, the style is applied to the entire
101/// line, and the line is truncated. `Line` also implements [`Styled`] which means you can use the
102/// methods of the [`Stylize`] trait.
103///
104/// ```rust
105/// use ratatui_core::{
106///     style::{Color, Modifier, Style, Stylize},
107///     text::Line,
108/// };
109///
110/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
111/// let line = Line::from("Hello world!").style(Color::Yellow);
112/// let line = Line::from("Hello world!").style((Color::Yellow, Color::Black));
113/// let line = Line::from("Hello world!").style((Color::Yellow, Modifier::ITALIC));
114/// let line = Line::from("Hello world!").yellow().italic();
115/// ```
116///
117/// ## Aligning Lines
118///
119/// The line's [`Alignment`] is used by the rendering widget to determine how to align the line
120/// within the available space. If the line is longer than the available space, the alignment is
121/// ignored and the line is truncated.
122///
123/// ```rust
124/// use ratatui_core::{layout::Alignment, text::Line};
125///
126/// let line = Line::from("Hello world!").alignment(Alignment::Right);
127/// let line = Line::from("Hello world!").centered();
128/// let line = Line::from("Hello world!").left_aligned();
129/// let line = Line::from("Hello world!").right_aligned();
130/// ```
131///
132/// ## Rendering Lines
133///
134/// `Line` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`].
135///
136/// ```rust
137/// use ratatui_core::{
138///     buffer::Buffer,
139///     layout::Rect,
140///     style::{Style, Stylize},
141///     text::Line,
142///     widgets::Widget,
143/// };
144///
145/// # fn render(area: Rect, buf: &mut Buffer) {
146/// // in another widget's render method
147/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
148/// line.render(area, buf);
149/// # }
150/// ```
151///
152/// Or you can use the `render_widget` method on the `Frame` in a `Terminal::draw` closure.
153///
154/// ```rust,ignore
155/// # use ratatui::{Frame, layout::Rect, text::Line};
156/// # fn draw(frame: &mut Frame, area: Rect) {
157/// let line = Line::from("Hello world!");
158/// frame.render_widget(line, area);
159/// # }
160/// ```
161/// ## Rendering Lines with a Paragraph widget
162///
163/// Usually apps will use the `Paragraph` widget instead of rendering a [`Line`] directly as it
164/// provides more functionality.
165///
166/// ```rust,ignore
167/// use ratatui::{
168///     buffer::Buffer,
169///     layout::Rect,
170///     style::Stylize,
171///     text::Line,
172///     widgets::{Paragraph, Widget, Wrap},
173/// };
174///
175/// # fn render(area: Rect, buf: &mut Buffer) {
176/// let line = Line::from("Hello world!").yellow().italic();
177/// Paragraph::new(line)
178///     .wrap(Wrap { trim: true })
179///     .render(area, buf);
180/// # }
181/// ```
182///
183/// [`Stylize`]: crate::style::Stylize
184#[derive(Default, Clone, Eq, PartialEq, Hash)]
185pub struct Line<'a> {
186    /// The style of this line of text.
187    pub style: Style,
188
189    /// The alignment of this line of text.
190    pub alignment: Option<Alignment>,
191
192    /// The spans that make up this line of text.
193    pub spans: Vec<Span<'a>>,
194}
195
196impl fmt::Debug for Line<'_> {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        if self.spans.is_empty() {
199            f.write_str("Line::default()")?;
200        } else if self.spans.len() == 1 && self.spans[0].style == Style::default() {
201            f.write_str(r#"Line::from(""#)?;
202            f.write_str(&self.spans[0].content)?;
203            f.write_str(r#"")"#)?;
204        } else if self.spans.len() == 1 {
205            f.write_str("Line::from(")?;
206            self.spans[0].fmt(f)?;
207            f.write_str(")")?;
208        } else {
209            f.write_str("Line::from_iter(")?;
210            f.debug_list().entries(&self.spans).finish()?;
211            f.write_str(")")?;
212        }
213        self.style.fmt_stylize(f)?;
214        match self.alignment {
215            Some(Alignment::Left) => write!(f, ".left_aligned()"),
216            Some(Alignment::Center) => write!(f, ".centered()"),
217            Some(Alignment::Right) => write!(f, ".right_aligned()"),
218            None => Ok(()),
219        }
220    }
221}
222
223fn cow_to_spans<'a>(content: impl Into<Cow<'a, str>>) -> Vec<Span<'a>> {
224    match content.into() {
225        Cow::Borrowed(s) => s.lines().map(Span::raw).collect(),
226        Cow::Owned(s) => s.lines().map(|v| Span::raw(v.to_string())).collect(),
227    }
228}
229
230impl<'a> Line<'a> {
231    /// Create a line with the default style.
232    ///
233    /// `content` can be any type that is convertible to [`Cow<str>`] (e.g. [`&str`], [`String`],
234    /// [`Cow<str>`], or your own type that implements [`Into<Cow<str>>`]).
235    ///
236    /// A [`Line`] can specify a [`Style`], which will be applied before the style of each [`Span`]
237    /// in the line.
238    ///
239    /// Any newlines in the content are removed.
240    ///
241    /// # Examples
242    ///
243    /// ```rust
244    /// use std::borrow::Cow;
245    ///
246    /// use ratatui_core::text::Line;
247    ///
248    /// Line::raw("test content");
249    /// Line::raw(String::from("test content"));
250    /// Line::raw(Cow::from("test content"));
251    /// ```
252    pub fn raw<T>(content: T) -> Self
253    where
254        T: Into<Cow<'a, str>>,
255    {
256        Self {
257            spans: cow_to_spans(content),
258            ..Default::default()
259        }
260    }
261
262    /// Create a line with the given style.
263    ///
264    /// `content` can be any type that is convertible to [`Cow<str>`] (e.g. [`&str`], [`String`],
265    /// [`Cow<str>`], or your own type that implements [`Into<Cow<str>>`]).
266    ///
267    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
268    /// your own type that implements [`Into<Style>`]).
269    ///
270    /// # Examples
271    ///
272    /// Any newlines in the content are removed.
273    ///
274    /// ```rust
275    /// use std::borrow::Cow;
276    ///
277    /// use ratatui_core::{
278    ///     style::{Style, Stylize},
279    ///     text::Line,
280    /// };
281    ///
282    /// let style = Style::new().yellow().italic();
283    /// Line::styled("My text", style);
284    /// Line::styled(String::from("My text"), style);
285    /// Line::styled(Cow::from("test content"), style);
286    /// ```
287    ///
288    /// [`Color`]: crate::style::Color
289    pub fn styled<T, S>(content: T, style: S) -> Self
290    where
291        T: Into<Cow<'a, str>>,
292        S: Into<Style>,
293    {
294        Self {
295            spans: cow_to_spans(content),
296            style: style.into(),
297            ..Default::default()
298        }
299    }
300
301    /// Sets the spans of this line of text.
302    ///
303    /// `spans` accepts any iterator that yields items that are convertible to [`Span`] (e.g.
304    /// [`&str`], [`String`], [`Span`], or your own type that implements [`Into<Span>`]).
305    ///
306    /// # Examples
307    ///
308    /// ```rust
309    /// use ratatui_core::{style::Stylize, text::Line};
310    ///
311    /// let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
312    /// let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {}", i)));
313    /// ```
314    #[must_use = "method moves the value of self and returns the modified value"]
315    pub fn spans<I>(mut self, spans: I) -> Self
316    where
317        I: IntoIterator,
318        I::Item: Into<Span<'a>>,
319    {
320        self.spans = spans.into_iter().map(Into::into).collect();
321        self
322    }
323
324    /// Sets the style of this line of text.
325    ///
326    /// Defaults to [`Style::default()`].
327    ///
328    /// Note: This field was added in v0.26.0. Prior to that, the style of a line was determined
329    /// only by the style of each [`Span`] contained in the line. For this reason, this field may
330    /// not be supported by all widgets (outside of the `ratatui` crate itself).
331    ///
332    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
333    /// your own type that implements [`Into<Style>`]).
334    ///
335    /// # Examples
336    /// ```rust
337    /// use ratatui_core::{
338    ///     style::{Style, Stylize},
339    ///     text::Line,
340    /// };
341    ///
342    /// let mut line = Line::from("foo").style(Style::new().red());
343    /// ```
344    ///
345    /// [`Color`]: crate::style::Color
346    #[must_use = "method moves the value of self and returns the modified value"]
347    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
348        self.style = style.into();
349        self
350    }
351
352    /// Sets the target alignment for this line of text.
353    ///
354    /// Defaults to: [`None`], meaning the alignment is determined by the rendering widget.
355    /// Setting the alignment of a Line generally overrides the alignment of its
356    /// parent Text or Widget.
357    ///
358    /// # Examples
359    ///
360    /// ```rust
361    /// use ratatui_core::{layout::Alignment, text::Line};
362    ///
363    /// let mut line = Line::from("Hi, what's up?");
364    /// assert_eq!(None, line.alignment);
365    /// assert_eq!(
366    ///     Some(Alignment::Right),
367    ///     line.alignment(Alignment::Right).alignment
368    /// )
369    /// ```
370    #[must_use = "method moves the value of self and returns the modified value"]
371    pub fn alignment(self, alignment: Alignment) -> Self {
372        Self {
373            alignment: Some(alignment),
374            ..self
375        }
376    }
377
378    /// Left-aligns this line of text.
379    ///
380    /// Convenience shortcut for `Line::alignment(Alignment::Left)`.
381    /// Setting the alignment of a Line generally overrides the alignment of its
382    /// parent Text or Widget, with the default alignment being inherited from the parent.
383    ///
384    /// # Examples
385    ///
386    /// ```rust
387    /// use ratatui_core::text::Line;
388    ///
389    /// let line = Line::from("Hi, what's up?").left_aligned();
390    /// ```
391    #[must_use = "method moves the value of self and returns the modified value"]
392    pub fn left_aligned(self) -> Self {
393        self.alignment(Alignment::Left)
394    }
395
396    /// Center-aligns this line of text.
397    ///
398    /// Convenience shortcut for `Line::alignment(Alignment::Center)`.
399    /// Setting the alignment of a Line generally overrides the alignment of its
400    /// parent Text or Widget, with the default alignment being inherited from the parent.
401    ///
402    /// # Examples
403    ///
404    /// ```rust
405    /// use ratatui_core::text::Line;
406    ///
407    /// let line = Line::from("Hi, what's up?").centered();
408    /// ```
409    #[must_use = "method moves the value of self and returns the modified value"]
410    pub fn centered(self) -> Self {
411        self.alignment(Alignment::Center)
412    }
413
414    /// Right-aligns this line of text.
415    ///
416    /// Convenience shortcut for `Line::alignment(Alignment::Right)`.
417    /// Setting the alignment of a Line generally overrides the alignment of its
418    /// parent Text or Widget, with the default alignment being inherited from the parent.
419    ///
420    /// # Examples
421    ///
422    /// ```rust
423    /// use ratatui_core::text::Line;
424    ///
425    /// let line = Line::from("Hi, what's up?").right_aligned();
426    /// ```
427    #[must_use = "method moves the value of self and returns the modified value"]
428    pub fn right_aligned(self) -> Self {
429        self.alignment(Alignment::Right)
430    }
431
432    /// Returns the width of the underlying string.
433    ///
434    /// # Examples
435    ///
436    /// ```rust
437    /// use ratatui_core::{style::Stylize, text::Line};
438    ///
439    /// let line = Line::from(vec!["Hello".blue(), " world!".green()]);
440    /// assert_eq!(12, line.width());
441    /// ```
442    pub fn width(&self) -> usize {
443        self.spans.iter().map(Span::width).sum()
444    }
445
446    /// Returns an iterator over the graphemes held by this line.
447    ///
448    /// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
449    /// the resulting [`Style`].
450    ///
451    /// `base_style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`],
452    /// or your own type that implements [`Into<Style>`]).
453    ///
454    /// # Examples
455    ///
456    /// ```rust
457    /// use std::iter::Iterator;
458    ///
459    /// use ratatui_core::{
460    ///     style::{Color, Style},
461    ///     text::{Line, StyledGrapheme},
462    /// };
463    ///
464    /// let line = Line::styled("Text", Style::default().fg(Color::Yellow));
465    /// let style = Style::default().fg(Color::Green).bg(Color::Black);
466    /// assert_eq!(
467    ///     line.styled_graphemes(style)
468    ///         .collect::<Vec<StyledGrapheme>>(),
469    ///     vec![
470    ///         StyledGrapheme::new("T", Style::default().fg(Color::Yellow).bg(Color::Black)),
471    ///         StyledGrapheme::new("e", Style::default().fg(Color::Yellow).bg(Color::Black)),
472    ///         StyledGrapheme::new("x", Style::default().fg(Color::Yellow).bg(Color::Black)),
473    ///         StyledGrapheme::new("t", Style::default().fg(Color::Yellow).bg(Color::Black)),
474    ///     ]
475    /// );
476    /// ```
477    ///
478    /// [`Color`]: crate::style::Color
479    pub fn styled_graphemes<S: Into<Style>>(
480        &'a self,
481        base_style: S,
482    ) -> impl Iterator<Item = StyledGrapheme<'a>> {
483        let style = base_style.into().patch(self.style);
484        self.spans
485            .iter()
486            .flat_map(move |span| span.styled_graphemes(style))
487    }
488
489    /// Patches the style of this Line, adding modifiers from the given style.
490    ///
491    /// This is useful for when you want to apply a style to a line that already has some styling.
492    /// In contrast to [`Line::style`], this method will not overwrite the existing style, but
493    /// instead will add the given style's modifiers to this Line's style.
494    ///
495    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
496    /// your own type that implements [`Into<Style>`]).
497    ///
498    /// This is a fluent setter method which must be chained or used as it consumes self
499    ///
500    /// # Examples
501    ///
502    /// ```rust
503    /// use ratatui_core::{
504    ///     style::{Color, Modifier},
505    ///     text::Line,
506    /// };
507    ///
508    /// let line = Line::styled("My text", Modifier::ITALIC);
509    ///
510    /// let styled_line = Line::styled("My text", (Color::Yellow, Modifier::ITALIC));
511    ///
512    /// assert_eq!(styled_line, line.patch_style(Color::Yellow));
513    /// ```
514    ///
515    /// [`Color`]: crate::style::Color
516    #[must_use = "method moves the value of self and returns the modified value"]
517    pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
518        self.style = self.style.patch(style);
519        self
520    }
521
522    /// Resets the style of this Line.
523    ///
524    /// Equivalent to calling `patch_style(Style::reset())`.
525    ///
526    /// This is a fluent setter method which must be chained or used as it consumes self
527    ///
528    /// # Examples
529    ///
530    /// ```rust
531    /// # let style = Style::default().yellow();
532    /// use ratatui_core::{
533    ///     style::{Style, Stylize},
534    ///     text::Line,
535    /// };
536    ///
537    /// let line = Line::styled("My text", style);
538    ///
539    /// assert_eq!(Style::reset(), line.reset_style().style);
540    /// ```
541    #[must_use = "method moves the value of self and returns the modified value"]
542    pub fn reset_style(self) -> Self {
543        self.patch_style(Style::reset())
544    }
545
546    /// Returns an iterator over the spans of this line.
547    pub fn iter(&self) -> std::slice::Iter<Span<'a>> {
548        self.spans.iter()
549    }
550
551    /// Returns a mutable iterator over the spans of this line.
552    pub fn iter_mut(&mut self) -> std::slice::IterMut<Span<'a>> {
553        self.spans.iter_mut()
554    }
555
556    /// Adds a span to the line.
557    ///
558    /// `span` can be any type that is convertible into a `Span`. For example, you can pass a
559    /// `&str`, a `String`, or a `Span`.
560    ///
561    /// # Examples
562    ///
563    /// ```rust
564    /// use ratatui_core::text::{Line, Span};
565    ///
566    /// let mut line = Line::from("Hello, ");
567    /// line.push_span(Span::raw("world!"));
568    /// line.push_span(" How are you?");
569    /// ```
570    pub fn push_span<T: Into<Span<'a>>>(&mut self, span: T) {
571        self.spans.push(span.into());
572    }
573}
574
575impl<'a> IntoIterator for Line<'a> {
576    type Item = Span<'a>;
577    type IntoIter = std::vec::IntoIter<Span<'a>>;
578
579    fn into_iter(self) -> Self::IntoIter {
580        self.spans.into_iter()
581    }
582}
583
584impl<'a> IntoIterator for &'a Line<'a> {
585    type Item = &'a Span<'a>;
586    type IntoIter = std::slice::Iter<'a, Span<'a>>;
587
588    fn into_iter(self) -> Self::IntoIter {
589        self.iter()
590    }
591}
592
593impl<'a> IntoIterator for &'a mut Line<'a> {
594    type Item = &'a mut Span<'a>;
595    type IntoIter = std::slice::IterMut<'a, Span<'a>>;
596
597    fn into_iter(self) -> Self::IntoIter {
598        self.iter_mut()
599    }
600}
601
602impl From<String> for Line<'_> {
603    fn from(s: String) -> Self {
604        Self::raw(s)
605    }
606}
607
608impl<'a> From<&'a str> for Line<'a> {
609    fn from(s: &'a str) -> Self {
610        Self::raw(s)
611    }
612}
613
614impl<'a> From<Cow<'a, str>> for Line<'a> {
615    fn from(s: Cow<'a, str>) -> Self {
616        Self::raw(s)
617    }
618}
619
620impl<'a> From<Vec<Span<'a>>> for Line<'a> {
621    fn from(spans: Vec<Span<'a>>) -> Self {
622        Self {
623            spans,
624            ..Default::default()
625        }
626    }
627}
628
629impl<'a> From<Span<'a>> for Line<'a> {
630    fn from(span: Span<'a>) -> Self {
631        Self::from(vec![span])
632    }
633}
634
635impl<'a> From<Line<'a>> for String {
636    fn from(line: Line<'a>) -> Self {
637        line.iter().fold(Self::new(), |mut acc, s| {
638            acc.push_str(s.content.as_ref());
639            acc
640        })
641    }
642}
643
644impl<'a, T> FromIterator<T> for Line<'a>
645where
646    T: Into<Span<'a>>,
647{
648    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
649        Self::from(iter.into_iter().map(Into::into).collect::<Vec<_>>())
650    }
651}
652
653/// Adds a `Span` to a `Line`, returning a new `Line` with the `Span` added.
654impl<'a> std::ops::Add<Span<'a>> for Line<'a> {
655    type Output = Self;
656
657    fn add(mut self, rhs: Span<'a>) -> Self::Output {
658        self.spans.push(rhs);
659        self
660    }
661}
662
663/// Adds two `Line`s together, returning a new `Text` with the contents of the two `Line`s.
664impl<'a> std::ops::Add<Self> for Line<'a> {
665    type Output = Text<'a>;
666
667    fn add(self, rhs: Self) -> Self::Output {
668        Text::from(vec![self, rhs])
669    }
670}
671
672impl<'a> std::ops::AddAssign<Span<'a>> for Line<'a> {
673    fn add_assign(&mut self, rhs: Span<'a>) {
674        self.spans.push(rhs);
675    }
676}
677
678impl<'a> Extend<Span<'a>> for Line<'a> {
679    fn extend<T: IntoIterator<Item = Span<'a>>>(&mut self, iter: T) {
680        self.spans.extend(iter);
681    }
682}
683
684impl Widget for Line<'_> {
685    fn render(self, area: Rect, buf: &mut Buffer) {
686        Widget::render(&self, area, buf);
687    }
688}
689
690impl Widget for &Line<'_> {
691    fn render(self, area: Rect, buf: &mut Buffer) {
692        self.render_with_alignment(area, buf, None);
693    }
694}
695
696impl Line<'_> {
697    /// An internal implementation method for `Widget::render` that allows the parent widget to
698    /// define a default alignment, to be used if `Line::alignment` is `None`.
699    pub(crate) fn render_with_alignment(
700        &self,
701        area: Rect,
702        buf: &mut Buffer,
703        parent_alignment: Option<Alignment>,
704    ) {
705        let area = area.intersection(buf.area);
706        if area.is_empty() {
707            return;
708        }
709        let area = Rect { height: 1, ..area };
710        let line_width = self.width();
711        if line_width == 0 {
712            return;
713        }
714
715        buf.set_style(area, self.style);
716
717        let alignment = self.alignment.or(parent_alignment);
718
719        let area_width = usize::from(area.width);
720        let can_render_complete_line = line_width <= area_width;
721        if can_render_complete_line {
722            let indent_width = match alignment {
723                Some(Alignment::Center) => (area_width.saturating_sub(line_width)) / 2,
724                Some(Alignment::Right) => area_width.saturating_sub(line_width),
725                Some(Alignment::Left) | None => 0,
726            };
727            let indent_width = u16::try_from(indent_width).unwrap_or(u16::MAX);
728            let area = area.indent_x(indent_width);
729            render_spans(&self.spans, area, buf, 0);
730        } else {
731            // There is not enough space to render the whole line. As the right side is truncated by
732            // the area width, only truncate the left.
733            let skip_width = match alignment {
734                Some(Alignment::Center) => (line_width.saturating_sub(area_width)) / 2,
735                Some(Alignment::Right) => line_width.saturating_sub(area_width),
736                Some(Alignment::Left) | None => 0,
737            };
738            render_spans(&self.spans, area, buf, skip_width);
739        };
740    }
741}
742
743/// Renders all the spans of the line that should be visible.
744fn render_spans(spans: &[Span], mut area: Rect, buf: &mut Buffer, span_skip_width: usize) {
745    for (span, span_width, offset) in spans_after_width(spans, span_skip_width) {
746        area = area.indent_x(offset);
747        if area.is_empty() {
748            break;
749        }
750        span.render(area, buf);
751        let span_width = u16::try_from(span_width).unwrap_or(u16::MAX);
752        area = area.indent_x(span_width);
753    }
754}
755
756/// Returns an iterator over the spans that lie after a given skip width from the start of the
757/// `Line` (including a partially visible span if the `skip_width` lands within a span).
758fn spans_after_width<'a>(
759    spans: &'a [Span],
760    mut skip_width: usize,
761) -> impl Iterator<Item = (Span<'a>, usize, u16)> {
762    spans
763        .iter()
764        .map(|span| (span, span.width()))
765        // Filter non visible spans out.
766        .filter_map(move |(span, span_width)| {
767            // Ignore spans that are completely before the offset. Decrement `span_skip_width` by
768            // the span width until we find a span that is partially or completely visible.
769            if skip_width >= span_width {
770                skip_width = skip_width.saturating_sub(span_width);
771                return None;
772            }
773
774            // Apply the skip from the start of the span, not the end as the end will be trimmed
775            // when rendering the span to the buffer.
776            let available_width = span_width.saturating_sub(skip_width);
777            skip_width = 0; // ensure the next span is rendered in full
778            Some((span, span_width, available_width))
779        })
780        .map(|(span, span_width, available_width)| {
781            if span_width <= available_width {
782                // Span is fully visible. Clone here is fast as the underlying content is `Cow`.
783                return (span.clone(), span_width, 0u16);
784            }
785            // Span is only partially visible. As the end is truncated by the area width, only
786            // truncate the start of the span.
787            let (content, actual_width) = span.content.unicode_truncate_start(available_width);
788
789            // When the first grapheme of the span was truncated, start rendering from a position
790            // that takes that into account by indenting the start of the area
791            let first_grapheme_offset = available_width.saturating_sub(actual_width);
792            let first_grapheme_offset = u16::try_from(first_grapheme_offset).unwrap_or(u16::MAX);
793            (
794                Span::styled(content, span.style),
795                actual_width,
796                first_grapheme_offset,
797            )
798        })
799}
800
801/// A trait for converting a value to a [`Line`].
802///
803/// This trait is automatically implemented for any type that implements the [`Display`] trait. As
804/// such, `ToLine` shouln't be implemented directly: [`Display`] should be implemented instead, and
805/// you get the `ToLine` implementation for free.
806///
807/// [`Display`]: std::fmt::Display
808pub trait ToLine {
809    /// Converts the value to a [`Line`].
810    fn to_line(&self) -> Line<'_>;
811}
812
813/// # Panics
814///
815/// In this implementation, the `to_line` method panics if the `Display` implementation returns an
816/// error. This indicates an incorrect `Display` implementation since `fmt::Write for String` never
817/// returns an error itself.
818impl<T: fmt::Display> ToLine for T {
819    fn to_line(&self) -> Line<'_> {
820        Line::from(self.to_string())
821    }
822}
823
824impl fmt::Display for Line<'_> {
825    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
826        for span in &self.spans {
827            write!(f, "{span}")?;
828        }
829        Ok(())
830    }
831}
832
833impl Styled for Line<'_> {
834    type Item = Self;
835
836    fn style(&self) -> Style {
837        self.style
838    }
839
840    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
841        self.style(style)
842    }
843}
844
845#[cfg(test)]
846mod tests {
847    use std::iter;
848
849    use rstest::{fixture, rstest};
850
851    use super::*;
852    use crate::style::{Color, Modifier, Stylize};
853
854    #[fixture]
855    fn small_buf() -> Buffer {
856        Buffer::empty(Rect::new(0, 0, 10, 1))
857    }
858
859    #[test]
860    fn raw_str() {
861        let line = Line::raw("test content");
862        assert_eq!(line.spans, [Span::raw("test content")]);
863        assert_eq!(line.alignment, None);
864
865        let line = Line::raw("a\nb");
866        assert_eq!(line.spans, [Span::raw("a"), Span::raw("b")]);
867        assert_eq!(line.alignment, None);
868    }
869
870    #[test]
871    fn styled_str() {
872        let style = Style::new().yellow();
873        let content = "Hello, world!";
874        let line = Line::styled(content, style);
875        assert_eq!(line.spans, [Span::raw(content)]);
876        assert_eq!(line.style, style);
877    }
878
879    #[test]
880    fn styled_string() {
881        let style = Style::new().yellow();
882        let content = String::from("Hello, world!");
883        let line = Line::styled(content.clone(), style);
884        assert_eq!(line.spans, [Span::raw(content)]);
885        assert_eq!(line.style, style);
886    }
887
888    #[test]
889    fn styled_cow() {
890        let style = Style::new().yellow();
891        let content = Cow::from("Hello, world!");
892        let line = Line::styled(content.clone(), style);
893        assert_eq!(line.spans, [Span::raw(content)]);
894        assert_eq!(line.style, style);
895    }
896
897    #[test]
898    fn spans_vec() {
899        let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
900        assert_eq!(
901            line.spans,
902            vec![
903                Span::styled("Hello", Style::new().blue()),
904                Span::styled(" world!", Style::new().green()),
905            ]
906        );
907    }
908
909    #[test]
910    fn spans_iter() {
911        let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {i}")));
912        assert_eq!(
913            line.spans,
914            vec![
915                Span::raw("Item 1"),
916                Span::raw("Item 2"),
917                Span::raw("Item 3"),
918            ]
919        );
920    }
921
922    #[test]
923    fn style() {
924        let line = Line::default().style(Style::new().red());
925        assert_eq!(line.style, Style::new().red());
926    }
927
928    #[test]
929    fn alignment() {
930        let line = Line::from("This is left").alignment(Alignment::Left);
931        assert_eq!(Some(Alignment::Left), line.alignment);
932
933        let line = Line::from("This is default");
934        assert_eq!(None, line.alignment);
935    }
936
937    #[test]
938    fn width() {
939        let line = Line::from(vec![
940            Span::styled("My", Style::default().fg(Color::Yellow)),
941            Span::raw(" text"),
942        ]);
943        assert_eq!(7, line.width());
944
945        let empty_line = Line::default();
946        assert_eq!(0, empty_line.width());
947    }
948
949    #[test]
950    fn patch_style() {
951        let raw_line = Line::styled("foobar", Color::Yellow);
952        let styled_line = Line::styled("foobar", (Color::Yellow, Modifier::ITALIC));
953
954        assert_ne!(raw_line, styled_line);
955
956        let raw_line = raw_line.patch_style(Modifier::ITALIC);
957        assert_eq!(raw_line, styled_line);
958    }
959
960    #[test]
961    fn reset_style() {
962        let line =
963            Line::styled("foobar", Style::default().yellow().on_red().italic()).reset_style();
964
965        assert_eq!(Style::reset(), line.style);
966    }
967
968    #[test]
969    fn stylize() {
970        assert_eq!(Line::default().green().style, Color::Green.into());
971        assert_eq!(
972            Line::default().on_green().style,
973            Style::new().bg(Color::Green)
974        );
975        assert_eq!(Line::default().italic().style, Modifier::ITALIC.into());
976    }
977
978    #[test]
979    fn from_string() {
980        let s = String::from("Hello, world!");
981        let line = Line::from(s);
982        assert_eq!(line.spans, [Span::from("Hello, world!")]);
983
984        let s = String::from("Hello\nworld!");
985        let line = Line::from(s);
986        assert_eq!(line.spans, [Span::from("Hello"), Span::from("world!")]);
987    }
988
989    #[test]
990    fn from_str() {
991        let s = "Hello, world!";
992        let line = Line::from(s);
993        assert_eq!(line.spans, [Span::from("Hello, world!")]);
994
995        let s = "Hello\nworld!";
996        let line = Line::from(s);
997        assert_eq!(line.spans, [Span::from("Hello"), Span::from("world!")]);
998    }
999
1000    #[test]
1001    fn to_line() {
1002        let line = 42.to_line();
1003        assert_eq!(line.spans, [Span::from("42")]);
1004    }
1005
1006    #[test]
1007    fn from_vec() {
1008        let spans = vec![
1009            Span::styled("Hello,", Style::default().fg(Color::Red)),
1010            Span::styled(" world!", Style::default().fg(Color::Green)),
1011        ];
1012        let line = Line::from(spans.clone());
1013        assert_eq!(line.spans, spans);
1014    }
1015
1016    #[test]
1017    fn from_iter() {
1018        let line = Line::from_iter(vec!["Hello".blue(), " world!".green()]);
1019        assert_eq!(
1020            line.spans,
1021            vec![
1022                Span::styled("Hello", Style::new().blue()),
1023                Span::styled(" world!", Style::new().green()),
1024            ]
1025        );
1026    }
1027
1028    #[test]
1029    fn collect() {
1030        let line: Line = iter::once("Hello".blue())
1031            .chain(iter::once(" world!".green()))
1032            .collect();
1033        assert_eq!(
1034            line.spans,
1035            vec![
1036                Span::styled("Hello", Style::new().blue()),
1037                Span::styled(" world!", Style::new().green()),
1038            ]
1039        );
1040    }
1041
1042    #[test]
1043    fn from_span() {
1044        let span = Span::styled("Hello, world!", Style::default().fg(Color::Yellow));
1045        let line = Line::from(span.clone());
1046        assert_eq!(line.spans, [span]);
1047    }
1048
1049    #[test]
1050    fn add_span() {
1051        assert_eq!(
1052            Line::raw("Red").red() + Span::raw("blue").blue(),
1053            Line {
1054                spans: vec![Span::raw("Red"), Span::raw("blue").blue()],
1055                style: Style::new().red(),
1056                alignment: None,
1057            },
1058        );
1059    }
1060
1061    #[test]
1062    fn add_line() {
1063        assert_eq!(
1064            Line::raw("Red").red() + Line::raw("Blue").blue(),
1065            Text {
1066                lines: vec![Line::raw("Red").red(), Line::raw("Blue").blue()],
1067                style: Style::default(),
1068                alignment: None,
1069            }
1070        );
1071    }
1072
1073    #[test]
1074    fn add_assign_span() {
1075        let mut line = Line::raw("Red").red();
1076        line += Span::raw("Blue").blue();
1077        assert_eq!(
1078            line,
1079            Line {
1080                spans: vec![Span::raw("Red"), Span::raw("Blue").blue()],
1081                style: Style::new().red(),
1082                alignment: None,
1083            },
1084        );
1085    }
1086
1087    #[test]
1088    fn extend() {
1089        let mut line = Line::from("Hello, ");
1090        line.extend([Span::raw("world!")]);
1091        assert_eq!(line.spans, [Span::raw("Hello, "), Span::raw("world!")]);
1092
1093        let mut line = Line::from("Hello, ");
1094        line.extend([Span::raw("world! "), Span::raw("How are you?")]);
1095        assert_eq!(
1096            line.spans,
1097            [
1098                Span::raw("Hello, "),
1099                Span::raw("world! "),
1100                Span::raw("How are you?")
1101            ]
1102        );
1103    }
1104
1105    #[test]
1106    fn into_string() {
1107        let line = Line::from(vec![
1108            Span::styled("Hello,", Style::default().fg(Color::Red)),
1109            Span::styled(" world!", Style::default().fg(Color::Green)),
1110        ]);
1111        let s: String = line.into();
1112        assert_eq!(s, "Hello, world!");
1113    }
1114
1115    #[test]
1116    fn styled_graphemes() {
1117        const RED: Style = Style::new().fg(Color::Red);
1118        const GREEN: Style = Style::new().fg(Color::Green);
1119        const BLUE: Style = Style::new().fg(Color::Blue);
1120        const RED_ON_WHITE: Style = Style::new().fg(Color::Red).bg(Color::White);
1121        const GREEN_ON_WHITE: Style = Style::new().fg(Color::Green).bg(Color::White);
1122        const BLUE_ON_WHITE: Style = Style::new().fg(Color::Blue).bg(Color::White);
1123
1124        let line = Line::from(vec![
1125            Span::styled("He", RED),
1126            Span::styled("ll", GREEN),
1127            Span::styled("o!", BLUE),
1128        ]);
1129        let styled_graphemes = line
1130            .styled_graphemes(Style::new().bg(Color::White))
1131            .collect::<Vec<StyledGrapheme>>();
1132        assert_eq!(
1133            styled_graphemes,
1134            vec![
1135                StyledGrapheme::new("H", RED_ON_WHITE),
1136                StyledGrapheme::new("e", RED_ON_WHITE),
1137                StyledGrapheme::new("l", GREEN_ON_WHITE),
1138                StyledGrapheme::new("l", GREEN_ON_WHITE),
1139                StyledGrapheme::new("o", BLUE_ON_WHITE),
1140                StyledGrapheme::new("!", BLUE_ON_WHITE),
1141            ],
1142        );
1143    }
1144
1145    #[test]
1146    fn display_line_from_vec() {
1147        let line_from_vec = Line::from(vec![Span::raw("Hello,"), Span::raw(" world!")]);
1148
1149        assert_eq!(format!("{line_from_vec}"), "Hello, world!");
1150    }
1151
1152    #[test]
1153    fn display_styled_line() {
1154        let styled_line = Line::styled("Hello, world!", Style::new().green().italic());
1155
1156        assert_eq!(format!("{styled_line}"), "Hello, world!");
1157    }
1158
1159    #[test]
1160    fn display_line_from_styled_span() {
1161        let styled_span = Span::styled("Hello, world!", Style::new().green().italic());
1162        let line_from_styled_span = Line::from(styled_span);
1163
1164        assert_eq!(format!("{line_from_styled_span}"), "Hello, world!");
1165    }
1166
1167    #[test]
1168    fn left_aligned() {
1169        let line = Line::from("Hello, world!").left_aligned();
1170        assert_eq!(line.alignment, Some(Alignment::Left));
1171    }
1172
1173    #[test]
1174    fn centered() {
1175        let line = Line::from("Hello, world!").centered();
1176        assert_eq!(line.alignment, Some(Alignment::Center));
1177    }
1178
1179    #[test]
1180    fn right_aligned() {
1181        let line = Line::from("Hello, world!").right_aligned();
1182        assert_eq!(line.alignment, Some(Alignment::Right));
1183    }
1184
1185    #[test]
1186    pub fn push_span() {
1187        let mut line = Line::from("A");
1188        line.push_span(Span::raw("B"));
1189        line.push_span("C");
1190        assert_eq!(
1191            line.spans,
1192            vec![Span::raw("A"), Span::raw("B"), Span::raw("C")]
1193        );
1194    }
1195
1196    mod widget {
1197        use unicode_segmentation::UnicodeSegmentation;
1198        use unicode_width::UnicodeWidthStr;
1199
1200        use super::*;
1201        use crate::buffer::Cell;
1202
1203        const BLUE: Style = Style::new().fg(Color::Blue);
1204        const GREEN: Style = Style::new().fg(Color::Green);
1205        const ITALIC: Style = Style::new().add_modifier(Modifier::ITALIC);
1206
1207        #[fixture]
1208        fn hello_world() -> Line<'static> {
1209            Line::from(vec![
1210                Span::styled("Hello ", BLUE),
1211                Span::styled("world!", GREEN),
1212            ])
1213            .style(ITALIC)
1214        }
1215
1216        #[test]
1217        fn render() {
1218            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1219            hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
1220            let mut expected = Buffer::with_lines(["Hello world!   "]);
1221            expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1222            expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1223            expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1224            assert_eq!(buf, expected);
1225        }
1226
1227        #[rstest]
1228        fn render_out_of_bounds(hello_world: Line<'static>, mut small_buf: Buffer) {
1229            let out_of_bounds = Rect::new(20, 20, 10, 1);
1230            hello_world.render(out_of_bounds, &mut small_buf);
1231            assert_eq!(small_buf, Buffer::empty(small_buf.area));
1232        }
1233
1234        #[test]
1235        fn render_only_styles_line_area() {
1236            let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
1237            hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
1238            let mut expected = Buffer::with_lines(["Hello world!        "]);
1239            expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1240            expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1241            expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1242            assert_eq!(buf, expected);
1243        }
1244
1245        #[test]
1246        fn render_only_styles_first_line() {
1247            let mut buf = Buffer::empty(Rect::new(0, 0, 20, 2));
1248            hello_world().render(buf.area, &mut buf);
1249            let mut expected = Buffer::with_lines(["Hello world!        ", "                    "]);
1250            expected.set_style(Rect::new(0, 0, 20, 1), ITALIC);
1251            expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1252            expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1253            assert_eq!(buf, expected);
1254        }
1255
1256        #[test]
1257        fn render_truncates() {
1258            let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
1259            Line::from("Hello world!").render(Rect::new(0, 0, 5, 1), &mut buf);
1260            assert_eq!(buf, Buffer::with_lines(["Hello     "]));
1261        }
1262
1263        #[test]
1264        fn render_centered() {
1265            let line = hello_world().alignment(Alignment::Center);
1266            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1267            line.render(Rect::new(0, 0, 15, 1), &mut buf);
1268            let mut expected = Buffer::with_lines([" Hello world!  "]);
1269            expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1270            expected.set_style(Rect::new(1, 0, 6, 1), BLUE);
1271            expected.set_style(Rect::new(7, 0, 6, 1), GREEN);
1272            assert_eq!(buf, expected);
1273        }
1274
1275        #[test]
1276        fn render_right_aligned() {
1277            let line = hello_world().alignment(Alignment::Right);
1278            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1279            line.render(Rect::new(0, 0, 15, 1), &mut buf);
1280            let mut expected = Buffer::with_lines(["   Hello world!"]);
1281            expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1282            expected.set_style(Rect::new(3, 0, 6, 1), BLUE);
1283            expected.set_style(Rect::new(9, 0, 6, 1), GREEN);
1284            assert_eq!(buf, expected);
1285        }
1286
1287        #[test]
1288        fn render_truncates_left() {
1289            let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1290            Line::from("Hello world")
1291                .left_aligned()
1292                .render(buf.area, &mut buf);
1293            assert_eq!(buf, Buffer::with_lines(["Hello"]));
1294        }
1295
1296        #[test]
1297        fn render_truncates_right() {
1298            let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1299            Line::from("Hello world")
1300                .right_aligned()
1301                .render(buf.area, &mut buf);
1302            assert_eq!(buf, Buffer::with_lines(["world"]));
1303        }
1304
1305        #[test]
1306        fn render_truncates_center() {
1307            let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1308            Line::from("Hello world")
1309                .centered()
1310                .render(buf.area, &mut buf);
1311            assert_eq!(buf, Buffer::with_lines(["lo wo"]));
1312        }
1313
1314        /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1315        /// found panics with truncating lines that contained multi-byte characters.
1316        #[test]
1317        fn regression_1032() {
1318            let line = Line::from(
1319                "🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得する"
1320            );
1321            let mut buf = Buffer::empty(Rect::new(0, 0, 83, 1));
1322            line.render(buf.area, &mut buf);
1323            assert_eq!(buf, Buffer::with_lines([
1324                "🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得 "
1325            ]));
1326        }
1327
1328        /// Documentary test to highlight the crab emoji width / length discrepancy
1329        ///
1330        /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1331        /// found panics with truncating lines that contained multi-byte characters.
1332        #[test]
1333        fn crab_emoji_width() {
1334            let crab = "🦀";
1335            assert_eq!(crab.len(), 4); // bytes
1336            assert_eq!(crab.chars().count(), 1);
1337            assert_eq!(crab.graphemes(true).count(), 1);
1338            assert_eq!(crab.width(), 2); // display width
1339        }
1340
1341        /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1342        /// found panics with truncating lines that contained multi-byte characters.
1343        #[rstest]
1344        #[case::left_4(Alignment::Left, 4, "1234")]
1345        #[case::left_5(Alignment::Left, 5, "1234 ")]
1346        #[case::left_6(Alignment::Left, 6, "1234🦀")]
1347        #[case::left_7(Alignment::Left, 7, "1234🦀7")]
1348        #[case::right_4(Alignment::Right, 4, "7890")]
1349        #[case::right_5(Alignment::Right, 5, " 7890")]
1350        #[case::right_6(Alignment::Right, 6, "🦀7890")]
1351        #[case::right_7(Alignment::Right, 7, "4🦀7890")]
1352        fn render_truncates_emoji(
1353            #[case] alignment: Alignment,
1354            #[case] buf_width: u16,
1355            #[case] expected: &str,
1356        ) {
1357            let line = Line::from("1234🦀7890").alignment(alignment);
1358            let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1359            line.render(buf.area, &mut buf);
1360            assert_eq!(buf, Buffer::with_lines([expected]));
1361        }
1362
1363        /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1364        /// found panics with truncating lines that contained multi-byte characters.
1365        ///
1366        /// centering is tricky because there's an ambiguity about whether to take one more char
1367        /// from the left or the right when the line width is odd. This interacts with the width of
1368        /// the crab emoji, which is 2 characters wide by hitting the left or right side of the
1369        /// emoji.
1370        #[rstest]
1371        #[case::center_6_0(6, 0, "")]
1372        #[case::center_6_1(6, 1, " ")] // lef side of "🦀"
1373        #[case::center_6_2(6, 2, "🦀")]
1374        #[case::center_6_3(6, 3, "b🦀")]
1375        #[case::center_6_4(6, 4, "b🦀c")]
1376        #[case::center_7_0(7, 0, "")]
1377        #[case::center_7_1(7, 1, " ")] // right side of "🦀"
1378        #[case::center_7_2(7, 2, "🦀")]
1379        #[case::center_7_3(7, 3, "🦀c")]
1380        #[case::center_7_4(7, 4, "b🦀c")]
1381        #[case::center_8_0(8, 0, "")]
1382        #[case::center_8_1(8, 1, " ")] // right side of "🦀"
1383        #[case::center_8_2(8, 2, " c")] // right side of "🦀c"
1384        #[case::center_8_3(8, 3, "🦀c")]
1385        #[case::center_8_4(8, 4, "🦀cd")]
1386        #[case::center_8_5(8, 5, "b🦀cd")]
1387        #[case::center_9_0(9, 0, "")]
1388        #[case::center_9_1(9, 1, "c")]
1389        #[case::center_9_2(9, 2, " c")] // right side of "🦀c"
1390        #[case::center_9_3(9, 3, " cd")]
1391        #[case::center_9_4(9, 4, "🦀cd")]
1392        #[case::center_9_5(9, 5, "🦀cde")]
1393        #[case::center_9_6(9, 6, "b🦀cde")]
1394        fn render_truncates_emoji_center(
1395            #[case] line_width: u16,
1396            #[case] buf_width: u16,
1397            #[case] expected: &str,
1398        ) {
1399            // because the crab emoji is 2 characters wide, it will can cause the centering tests
1400            // intersect with either the left or right part of the emoji, which causes the emoji to
1401            // be not rendered. Checking for four different widths of the line is enough to cover
1402            // all the possible cases.
1403            let value = match line_width {
1404                6 => "ab🦀cd",
1405                7 => "ab🦀cde",
1406                8 => "ab🦀cdef",
1407                9 => "ab🦀cdefg",
1408                _ => unreachable!(),
1409            };
1410            let line = Line::from(value).centered();
1411            let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1412            line.render(buf.area, &mut buf);
1413            assert_eq!(buf, Buffer::with_lines([expected]));
1414        }
1415
1416        /// Ensures the rendering also works away from the 0x0 position.
1417        ///
1418        /// Particularly of note is that an emoji that is truncated will not overwrite the
1419        /// characters that are already in the buffer. This is inentional (consider how a line
1420        /// that is rendered on a border should not overwrite the border with a partial emoji).
1421        #[rstest]
1422        #[case::left(Alignment::Left, "XXa🦀bcXXX")]
1423        #[case::center(Alignment::Center, "XX🦀bc🦀XX")]
1424        #[case::right(Alignment::Right, "XXXbc🦀dXX")]
1425        fn render_truncates_away_from_0x0(#[case] alignment: Alignment, #[case] expected: &str) {
1426            let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).alignment(alignment);
1427            // Fill buffer with stuff to ensure the output is indeed padded
1428            let mut buf = Buffer::filled(Rect::new(0, 0, 10, 1), Cell::new("X"));
1429            let area = Rect::new(2, 0, 6, 1);
1430            line.render(area, &mut buf);
1431            assert_eq!(buf, Buffer::with_lines([expected]));
1432        }
1433
1434        /// When two spans are rendered after each other the first needs to be padded in accordance
1435        /// to the skipped unicode width. In this case the first crab does not fit at width 6 which
1436        /// takes a front white space.
1437        #[rstest]
1438        #[case::right_4(4, "c🦀d")]
1439        #[case::right_5(5, "bc🦀d")]
1440        #[case::right_6(6, "Xbc🦀d")]
1441        #[case::right_7(7, "🦀bc🦀d")]
1442        #[case::right_8(8, "a🦀bc🦀d")]
1443        fn render_right_aligned_multi_span(#[case] buf_width: u16, #[case] expected: &str) {
1444            let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).right_aligned();
1445            let area = Rect::new(0, 0, buf_width, 1);
1446            // Fill buffer with stuff to ensure the output is indeed padded
1447            let mut buf = Buffer::filled(area, Cell::new("X"));
1448            line.render(buf.area, &mut buf);
1449            assert_eq!(buf, Buffer::with_lines([expected]));
1450        }
1451
1452        /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1453        /// found panics with truncating lines that contained multi-byte characters.
1454        ///
1455        /// Flag emoji are actually two independent characters, so they can be truncated in the
1456        /// middle of the emoji. This test documents just the emoji part of the test.
1457        #[test]
1458        fn flag_emoji() {
1459            let str = "🇺🇸1234";
1460            assert_eq!(str.len(), 12); // flag is 4 bytes
1461            assert_eq!(str.chars().count(), 6); // flag is 2 chars
1462            assert_eq!(str.graphemes(true).count(), 5); // flag is 1 grapheme
1463            assert_eq!(str.width(), 6); // flag is 2 display width
1464        }
1465
1466        /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1467        /// found panics with truncating lines that contained multi-byte characters.
1468        #[rstest]
1469        #[case::flag_1(1, " ")]
1470        #[case::flag_2(2, "🇺🇸")]
1471        #[case::flag_3(3, "🇺🇸1")]
1472        #[case::flag_4(4, "🇺🇸12")]
1473        #[case::flag_5(5, "🇺🇸123")]
1474        #[case::flag_6(6, "🇺🇸1234")]
1475        #[case::flag_7(7, "🇺🇸1234 ")]
1476        fn render_truncates_flag(#[case] buf_width: u16, #[case] expected: &str) {
1477            let line = Line::from("🇺🇸1234");
1478            let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1479            line.render(buf.area, &mut buf);
1480            assert_eq!(buf, Buffer::with_lines([expected]));
1481        }
1482
1483        // Buffer width is `u16`. A line can be longer.
1484        #[rstest]
1485        #[case::left(Alignment::Left, "This is some content with a some")]
1486        #[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
1487        fn render_truncates_very_long_line_of_many_spans(
1488            #[case] alignment: Alignment,
1489            #[case] expected: &str,
1490        ) {
1491            let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
1492            let min_width = usize::from(u16::MAX).saturating_add(1);
1493
1494            // width == len as only ASCII is used here
1495            let factor = min_width.div_ceil(part.len());
1496
1497            let line = Line::from(vec![Span::raw(part); factor]).alignment(alignment);
1498
1499            dbg!(line.width());
1500            assert!(line.width() >= min_width);
1501
1502            let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
1503            line.render(buf.area, &mut buf);
1504            assert_eq!(buf, Buffer::with_lines([expected]));
1505        }
1506
1507        // Buffer width is `u16`. A single span inside a line can be longer.
1508        #[rstest]
1509        #[case::left(Alignment::Left, "This is some content with a some")]
1510        #[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
1511        fn render_truncates_very_long_single_span_line(
1512            #[case] alignment: Alignment,
1513            #[case] expected: &str,
1514        ) {
1515            let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
1516            let min_width = usize::from(u16::MAX).saturating_add(1);
1517
1518            // width == len as only ASCII is used here
1519            let factor = min_width.div_ceil(part.len());
1520
1521            let line = Line::from(vec![Span::raw(part.repeat(factor))]).alignment(alignment);
1522
1523            dbg!(line.width());
1524            assert!(line.width() >= min_width);
1525
1526            let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
1527            line.render(buf.area, &mut buf);
1528            assert_eq!(buf, Buffer::with_lines([expected]));
1529        }
1530
1531        #[test]
1532        fn render_with_newlines() {
1533            let mut buf = Buffer::empty(Rect::new(0, 0, 11, 1));
1534            Line::from("Hello\nworld!").render(Rect::new(0, 0, 11, 1), &mut buf);
1535            assert_eq!(buf, Buffer::with_lines(["Helloworld!"]));
1536        }
1537    }
1538
1539    mod iterators {
1540        use super::*;
1541
1542        /// a fixture used in the tests below to avoid repeating the same setup
1543        #[fixture]
1544        fn hello_world() -> Line<'static> {
1545            Line::from(vec![
1546                Span::styled("Hello ", Color::Blue),
1547                Span::styled("world!", Color::Green),
1548            ])
1549        }
1550
1551        #[rstest]
1552        fn iter(hello_world: Line<'_>) {
1553            let mut iter = hello_world.iter();
1554            assert_eq!(iter.next(), Some(&Span::styled("Hello ", Color::Blue)));
1555            assert_eq!(iter.next(), Some(&Span::styled("world!", Color::Green)));
1556            assert_eq!(iter.next(), None);
1557        }
1558
1559        #[rstest]
1560        fn iter_mut(mut hello_world: Line<'_>) {
1561            let mut iter = hello_world.iter_mut();
1562            assert_eq!(iter.next(), Some(&mut Span::styled("Hello ", Color::Blue)));
1563            assert_eq!(iter.next(), Some(&mut Span::styled("world!", Color::Green)));
1564            assert_eq!(iter.next(), None);
1565        }
1566
1567        #[rstest]
1568        fn into_iter(hello_world: Line<'_>) {
1569            let mut iter = hello_world.into_iter();
1570            assert_eq!(iter.next(), Some(Span::styled("Hello ", Color::Blue)));
1571            assert_eq!(iter.next(), Some(Span::styled("world!", Color::Green)));
1572            assert_eq!(iter.next(), None);
1573        }
1574
1575        #[rstest]
1576        fn into_iter_ref(hello_world: Line<'_>) {
1577            let mut iter = (&hello_world).into_iter();
1578            assert_eq!(iter.next(), Some(&Span::styled("Hello ", Color::Blue)));
1579            assert_eq!(iter.next(), Some(&Span::styled("world!", Color::Green)));
1580            assert_eq!(iter.next(), None);
1581        }
1582
1583        #[test]
1584        fn into_iter_mut_ref() {
1585            let mut hello_world = Line::from(vec![
1586                Span::styled("Hello ", Color::Blue),
1587                Span::styled("world!", Color::Green),
1588            ]);
1589            let mut iter = (&mut hello_world).into_iter();
1590            assert_eq!(iter.next(), Some(&mut Span::styled("Hello ", Color::Blue)));
1591            assert_eq!(iter.next(), Some(&mut Span::styled("world!", Color::Green)));
1592            assert_eq!(iter.next(), None);
1593        }
1594
1595        #[rstest]
1596        fn for_loop_ref(hello_world: Line<'_>) {
1597            let mut result = String::new();
1598            for span in &hello_world {
1599                result.push_str(span.content.as_ref());
1600            }
1601            assert_eq!(result, "Hello world!");
1602        }
1603
1604        #[rstest]
1605        fn for_loop_mut_ref() {
1606            let mut hello_world = Line::from(vec![
1607                Span::styled("Hello ", Color::Blue),
1608                Span::styled("world!", Color::Green),
1609            ]);
1610            let mut result = String::new();
1611            for span in &mut hello_world {
1612                result.push_str(span.content.as_ref());
1613            }
1614            assert_eq!(result, "Hello world!");
1615        }
1616
1617        #[rstest]
1618        fn for_loop_into(hello_world: Line<'_>) {
1619            let mut result = String::new();
1620            for span in hello_world {
1621                result.push_str(span.content.as_ref());
1622            }
1623            assert_eq!(result, "Hello world!");
1624        }
1625    }
1626
1627    #[rstest]
1628    #[case::empty(Line::default(), "Line::default()")]
1629    #[case::raw(Line::raw("Hello, world!"), r#"Line::from("Hello, world!")"#)]
1630    #[case::styled(
1631        Line::styled("Hello, world!", Color::Yellow),
1632        r#"Line::from("Hello, world!").yellow()"#
1633    )]
1634    #[case::styled_complex(
1635        Line::from(String::from("Hello, world!")).green().on_blue().bold().italic().not_dim(),
1636        r#"Line::from("Hello, world!").green().on_blue().bold().italic().not_dim()"#
1637    )]
1638    #[case::styled_span(
1639        Line::from(Span::styled("Hello, world!", Color::Yellow)),
1640        r#"Line::from(Span::from("Hello, world!").yellow())"#
1641    )]
1642    #[case::styled_line_and_span(
1643        Line::from(vec![
1644            Span::styled("Hello", Color::Yellow),
1645            Span::styled(" world!", Color::Green),
1646        ]).italic(),
1647        r#"Line::from_iter([Span::from("Hello").yellow(), Span::from(" world!").green()]).italic()"#
1648    )]
1649    #[case::spans_vec(
1650        Line::from(vec![
1651            Span::styled("Hello", Color::Blue),
1652            Span::styled(" world!", Color::Green),
1653        ]),
1654        r#"Line::from_iter([Span::from("Hello").blue(), Span::from(" world!").green()])"#,
1655    )]
1656    #[case::left_aligned(
1657        Line::from("Hello, world!").left_aligned(),
1658        r#"Line::from("Hello, world!").left_aligned()"#
1659    )]
1660    #[case::centered(
1661        Line::from("Hello, world!").centered(),
1662        r#"Line::from("Hello, world!").centered()"#
1663    )]
1664    #[case::right_aligned(
1665        Line::from("Hello, world!").right_aligned(),
1666        r#"Line::from("Hello, world!").right_aligned()"#
1667    )]
1668    fn debug(#[case] line: Line, #[case] expected: &str) {
1669        assert_eq!(format!("{line:?}"), expected);
1670    }
1671}