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