Skip to main content

ratatui_core/text/
line.rs

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