1#![deny(missing_docs)]
2#![warn(clippy::pedantic, clippy::nursery, clippy::arithmetic_side_effects)]
3use std::{borrow::Cow, fmt};
4
5use unicode_truncate::UnicodeTruncateStr;
6
7use crate::{
8 buffer::Buffer,
9 layout::{Alignment, Rect},
10 style::{Style, Styled},
11 text::{Span, StyledGrapheme, Text},
12 widgets::{Widget, WidgetRef},
13};
14
15#[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
288 where
289 T: Into<Cow<'a, str>>,
290 S: Into<Style>,
291 {
292 Self {
293 spans: cow_to_spans(content),
294 style: style.into(),
295 ..Default::default()
296 }
297 }
298
299 #[must_use = "method moves the value of self and returns the modified value"]
313 pub fn spans<I>(mut self, spans: I) -> Self
314 where
315 I: IntoIterator,
316 I::Item: Into<Span<'a>>,
317 {
318 self.spans = spans.into_iter().map(Into::into).collect();
319 self
320 }
321
322 #[must_use = "method moves the value of self and returns the modified value"]
345 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
346 self.style = style.into();
347 self
348 }
349
350 #[must_use = "method moves the value of self and returns the modified value"]
369 pub fn alignment(self, alignment: Alignment) -> Self {
370 Self {
371 alignment: Some(alignment),
372 ..self
373 }
374 }
375
376 #[must_use = "method moves the value of self and returns the modified value"]
390 pub fn left_aligned(self) -> Self {
391 self.alignment(Alignment::Left)
392 }
393
394 #[must_use = "method moves the value of self and returns the modified value"]
408 pub fn centered(self) -> Self {
409 self.alignment(Alignment::Center)
410 }
411
412 #[must_use = "method moves the value of self and returns the modified value"]
426 pub fn right_aligned(self) -> Self {
427 self.alignment(Alignment::Right)
428 }
429
430 pub fn width(&self) -> usize {
441 self.spans.iter().map(Span::width).sum()
442 }
443
444 pub fn styled_graphemes<S: Into<Style>>(
478 &'a self,
479 base_style: S,
480 ) -> impl Iterator<Item = StyledGrapheme<'a>> {
481 let style = base_style.into().patch(self.style);
482 self.spans
483 .iter()
484 .flat_map(move |span| span.styled_graphemes(style))
485 }
486
487 #[must_use = "method moves the value of self and returns the modified value"]
515 pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
516 self.style = self.style.patch(style);
517 self
518 }
519
520 #[must_use = "method moves the value of self and returns the modified value"]
540 pub fn reset_style(self) -> Self {
541 self.patch_style(Style::reset())
542 }
543
544 pub fn iter(&self) -> std::slice::Iter<Span<'a>> {
546 self.spans.iter()
547 }
548
549 pub fn iter_mut(&mut self) -> std::slice::IterMut<Span<'a>> {
551 self.spans.iter_mut()
552 }
553
554 pub fn push_span<T: Into<Span<'a>>>(&mut self, span: T) {
569 self.spans.push(span.into());
570 }
571}
572
573impl<'a> IntoIterator for Line<'a> {
574 type Item = Span<'a>;
575 type IntoIter = std::vec::IntoIter<Span<'a>>;
576
577 fn into_iter(self) -> Self::IntoIter {
578 self.spans.into_iter()
579 }
580}
581
582impl<'a> IntoIterator for &'a Line<'a> {
583 type Item = &'a Span<'a>;
584 type IntoIter = std::slice::Iter<'a, Span<'a>>;
585
586 fn into_iter(self) -> Self::IntoIter {
587 self.iter()
588 }
589}
590
591impl<'a> IntoIterator for &'a mut Line<'a> {
592 type Item = &'a mut Span<'a>;
593 type IntoIter = std::slice::IterMut<'a, Span<'a>>;
594
595 fn into_iter(self) -> Self::IntoIter {
596 self.iter_mut()
597 }
598}
599
600impl<'a> From<String> for Line<'a> {
601 fn from(s: String) -> Self {
602 Self::raw(s)
603 }
604}
605
606impl<'a> From<&'a str> for Line<'a> {
607 fn from(s: &'a str) -> Self {
608 Self::raw(s)
609 }
610}
611
612impl<'a> From<Cow<'a, str>> for Line<'a> {
613 fn from(s: Cow<'a, str>) -> Self {
614 Self::raw(s)
615 }
616}
617
618impl<'a> From<Vec<Span<'a>>> for Line<'a> {
619 fn from(spans: Vec<Span<'a>>) -> Self {
620 Self {
621 spans,
622 ..Default::default()
623 }
624 }
625}
626
627impl<'a> From<Span<'a>> for Line<'a> {
628 fn from(span: Span<'a>) -> Self {
629 Self::from(vec![span])
630 }
631}
632
633impl<'a> From<Line<'a>> for String {
634 fn from(line: Line<'a>) -> Self {
635 line.iter().fold(Self::new(), |mut acc, s| {
636 acc.push_str(s.content.as_ref());
637 acc
638 })
639 }
640}
641
642impl<'a, T> FromIterator<T> for Line<'a>
643where
644 T: Into<Span<'a>>,
645{
646 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
647 Self::from(iter.into_iter().map(Into::into).collect::<Vec<_>>())
648 }
649}
650
651impl<'a> std::ops::Add<Span<'a>> for Line<'a> {
653 type Output = Self;
654
655 fn add(mut self, rhs: Span<'a>) -> Self::Output {
656 self.spans.push(rhs);
657 self
658 }
659}
660
661impl<'a> std::ops::Add<Self> for Line<'a> {
663 type Output = Text<'a>;
664
665 fn add(self, rhs: Self) -> Self::Output {
666 Text::from(vec![self, rhs])
667 }
668}
669
670impl<'a> std::ops::AddAssign<Span<'a>> for Line<'a> {
671 fn add_assign(&mut self, rhs: Span<'a>) {
672 self.spans.push(rhs);
673 }
674}
675
676impl<'a> Extend<Span<'a>> for Line<'a> {
677 fn extend<T: IntoIterator<Item = Span<'a>>>(&mut self, iter: T) {
678 self.spans.extend(iter);
679 }
680}
681
682impl Widget for Line<'_> {
683 fn render(self, area: Rect, buf: &mut Buffer) {
684 self.render_ref(area, buf);
685 }
686}
687
688impl WidgetRef for Line<'_> {
689 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
690 self.render_with_alignment(area, buf, None);
691 }
692}
693
694impl Line<'_> {
695 pub(crate) fn render_with_alignment(
700 &self,
701 area: Rect,
702 buf: &mut Buffer,
703 parent_alignment: Option<Alignment>,
704 ) {
705 let area = area.intersection(buf.area);
706 if area.is_empty() {
707 return;
708 }
709 let area = Rect { height: 1, ..area };
710 let line_width = self.width();
711 if line_width == 0 {
712 return;
713 }
714
715 buf.set_style(area, self.style);
716
717 let alignment = self.alignment.or(parent_alignment);
718
719 let area_width = usize::from(area.width);
720 let can_render_complete_line = line_width <= area_width;
721 if can_render_complete_line {
722 let indent_width = match alignment {
723 Some(Alignment::Center) => (area_width.saturating_sub(line_width)) / 2,
724 Some(Alignment::Right) => area_width.saturating_sub(line_width),
725 Some(Alignment::Left) | None => 0,
726 };
727 let indent_width = u16::try_from(indent_width).unwrap_or(u16::MAX);
728 let area = area.indent_x(indent_width);
729 render_spans(&self.spans, area, buf, 0);
730 } else {
731 let skip_width = match alignment {
734 Some(Alignment::Center) => (line_width.saturating_sub(area_width)) / 2,
735 Some(Alignment::Right) => line_width.saturating_sub(area_width),
736 Some(Alignment::Left) | None => 0,
737 };
738 render_spans(&self.spans, area, buf, skip_width);
739 };
740 }
741}
742
743fn render_spans(spans: &[Span], mut area: Rect, buf: &mut Buffer, span_skip_width: usize) {
745 for (span, span_width, offset) in spans_after_width(spans, span_skip_width) {
746 area = area.indent_x(offset);
747 if area.is_empty() {
748 break;
749 }
750 span.render_ref(area, buf);
751 let span_width = u16::try_from(span_width).unwrap_or(u16::MAX);
752 area = area.indent_x(span_width);
753 }
754}
755
756fn spans_after_width<'a>(
759 spans: &'a [Span],
760 mut skip_width: usize,
761) -> impl Iterator<Item = (Span<'a>, usize, u16)> {
762 spans
763 .iter()
764 .map(|span| (span, span.width()))
765 .filter_map(move |(span, span_width)| {
767 if skip_width >= span_width {
770 skip_width = skip_width.saturating_sub(span_width);
771 return None;
772 }
773
774 let available_width = span_width.saturating_sub(skip_width);
777 skip_width = 0; Some((span, span_width, available_width))
779 })
780 .map(|(span, span_width, available_width)| {
781 if span_width <= available_width {
782 return (span.clone(), span_width, 0u16);
784 }
785 let (content, actual_width) = span.content.unicode_truncate_start(available_width);
788
789 let first_grapheme_offset = available_width.saturating_sub(actual_width);
792 let first_grapheme_offset = u16::try_from(first_grapheme_offset).unwrap_or(u16::MAX);
793 (
794 Span::styled(content, span.style),
795 actual_width,
796 first_grapheme_offset,
797 )
798 })
799}
800
801pub trait ToLine {
809 fn to_line(&self) -> Line<'_>;
811}
812
813impl<T: fmt::Display> ToLine for T {
819 fn to_line(&self) -> Line<'_> {
820 Line::from(self.to_string())
821 }
822}
823
824impl fmt::Display for Line<'_> {
825 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
826 for span in &self.spans {
827 write!(f, "{span}")?;
828 }
829 Ok(())
830 }
831}
832
833impl<'a> Styled for Line<'a> {
834 type Item = Self;
835
836 fn style(&self) -> Style {
837 self.style
838 }
839
840 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
841 self.style(style)
842 }
843}
844
845#[cfg(test)]
846mod tests {
847 use std::iter;
848
849 use rstest::{fixture, rstest};
850
851 use super::*;
852 use crate::style::{Color, Modifier, Stylize};
853
854 #[fixture]
855 fn small_buf() -> Buffer {
856 Buffer::empty(Rect::new(0, 0, 10, 1))
857 }
858
859 #[test]
860 fn raw_str() {
861 let line = Line::raw("test content");
862 assert_eq!(line.spans, [Span::raw("test content")]);
863 assert_eq!(line.alignment, None);
864
865 let line = Line::raw("a\nb");
866 assert_eq!(line.spans, [Span::raw("a"), Span::raw("b")]);
867 assert_eq!(line.alignment, None);
868 }
869
870 #[test]
871 fn styled_str() {
872 let style = Style::new().yellow();
873 let content = "Hello, world!";
874 let line = Line::styled(content, style);
875 assert_eq!(line.spans, [Span::raw(content)]);
876 assert_eq!(line.style, style);
877 }
878
879 #[test]
880 fn styled_string() {
881 let style = Style::new().yellow();
882 let content = String::from("Hello, world!");
883 let line = Line::styled(content.clone(), style);
884 assert_eq!(line.spans, [Span::raw(content)]);
885 assert_eq!(line.style, style);
886 }
887
888 #[test]
889 fn styled_cow() {
890 let style = Style::new().yellow();
891 let content = Cow::from("Hello, world!");
892 let line = Line::styled(content.clone(), style);
893 assert_eq!(line.spans, [Span::raw(content)]);
894 assert_eq!(line.style, style);
895 }
896
897 #[test]
898 fn spans_vec() {
899 let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
900 assert_eq!(
901 line.spans,
902 vec![
903 Span::styled("Hello", Style::new().blue()),
904 Span::styled(" world!", Style::new().green()),
905 ]
906 );
907 }
908
909 #[test]
910 fn spans_iter() {
911 let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {i}")));
912 assert_eq!(
913 line.spans,
914 vec![
915 Span::raw("Item 1"),
916 Span::raw("Item 2"),
917 Span::raw("Item 3"),
918 ]
919 );
920 }
921
922 #[test]
923 fn style() {
924 let line = Line::default().style(Style::new().red());
925 assert_eq!(line.style, Style::new().red());
926 }
927
928 #[test]
929 fn alignment() {
930 let line = Line::from("This is left").alignment(Alignment::Left);
931 assert_eq!(Some(Alignment::Left), line.alignment);
932
933 let line = Line::from("This is default");
934 assert_eq!(None, line.alignment);
935 }
936
937 #[test]
938 fn width() {
939 let line = Line::from(vec![
940 Span::styled("My", Style::default().fg(Color::Yellow)),
941 Span::raw(" text"),
942 ]);
943 assert_eq!(7, line.width());
944
945 let empty_line = Line::default();
946 assert_eq!(0, empty_line.width());
947 }
948
949 #[test]
950 fn patch_style() {
951 let raw_line = Line::styled("foobar", Color::Yellow);
952 let styled_line = Line::styled("foobar", (Color::Yellow, Modifier::ITALIC));
953
954 assert_ne!(raw_line, styled_line);
955
956 let raw_line = raw_line.patch_style(Modifier::ITALIC);
957 assert_eq!(raw_line, styled_line);
958 }
959
960 #[test]
961 fn reset_style() {
962 let line =
963 Line::styled("foobar", Style::default().yellow().on_red().italic()).reset_style();
964
965 assert_eq!(Style::reset(), line.style);
966 }
967
968 #[test]
969 fn stylize() {
970 assert_eq!(Line::default().green().style, Color::Green.into());
971 assert_eq!(
972 Line::default().on_green().style,
973 Style::new().bg(Color::Green)
974 );
975 assert_eq!(Line::default().italic().style, Modifier::ITALIC.into());
976 }
977
978 #[test]
979 fn from_string() {
980 let s = String::from("Hello, world!");
981 let line = Line::from(s);
982 assert_eq!(line.spans, [Span::from("Hello, world!")]);
983
984 let s = String::from("Hello\nworld!");
985 let line = Line::from(s);
986 assert_eq!(line.spans, [Span::from("Hello"), Span::from("world!")]);
987 }
988
989 #[test]
990 fn from_str() {
991 let s = "Hello, world!";
992 let line = Line::from(s);
993 assert_eq!(line.spans, [Span::from("Hello, world!")]);
994
995 let s = "Hello\nworld!";
996 let line = Line::from(s);
997 assert_eq!(line.spans, [Span::from("Hello"), Span::from("world!")]);
998 }
999
1000 #[test]
1001 fn to_line() {
1002 let line = 42.to_line();
1003 assert_eq!(line.spans, [Span::from("42")]);
1004 }
1005
1006 #[test]
1007 fn from_vec() {
1008 let spans = vec![
1009 Span::styled("Hello,", Style::default().fg(Color::Red)),
1010 Span::styled(" world!", Style::default().fg(Color::Green)),
1011 ];
1012 let line = Line::from(spans.clone());
1013 assert_eq!(line.spans, spans);
1014 }
1015
1016 #[test]
1017 fn from_iter() {
1018 let line = Line::from_iter(vec!["Hello".blue(), " world!".green()]);
1019 assert_eq!(
1020 line.spans,
1021 vec![
1022 Span::styled("Hello", Style::new().blue()),
1023 Span::styled(" world!", Style::new().green()),
1024 ]
1025 );
1026 }
1027
1028 #[test]
1029 fn collect() {
1030 let line: Line = iter::once("Hello".blue())
1031 .chain(iter::once(" world!".green()))
1032 .collect();
1033 assert_eq!(
1034 line.spans,
1035 vec![
1036 Span::styled("Hello", Style::new().blue()),
1037 Span::styled(" world!", Style::new().green()),
1038 ]
1039 );
1040 }
1041
1042 #[test]
1043 fn from_span() {
1044 let span = Span::styled("Hello, world!", Style::default().fg(Color::Yellow));
1045 let line = Line::from(span.clone());
1046 assert_eq!(line.spans, [span]);
1047 }
1048
1049 #[test]
1050 fn add_span() {
1051 assert_eq!(
1052 Line::raw("Red").red() + Span::raw("blue").blue(),
1053 Line {
1054 spans: vec![Span::raw("Red"), Span::raw("blue").blue()],
1055 style: Style::new().red(),
1056 alignment: None,
1057 },
1058 );
1059 }
1060
1061 #[test]
1062 fn add_line() {
1063 assert_eq!(
1064 Line::raw("Red").red() + Line::raw("Blue").blue(),
1065 Text {
1066 lines: vec![Line::raw("Red").red(), Line::raw("Blue").blue()],
1067 style: Style::default(),
1068 alignment: None,
1069 }
1070 );
1071 }
1072
1073 #[test]
1074 fn add_assign_span() {
1075 let mut line = Line::raw("Red").red();
1076 line += Span::raw("Blue").blue();
1077 assert_eq!(
1078 line,
1079 Line {
1080 spans: vec![Span::raw("Red"), Span::raw("Blue").blue()],
1081 style: Style::new().red(),
1082 alignment: None,
1083 },
1084 );
1085 }
1086
1087 #[test]
1088 fn extend() {
1089 let mut line = Line::from("Hello, ");
1090 line.extend([Span::raw("world!")]);
1091 assert_eq!(line.spans, [Span::raw("Hello, "), Span::raw("world!")]);
1092
1093 let mut line = Line::from("Hello, ");
1094 line.extend([Span::raw("world! "), Span::raw("How are you?")]);
1095 assert_eq!(
1096 line.spans,
1097 [
1098 Span::raw("Hello, "),
1099 Span::raw("world! "),
1100 Span::raw("How are you?")
1101 ]
1102 );
1103 }
1104
1105 #[test]
1106 fn into_string() {
1107 let line = Line::from(vec![
1108 Span::styled("Hello,", Style::default().fg(Color::Red)),
1109 Span::styled(" world!", Style::default().fg(Color::Green)),
1110 ]);
1111 let s: String = line.into();
1112 assert_eq!(s, "Hello, world!");
1113 }
1114
1115 #[test]
1116 fn styled_graphemes() {
1117 const RED: Style = Style::new().fg(Color::Red);
1118 const GREEN: Style = Style::new().fg(Color::Green);
1119 const BLUE: Style = Style::new().fg(Color::Blue);
1120 const RED_ON_WHITE: Style = Style::new().fg(Color::Red).bg(Color::White);
1121 const GREEN_ON_WHITE: Style = Style::new().fg(Color::Green).bg(Color::White);
1122 const BLUE_ON_WHITE: Style = Style::new().fg(Color::Blue).bg(Color::White);
1123
1124 let line = Line::from(vec![
1125 Span::styled("He", RED),
1126 Span::styled("ll", GREEN),
1127 Span::styled("o!", BLUE),
1128 ]);
1129 let styled_graphemes = line
1130 .styled_graphemes(Style::new().bg(Color::White))
1131 .collect::<Vec<StyledGrapheme>>();
1132 assert_eq!(
1133 styled_graphemes,
1134 vec![
1135 StyledGrapheme::new("H", RED_ON_WHITE),
1136 StyledGrapheme::new("e", RED_ON_WHITE),
1137 StyledGrapheme::new("l", GREEN_ON_WHITE),
1138 StyledGrapheme::new("l", GREEN_ON_WHITE),
1139 StyledGrapheme::new("o", BLUE_ON_WHITE),
1140 StyledGrapheme::new("!", BLUE_ON_WHITE),
1141 ],
1142 );
1143 }
1144
1145 #[test]
1146 fn display_line_from_vec() {
1147 let line_from_vec = Line::from(vec![Span::raw("Hello,"), Span::raw(" world!")]);
1148
1149 assert_eq!(format!("{line_from_vec}"), "Hello, world!");
1150 }
1151
1152 #[test]
1153 fn display_styled_line() {
1154 let styled_line = Line::styled("Hello, world!", Style::new().green().italic());
1155
1156 assert_eq!(format!("{styled_line}"), "Hello, world!");
1157 }
1158
1159 #[test]
1160 fn display_line_from_styled_span() {
1161 let styled_span = Span::styled("Hello, world!", Style::new().green().italic());
1162 let line_from_styled_span = Line::from(styled_span);
1163
1164 assert_eq!(format!("{line_from_styled_span}"), "Hello, world!");
1165 }
1166
1167 #[test]
1168 fn left_aligned() {
1169 let line = Line::from("Hello, world!").left_aligned();
1170 assert_eq!(line.alignment, Some(Alignment::Left));
1171 }
1172
1173 #[test]
1174 fn centered() {
1175 let line = Line::from("Hello, world!").centered();
1176 assert_eq!(line.alignment, Some(Alignment::Center));
1177 }
1178
1179 #[test]
1180 fn right_aligned() {
1181 let line = Line::from("Hello, world!").right_aligned();
1182 assert_eq!(line.alignment, Some(Alignment::Right));
1183 }
1184
1185 #[test]
1186 pub fn push_span() {
1187 let mut line = Line::from("A");
1188 line.push_span(Span::raw("B"));
1189 line.push_span("C");
1190 assert_eq!(
1191 line.spans,
1192 vec![Span::raw("A"), Span::raw("B"), Span::raw("C")]
1193 );
1194 }
1195
1196 mod widget {
1197 use unicode_segmentation::UnicodeSegmentation;
1198 use unicode_width::UnicodeWidthStr;
1199
1200 use super::*;
1201 use crate::buffer::Cell;
1202
1203 const BLUE: Style = Style::new().fg(Color::Blue);
1204 const GREEN: Style = Style::new().fg(Color::Green);
1205 const ITALIC: Style = Style::new().add_modifier(Modifier::ITALIC);
1206
1207 #[fixture]
1208 fn hello_world() -> Line<'static> {
1209 Line::from(vec![
1210 Span::styled("Hello ", BLUE),
1211 Span::styled("world!", GREEN),
1212 ])
1213 .style(ITALIC)
1214 }
1215
1216 #[test]
1217 fn render() {
1218 let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1219 hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
1220 let mut expected = Buffer::with_lines(["Hello world! "]);
1221 expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1222 expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1223 expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1224 assert_eq!(buf, expected);
1225 }
1226
1227 #[rstest]
1228 fn render_out_of_bounds(hello_world: Line<'static>, mut small_buf: Buffer) {
1229 let out_of_bounds = Rect::new(20, 20, 10, 1);
1230 hello_world.render(out_of_bounds, &mut small_buf);
1231 assert_eq!(small_buf, Buffer::empty(small_buf.area));
1232 }
1233
1234 #[test]
1235 fn render_only_styles_line_area() {
1236 let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
1237 hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
1238 let mut expected = Buffer::with_lines(["Hello world! "]);
1239 expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1240 expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1241 expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1242 assert_eq!(buf, expected);
1243 }
1244
1245 #[test]
1246 fn render_only_styles_first_line() {
1247 let mut buf = Buffer::empty(Rect::new(0, 0, 20, 2));
1248 hello_world().render(buf.area, &mut buf);
1249 let mut expected = Buffer::with_lines(["Hello world! ", " "]);
1250 expected.set_style(Rect::new(0, 0, 20, 1), ITALIC);
1251 expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1252 expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1253 assert_eq!(buf, expected);
1254 }
1255
1256 #[test]
1257 fn render_truncates() {
1258 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
1259 Line::from("Hello world!").render(Rect::new(0, 0, 5, 1), &mut buf);
1260 assert_eq!(buf, Buffer::with_lines(["Hello "]));
1261 }
1262
1263 #[test]
1264 fn render_centered() {
1265 let line = hello_world().alignment(Alignment::Center);
1266 let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1267 line.render(Rect::new(0, 0, 15, 1), &mut buf);
1268 let mut expected = Buffer::with_lines([" Hello world! "]);
1269 expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1270 expected.set_style(Rect::new(1, 0, 6, 1), BLUE);
1271 expected.set_style(Rect::new(7, 0, 6, 1), GREEN);
1272 assert_eq!(buf, expected);
1273 }
1274
1275 #[test]
1276 fn render_right_aligned() {
1277 let line = hello_world().alignment(Alignment::Right);
1278 let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1279 line.render(Rect::new(0, 0, 15, 1), &mut buf);
1280 let mut expected = Buffer::with_lines([" Hello world!"]);
1281 expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1282 expected.set_style(Rect::new(3, 0, 6, 1), BLUE);
1283 expected.set_style(Rect::new(9, 0, 6, 1), GREEN);
1284 assert_eq!(buf, expected);
1285 }
1286
1287 #[test]
1288 fn render_truncates_left() {
1289 let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1290 Line::from("Hello world")
1291 .left_aligned()
1292 .render(buf.area, &mut buf);
1293 assert_eq!(buf, Buffer::with_lines(["Hello"]));
1294 }
1295
1296 #[test]
1297 fn render_truncates_right() {
1298 let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1299 Line::from("Hello world")
1300 .right_aligned()
1301 .render(buf.area, &mut buf);
1302 assert_eq!(buf, Buffer::with_lines(["world"]));
1303 }
1304
1305 #[test]
1306 fn render_truncates_center() {
1307 let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1308 Line::from("Hello world")
1309 .centered()
1310 .render(buf.area, &mut buf);
1311 assert_eq!(buf, Buffer::with_lines(["lo wo"]));
1312 }
1313
1314 #[test]
1317 fn regression_1032() {
1318 let line = Line::from(
1319 "🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得する"
1320 );
1321 let mut buf = Buffer::empty(Rect::new(0, 0, 83, 1));
1322 line.render_ref(buf.area, &mut buf);
1323 assert_eq!(buf, Buffer::with_lines([
1324 "🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得 "
1325 ]));
1326 }
1327
1328 #[test]
1333 fn crab_emoji_width() {
1334 let crab = "🦀";
1335 assert_eq!(crab.len(), 4); assert_eq!(crab.chars().count(), 1);
1337 assert_eq!(crab.graphemes(true).count(), 1);
1338 assert_eq!(crab.width(), 2); }
1340
1341 #[rstest]
1344 #[case::left_4(Alignment::Left, 4, "1234")]
1345 #[case::left_5(Alignment::Left, 5, "1234 ")]
1346 #[case::left_6(Alignment::Left, 6, "1234🦀")]
1347 #[case::left_7(Alignment::Left, 7, "1234🦀7")]
1348 #[case::right_4(Alignment::Right, 4, "7890")]
1349 #[case::right_5(Alignment::Right, 5, " 7890")]
1350 #[case::right_6(Alignment::Right, 6, "🦀7890")]
1351 #[case::right_7(Alignment::Right, 7, "4🦀7890")]
1352 fn render_truncates_emoji(
1353 #[case] alignment: Alignment,
1354 #[case] buf_width: u16,
1355 #[case] expected: &str,
1356 ) {
1357 let line = Line::from("1234🦀7890").alignment(alignment);
1358 let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1359 line.render_ref(buf.area, &mut buf);
1360 assert_eq!(buf, Buffer::with_lines([expected]));
1361 }
1362
1363 #[rstest]
1371 #[case::center_6_0(6, 0, "")]
1372 #[case::center_6_1(6, 1, " ")] #[case::center_6_2(6, 2, "🦀")]
1374 #[case::center_6_3(6, 3, "b🦀")]
1375 #[case::center_6_4(6, 4, "b🦀c")]
1376 #[case::center_7_0(7, 0, "")]
1377 #[case::center_7_1(7, 1, " ")] #[case::center_7_2(7, 2, "🦀")]
1379 #[case::center_7_3(7, 3, "🦀c")]
1380 #[case::center_7_4(7, 4, "b🦀c")]
1381 #[case::center_8_0(8, 0, "")]
1382 #[case::center_8_1(8, 1, " ")] #[case::center_8_2(8, 2, " c")] #[case::center_8_3(8, 3, "🦀c")]
1385 #[case::center_8_4(8, 4, "🦀cd")]
1386 #[case::center_8_5(8, 5, "b🦀cd")]
1387 #[case::center_9_0(9, 0, "")]
1388 #[case::center_9_1(9, 1, "c")]
1389 #[case::center_9_2(9, 2, " c")] #[case::center_9_3(9, 3, " cd")]
1391 #[case::center_9_4(9, 4, "🦀cd")]
1392 #[case::center_9_5(9, 5, "🦀cde")]
1393 #[case::center_9_6(9, 6, "b🦀cde")]
1394 fn render_truncates_emoji_center(
1395 #[case] line_width: u16,
1396 #[case] buf_width: u16,
1397 #[case] expected: &str,
1398 ) {
1399 let value = match line_width {
1404 6 => "ab🦀cd",
1405 7 => "ab🦀cde",
1406 8 => "ab🦀cdef",
1407 9 => "ab🦀cdefg",
1408 _ => unreachable!(),
1409 };
1410 let line = Line::from(value).centered();
1411 let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1412 line.render_ref(buf.area, &mut buf);
1413 assert_eq!(buf, Buffer::with_lines([expected]));
1414 }
1415
1416 #[rstest]
1422 #[case::left(Alignment::Left, "XXa🦀bcXXX")]
1423 #[case::center(Alignment::Center, "XX🦀bc🦀XX")]
1424 #[case::right(Alignment::Right, "XXXbc🦀dXX")]
1425 fn render_truncates_away_from_0x0(#[case] alignment: Alignment, #[case] expected: &str) {
1426 let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).alignment(alignment);
1427 let mut buf = Buffer::filled(Rect::new(0, 0, 10, 1), Cell::new("X"));
1429 let area = Rect::new(2, 0, 6, 1);
1430 line.render_ref(area, &mut buf);
1431 assert_eq!(buf, Buffer::with_lines([expected]));
1432 }
1433
1434 #[rstest]
1438 #[case::right_4(4, "c🦀d")]
1439 #[case::right_5(5, "bc🦀d")]
1440 #[case::right_6(6, "Xbc🦀d")]
1441 #[case::right_7(7, "🦀bc🦀d")]
1442 #[case::right_8(8, "a🦀bc🦀d")]
1443 fn render_right_aligned_multi_span(#[case] buf_width: u16, #[case] expected: &str) {
1444 let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).right_aligned();
1445 let area = Rect::new(0, 0, buf_width, 1);
1446 let mut buf = Buffer::filled(area, Cell::new("X"));
1448 line.render_ref(buf.area, &mut buf);
1449 assert_eq!(buf, Buffer::with_lines([expected]));
1450 }
1451
1452 #[test]
1458 fn flag_emoji() {
1459 let str = "🇺🇸1234";
1460 assert_eq!(str.len(), 12); assert_eq!(str.chars().count(), 6); assert_eq!(str.graphemes(true).count(), 5); assert_eq!(str.width(), 6); }
1465
1466 #[rstest]
1469 #[case::flag_1(1, " ")]
1470 #[case::flag_2(2, "🇺🇸")]
1471 #[case::flag_3(3, "🇺🇸1")]
1472 #[case::flag_4(4, "🇺🇸12")]
1473 #[case::flag_5(5, "🇺🇸123")]
1474 #[case::flag_6(6, "🇺🇸1234")]
1475 #[case::flag_7(7, "🇺🇸1234 ")]
1476 fn render_truncates_flag(#[case] buf_width: u16, #[case] expected: &str) {
1477 let line = Line::from("🇺🇸1234");
1478 let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1479 line.render_ref(buf.area, &mut buf);
1480 assert_eq!(buf, Buffer::with_lines([expected]));
1481 }
1482
1483 #[rstest]
1485 #[case::left(Alignment::Left, "This is some content with a some")]
1486 #[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
1487 fn render_truncates_very_long_line_of_many_spans(
1488 #[case] alignment: Alignment,
1489 #[case] expected: &str,
1490 ) {
1491 let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
1492 let min_width = usize::from(u16::MAX).saturating_add(1);
1493
1494 let factor = min_width.div_ceil(part.len());
1496
1497 let line = Line::from(vec![Span::raw(part); factor]).alignment(alignment);
1498
1499 dbg!(line.width());
1500 assert!(line.width() >= min_width);
1501
1502 let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
1503 line.render_ref(buf.area, &mut buf);
1504 assert_eq!(buf, Buffer::with_lines([expected]));
1505 }
1506
1507 #[rstest]
1509 #[case::left(Alignment::Left, "This is some content with a some")]
1510 #[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
1511 fn render_truncates_very_long_single_span_line(
1512 #[case] alignment: Alignment,
1513 #[case] expected: &str,
1514 ) {
1515 let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
1516 let min_width = usize::from(u16::MAX).saturating_add(1);
1517
1518 let factor = min_width.div_ceil(part.len());
1520
1521 let line = Line::from(vec![Span::raw(part.repeat(factor))]).alignment(alignment);
1522
1523 dbg!(line.width());
1524 assert!(line.width() >= min_width);
1525
1526 let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
1527 line.render_ref(buf.area, &mut buf);
1528 assert_eq!(buf, Buffer::with_lines([expected]));
1529 }
1530
1531 #[test]
1532 fn render_with_newlines() {
1533 let mut buf = Buffer::empty(Rect::new(0, 0, 11, 1));
1534 Line::from("Hello\nworld!").render(Rect::new(0, 0, 11, 1), &mut buf);
1535 assert_eq!(buf, Buffer::with_lines(["Helloworld!"]));
1536 }
1537 }
1538
1539 mod iterators {
1540 use super::*;
1541
1542 #[fixture]
1544 fn hello_world() -> Line<'static> {
1545 Line::from(vec![
1546 Span::styled("Hello ", Color::Blue),
1547 Span::styled("world!", Color::Green),
1548 ])
1549 }
1550
1551 #[rstest]
1552 fn iter(hello_world: Line<'_>) {
1553 let mut iter = hello_world.iter();
1554 assert_eq!(iter.next(), Some(&Span::styled("Hello ", Color::Blue)));
1555 assert_eq!(iter.next(), Some(&Span::styled("world!", Color::Green)));
1556 assert_eq!(iter.next(), None);
1557 }
1558
1559 #[rstest]
1560 fn iter_mut(mut hello_world: Line<'_>) {
1561 let mut iter = hello_world.iter_mut();
1562 assert_eq!(iter.next(), Some(&mut Span::styled("Hello ", Color::Blue)));
1563 assert_eq!(iter.next(), Some(&mut Span::styled("world!", Color::Green)));
1564 assert_eq!(iter.next(), None);
1565 }
1566
1567 #[rstest]
1568 fn into_iter(hello_world: Line<'_>) {
1569 let mut iter = hello_world.into_iter();
1570 assert_eq!(iter.next(), Some(Span::styled("Hello ", Color::Blue)));
1571 assert_eq!(iter.next(), Some(Span::styled("world!", Color::Green)));
1572 assert_eq!(iter.next(), None);
1573 }
1574
1575 #[rstest]
1576 fn into_iter_ref(hello_world: Line<'_>) {
1577 let mut iter = (&hello_world).into_iter();
1578 assert_eq!(iter.next(), Some(&Span::styled("Hello ", Color::Blue)));
1579 assert_eq!(iter.next(), Some(&Span::styled("world!", Color::Green)));
1580 assert_eq!(iter.next(), None);
1581 }
1582
1583 #[test]
1584 fn into_iter_mut_ref() {
1585 let mut hello_world = Line::from(vec![
1586 Span::styled("Hello ", Color::Blue),
1587 Span::styled("world!", Color::Green),
1588 ]);
1589 let mut iter = (&mut hello_world).into_iter();
1590 assert_eq!(iter.next(), Some(&mut Span::styled("Hello ", Color::Blue)));
1591 assert_eq!(iter.next(), Some(&mut Span::styled("world!", Color::Green)));
1592 assert_eq!(iter.next(), None);
1593 }
1594
1595 #[rstest]
1596 fn for_loop_ref(hello_world: Line<'_>) {
1597 let mut result = String::new();
1598 for span in &hello_world {
1599 result.push_str(span.content.as_ref());
1600 }
1601 assert_eq!(result, "Hello world!");
1602 }
1603
1604 #[rstest]
1605 fn for_loop_mut_ref() {
1606 let mut hello_world = Line::from(vec![
1607 Span::styled("Hello ", Color::Blue),
1608 Span::styled("world!", Color::Green),
1609 ]);
1610 let mut result = String::new();
1611 for span in &mut hello_world {
1612 result.push_str(span.content.as_ref());
1613 }
1614 assert_eq!(result, "Hello world!");
1615 }
1616
1617 #[rstest]
1618 fn for_loop_into(hello_world: Line<'_>) {
1619 let mut result = String::new();
1620 for span in hello_world {
1621 result.push_str(span.content.as_ref());
1622 }
1623 assert_eq!(result, "Hello world!");
1624 }
1625 }
1626
1627 #[rstest]
1628 #[case::empty(Line::default(), "Line::default()")]
1629 #[case::raw(Line::raw("Hello, world!"), r#"Line::from("Hello, world!")"#)]
1630 #[case::styled(
1631 Line::styled("Hello, world!", Color::Yellow),
1632 r#"Line::from("Hello, world!").yellow()"#
1633 )]
1634 #[case::styled_complex(
1635 Line::from(String::from("Hello, world!")).green().on_blue().bold().italic().not_dim(),
1636 r#"Line::from("Hello, world!").green().on_blue().bold().italic().not_dim()"#
1637 )]
1638 #[case::styled_span(
1639 Line::from(Span::styled("Hello, world!", Color::Yellow)),
1640 r#"Line::from(Span::from("Hello, world!").yellow())"#
1641 )]
1642 #[case::styled_line_and_span(
1643 Line::from(vec![
1644 Span::styled("Hello", Color::Yellow),
1645 Span::styled(" world!", Color::Green),
1646 ]).italic(),
1647 r#"Line::from_iter([Span::from("Hello").yellow(), Span::from(" world!").green()]).italic()"#
1648 )]
1649 #[case::spans_vec(
1650 Line::from(vec![
1651 Span::styled("Hello", Color::Blue),
1652 Span::styled(" world!", Color::Green),
1653 ]),
1654 r#"Line::from_iter([Span::from("Hello").blue(), Span::from(" world!").green()])"#,
1655 )]
1656 #[case::left_aligned(
1657 Line::from("Hello, world!").left_aligned(),
1658 r#"Line::from("Hello, world!").left_aligned()"#
1659 )]
1660 #[case::centered(
1661 Line::from("Hello, world!").centered(),
1662 r#"Line::from("Hello, world!").centered()"#
1663 )]
1664 #[case::right_aligned(
1665 Line::from("Hello, world!").right_aligned(),
1666 r#"Line::from("Hello, world!").right_aligned()"#
1667 )]
1668 fn debug(#[case] line: Line, #[case] expected: &str) {
1669 assert_eq!(format!("{line:?}"), expected);
1670 }
1671}