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#[derive(Default, Clone, Eq, PartialEq, Hash)]
183pub struct Line<'a> {
184 pub style: Style,
186
187 pub alignment: Option<Alignment>,
189
190 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
440 pub fn width(&self) -> usize {
441 UnicodeWidthStr::width(self)
442 }
443
444 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 #[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 #[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 pub fn iter(&self) -> core::slice::Iter<'_, Span<'a>> {
540 self.spans.iter()
541 }
542
543 pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Span<'a>> {
545 self.spans.iter_mut()
546 }
547
548 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
655impl<'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
665impl<'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 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 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
745fn 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
758fn 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_map(move |(span, span_width)| {
769 if skip_width >= span_width {
772 skip_width = skip_width.saturating_sub(span_width);
773 return None;
774 }
775
776 let available_width = span_width.saturating_sub(skip_width);
779 skip_width = 0; Some((span, span_width, available_width))
781 })
782 .map(|(span, span_width, available_width)| {
783 if span_width <= available_width {
784 return (span.clone(), span_width, 0u16);
786 }
787 let (content, actual_width) = span.content.unicode_truncate_start(available_width);
790
791 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
803pub trait ToLine {
811 fn to_line(&self) -> Line<'_>;
813}
814
815impl<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 #[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 #[test]
1340 fn crab_emoji_width() {
1341 let crab = "🦀";
1342 assert_eq!(crab.len(), 4); assert_eq!(crab.chars().count(), 1);
1344 assert_eq!(crab.graphemes(true).count(), 1);
1345 assert_eq!(crab.width(), 2); }
1347
1348 #[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 #[rstest]
1378 #[case::center_6_0(6, 0, "")]
1379 #[case::center_6_1(6, 1, " ")] #[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, " ")] #[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, " ")] #[case::center_8_2(8, 2, " c")] #[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")] #[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 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 #[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 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 #[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 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 #[test]
1465 fn flag_emoji() {
1466 let str = "🇺🇸1234";
1467 assert_eq!(str.len(), 12); assert_eq!(str.chars().count(), 6); assert_eq!(str.graphemes(true).count(), 5); assert_eq!(str.width(), 6); }
1472
1473 #[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 #[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 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 #[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 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 #[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}