1#![forbid(unsafe_code)]
2
3use crate::grapheme_width;
30use ftui_style::Style;
31use smallvec::SmallVec;
32use std::borrow::Cow;
33use unicode_segmentation::UnicodeSegmentation;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum ControlCode {
41 CarriageReturn,
43 LineFeed,
45 Bell,
47 Backspace,
49 Tab,
51 Home,
53 ClearToEndOfLine,
55 ClearLine,
57}
58
59impl ControlCode {
60 #[inline]
62 #[must_use]
63 pub const fn is_newline(&self) -> bool {
64 matches!(self, Self::LineFeed)
65 }
66
67 #[inline]
69 #[must_use]
70 pub const fn is_cr(&self) -> bool {
71 matches!(self, Self::CarriageReturn)
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct Segment<'a> {
85 pub text: Cow<'a, str>,
87 pub style: Option<Style>,
89 pub control: Option<SmallVec<[ControlCode; 2]>>,
91}
92
93impl<'a> Segment<'a> {
94 #[inline]
96 #[must_use]
97 pub fn text(s: impl Into<Cow<'a, str>>) -> Self {
98 Self {
99 text: s.into(),
100 style: None,
101 control: None,
102 }
103 }
104
105 #[inline]
107 #[must_use]
108 pub fn styled(s: impl Into<Cow<'a, str>>, style: Style) -> Self {
109 Self {
110 text: s.into(),
111 style: Some(style),
112 control: None,
113 }
114 }
115
116 #[inline]
118 #[must_use]
119 pub fn control(code: ControlCode) -> Self {
120 let mut codes = SmallVec::new();
121 codes.push(code);
122 Self {
123 text: Cow::Borrowed(""),
124 style: None,
125 control: Some(codes),
126 }
127 }
128
129 #[inline]
131 #[must_use]
132 pub fn newline() -> Self {
133 Self::control(ControlCode::LineFeed)
134 }
135
136 #[inline]
138 #[must_use]
139 pub const fn empty() -> Self {
140 Self {
141 text: Cow::Borrowed(""),
142 style: None,
143 control: None,
144 }
145 }
146
147 #[inline]
149 #[must_use]
150 pub fn as_str(&self) -> &str {
151 &self.text
152 }
153
154 #[inline]
156 #[must_use]
157 pub fn is_empty(&self) -> bool {
158 self.text.is_empty() && self.control.is_none()
159 }
160
161 #[inline]
163 #[must_use]
164 pub fn has_text(&self) -> bool {
165 !self.text.is_empty()
166 }
167
168 #[inline]
170 #[must_use]
171 pub fn is_control(&self) -> bool {
172 self.control.is_some() && self.text.is_empty()
173 }
174
175 #[inline]
177 #[must_use]
178 pub fn is_newline(&self) -> bool {
179 self.control
180 .as_ref()
181 .is_some_and(|codes| codes.iter().any(|c| c.is_newline()))
182 }
183
184 #[inline]
189 #[must_use]
190 pub fn cell_length(&self) -> usize {
191 if self.is_control() {
192 return 0;
193 }
194 crate::display_width(&self.text)
195 }
196
197 #[inline]
202 #[must_use]
203 pub fn cell_length_with<F>(&self, width_fn: F) -> usize
204 where
205 F: Fn(&str) -> usize,
206 {
207 if self.is_control() {
208 return 0;
209 }
210 width_fn(&self.text)
211 }
212
213 #[must_use]
226 pub fn split_at_cell(&self, cell_pos: usize) -> (Self, Self) {
227 if self.is_control() {
229 if cell_pos == 0 {
230 return (Self::empty(), self.clone());
231 }
232 return (self.clone(), Self::empty());
233 }
234
235 if self.text.is_empty() || cell_pos == 0 {
237 return (
238 Self {
239 text: Cow::Borrowed(""),
240 style: self.style,
241 control: None,
242 },
243 self.clone(),
244 );
245 }
246
247 let total_width = self.cell_length();
248 if cell_pos >= total_width {
249 return (
250 self.clone(),
251 Self {
252 text: Cow::Borrowed(""),
253 style: self.style,
254 control: None,
255 },
256 );
257 }
258
259 let (byte_pos, _actual_width) = find_cell_boundary(&self.text, cell_pos);
261
262 let left_text = &self.text[..byte_pos];
263 let right_text = &self.text[byte_pos..];
264
265 (
266 Self {
267 text: Cow::Owned(left_text.to_string()),
268 style: self.style,
269 control: None,
270 },
271 Self {
272 text: Cow::Owned(right_text.to_string()),
273 style: self.style,
274 control: None,
275 },
276 )
277 }
278
279 #[inline]
281 #[must_use]
282 pub fn with_style(mut self, style: Style) -> Self {
283 self.style = Some(style);
284 self
285 }
286
287 #[must_use]
289 pub fn into_owned(self) -> Segment<'static> {
290 Segment {
291 text: Cow::Owned(self.text.into_owned()),
292 style: self.style,
293 control: self.control,
294 }
295 }
296
297 #[must_use]
299 pub fn with_control(mut self, code: ControlCode) -> Self {
300 if let Some(ref mut codes) = self.control {
301 codes.push(code);
302 } else {
303 let mut codes = SmallVec::new();
304 codes.push(code);
305 self.control = Some(codes);
306 }
307 self
308 }
309}
310
311impl<'a> Default for Segment<'a> {
312 fn default() -> Self {
313 Self::empty()
314 }
315}
316
317impl<'a> From<&'a str> for Segment<'a> {
318 fn from(s: &'a str) -> Self {
319 Self::text(s)
320 }
321}
322
323impl From<String> for Segment<'static> {
324 fn from(s: String) -> Self {
325 Self::text(s)
326 }
327}
328
329pub fn find_cell_boundary(text: &str, target_cells: usize) -> (usize, usize) {
335 let mut current_cells = 0;
336 let mut byte_pos = 0;
337
338 for grapheme in text.graphemes(true) {
339 let grapheme_width = grapheme_width(grapheme);
340
341 if current_cells + grapheme_width > target_cells {
343 break;
345 }
346
347 current_cells += grapheme_width;
348 byte_pos += grapheme.len();
349
350 if current_cells >= target_cells {
351 break;
352 }
353 }
354
355 (byte_pos, current_cells)
356}
357
358#[derive(Debug, Clone, Default, PartialEq, Eq)]
362pub struct SegmentLine<'a> {
363 segments: Vec<Segment<'a>>,
364}
365
366impl<'a> SegmentLine<'a> {
367 #[inline]
369 #[must_use]
370 pub const fn new() -> Self {
371 Self {
372 segments: Vec::new(),
373 }
374 }
375
376 #[inline]
378 #[must_use]
379 pub fn from_segments(segments: Vec<Segment<'a>>) -> Self {
380 Self { segments }
381 }
382
383 #[inline]
385 #[must_use]
386 pub fn from_segment(segment: Segment<'a>) -> Self {
387 Self {
388 segments: vec![segment],
389 }
390 }
391
392 #[inline]
394 #[must_use]
395 pub fn is_empty(&self) -> bool {
396 self.segments.is_empty() || self.segments.iter().all(|s| s.is_empty())
397 }
398
399 #[inline]
401 #[must_use]
402 pub fn len(&self) -> usize {
403 self.segments.len()
404 }
405
406 #[must_use]
408 pub fn cell_length(&self) -> usize {
409 self.segments.iter().map(|s| s.cell_length()).sum()
410 }
411
412 #[inline]
414 pub fn push(&mut self, segment: Segment<'a>) {
415 self.segments.push(segment);
416 }
417
418 #[inline]
420 #[must_use]
421 pub fn segments(&self) -> &[Segment<'a>] {
422 &self.segments
423 }
424
425 #[inline]
427 pub fn segments_mut(&mut self) -> &mut Vec<Segment<'a>> {
428 &mut self.segments
429 }
430
431 #[inline]
433 pub fn iter(&self) -> impl Iterator<Item = &Segment<'a>> {
434 self.segments.iter()
435 }
436
437 #[must_use]
441 pub fn split_at_cell(&self, cell_pos: usize) -> (Self, Self) {
442 if cell_pos == 0 {
443 return (Self::new(), self.clone());
444 }
445
446 let total_width = self.cell_length();
447 if cell_pos >= total_width {
448 return (self.clone(), Self::new());
449 }
450
451 let mut left_segments = Vec::new();
452 let mut right_segments = Vec::new();
453 let mut consumed = 0;
454 let mut found_split = false;
455
456 for segment in &self.segments {
457 if found_split {
458 right_segments.push(segment.clone());
459 continue;
460 }
461
462 let seg_width = segment.cell_length();
463 if consumed + seg_width <= cell_pos {
464 left_segments.push(segment.clone());
466 consumed += seg_width;
467 } else if consumed >= cell_pos {
468 right_segments.push(segment.clone());
470 found_split = true;
471 } else {
472 let split_at = cell_pos - consumed;
474 let (left, right) = segment.split_at_cell(split_at);
475 if left.has_text() {
476 left_segments.push(left);
477 }
478 if right.has_text() {
479 right_segments.push(right);
480 }
481 found_split = true;
482 }
483 }
484
485 (
486 Self::from_segments(left_segments),
487 Self::from_segments(right_segments),
488 )
489 }
490
491 #[must_use]
493 pub fn to_plain_text(&self) -> String {
494 self.segments.iter().map(|s| s.as_str()).collect()
495 }
496
497 #[must_use]
499 pub fn into_owned(self) -> SegmentLine<'static> {
500 SegmentLine {
501 segments: self.segments.into_iter().map(|s| s.into_owned()).collect(),
502 }
503 }
504}
505
506impl<'a> IntoIterator for SegmentLine<'a> {
507 type Item = Segment<'a>;
508 type IntoIter = std::vec::IntoIter<Segment<'a>>;
509
510 fn into_iter(self) -> Self::IntoIter {
511 self.segments.into_iter()
512 }
513}
514
515impl<'a, 'b> IntoIterator for &'b SegmentLine<'a> {
516 type Item = &'b Segment<'a>;
517 type IntoIter = std::slice::Iter<'b, Segment<'a>>;
518
519 fn into_iter(self) -> Self::IntoIter {
520 self.segments.iter()
521 }
522}
523
524#[derive(Debug, Clone, Default, PartialEq, Eq)]
526pub struct SegmentLines<'a> {
527 lines: Vec<SegmentLine<'a>>,
528}
529
530impl<'a> SegmentLines<'a> {
531 #[inline]
533 #[must_use]
534 pub const fn new() -> Self {
535 Self { lines: Vec::new() }
536 }
537
538 #[inline]
540 #[must_use]
541 pub fn from_lines(lines: Vec<SegmentLine<'a>>) -> Self {
542 Self { lines }
543 }
544
545 #[inline]
547 #[must_use]
548 pub fn is_empty(&self) -> bool {
549 self.lines.is_empty()
550 }
551
552 #[inline]
554 #[must_use]
555 pub fn len(&self) -> usize {
556 self.lines.len()
557 }
558
559 #[inline]
561 pub fn push(&mut self, line: SegmentLine<'a>) {
562 self.lines.push(line);
563 }
564
565 #[inline]
567 #[must_use]
568 pub fn lines(&self) -> &[SegmentLine<'a>] {
569 &self.lines
570 }
571
572 #[inline]
574 pub fn iter(&self) -> impl Iterator<Item = &SegmentLine<'a>> {
575 self.lines.iter()
576 }
577
578 #[must_use]
580 pub fn max_width(&self) -> usize {
581 self.lines
582 .iter()
583 .map(|l| l.cell_length())
584 .max()
585 .unwrap_or(0)
586 }
587
588 #[must_use]
590 pub fn into_owned(self) -> SegmentLines<'static> {
591 SegmentLines {
592 lines: self.lines.into_iter().map(|l| l.into_owned()).collect(),
593 }
594 }
595}
596
597impl<'a> IntoIterator for SegmentLines<'a> {
598 type Item = SegmentLine<'a>;
599 type IntoIter = std::vec::IntoIter<SegmentLine<'a>>;
600
601 fn into_iter(self) -> Self::IntoIter {
602 self.lines.into_iter()
603 }
604}
605
606#[must_use]
614pub fn split_into_lines<'a>(segments: impl IntoIterator<Item = Segment<'a>>) -> SegmentLines<'a> {
615 let mut lines = SegmentLines::new();
616 let mut current_line = SegmentLine::new();
617 let mut has_content = false;
618
619 for segment in segments {
620 has_content = true;
621 if segment.is_newline() {
622 lines.push(std::mem::take(&mut current_line));
623 } else if segment.has_text() {
624 let text = segment.as_str();
626 if text.contains('\n') {
627 let parts: Vec<&str> = text.split('\n').collect();
629 for (i, part) in parts.iter().enumerate() {
630 if !part.is_empty() {
631 current_line.push(Segment {
632 text: Cow::Owned((*part).to_string()),
633 style: segment.style,
634 control: None,
635 });
636 }
637 if i < parts.len() - 1 {
639 lines.push(std::mem::take(&mut current_line));
640 }
641 }
642 } else {
643 current_line.push(segment);
644 }
645 } else if !segment.is_empty() {
646 current_line.push(segment);
647 }
648 }
649
650 if has_content || lines.is_empty() {
653 lines.push(current_line);
654 }
655
656 lines
657}
658
659pub fn join_lines<'a>(lines: &SegmentLines<'a>) -> Vec<Segment<'a>> {
661 let mut result = Vec::new();
662 let line_count = lines.len();
663
664 for (i, line) in lines.iter().enumerate() {
665 for segment in line.iter() {
666 result.push(segment.clone());
667 }
668 if i < line_count - 1 {
669 result.push(Segment::newline());
670 }
671 }
672
673 result
674}
675
676#[cfg(test)]
677mod tests {
678 use super::*;
679
680 #[test]
685 fn segment_text_creates_unstyled_segment() {
686 let seg = Segment::text("hello");
687 assert_eq!(seg.as_str(), "hello");
688 assert!(seg.style.is_none());
689 assert!(seg.control.is_none());
690 }
691
692 #[test]
693 fn segment_styled_creates_styled_segment() {
694 let style = Style::new().bold();
695 let seg = Segment::styled("hello", style);
696 assert_eq!(seg.as_str(), "hello");
697 assert_eq!(seg.style, Some(style));
698 }
699
700 #[test]
701 fn segment_control_creates_control_segment() {
702 let seg = Segment::control(ControlCode::LineFeed);
703 assert!(seg.is_control());
704 assert!(seg.is_newline());
705 assert_eq!(seg.cell_length(), 0);
706 }
707
708 #[test]
709 fn segment_empty_is_empty() {
710 let seg = Segment::empty();
711 assert!(seg.is_empty());
712 assert!(!seg.has_text());
713 assert_eq!(seg.cell_length(), 0);
714 }
715
716 #[test]
721 fn cell_length_ascii() {
722 let seg = Segment::text("hello");
723 assert_eq!(seg.cell_length(), 5);
724 }
725
726 #[test]
727 fn cell_length_cjk() {
728 let seg = Segment::text("你好");
730 assert_eq!(seg.cell_length(), 4); }
732
733 #[test]
734 fn cell_length_mixed() {
735 let seg = Segment::text("hi你好");
736 assert_eq!(seg.cell_length(), 6); }
738
739 #[test]
740 fn cell_length_emoji() {
741 let seg = Segment::text("😀");
743 assert!(seg.cell_length() >= 1);
745 }
746
747 #[test]
748 fn cell_length_zwj_sequence() {
749 let seg = Segment::text("👨👩👧");
751 let _width = seg.cell_length();
753 }
754
755 #[test]
756 fn cell_length_control_is_zero() {
757 let seg = Segment::control(ControlCode::Bell);
758 assert_eq!(seg.cell_length(), 0);
759 }
760
761 #[test]
766 fn split_at_cell_ascii() {
767 let seg = Segment::text("hello world");
768 let (left, right) = seg.split_at_cell(5);
769 assert_eq!(left.as_str(), "hello");
770 assert_eq!(right.as_str(), " world");
771 }
772
773 #[test]
774 fn split_at_cell_zero() {
775 let seg = Segment::text("hello");
776 let (left, right) = seg.split_at_cell(0);
777 assert_eq!(left.as_str(), "");
778 assert_eq!(right.as_str(), "hello");
779 }
780
781 #[test]
782 fn split_at_cell_beyond_length() {
783 let seg = Segment::text("hi");
784 let (left, right) = seg.split_at_cell(10);
785 assert_eq!(left.as_str(), "hi");
786 assert_eq!(right.as_str(), "");
787 }
788
789 #[test]
790 fn split_at_cell_cjk() {
791 let seg = Segment::text("你好世界");
793 let (left, right) = seg.split_at_cell(2);
794 assert_eq!(left.as_str(), "你");
795 assert_eq!(right.as_str(), "好世界");
796 }
797
798 #[test]
799 fn split_at_cell_cjk_mid_char() {
800 let seg = Segment::text("你好");
803 let (left, right) = seg.split_at_cell(1);
804 assert_eq!(left.as_str(), "");
806 assert_eq!(right.as_str(), "你好");
807 }
808
809 #[test]
810 fn split_at_cell_mixed() {
811 let seg = Segment::text("hi你");
812 let (left, right) = seg.split_at_cell(2);
813 assert_eq!(left.as_str(), "hi");
814 assert_eq!(right.as_str(), "你");
815 }
816
817 #[test]
818 fn split_at_cell_preserves_style() {
819 let style = Style::new().bold();
820 let seg = Segment::styled("hello", style);
821 let (left, right) = seg.split_at_cell(2);
822 assert_eq!(left.style, Some(style));
823 assert_eq!(right.style, Some(style));
824 }
825
826 #[test]
827 fn split_at_cell_control_segment() {
828 let seg = Segment::control(ControlCode::LineFeed);
829 let (left, right) = seg.split_at_cell(0);
830 assert!(left.is_empty());
831 assert!(right.is_control());
832 }
833
834 #[test]
839 fn segment_line_cell_length() {
840 let mut line = SegmentLine::new();
841 line.push(Segment::text("hello "));
842 line.push(Segment::text("world"));
843 assert_eq!(line.cell_length(), 11);
844 }
845
846 #[test]
847 fn segment_line_split_at_cell() {
848 let mut line = SegmentLine::new();
849 line.push(Segment::text("hello "));
850 line.push(Segment::text("world"));
851
852 let (left, right) = line.split_at_cell(8);
853 assert_eq!(left.to_plain_text(), "hello wo");
854 assert_eq!(right.to_plain_text(), "rld");
855 }
856
857 #[test]
858 fn segment_line_split_at_segment_boundary() {
859 let mut line = SegmentLine::new();
860 line.push(Segment::text("hello"));
861 line.push(Segment::text(" world"));
862
863 let (left, right) = line.split_at_cell(5);
864 assert_eq!(left.to_plain_text(), "hello");
865 assert_eq!(right.to_plain_text(), " world");
866 }
867
868 #[test]
873 fn split_into_lines_single_line() {
874 let segments = vec![Segment::text("hello world")];
875 let lines = split_into_lines(segments);
876 assert_eq!(lines.len(), 1);
877 assert_eq!(lines.lines()[0].to_plain_text(), "hello world");
878 }
879
880 #[test]
881 fn split_into_lines_with_newline_control() {
882 let segments = vec![
883 Segment::text("line one"),
884 Segment::newline(),
885 Segment::text("line two"),
886 ];
887 let lines = split_into_lines(segments);
888 assert_eq!(lines.len(), 2);
889 assert_eq!(lines.lines()[0].to_plain_text(), "line one");
890 assert_eq!(lines.lines()[1].to_plain_text(), "line two");
891 }
892
893 #[test]
894 fn split_into_lines_with_embedded_newline() {
895 let segments = vec![Segment::text("line one\nline two")];
896 let lines = split_into_lines(segments);
897 assert_eq!(lines.len(), 2);
898 assert_eq!(lines.lines()[0].to_plain_text(), "line one");
899 assert_eq!(lines.lines()[1].to_plain_text(), "line two");
900 }
901
902 #[test]
903 fn split_into_lines_empty_input() {
904 let segments: Vec<Segment> = vec![];
905 let lines = split_into_lines(segments);
906 assert_eq!(lines.len(), 1); assert!(lines.lines()[0].is_empty());
908 }
909
910 #[test]
915 fn join_lines_roundtrip() {
916 let segments = vec![
917 Segment::text("line one"),
918 Segment::newline(),
919 Segment::text("line two"),
920 ];
921 let lines = split_into_lines(segments);
922 let joined = join_lines(&lines);
923
924 assert_eq!(joined.len(), 3);
926 assert_eq!(joined[0].as_str(), "line one");
927 assert!(joined[1].is_newline());
928 assert_eq!(joined[2].as_str(), "line two");
929 }
930
931 #[test]
936 fn segment_into_owned() {
937 let s = String::from("hello");
938 let seg: Segment = Segment::text(&s[..]);
939 let owned: Segment<'static> = seg.into_owned();
940 assert_eq!(owned.as_str(), "hello");
941 }
942
943 #[test]
944 fn segment_from_string() {
945 let seg: Segment<'static> = Segment::from(String::from("hello"));
946 assert_eq!(seg.as_str(), "hello");
947 }
948
949 #[test]
950 fn segment_from_str() {
951 let seg: Segment = Segment::from("hello");
952 assert_eq!(seg.as_str(), "hello");
953 }
954
955 #[test]
960 fn control_code_is_newline() {
961 assert!(ControlCode::LineFeed.is_newline());
962 assert!(!ControlCode::CarriageReturn.is_newline());
963 assert!(!ControlCode::Bell.is_newline());
964 }
965
966 #[test]
967 fn control_code_is_cr() {
968 assert!(ControlCode::CarriageReturn.is_cr());
969 assert!(!ControlCode::LineFeed.is_cr());
970 }
971
972 #[test]
973 fn segment_with_control() {
974 let seg = Segment::text("hello").with_control(ControlCode::Bell);
975 assert!(seg.control.is_some());
976 assert_eq!(seg.control.as_ref().unwrap().len(), 1);
977 }
978
979 #[test]
984 fn split_empty_segment() {
985 let seg = Segment::text("");
986 let (left, right) = seg.split_at_cell(5);
987 assert_eq!(left.as_str(), "");
988 assert_eq!(right.as_str(), "");
989 }
990
991 #[test]
992 fn combining_characters() {
993 let seg = Segment::text("e\u{0301}"); let width = seg.cell_length();
997 assert!(width >= 1);
998
999 let (left, right) = seg.split_at_cell(1);
1001 assert_eq!(left.cell_length() + right.cell_length(), width);
1002 }
1003
1004 #[test]
1005 fn segment_line_is_empty() {
1006 let line = SegmentLine::new();
1007 assert!(line.is_empty());
1008
1009 let mut line2 = SegmentLine::new();
1010 line2.push(Segment::empty());
1011 assert!(line2.is_empty());
1012
1013 let mut line3 = SegmentLine::new();
1014 line3.push(Segment::text("x"));
1015 assert!(!line3.is_empty());
1016 }
1017
1018 #[test]
1023 fn cow_borrowed_from_static_str() {
1024 let seg = Segment::text("static string");
1025 assert!(matches!(seg.text, Cow::Borrowed(_)));
1027 }
1028
1029 #[test]
1030 fn cow_owned_from_string() {
1031 let owned = String::from("owned string");
1032 let seg = Segment::text(owned);
1033 assert!(matches!(seg.text, Cow::Owned(_)));
1035 }
1036
1037 #[test]
1038 fn cow_borrowed_reference() {
1039 let s = String::from("reference");
1040 let seg = Segment::text(&s[..]);
1041 assert!(matches!(seg.text, Cow::Borrowed(_)));
1043 }
1044
1045 #[test]
1046 fn into_owned_converts_borrowed_to_owned() {
1047 let seg = Segment::text("borrowed");
1048 assert!(matches!(seg.text, Cow::Borrowed(_)));
1049
1050 let owned = seg.into_owned();
1051 assert!(matches!(owned.text, Cow::Owned(_)));
1053 assert_eq!(owned.as_str(), "borrowed");
1054 }
1055
1056 #[test]
1057 fn clone_borrowed_segment_stays_borrowed() {
1058 let seg = Segment::text("static");
1059 let cloned = seg.clone();
1060 assert!(matches!(cloned.text, Cow::Borrowed(_)));
1062 }
1063
1064 #[test]
1065 fn clone_owned_segment_allocates() {
1066 let owned = String::from("owned");
1067 let seg = Segment::text(owned);
1068 let cloned = seg.clone();
1069 assert!(matches!(cloned.text, Cow::Owned(_)));
1071 assert_eq!(cloned.as_str(), "owned");
1072 }
1073
1074 #[test]
1079 fn segment_default_is_empty() {
1080 let seg = Segment::default();
1081 assert!(seg.is_empty());
1082 assert_eq!(seg.as_str(), "");
1083 assert!(seg.style.is_none());
1084 assert!(seg.control.is_none());
1085 }
1086
1087 #[test]
1088 fn segment_line_default_is_empty() {
1089 let line = SegmentLine::default();
1090 assert!(line.is_empty());
1091 assert_eq!(line.len(), 0);
1092 }
1093
1094 #[test]
1095 fn segment_lines_default_is_empty() {
1096 let lines = SegmentLines::default();
1097 assert!(lines.is_empty());
1098 assert_eq!(lines.len(), 0);
1099 }
1100
1101 #[test]
1106 fn segment_debug_impl() {
1107 let seg = Segment::text("hello");
1108 let debug = format!("{:?}", seg);
1109 assert!(debug.contains("Segment"));
1110 assert!(debug.contains("hello"));
1111 }
1112
1113 #[test]
1114 fn control_code_debug_impl() {
1115 let code = ControlCode::LineFeed;
1116 let debug = format!("{:?}", code);
1117 assert!(debug.contains("LineFeed"));
1118 }
1119
1120 #[test]
1121 fn segment_line_debug_impl() {
1122 let line = SegmentLine::from_segment(Segment::text("test"));
1123 let debug = format!("{:?}", line);
1124 assert!(debug.contains("SegmentLine"));
1125 }
1126
1127 #[test]
1132 fn segment_line_into_owned() {
1133 let s = String::from("test");
1134 let mut line = SegmentLine::new();
1135 line.push(Segment::text(&s[..]));
1136
1137 let owned = line.into_owned();
1138 drop(s); assert_eq!(owned.to_plain_text(), "test");
1140 }
1141
1142 #[test]
1143 fn segment_line_segments_mut() {
1144 let mut line = SegmentLine::from_segment(Segment::text("hello"));
1145 line.segments_mut().push(Segment::text(" world"));
1146 assert_eq!(line.to_plain_text(), "hello world");
1147 }
1148
1149 #[test]
1150 fn segment_line_iter() {
1151 let line = SegmentLine::from_segments(vec![Segment::text("a"), Segment::text("b")]);
1152 let collected: Vec<_> = line.iter().collect();
1153 assert_eq!(collected.len(), 2);
1154 }
1155
1156 #[test]
1157 fn segment_line_into_iter_ref() {
1158 let line = SegmentLine::from_segments(vec![Segment::text("x"), Segment::text("y")]);
1159 let mut count = 0;
1160 for _seg in &line {
1161 count += 1;
1162 }
1163 assert_eq!(count, 2);
1164 }
1165
1166 #[test]
1171 fn segment_lines_into_owned() {
1172 let s = String::from("line one");
1173 let mut lines = SegmentLines::new();
1174 let mut line = SegmentLine::new();
1175 line.push(Segment::text(&s[..]));
1176 lines.push(line);
1177
1178 let owned = lines.into_owned();
1179 drop(s);
1180 assert_eq!(owned.lines()[0].to_plain_text(), "line one");
1181 }
1182
1183 #[test]
1184 fn segment_lines_iter() {
1185 let mut lines = SegmentLines::new();
1186 lines.push(SegmentLine::from_segment(Segment::text("a")));
1187 lines.push(SegmentLine::from_segment(Segment::text("b")));
1188
1189 let collected: Vec<_> = lines.iter().collect();
1190 assert_eq!(collected.len(), 2);
1191 }
1192
1193 #[test]
1194 fn segment_lines_max_width() {
1195 let mut lines = SegmentLines::new();
1196 lines.push(SegmentLine::from_segment(Segment::text("short")));
1197 lines.push(SegmentLine::from_segment(Segment::text("longer line here")));
1198 lines.push(SegmentLine::from_segment(Segment::text("med")));
1199
1200 assert_eq!(lines.max_width(), 16);
1201 }
1202
1203 #[test]
1204 fn segment_lines_max_width_empty() {
1205 let lines = SegmentLines::new();
1206 assert_eq!(lines.max_width(), 0);
1207 }
1208
1209 #[test]
1214 fn segment_with_style_applies_style() {
1215 let style = Style::new().bold();
1216 let seg = Segment::text("hello").with_style(style);
1217 assert_eq!(seg.style, Some(style));
1218 }
1219
1220 #[test]
1221 fn segment_with_multiple_controls() {
1222 let seg = Segment::text("x")
1223 .with_control(ControlCode::Bell)
1224 .with_control(ControlCode::Tab);
1225 let codes = seg.control.unwrap();
1226 assert_eq!(codes.len(), 2);
1227 }
1228
1229 #[test]
1234 fn cell_length_with_custom_width() {
1235 let seg = Segment::text("hello");
1236 let width = seg.cell_length_with(|s| s.len() * 2);
1238 assert_eq!(width, 10);
1239 }
1240
1241 #[test]
1242 fn cell_length_with_on_control_is_zero() {
1243 let seg = Segment::control(ControlCode::Bell);
1244 let width = seg.cell_length_with(|_| 100);
1245 assert_eq!(width, 0);
1246 }
1247
1248 #[test]
1253 fn control_code_equality() {
1254 assert_eq!(ControlCode::LineFeed, ControlCode::LineFeed);
1255 assert_ne!(ControlCode::LineFeed, ControlCode::CarriageReturn);
1256 }
1257
1258 #[test]
1259 fn control_code_hash_consistency() {
1260 use std::collections::HashSet;
1261 let mut set = HashSet::new();
1262 set.insert(ControlCode::LineFeed);
1263 set.insert(ControlCode::CarriageReturn);
1264 assert_eq!(set.len(), 2);
1265 assert!(set.contains(&ControlCode::LineFeed));
1266 }
1267
1268 #[test]
1273 fn split_at_cell_exact_boundary() {
1274 let seg = Segment::text("abcde");
1275 let (left, right) = seg.split_at_cell(5);
1276 assert_eq!(left.as_str(), "abcde");
1277 assert_eq!(right.as_str(), "");
1278 }
1279
1280 #[test]
1281 fn segment_line_split_at_zero() {
1282 let line = SegmentLine::from_segment(Segment::text("hello"));
1283 let (left, right) = line.split_at_cell(0);
1284 assert!(left.is_empty());
1285 assert_eq!(right.to_plain_text(), "hello");
1286 }
1287
1288 #[test]
1289 fn segment_line_split_at_end() {
1290 let line = SegmentLine::from_segment(Segment::text("hello"));
1291 let (left, right) = line.split_at_cell(100);
1292 assert_eq!(left.to_plain_text(), "hello");
1293 assert!(right.is_empty());
1294 }
1295
1296 #[test]
1297 fn join_lines_single_line() {
1298 let mut lines = SegmentLines::new();
1299 lines.push(SegmentLine::from_segment(Segment::text("only")));
1300 let joined = join_lines(&lines);
1301 assert_eq!(joined.len(), 1);
1302 assert_eq!(joined[0].as_str(), "only");
1303 }
1304
1305 #[test]
1306 fn join_lines_empty() {
1307 let lines = SegmentLines::new();
1308 let joined = join_lines(&lines);
1309 assert!(joined.is_empty());
1310 }
1311
1312 #[test]
1313 fn split_into_lines_multiple_newlines() {
1314 let segments = vec![
1315 Segment::text("a"),
1316 Segment::newline(),
1317 Segment::newline(),
1318 Segment::text("b"),
1319 ];
1320 let lines = split_into_lines(segments);
1321 assert_eq!(lines.len(), 3);
1322 assert_eq!(lines.lines()[0].to_plain_text(), "a");
1323 assert!(lines.lines()[1].is_empty());
1324 assert_eq!(lines.lines()[2].to_plain_text(), "b");
1325 }
1326
1327 #[test]
1328 fn split_into_lines_trailing_newline() {
1329 let segments = vec![Segment::text("hello"), Segment::newline()];
1330 let lines = split_into_lines(segments);
1331 assert_eq!(lines.len(), 2);
1332 assert_eq!(lines.lines()[0].to_plain_text(), "hello");
1333 assert!(lines.lines()[1].is_empty());
1334 }
1335}
1336
1337#[cfg(test)]
1338mod proptests {
1339 use super::*;
1340 use proptest::prelude::*;
1341
1342 proptest! {
1343 #[test]
1344 fn split_preserves_total_width(s in "[a-zA-Z0-9 ]{1,100}", pos in 0usize..200) {
1345 let seg = Segment::text(s);
1346 let total = seg.cell_length();
1347 let (left, right) = seg.split_at_cell(pos);
1348
1349 prop_assert_eq!(left.cell_length() + right.cell_length(), total);
1351 }
1352
1353 #[test]
1354 fn split_preserves_content(s in "[a-zA-Z0-9 ]{1,100}", pos in 0usize..200) {
1355 let seg = Segment::text(s.clone());
1356 let (left, right) = seg.split_at_cell(pos);
1357
1358 let combined = format!("{}{}", left.as_str(), right.as_str());
1360 prop_assert_eq!(combined, s);
1361 }
1362
1363 #[test]
1364 fn cell_length_matches_display_width(s in "[a-zA-Z0-9 ]{1,100}") {
1365 let seg = Segment::text(s.clone());
1366 let expected = crate::display_width(s.as_str());
1367 prop_assert_eq!(seg.cell_length(), expected);
1368 }
1369
1370 #[test]
1371 fn line_split_preserves_total_width(
1372 parts in prop::collection::vec("[a-z]{1,10}", 1..5),
1373 pos in 0usize..100
1374 ) {
1375 let mut line = SegmentLine::new();
1376 for part in &parts {
1377 line.push(Segment::text(part.as_str()));
1378 }
1379
1380 let total = line.cell_length();
1381 let (left, right) = line.split_at_cell(pos);
1382
1383 prop_assert_eq!(left.cell_length() + right.cell_length(), total);
1384 }
1385
1386 #[test]
1387 fn split_into_lines_preserves_content(s in "[a-zA-Z0-9 \n]{1,200}") {
1388 let segments = vec![Segment::text(s.clone())];
1389 let lines = split_into_lines(segments);
1390
1391 let mut result = String::new();
1393 for (i, line) in lines.lines().iter().enumerate() {
1394 if i > 0 {
1395 result.push('\n');
1396 }
1397 result.push_str(&line.to_plain_text());
1398 }
1399
1400 prop_assert_eq!(result, s);
1401 }
1402 }
1403}