ratatui/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, WidgetRef},
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::{
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::{
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::{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::{
138///     buffer::Buffer,
139///     layout::Rect,
140///     style::{Style, Stylize},
141///     text::Line,
142///     widgets::Widget,
143///     Frame,
144/// };
145///
146/// # fn render(area: Rect, buf: &mut Buffer) {
147/// // in another widget's render method
148/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
149/// line.render(area, buf);
150/// # }
151///
152/// # fn draw(frame: &mut Frame, area: Rect) {
153/// // in a terminal.draw closure
154/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
155/// frame.render_widget(line, area);
156/// # }
157/// ```
158/// ## Rendering Lines with a Paragraph widget
159///
160/// Usually apps will use the [`Paragraph`] widget instead of rendering a [`Line`] directly as it
161/// provides more functionality.
162///
163/// ```rust
164/// use ratatui::{
165///     buffer::Buffer,
166///     layout::Rect,
167///     style::Stylize,
168///     text::Line,
169///     widgets::{Paragraph, Widget, Wrap},
170/// };
171///
172/// # fn render(area: Rect, buf: &mut Buffer) {
173/// let line = Line::from("Hello world!").yellow().italic();
174/// Paragraph::new(line)
175///     .wrap(Wrap { trim: true })
176///     .render(area, buf);
177/// # }
178/// ```
179///
180/// [`Paragraph`]: crate::widgets::Paragraph
181/// [`Stylize`]: crate::style::Stylize
182#[derive(Default, Clone, Eq, PartialEq, Hash)]
183pub struct Line<'a> {
184    /// The style of this line of text.
185    pub style: Style,
186
187    /// The alignment of this line of text.
188    pub alignment: Option<Alignment>,
189
190    /// The spans that make up this line of text.
191    pub spans: Vec<Span<'a>>,
192}
193
194impl fmt::Debug for Line<'_> {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        if self.spans.is_empty() {
197            f.write_str("Line::default()")?;
198        } else if self.spans.len() == 1 && self.spans[0].style == Style::default() {
199            f.write_str(r#"Line::from(""#)?;
200            f.write_str(&self.spans[0].content)?;
201            f.write_str(r#"")"#)?;
202        } else if self.spans.len() == 1 {
203            f.write_str("Line::from(")?;
204            self.spans[0].fmt(f)?;
205            f.write_str(")")?;
206        } else {
207            f.write_str("Line::from_iter(")?;
208            f.debug_list().entries(&self.spans).finish()?;
209            f.write_str(")")?;
210        }
211        self.style.fmt_stylize(f)?;
212        match self.alignment {
213            Some(Alignment::Left) => write!(f, ".left_aligned()"),
214            Some(Alignment::Center) => write!(f, ".centered()"),
215            Some(Alignment::Right) => write!(f, ".right_aligned()"),
216            None => Ok(()),
217        }
218    }
219}
220
221fn cow_to_spans<'a>(content: impl Into<Cow<'a, str>>) -> Vec<Span<'a>> {
222    match content.into() {
223        Cow::Borrowed(s) => s.lines().map(Span::raw).collect(),
224        Cow::Owned(s) => s.lines().map(|v| Span::raw(v.to_string())).collect(),
225    }
226}
227
228impl<'a> Line<'a> {
229    /// Create a line with the default style.
230    ///
231    /// `content` can be any type that is convertible to [`Cow<str>`] (e.g. [`&str`], [`String`],
232    /// [`Cow<str>`], or your own type that implements [`Into<Cow<str>>`]).
233    ///
234    /// A [`Line`] can specify a [`Style`], which will be applied before the style of each [`Span`]
235    /// in the line.
236    ///
237    /// Any newlines in the content are removed.
238    ///
239    /// # Examples
240    ///
241    /// ```rust
242    /// use std::borrow::Cow;
243    ///
244    /// use ratatui::text::Line;
245    ///
246    /// Line::raw("test content");
247    /// Line::raw(String::from("test content"));
248    /// Line::raw(Cow::from("test content"));
249    /// ```
250    pub fn raw<T>(content: T) -> Self
251    where
252        T: Into<Cow<'a, str>>,
253    {
254        Self {
255            spans: cow_to_spans(content),
256            ..Default::default()
257        }
258    }
259
260    /// Create a line with the given style.
261    ///
262    /// `content` can be any type that is convertible to [`Cow<str>`] (e.g. [`&str`], [`String`],
263    /// [`Cow<str>`], or your own type that implements [`Into<Cow<str>>`]).
264    ///
265    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
266    /// your own type that implements [`Into<Style>`]).
267    ///
268    /// # Examples
269    ///
270    /// Any newlines in the content are removed.
271    ///
272    /// ```rust
273    /// use std::borrow::Cow;
274    ///
275    /// use ratatui::{
276    ///     style::{Style, Stylize},
277    ///     text::Line,
278    /// };
279    ///
280    /// let style = Style::new().yellow().italic();
281    /// Line::styled("My text", style);
282    /// Line::styled(String::from("My text"), style);
283    /// Line::styled(Cow::from("test content"), style);
284    /// ```
285    ///
286    /// [`Color`]: crate::style::Color
287    pub fn styled<T, S>(content: T, style: S) -> Self
288    where
289        T: Into<Cow<'a, str>>,
290        S: Into<Style>,
291    {
292        Self {
293            spans: cow_to_spans(content),
294            style: style.into(),
295            ..Default::default()
296        }
297    }
298
299    /// Sets the spans of this line of text.
300    ///
301    /// `spans` accepts any iterator that yields items that are convertible to [`Span`] (e.g.
302    /// [`&str`], [`String`], [`Span`], or your own type that implements [`Into<Span>`]).
303    ///
304    /// # Examples
305    ///
306    /// ```rust
307    /// use ratatui::{style::Stylize, text::Line};
308    ///
309    /// let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
310    /// let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {}", i)));
311    /// ```
312    #[must_use = "method moves the value of self and returns the modified value"]
313    pub fn spans<I>(mut self, spans: I) -> Self
314    where
315        I: IntoIterator,
316        I::Item: Into<Span<'a>>,
317    {
318        self.spans = spans.into_iter().map(Into::into).collect();
319        self
320    }
321
322    /// Sets the style of this line of text.
323    ///
324    /// Defaults to [`Style::default()`].
325    ///
326    /// Note: This field was added in v0.26.0. Prior to that, the style of a line was determined
327    /// only by the style of each [`Span`] contained in the line. For this reason, this field may
328    /// not be supported by all widgets (outside of the `ratatui` crate itself).
329    ///
330    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
331    /// your own type that implements [`Into<Style>`]).
332    ///
333    /// # Examples
334    /// ```rust
335    /// use ratatui::{
336    ///     style::{Style, Stylize},
337    ///     text::Line,
338    /// };
339    ///
340    /// let mut line = Line::from("foo").style(Style::new().red());
341    /// ```
342    ///
343    /// [`Color`]: crate::style::Color
344    #[must_use = "method moves the value of self and returns the modified value"]
345    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
346        self.style = style.into();
347        self
348    }
349
350    /// Sets the target alignment for this line of text.
351    ///
352    /// Defaults to: [`None`], meaning the alignment is determined by the rendering widget.
353    /// Setting the alignment of a Line generally overrides the alignment of its
354    /// parent Text or Widget.
355    ///
356    /// # Examples
357    ///
358    /// ```rust
359    /// use ratatui::{layout::Alignment, text::Line};
360    ///
361    /// let mut line = Line::from("Hi, what's up?");
362    /// assert_eq!(None, line.alignment);
363    /// assert_eq!(
364    ///     Some(Alignment::Right),
365    ///     line.alignment(Alignment::Right).alignment
366    /// )
367    /// ```
368    #[must_use = "method moves the value of self and returns the modified value"]
369    pub fn alignment(self, alignment: Alignment) -> Self {
370        Self {
371            alignment: Some(alignment),
372            ..self
373        }
374    }
375
376    /// Left-aligns this line of text.
377    ///
378    /// Convenience shortcut for `Line::alignment(Alignment::Left)`.
379    /// Setting the alignment of a Line generally overrides the alignment of its
380    /// parent Text or Widget, with the default alignment being inherited from the parent.
381    ///
382    /// # Examples
383    ///
384    /// ```rust
385    /// use ratatui::text::Line;
386    ///
387    /// let line = Line::from("Hi, what's up?").left_aligned();
388    /// ```
389    #[must_use = "method moves the value of self and returns the modified value"]
390    pub fn left_aligned(self) -> Self {
391        self.alignment(Alignment::Left)
392    }
393
394    /// Center-aligns this line of text.
395    ///
396    /// Convenience shortcut for `Line::alignment(Alignment::Center)`.
397    /// Setting the alignment of a Line generally overrides the alignment of its
398    /// parent Text or Widget, with the default alignment being inherited from the parent.
399    ///
400    /// # Examples
401    ///
402    /// ```rust
403    /// use ratatui::text::Line;
404    ///
405    /// let line = Line::from("Hi, what's up?").centered();
406    /// ```
407    #[must_use = "method moves the value of self and returns the modified value"]
408    pub fn centered(self) -> Self {
409        self.alignment(Alignment::Center)
410    }
411
412    /// Right-aligns this line of text.
413    ///
414    /// Convenience shortcut for `Line::alignment(Alignment::Right)`.
415    /// Setting the alignment of a Line generally overrides the alignment of its
416    /// parent Text or Widget, with the default alignment being inherited from the parent.
417    ///
418    /// # Examples
419    ///
420    /// ```rust
421    /// use ratatui::text::Line;
422    ///
423    /// let line = Line::from("Hi, what's up?").right_aligned();
424    /// ```
425    #[must_use = "method moves the value of self and returns the modified value"]
426    pub fn right_aligned(self) -> Self {
427        self.alignment(Alignment::Right)
428    }
429
430    /// Returns the width of the underlying string.
431    ///
432    /// # Examples
433    ///
434    /// ```rust
435    /// use ratatui::{style::Stylize, text::Line};
436    ///
437    /// let line = Line::from(vec!["Hello".blue(), " world!".green()]);
438    /// assert_eq!(12, line.width());
439    /// ```
440    pub fn width(&self) -> usize {
441        self.spans.iter().map(Span::width).sum()
442    }
443
444    /// Returns an iterator over the graphemes held by this line.
445    ///
446    /// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
447    /// the resulting [`Style`].
448    ///
449    /// `base_style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`],
450    /// or your own type that implements [`Into<Style>`]).
451    ///
452    /// # Examples
453    ///
454    /// ```rust
455    /// use std::iter::Iterator;
456    ///
457    /// use ratatui::{
458    ///     style::{Color, Style},
459    ///     text::{Line, StyledGrapheme},
460    /// };
461    ///
462    /// let line = Line::styled("Text", Style::default().fg(Color::Yellow));
463    /// let style = Style::default().fg(Color::Green).bg(Color::Black);
464    /// assert_eq!(
465    ///     line.styled_graphemes(style)
466    ///         .collect::<Vec<StyledGrapheme>>(),
467    ///     vec![
468    ///         StyledGrapheme::new("T", Style::default().fg(Color::Yellow).bg(Color::Black)),
469    ///         StyledGrapheme::new("e", Style::default().fg(Color::Yellow).bg(Color::Black)),
470    ///         StyledGrapheme::new("x", Style::default().fg(Color::Yellow).bg(Color::Black)),
471    ///         StyledGrapheme::new("t", Style::default().fg(Color::Yellow).bg(Color::Black)),
472    ///     ]
473    /// );
474    /// ```
475    ///
476    /// [`Color`]: crate::style::Color
477    pub fn styled_graphemes<S: Into<Style>>(
478        &'a self,
479        base_style: S,
480    ) -> impl Iterator<Item = StyledGrapheme<'a>> {
481        let style = base_style.into().patch(self.style);
482        self.spans
483            .iter()
484            .flat_map(move |span| span.styled_graphemes(style))
485    }
486
487    /// Patches the style of this Line, adding modifiers from the given style.
488    ///
489    /// This is useful for when you want to apply a style to a line that already has some styling.
490    /// In contrast to [`Line::style`], this method will not overwrite the existing style, but
491    /// instead will add the given style's modifiers to this Line's style.
492    ///
493    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
494    /// your own type that implements [`Into<Style>`]).
495    ///
496    /// This is a fluent setter method which must be chained or used as it consumes self
497    ///
498    /// # Examples
499    ///
500    /// ```rust
501    /// use ratatui::{
502    ///     style::{Color, Modifier},
503    ///     text::Line,
504    /// };
505    ///
506    /// let line = Line::styled("My text", Modifier::ITALIC);
507    ///
508    /// let styled_line = Line::styled("My text", (Color::Yellow, Modifier::ITALIC));
509    ///
510    /// assert_eq!(styled_line, line.patch_style(Color::Yellow));
511    /// ```
512    ///
513    /// [`Color`]: crate::style::Color
514    #[must_use = "method moves the value of self and returns the modified value"]
515    pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
516        self.style = self.style.patch(style);
517        self
518    }
519
520    /// Resets the style of this Line.
521    ///
522    /// Equivalent to calling `patch_style(Style::reset())`.
523    ///
524    /// This is a fluent setter method which must be chained or used as it consumes self
525    ///
526    /// # Examples
527    ///
528    /// ```rust
529    /// # let style = Style::default().yellow();
530    /// use ratatui::{
531    ///     style::{Style, Stylize},
532    ///     text::Line,
533    /// };
534    ///
535    /// let line = Line::styled("My text", style);
536    ///
537    /// assert_eq!(Style::reset(), line.reset_style().style);
538    /// ```
539    #[must_use = "method moves the value of self and returns the modified value"]
540    pub fn reset_style(self) -> Self {
541        self.patch_style(Style::reset())
542    }
543
544    /// Returns an iterator over the spans of this line.
545    pub fn iter(&self) -> std::slice::Iter<Span<'a>> {
546        self.spans.iter()
547    }
548
549    /// Returns a mutable iterator over the spans of this line.
550    pub fn iter_mut(&mut self) -> std::slice::IterMut<Span<'a>> {
551        self.spans.iter_mut()
552    }
553
554    /// Adds a span to the line.
555    ///
556    /// `span` can be any type that is convertible into a `Span`. For example, you can pass a
557    /// `&str`, a `String`, or a `Span`.
558    ///
559    /// # Examples
560    ///
561    /// ```rust
562    /// use ratatui::text::{Line, Span};
563    ///
564    /// let mut line = Line::from("Hello, ");
565    /// line.push_span(Span::raw("world!"));
566    /// line.push_span(" How are you?");
567    /// ```
568    pub fn push_span<T: Into<Span<'a>>>(&mut self, span: T) {
569        self.spans.push(span.into());
570    }
571}
572
573impl<'a> IntoIterator for Line<'a> {
574    type Item = Span<'a>;
575    type IntoIter = std::vec::IntoIter<Span<'a>>;
576
577    fn into_iter(self) -> Self::IntoIter {
578        self.spans.into_iter()
579    }
580}
581
582impl<'a> IntoIterator for &'a Line<'a> {
583    type Item = &'a Span<'a>;
584    type IntoIter = std::slice::Iter<'a, Span<'a>>;
585
586    fn into_iter(self) -> Self::IntoIter {
587        self.iter()
588    }
589}
590
591impl<'a> IntoIterator for &'a mut Line<'a> {
592    type Item = &'a mut Span<'a>;
593    type IntoIter = std::slice::IterMut<'a, Span<'a>>;
594
595    fn into_iter(self) -> Self::IntoIter {
596        self.iter_mut()
597    }
598}
599
600impl<'a> From<String> for Line<'a> {
601    fn from(s: String) -> Self {
602        Self::raw(s)
603    }
604}
605
606impl<'a> From<&'a str> for Line<'a> {
607    fn from(s: &'a str) -> Self {
608        Self::raw(s)
609    }
610}
611
612impl<'a> From<Cow<'a, str>> for Line<'a> {
613    fn from(s: Cow<'a, str>) -> Self {
614        Self::raw(s)
615    }
616}
617
618impl<'a> From<Vec<Span<'a>>> for Line<'a> {
619    fn from(spans: Vec<Span<'a>>) -> Self {
620        Self {
621            spans,
622            ..Default::default()
623        }
624    }
625}
626
627impl<'a> From<Span<'a>> for Line<'a> {
628    fn from(span: Span<'a>) -> Self {
629        Self::from(vec![span])
630    }
631}
632
633impl<'a> From<Line<'a>> for String {
634    fn from(line: Line<'a>) -> Self {
635        line.iter().fold(Self::new(), |mut acc, s| {
636            acc.push_str(s.content.as_ref());
637            acc
638        })
639    }
640}
641
642impl<'a, T> FromIterator<T> for Line<'a>
643where
644    T: Into<Span<'a>>,
645{
646    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
647        Self::from(iter.into_iter().map(Into::into).collect::<Vec<_>>())
648    }
649}
650
651/// Adds a `Span` to a `Line`, returning a new `Line` with the `Span` added.
652impl<'a> std::ops::Add<Span<'a>> for Line<'a> {
653    type Output = Self;
654
655    fn add(mut self, rhs: Span<'a>) -> Self::Output {
656        self.spans.push(rhs);
657        self
658    }
659}
660
661/// Adds two `Line`s together, returning a new `Text` with the contents of the two `Line`s.
662impl<'a> std::ops::Add<Self> for Line<'a> {
663    type Output = Text<'a>;
664
665    fn add(self, rhs: Self) -> Self::Output {
666        Text::from(vec![self, rhs])
667    }
668}
669
670impl<'a> std::ops::AddAssign<Span<'a>> for Line<'a> {
671    fn add_assign(&mut self, rhs: Span<'a>) {
672        self.spans.push(rhs);
673    }
674}
675
676impl<'a> Extend<Span<'a>> for Line<'a> {
677    fn extend<T: IntoIterator<Item = Span<'a>>>(&mut self, iter: T) {
678        self.spans.extend(iter);
679    }
680}
681
682impl Widget for Line<'_> {
683    fn render(self, area: Rect, buf: &mut Buffer) {
684        self.render_ref(area, buf);
685    }
686}
687
688impl WidgetRef for Line<'_> {
689    fn render_ref(&self, area: Rect, buf: &mut Buffer) {
690        self.render_with_alignment(area, buf, None);
691    }
692}
693
694impl Line<'_> {
695    /// An internal implementation method for `WidgetRef::render_ref`
696    ///
697    /// Allows the parent widget to define a default alignment, to be
698    /// 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_ref(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 widtch 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<'a> Styled for Line<'a> {
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_ref(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_ref(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_ref(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_ref(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_ref(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_ref(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_ref(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_ref(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}