Skip to main content

rich_rs/
segment.rs

1//! Segment: the atomic unit of terminal output.
2//!
3//! Everything in Rich ultimately becomes a sequence of Segments.
4
5use smallvec::SmallVec;
6use std::borrow::Cow;
7
8use crate::cells::{cell_len, char_width, set_cell_size};
9use crate::style::{Style, StyleMeta};
10use std::sync::Arc;
11
12/// Control codes that can be embedded in output.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ControlType {
15    /// Ring the terminal bell.
16    Bell,
17    /// Carriage return.
18    CarriageReturn,
19    /// Move cursor to home position.
20    Home,
21    /// Clear the screen.
22    Clear,
23    /// Show the cursor.
24    ShowCursor,
25    /// Hide the cursor.
26    HideCursor,
27    /// Enable alternate screen buffer.
28    EnableAltScreen,
29    /// Disable alternate screen buffer.
30    DisableAltScreen,
31    /// Set window title.
32    SetTitle,
33    /// Move cursor up N lines.
34    CursorUp(u16),
35    /// Move cursor down N lines.
36    CursorDown(u16),
37    /// Move cursor forward N columns.
38    CursorForward(u16),
39    /// Move cursor backward N columns.
40    CursorBackward(u16),
41    /// Erase in line (0=cursor to end, 1=start to cursor, 2=entire line).
42    EraseInLine(u8),
43    /// Start an OSC 8 hyperlink.
44    HyperlinkStart { url: Arc<str>, id: Option<Arc<str>> },
45    /// End an OSC 8 hyperlink.
46    HyperlinkEnd,
47    /// Move the cursor to an absolute position (x, y), 0-based.
48    MoveTo { x: u16, y: u16 },
49}
50
51/// A segment of text with optional style and control codes.
52///
53/// This is the fundamental unit of output in Rich. All renderables
54/// produce sequences of Segments.
55///
56/// Uses `Cow<'static, str>` for text to allow both owned and static strings
57/// without lifetime complexity in the API. This is a deliberate tradeoff
58/// favoring API simplicity over zero-copy for borrowed non-static input.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Segment {
61    /// The text content.
62    pub text: Cow<'static, str>,
63    /// Optional style to apply.
64    pub style: Option<Style>,
65    /// Optional style metadata (hyperlinks, Textual handlers, etc.).
66    pub meta: Option<StyleMeta>,
67    /// Optional control code (if set, text is typically empty).
68    pub control: Option<ControlType>,
69}
70
71impl Segment {
72    /// Create a new text segment.
73    pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
74        Segment {
75            text: text.into(),
76            style: None,
77            meta: None,
78            control: None,
79        }
80    }
81
82    /// Create a new styled segment.
83    pub fn styled(text: impl Into<Cow<'static, str>>, style: Style) -> Self {
84        Segment {
85            text: text.into(),
86            style: Some(style),
87            meta: None,
88            control: None,
89        }
90    }
91
92    /// Create a new styled segment with metadata.
93    pub fn styled_with_meta(
94        text: impl Into<Cow<'static, str>>,
95        style: Style,
96        meta: StyleMeta,
97    ) -> Self {
98        Segment {
99            text: text.into(),
100            style: Some(style),
101            meta: if meta.is_empty() { None } else { Some(meta) },
102            control: None,
103        }
104    }
105
106    /// Create a new segment with metadata and no style.
107    pub fn new_with_meta(text: impl Into<Cow<'static, str>>, meta: StyleMeta) -> Self {
108        Segment {
109            text: text.into(),
110            style: None,
111            meta: if meta.is_empty() { None } else { Some(meta) },
112            control: None,
113        }
114    }
115
116    /// Create a control segment.
117    pub fn control(control: ControlType) -> Self {
118        Segment {
119            text: Cow::Borrowed(""),
120            style: None,
121            meta: None,
122            control: Some(control),
123        }
124    }
125
126    /// Create a newline segment.
127    pub fn line() -> Self {
128        Segment::new("\n")
129    }
130
131    /// Check if this segment is a control segment.
132    pub fn is_control(&self) -> bool {
133        self.control.is_some()
134    }
135
136    /// Get the cell width of this segment's text.
137    pub fn cell_len(&self) -> usize {
138        crate::cells::cell_len(&self.text)
139    }
140
141    /// Apply a style to this segment, combining with any existing style.
142    pub fn apply_style(&self, style: &Style) -> Self {
143        Segment {
144            text: self.text.clone(),
145            style: Some(match &self.style {
146                Some(existing) => existing.combine(style),
147                None => *style,
148            }),
149            meta: self.meta.clone(),
150            control: self.control.clone(),
151        }
152    }
153
154    /// Split segment into two segments at the specified cell position.
155    ///
156    /// If the cut point falls in the middle of a 2-cell wide character then it is replaced
157    /// by two spaces, to preserve the display width of the parent segment.
158    ///
159    /// # Arguments
160    ///
161    /// * `cut` - Cell offset within the segment to cut at.
162    ///
163    /// # Returns
164    ///
165    /// A tuple of two segments: (before, after).
166    ///
167    /// # Example
168    ///
169    /// ```
170    /// use rich_rs::Segment;
171    ///
172    /// let seg = Segment::new("hello");
173    /// let (before, after) = seg.split_cells(3);
174    /// assert_eq!(&*before.text, "hel");
175    /// assert_eq!(&*after.text, "lo");
176    /// ```
177    pub fn split_cells(&self, cut: usize) -> (Segment, Segment) {
178        let text = &self.text;
179        let style = self.style;
180        let meta = self.meta.clone();
181        let control = self.control.clone();
182
183        // Control segments have no visual width
184        if control.is_some() {
185            return (
186                self.clone(),
187                Segment::new_with_style_control("", style, meta, control),
188            );
189        }
190
191        let segment_cell_len = cell_len(text);
192
193        // If cut is at or beyond the end, return original and empty
194        if cut >= segment_cell_len {
195            return (
196                self.clone(),
197                Segment::new_with_style_control("", style, meta, control),
198            );
199        }
200
201        // If cut is at the start, return empty and original
202        if cut == 0 {
203            return (
204                Segment::new_with_style_control("", style, meta, control),
205                self.clone(),
206            );
207        }
208
209        // Fast path: check if all characters are single-width ASCII
210        if text.is_ascii() {
211            // ASCII characters are all single-cell width
212            let before = &text[..cut];
213            let after = &text[cut..];
214            return (
215                Segment::new_with_style_control(
216                    before.to_string(),
217                    style,
218                    meta.clone(),
219                    control.clone(),
220                ),
221                Segment::new_with_style_control(after.to_string(), style, meta, control),
222            );
223        }
224
225        // Slow path: iterate through characters tracking cell position
226        let mut current_cell_pos = 0;
227
228        for (byte_idx, c) in text.char_indices() {
229            let c_width = char_width(c);
230
231            if current_cell_pos == cut {
232                // Exact cut point
233                let before = &text[..byte_idx];
234                let after = &text[byte_idx..];
235                return (
236                    Segment::new_with_style_control(
237                        before.to_string(),
238                        style,
239                        meta.clone(),
240                        control.clone(),
241                    ),
242                    Segment::new_with_style_control(
243                        after.to_string(),
244                        style,
245                        meta.clone(),
246                        control,
247                    ),
248                );
249            }
250
251            if current_cell_pos + c_width > cut {
252                // Cut falls in the middle of a double-width character
253                // Replace with spaces to preserve total width
254                let before = &text[..byte_idx];
255                let after_start_byte = byte_idx + c.len_utf8();
256                let after = &text[after_start_byte..];
257
258                // We need to add spaces: one at the end of `before`, one at the start of `after`
259                let before_with_space = format!("{} ", before);
260                let after_with_space = format!(" {}", after);
261
262                return (
263                    Segment::new_with_style_control(
264                        before_with_space,
265                        style,
266                        meta.clone(),
267                        control.clone(),
268                    ),
269                    Segment::new_with_style_control(after_with_space, style, meta.clone(), control),
270                );
271            }
272
273            current_cell_pos += c_width;
274        }
275
276        // Shouldn't reach here, but return original and empty as fallback
277        (
278            self.clone(),
279            Segment::new_with_style_control("", style, meta, control),
280        )
281    }
282
283    /// Internal helper to create a segment with optional style and control.
284    fn new_with_style_control(
285        text: impl Into<Cow<'static, str>>,
286        style: Option<Style>,
287        meta: Option<StyleMeta>,
288        control: Option<ControlType>,
289    ) -> Self {
290        Segment {
291            text: text.into(),
292            style,
293            meta,
294            control,
295        }
296    }
297
298    // ========================================================================
299    // Associated functions (class methods in Python)
300    // ========================================================================
301
302    /// Split a sequence of segments into lines on newline characters.
303    ///
304    /// # Arguments
305    ///
306    /// * `segments` - Segments potentially containing newlines.
307    ///
308    /// # Returns
309    ///
310    /// A vector of lines, where each line is a vector of segments.
311    pub fn split_lines(segments: impl IntoIterator<Item = Segment>) -> Vec<Vec<Segment>> {
312        let mut lines: Vec<Vec<Segment>> = Vec::new();
313        let mut current_line: Vec<Segment> = Vec::new();
314
315        for segment in segments {
316            if segment.text.contains('\n') && segment.control.is_none() {
317                let text = segment.text.to_string();
318                let style = segment.style;
319                let meta = segment.meta.clone();
320                let mut remaining = text.as_str();
321
322                while !remaining.is_empty() {
323                    if let Some(newline_pos) = remaining.find('\n') {
324                        let before = &remaining[..newline_pos];
325                        if !before.is_empty() {
326                            current_line.push(Segment::new_with_style_control(
327                                before.to_string(),
328                                style,
329                                meta.clone(),
330                                None,
331                            ));
332                        }
333                        lines.push(std::mem::take(&mut current_line));
334                        remaining = &remaining[newline_pos + 1..];
335                    } else {
336                        // No more newlines
337                        if !remaining.is_empty() {
338                            current_line.push(Segment::new_with_style_control(
339                                remaining.to_string(),
340                                style,
341                                meta.clone(),
342                                None,
343                            ));
344                        }
345                        break;
346                    }
347                }
348            } else {
349                current_line.push(segment);
350            }
351        }
352
353        // Don't forget the last line if it's non-empty
354        if !current_line.is_empty() {
355            lines.push(current_line);
356        }
357
358        lines
359    }
360
361    /// Split segments into lines and crop/pad each line to a specific length.
362    ///
363    /// # Arguments
364    ///
365    /// * `segments` - An iterable of segments to process.
366    /// * `length` - Desired line length in cells.
367    /// * `style` - Style to use for padding.
368    /// * `pad` - Whether to pad lines shorter than `length`.
369    /// * `include_new_lines` - Whether to append newline segments to each line.
370    ///
371    /// # Returns
372    ///
373    /// A vector of lines, each cropped/padded to the desired length.
374    pub fn split_and_crop_lines(
375        segments: impl IntoIterator<Item = Segment>,
376        length: usize,
377        style: Option<Style>,
378        pad: bool,
379        include_new_lines: bool,
380    ) -> Vec<Vec<Segment>> {
381        let mut lines: Vec<Vec<Segment>> = Vec::new();
382        let mut current_line: Vec<Segment> = Vec::new();
383        let new_line_segment = Segment::line();
384
385        for segment in segments {
386            if segment.text.contains('\n') && segment.control.is_none() {
387                let text = segment.text.to_string();
388                let segment_style = segment.style;
389                let segment_meta = segment.meta.clone();
390                let mut remaining = text.as_str();
391
392                while !remaining.is_empty() {
393                    if let Some(newline_pos) = remaining.find('\n') {
394                        let before = &remaining[..newline_pos];
395                        if !before.is_empty() {
396                            current_line.push(Segment::new_with_style_control(
397                                before.to_string(),
398                                segment_style,
399                                segment_meta.clone(),
400                                None,
401                            ));
402                        }
403                        let mut cropped =
404                            Self::adjust_line_length(&current_line, length, style, pad);
405                        if include_new_lines {
406                            cropped.push(new_line_segment.clone());
407                        }
408                        lines.push(cropped);
409                        current_line.clear();
410                        remaining = &remaining[newline_pos + 1..];
411                    } else {
412                        if !remaining.is_empty() {
413                            current_line.push(Segment::new_with_style_control(
414                                remaining.to_string(),
415                                segment_style,
416                                segment_meta.clone(),
417                                None,
418                            ));
419                        }
420                        break;
421                    }
422                }
423            } else {
424                current_line.push(segment);
425            }
426        }
427
428        // Handle the last line
429        if !current_line.is_empty() {
430            lines.push(Self::adjust_line_length(&current_line, length, style, pad));
431        }
432
433        lines
434    }
435
436    /// Adjust a line to a given width by cropping or padding.
437    ///
438    /// # Arguments
439    ///
440    /// * `line` - A slice of segments representing a single line.
441    /// * `length` - Desired width in cells.
442    /// * `style` - Style to use for padding.
443    /// * `pad` - Whether to pad lines shorter than `length`.
444    ///
445    /// # Returns
446    ///
447    /// A new vector of segments with the desired length.
448    pub fn adjust_line_length(
449        line: &[Segment],
450        length: usize,
451        style: Option<Style>,
452        pad: bool,
453    ) -> Vec<Segment> {
454        let line_length = Self::get_line_length(line);
455
456        if line_length < length {
457            // Line is shorter than desired
458            if pad {
459                let mut new_line = line.to_vec();
460                let padding = " ".repeat(length - line_length);
461                let end_style = line.iter().rev().find_map(|seg| {
462                    if seg.control.is_some() {
463                        return None;
464                    }
465                    seg.style
466                });
467                // Padding should extend *background* colors to avoid hairlines, but should not
468                // inherit decoration attributes like underline/bold/dim from the preceding text.
469                let padding_style = match (style, end_style) {
470                    (Some(mut base), Some(end)) => {
471                        if base.bgcolor.is_none() {
472                            if let Some(bg) = end.bgcolor {
473                                base.bgcolor = Some(bg);
474                            }
475                        }
476                        Some(base)
477                    }
478                    (Some(base), None) => Some(base),
479                    (None, Some(end)) => end.bgcolor.map(|bg| Style::new().with_bgcolor(bg)),
480                    (None, None) => None,
481                };
482                new_line.push(Segment::new_with_style_control(
483                    padding,
484                    padding_style,
485                    None,
486                    None,
487                ));
488                new_line
489            } else {
490                line.to_vec()
491            }
492        } else if line_length > length {
493            // Line is longer than desired - crop it
494            let mut new_line = Vec::new();
495            let mut current_length = 0;
496
497            for segment in line {
498                let segment_length = segment.cell_len();
499
500                if segment.control.is_some() {
501                    // Control segments don't contribute to visual length
502                    new_line.push(segment.clone());
503                    continue;
504                }
505
506                if current_length + segment_length <= length {
507                    // Segment fits entirely
508                    new_line.push(segment.clone());
509                    current_length += segment_length;
510                } else {
511                    // Segment needs to be cropped
512                    let remaining_space = length - current_length;
513                    if remaining_space > 0 {
514                        let cropped_text = set_cell_size(&segment.text, remaining_space);
515                        new_line.push(Segment::new_with_style_control(
516                            cropped_text,
517                            segment.style,
518                            segment.meta.clone(),
519                            None,
520                        ));
521                    }
522                    break;
523                }
524            }
525
526            new_line
527        } else {
528            // Line is exactly the right length
529            line.to_vec()
530        }
531    }
532
533    /// Get the last non-control style in a line.
534    ///
535    /// This is useful for determining the "end of line" style when padding with spaces,
536    /// so background colors extend to the full width.
537    pub fn get_last_style(line: &[Segment]) -> Option<Style> {
538        line.iter().rev().find_map(|seg| {
539            if seg.control.is_some() {
540                None
541            } else {
542                seg.style
543            }
544        })
545    }
546
547    /// Simplify segments by merging adjacent segments with the same style.
548    ///
549    /// # Arguments
550    ///
551    /// * `segments` - An iterable of segments to simplify.
552    ///
553    /// # Returns
554    ///
555    /// A `Segments` collection with adjacent same-style segments merged.
556    pub fn simplify(segments: impl IntoIterator<Item = Segment>) -> Segments {
557        let mut result = Segments::new();
558        let mut iter = segments.into_iter();
559
560        let Some(mut last_segment) = iter.next() else {
561            return result;
562        };
563
564        for segment in iter {
565            // Only merge non-control segments with same style
566            if last_segment.style == segment.style
567                && last_segment.meta == segment.meta
568                && last_segment.control.is_none()
569                && segment.control.is_none()
570            {
571                // Merge text
572                let merged_text = format!("{}{}", last_segment.text, segment.text);
573                last_segment = Segment::new_with_style_control(
574                    merged_text,
575                    last_segment.style,
576                    last_segment.meta.clone(),
577                    None,
578                );
579            } else {
580                result.push(last_segment);
581                last_segment = segment;
582            }
583        }
584
585        result.push(last_segment);
586        result
587    }
588
589    /// Divide segments at multiple cell positions.
590    ///
591    /// # Arguments
592    ///
593    /// * `segments` - Segments to divide.
594    /// * `cuts` - Cell positions where to divide (must be sorted in ascending order).
595    ///
596    /// # Returns
597    ///
598    /// A vector of segment vectors, one for each division. Always includes a trailing
599    /// partition containing any remaining content after the last cut.
600    ///
601    /// # Panics (debug mode only)
602    ///
603    /// Debug-asserts that cuts are sorted in ascending order.
604    pub fn divide(
605        segments: impl IntoIterator<Item = Segment>,
606        cuts: &[usize],
607    ) -> Vec<Vec<Segment>> {
608        // Precondition: cuts must be sorted ascending
609        debug_assert!(
610            cuts.windows(2).all(|w| w[0] <= w[1]),
611            "cuts must be sorted in ascending order"
612        );
613
614        if cuts.is_empty() {
615            return Vec::new();
616        }
617
618        let mut result: Vec<Vec<Segment>> = Vec::new();
619        let mut split_segments: Vec<Segment> = Vec::new();
620        let mut cut_iter = cuts.iter().copied();
621
622        // Handle leading zeros
623        let mut current_cut;
624        loop {
625            match cut_iter.next() {
626                None => return result,
627                Some(0) => result.push(Vec::new()),
628                Some(c) => {
629                    current_cut = c;
630                    break;
631                }
632            }
633        }
634
635        let mut pos: usize = 0;
636        let mut cuts_exhausted = false;
637
638        for segment in segments {
639            // Control segments don't contribute to position
640            if segment.control.is_some() {
641                split_segments.push(segment);
642                continue;
643            }
644
645            // If cuts are exhausted, just accumulate remaining segments
646            if cuts_exhausted {
647                split_segments.push(segment);
648                continue;
649            }
650
651            let mut current_segment = segment;
652
653            loop {
654                let text = &current_segment.text;
655                if text.is_empty() {
656                    break;
657                }
658
659                let seg_len = cell_len(text);
660                let end_pos = pos + seg_len;
661
662                if end_pos < current_cut {
663                    // Entire segment fits before cut
664                    split_segments.push(current_segment);
665                    pos = end_pos;
666                    break;
667                } else if end_pos == current_cut {
668                    // Segment ends exactly at cut
669                    split_segments.push(current_segment);
670                    result.push(std::mem::take(&mut split_segments));
671                    pos = end_pos;
672
673                    // Move to next cut
674                    match cut_iter.next() {
675                        None => {
676                            // No more cuts - set flag and continue accumulating
677                            cuts_exhausted = true;
678                        }
679                        Some(next_cut) => current_cut = next_cut,
680                    }
681                    break;
682                } else {
683                    // Segment crosses the cut boundary - split it
684                    let split_point = current_cut - pos;
685                    let (before, after) = current_segment.split_cells(split_point);
686
687                    if !before.text.is_empty() {
688                        split_segments.push(before);
689                    }
690                    result.push(std::mem::take(&mut split_segments));
691                    pos = current_cut;
692
693                    // Continue processing with the remaining part
694                    current_segment = after;
695
696                    // Move to next cut
697                    match cut_iter.next() {
698                        None => {
699                            // No more cuts - add remaining segment and set flag
700                            if !current_segment.text.is_empty() {
701                                split_segments.push(current_segment);
702                            }
703                            cuts_exhausted = true;
704                            break;
705                        }
706                        Some(next_cut) => current_cut = next_cut,
707                    }
708                    // Continue the inner loop to process remaining segment
709                }
710            }
711        }
712
713        // Always yield the trailing partition (matches Python Rich's `yield segments_copy()`)
714        result.push(split_segments);
715        result
716    }
717
718    /// Apply style to all segments.
719    ///
720    /// Returns segments where the style is replaced by `style + segment.style + post_style`.
721    ///
722    /// # Arguments
723    ///
724    /// * `segments` - Segments to process.
725    /// * `style` - Base style to apply first.
726    /// * `post_style` - Style to apply after segment's own style.
727    ///
728    /// # Returns
729    ///
730    /// A new `Segments` collection with styles applied.
731    pub fn apply_style_to_segments(
732        segments: impl IntoIterator<Item = Segment>,
733        style: Option<Style>,
734        post_style: Option<Style>,
735    ) -> Segments {
736        let mut result = Segments::new();
737
738        for segment in segments {
739            if segment.control.is_some() {
740                // Don't apply style to control segments
741                result.push(segment);
742                continue;
743            }
744
745            let mut new_style = segment.style;
746
747            // Apply base style first
748            if let Some(base) = style {
749                new_style = Some(match new_style {
750                    Some(existing) => base.combine(&existing),
751                    None => base,
752                });
753            }
754
755            // Apply post style
756            if let Some(post) = post_style {
757                new_style = Some(match new_style {
758                    Some(existing) => existing.combine(&post),
759                    None => post,
760                });
761            }
762
763            result.push(Segment {
764                text: segment.text,
765                style: new_style,
766                meta: segment.meta,
767                control: None,
768            });
769        }
770
771        result
772    }
773
774    /// Filter segments by control status.
775    ///
776    /// # Arguments
777    ///
778    /// * `segments` - Segments to filter.
779    /// * `is_control` - If true, keep only control segments; if false, keep only non-control segments.
780    ///
781    /// # Returns
782    ///
783    /// A new `Segments` collection with only matching segments.
784    pub fn filter_control(
785        segments: impl IntoIterator<Item = Segment>,
786        is_control: bool,
787    ) -> Segments {
788        segments
789            .into_iter()
790            .filter(|s| s.is_control() == is_control)
791            .collect()
792    }
793
794    /// Remove all styles from segments.
795    ///
796    /// # Arguments
797    ///
798    /// * `segments` - Segments to process.
799    ///
800    /// # Returns
801    ///
802    /// A new `Segments` collection with all styles removed.
803    pub fn strip_styles(segments: impl IntoIterator<Item = Segment>) -> Segments {
804        segments
805            .into_iter()
806            .map(|s| Segment {
807                text: s.text,
808                style: None,
809                meta: None,
810                control: s.control,
811            })
812            .collect()
813    }
814
815    /// Get the total cell width of a line of segments.
816    ///
817    /// # Arguments
818    ///
819    /// * `line` - A slice of segments representing a single line (no newlines).
820    ///
821    /// # Returns
822    ///
823    /// The total cell width of all non-control segments.
824    pub fn get_line_length(line: &[Segment]) -> usize {
825        line.iter()
826            .filter(|s| s.control.is_none())
827            .map(|s| cell_len(&s.text))
828            .sum()
829    }
830
831    /// Get the shape (enclosing rectangle) of a list of lines.
832    ///
833    /// # Arguments
834    ///
835    /// * `lines` - A list of lines (no newline characters).
836    ///
837    /// # Returns
838    ///
839    /// A tuple of (width, height) representing the enclosing rectangle.
840    pub fn get_shape(lines: &[Vec<Segment>]) -> (usize, usize) {
841        if lines.is_empty() {
842            return (0, 0);
843        }
844
845        let max_width = lines
846            .iter()
847            .map(|line| Self::get_line_length(line))
848            .max()
849            .unwrap_or(0);
850        (max_width, lines.len())
851    }
852
853    /// Set the shape of a list of lines to a specific rectangle.
854    ///
855    /// # Arguments
856    ///
857    /// * `lines` - A list of lines.
858    /// * `width` - Desired width.
859    /// * `height` - Desired height (if None, uses current height).
860    /// * `style` - Style for padding.
861    /// * `new_lines` - Whether padded lines should include newline characters.
862    ///
863    /// # Returns
864    ///
865    /// A new list of lines with the specified shape.
866    pub fn set_shape(
867        lines: &[Vec<Segment>],
868        width: usize,
869        height: Option<usize>,
870        style: Option<Style>,
871        new_lines: bool,
872    ) -> Vec<Vec<Segment>> {
873        let target_height = height.unwrap_or(lines.len());
874
875        // Create blank line for padding
876        let blank_text = if new_lines {
877            format!("{}\n", " ".repeat(width))
878        } else {
879            " ".repeat(width)
880        };
881        let blank = vec![Segment::new_with_style_control(
882            blank_text, style, None, None,
883        )];
884
885        let mut result: Vec<Vec<Segment>> = lines
886            .iter()
887            .take(target_height)
888            .map(|line| Self::adjust_line_length(line, width, style, true))
889            .collect();
890
891        // Add blank lines if needed
892        while result.len() < target_height {
893            result.push(blank.clone());
894        }
895
896        result
897    }
898
899    // ========================================================================
900    // Vertical alignment functions
901    // ========================================================================
902
903    /// Align lines to the top by padding the bottom with blank lines.
904    ///
905    /// # Arguments
906    ///
907    /// * `lines` - A list of lines.
908    /// * `width` - Desired width of blank lines in cells.
909    /// * `height` - Desired total height.
910    /// * `style` - Style for padding.
911    /// * `new_lines` - Whether blank lines should include "\n".
912    pub fn align_top(
913        lines: &[Vec<Segment>],
914        width: usize,
915        height: usize,
916        style: Option<Style>,
917        new_lines: bool,
918    ) -> Vec<Vec<Segment>> {
919        let extra_lines = height.saturating_sub(lines.len());
920        if extra_lines == 0 {
921            return lines[..height.min(lines.len())].to_vec();
922        }
923        let mut result: Vec<Vec<Segment>> = lines[..height.min(lines.len())].to_vec();
924        let blank_text = if new_lines {
925            format!("{}\n", " ".repeat(width))
926        } else {
927            " ".repeat(width)
928        };
929        let blank = vec![Segment::new_with_style_control(
930            blank_text, style, None, None,
931        )];
932        for _ in 0..extra_lines {
933            result.push(blank.clone());
934        }
935        result
936    }
937
938    /// Align lines to the bottom by padding the top with blank lines.
939    pub fn align_bottom(
940        lines: &[Vec<Segment>],
941        width: usize,
942        height: usize,
943        style: Option<Style>,
944        new_lines: bool,
945    ) -> Vec<Vec<Segment>> {
946        let extra_lines = height.saturating_sub(lines.len());
947        if extra_lines == 0 {
948            return lines[..height.min(lines.len())].to_vec();
949        }
950        let blank_text = if new_lines {
951            format!("{}\n", " ".repeat(width))
952        } else {
953            " ".repeat(width)
954        };
955        let blank = vec![Segment::new_with_style_control(
956            blank_text, style, None, None,
957        )];
958        let mut result: Vec<Vec<Segment>> = Vec::with_capacity(height);
959        for _ in 0..extra_lines {
960            result.push(blank.clone());
961        }
962        result.extend_from_slice(&lines[..height.min(lines.len())]);
963        result
964    }
965
966    /// Align lines to the middle by padding top and bottom with blank lines.
967    pub fn align_middle(
968        lines: &[Vec<Segment>],
969        width: usize,
970        height: usize,
971        style: Option<Style>,
972        new_lines: bool,
973    ) -> Vec<Vec<Segment>> {
974        let extra_lines = height.saturating_sub(lines.len());
975        if extra_lines == 0 {
976            return lines[..height.min(lines.len())].to_vec();
977        }
978        let blank_text = if new_lines {
979            format!("{}\n", " ".repeat(width))
980        } else {
981            " ".repeat(width)
982        };
983        let blank = vec![Segment::new_with_style_control(
984            blank_text, style, None, None,
985        )];
986        let top_lines = extra_lines / 2;
987        let bottom_lines = extra_lines - top_lines;
988        let mut result: Vec<Vec<Segment>> = Vec::with_capacity(height);
989        for _ in 0..top_lines {
990            result.push(blank.clone());
991        }
992        result.extend_from_slice(&lines[..height.min(lines.len())]);
993        for _ in 0..bottom_lines {
994            result.push(blank.clone());
995        }
996        result
997    }
998
999    /// Split segments into lines, preserving a boolean flag indicating whether
1000    /// a newline character was encountered (true) or end of content (false).
1001    ///
1002    /// This is equivalent to Python Rich's `Segment.split_lines_terminator`.
1003    pub fn split_lines_terminator(
1004        segments: impl IntoIterator<Item = Segment>,
1005    ) -> Vec<(Vec<Segment>, bool)> {
1006        let mut result: Vec<(Vec<Segment>, bool)> = Vec::new();
1007        let mut current_line: Vec<Segment> = Vec::new();
1008
1009        for segment in segments {
1010            if segment.text.contains('\n') && segment.control.is_none() {
1011                let text = segment.text.to_string();
1012                let style = segment.style;
1013                let meta = segment.meta.clone();
1014                let mut remaining = text.as_str();
1015
1016                while !remaining.is_empty() {
1017                    if let Some(newline_pos) = remaining.find('\n') {
1018                        let before = &remaining[..newline_pos];
1019                        if !before.is_empty() {
1020                            current_line.push(Segment::new_with_style_control(
1021                                before.to_string(),
1022                                style,
1023                                meta.clone(),
1024                                None,
1025                            ));
1026                        }
1027                        result.push((std::mem::take(&mut current_line), true));
1028                        remaining = &remaining[newline_pos + 1..];
1029                    } else {
1030                        if !remaining.is_empty() {
1031                            current_line.push(Segment::new_with_style_control(
1032                                remaining.to_string(),
1033                                style,
1034                                meta.clone(),
1035                                None,
1036                            ));
1037                        }
1038                        break;
1039                    }
1040                }
1041            } else {
1042                current_line.push(segment);
1043            }
1044        }
1045
1046        if !current_line.is_empty() {
1047            result.push((current_line, false));
1048        }
1049
1050        result
1051    }
1052
1053    /// Remove link metadata from segments.
1054    ///
1055    /// This is equivalent to Python Rich's `Segment.strip_links`.
1056    pub fn strip_links(segments: impl IntoIterator<Item = Segment>) -> Segments {
1057        segments
1058            .into_iter()
1059            .map(|s| Segment {
1060                text: s.text,
1061                style: s.style,
1062                meta: None,
1063                control: s.control,
1064            })
1065            .collect()
1066    }
1067
1068    /// Remove color information from segments, keeping text and attributes.
1069    ///
1070    /// This is equivalent to Python Rich's `Segment.remove_color`.
1071    pub fn remove_color(segments: impl IntoIterator<Item = Segment>) -> Segments {
1072        segments
1073            .into_iter()
1074            .map(|s| {
1075                let new_style = s.style.map(|style| style.without_color());
1076                Segment {
1077                    text: s.text,
1078                    style: new_style,
1079                    meta: s.meta,
1080                    control: s.control,
1081                }
1082            })
1083            .collect()
1084    }
1085}
1086
1087/// A simple renderable wrapping pre-rendered lines of segments.
1088///
1089/// This allows pre-rendered content to be passed through the rendering pipeline.
1090/// Equivalent to Python Rich's `SegmentLines`.
1091#[derive(Debug, Clone)]
1092pub struct SegmentLines {
1093    /// The pre-rendered lines.
1094    pub lines: Vec<Vec<Segment>>,
1095    /// Whether to insert newlines after each line.
1096    pub new_lines: bool,
1097}
1098
1099impl SegmentLines {
1100    /// Create a new SegmentLines.
1101    pub fn new(lines: Vec<Vec<Segment>>, new_lines: bool) -> Self {
1102        SegmentLines { lines, new_lines }
1103    }
1104
1105    /// Convert to a flat Segments collection for rendering.
1106    pub fn to_segments(&self) -> Segments {
1107        let mut result = Segments::new();
1108        let new_line = Segment::line();
1109        for line in &self.lines {
1110            for seg in line {
1111                result.push(seg.clone());
1112            }
1113            if self.new_lines {
1114                result.push(new_line.clone());
1115            }
1116        }
1117        result
1118    }
1119}
1120
1121impl Default for Segment {
1122    fn default() -> Self {
1123        Segment::new("")
1124    }
1125}
1126
1127impl From<&'static str> for Segment {
1128    fn from(s: &'static str) -> Self {
1129        Segment::new(s)
1130    }
1131}
1132
1133impl From<String> for Segment {
1134    fn from(s: String) -> Self {
1135        Segment::new(s)
1136    }
1137}
1138
1139/// A collection of segments, backed by SmallVec for efficiency.
1140///
1141/// This newtype abstracts over the underlying storage, allowing future
1142/// optimization (e.g., streaming) without breaking the API.
1143#[derive(Debug, Clone, Default)]
1144pub struct Segments(SmallVec<[Segment; 8]>);
1145
1146impl Segments {
1147    /// Create an empty Segments collection.
1148    pub fn new() -> Self {
1149        Segments(SmallVec::new())
1150    }
1151
1152    /// Create a Segments collection with a single segment.
1153    pub fn one(segment: Segment) -> Self {
1154        let mut sv = SmallVec::new();
1155        sv.push(segment);
1156        Segments(sv)
1157    }
1158
1159    /// Add a segment to the collection.
1160    pub fn push(&mut self, segment: Segment) {
1161        self.0.push(segment);
1162    }
1163
1164    /// Extend with segments from an iterator.
1165    pub fn extend(&mut self, iter: impl IntoIterator<Item = Segment>) {
1166        self.0.extend(iter);
1167    }
1168
1169    /// Get the number of segments.
1170    pub fn len(&self) -> usize {
1171        self.0.len()
1172    }
1173
1174    /// Check if empty.
1175    pub fn is_empty(&self) -> bool {
1176        self.0.is_empty()
1177    }
1178
1179    /// Iterate over segments.
1180    pub fn iter(&self) -> impl Iterator<Item = &Segment> {
1181        self.0.iter()
1182    }
1183
1184    /// Get the total cell width of all segments.
1185    pub fn cell_len(&self) -> usize {
1186        self.0.iter().map(|s| s.cell_len()).sum()
1187    }
1188
1189    /// Convert to a Vec (consumes self).
1190    pub fn into_vec(self) -> Vec<Segment> {
1191        self.0.into_vec()
1192    }
1193}
1194
1195impl From<Segment> for Segments {
1196    fn from(segment: Segment) -> Self {
1197        Segments::one(segment)
1198    }
1199}
1200
1201impl From<Vec<Segment>> for Segments {
1202    fn from(vec: Vec<Segment>) -> Self {
1203        Segments(SmallVec::from_vec(vec))
1204    }
1205}
1206
1207impl FromIterator<Segment> for Segments {
1208    fn from_iter<I: IntoIterator<Item = Segment>>(iter: I) -> Self {
1209        Segments(iter.into_iter().collect())
1210    }
1211}
1212
1213impl IntoIterator for Segments {
1214    type Item = Segment;
1215    type IntoIter = smallvec::IntoIter<[Segment; 8]>;
1216
1217    fn into_iter(self) -> Self::IntoIter {
1218        self.0.into_iter()
1219    }
1220}
1221
1222impl<'a> IntoIterator for &'a Segments {
1223    type Item = &'a Segment;
1224    type IntoIter = std::slice::Iter<'a, Segment>;
1225
1226    fn into_iter(self) -> Self::IntoIter {
1227        self.0.iter()
1228    }
1229}
1230
1231#[cfg(test)]
1232mod tests {
1233    use super::*;
1234
1235    #[test]
1236    fn test_new_segment() {
1237        let seg = Segment::new("hello");
1238        assert_eq!(&*seg.text, "hello");
1239        assert!(seg.style.is_none());
1240        assert!(seg.control.is_none());
1241    }
1242
1243    #[test]
1244    fn test_cell_len() {
1245        let seg = Segment::new("hello");
1246        assert_eq!(seg.cell_len(), 5);
1247    }
1248
1249    #[test]
1250    fn test_segments_collection() {
1251        let mut segs = Segments::new();
1252        segs.push(Segment::new("hello"));
1253        segs.push(Segment::new(" "));
1254        segs.push(Segment::new("world"));
1255        assert_eq!(segs.len(), 3);
1256        assert_eq!(segs.cell_len(), 11);
1257    }
1258
1259    #[test]
1260    fn test_segments_from_iter() {
1261        let segs: Segments = vec![Segment::new("a"), Segment::new("b"), Segment::new("c")]
1262            .into_iter()
1263            .collect();
1264        assert_eq!(segs.len(), 3);
1265    }
1266
1267    // ==================== split_cells tests ====================
1268
1269    #[test]
1270    fn test_split_cells_ascii() {
1271        let seg = Segment::new("hello");
1272        let (before, after) = seg.split_cells(3);
1273        assert_eq!(&*before.text, "hel");
1274        assert_eq!(&*after.text, "lo");
1275    }
1276
1277    #[test]
1278    fn test_split_cells_at_start() {
1279        let seg = Segment::new("hello");
1280        let (before, after) = seg.split_cells(0);
1281        assert_eq!(&*before.text, "");
1282        assert_eq!(&*after.text, "hello");
1283    }
1284
1285    #[test]
1286    fn test_split_cells_at_end() {
1287        let seg = Segment::new("hello");
1288        let (before, after) = seg.split_cells(5);
1289        assert_eq!(&*before.text, "hello");
1290        assert_eq!(&*after.text, "");
1291    }
1292
1293    #[test]
1294    fn test_split_cells_beyond_end() {
1295        let seg = Segment::new("hello");
1296        let (before, after) = seg.split_cells(10);
1297        assert_eq!(&*before.text, "hello");
1298        assert_eq!(&*after.text, "");
1299    }
1300
1301    #[test]
1302    fn test_split_cells_cjk_exact() {
1303        // CJK characters are 2 cells wide
1304        let seg = Segment::new("你好");
1305        let (before, after) = seg.split_cells(2);
1306        assert_eq!(&*before.text, "你");
1307        assert_eq!(&*after.text, "好");
1308    }
1309
1310    #[test]
1311    fn test_split_cells_cjk_middle() {
1312        // Splitting in the middle of a double-width char should replace with spaces
1313        let seg = Segment::new("你好");
1314        let (before, after) = seg.split_cells(1);
1315        assert_eq!(&*before.text, " "); // Space replaces first half of 你
1316        assert_eq!(&*after.text, " 好"); // Space + remaining chars
1317    }
1318
1319    #[test]
1320    fn test_split_cells_cjk_middle_complex() {
1321        // "你好世界" = 8 cells total
1322        let seg = Segment::new("你好世界");
1323        let (before, after) = seg.split_cells(3);
1324        // Cut at 3 is in the middle of 好 (which spans cells 2-3)
1325        assert_eq!(&*before.text, "你 "); // 你 + space
1326        assert_eq!(&*after.text, " 世界"); // space + 世界
1327    }
1328
1329    #[test]
1330    fn test_split_cells_mixed_content() {
1331        // "a你b" = 1 + 2 + 1 = 4 cells
1332        let seg = Segment::new("a你b");
1333        let (before, after) = seg.split_cells(3);
1334        assert_eq!(&*before.text, "a你");
1335        assert_eq!(&*after.text, "b");
1336    }
1337
1338    #[test]
1339    fn test_split_cells_preserves_style() {
1340        let style = Style::new().with_bold(true);
1341        let seg = Segment::styled("hello", style);
1342        let (before, after) = seg.split_cells(2);
1343        assert_eq!(before.style, Some(style));
1344        assert_eq!(after.style, Some(style));
1345    }
1346
1347    #[test]
1348    fn test_split_cells_control_segment() {
1349        let seg = Segment::control(ControlType::Bell);
1350        let (before, after) = seg.split_cells(5);
1351        assert!(before.control.is_some());
1352        assert_eq!(&*after.text, "");
1353    }
1354
1355    #[test]
1356    fn test_split_cells_emoji() {
1357        // Emoji are typically 2 cells wide
1358        let seg = Segment::new("😀hello");
1359        let (before, after) = seg.split_cells(2);
1360        assert_eq!(&*before.text, "😀");
1361        assert_eq!(&*after.text, "hello");
1362    }
1363
1364    // ==================== split_lines tests ====================
1365
1366    #[test]
1367    fn test_split_lines_no_newlines() {
1368        let segments = vec![Segment::new("hello"), Segment::new(" world")];
1369        let lines = Segment::split_lines(segments);
1370        assert_eq!(lines.len(), 1);
1371        assert_eq!(lines[0].len(), 2);
1372    }
1373
1374    #[test]
1375    fn test_split_lines_single_newline() {
1376        let segments = vec![Segment::new("hello\nworld")];
1377        let lines = Segment::split_lines(segments);
1378        assert_eq!(lines.len(), 2);
1379        assert_eq!(&*lines[0][0].text, "hello");
1380        assert_eq!(&*lines[1][0].text, "world");
1381    }
1382
1383    #[test]
1384    fn test_split_lines_multiple_newlines() {
1385        let segments = vec![Segment::new("a\nb\nc")];
1386        let lines = Segment::split_lines(segments);
1387        assert_eq!(lines.len(), 3);
1388        assert_eq!(&*lines[0][0].text, "a");
1389        assert_eq!(&*lines[1][0].text, "b");
1390        assert_eq!(&*lines[2][0].text, "c");
1391    }
1392
1393    #[test]
1394    fn test_split_lines_trailing_newline() {
1395        let segments = vec![Segment::new("hello\n")];
1396        let lines = Segment::split_lines(segments);
1397        assert_eq!(lines.len(), 1);
1398        assert_eq!(&*lines[0][0].text, "hello");
1399    }
1400
1401    #[test]
1402    fn test_split_lines_preserves_style() {
1403        let style = Style::new().with_bold(true);
1404        let segments = vec![Segment::styled("hello\nworld", style)];
1405        let lines = Segment::split_lines(segments);
1406        assert_eq!(lines[0][0].style, Some(style));
1407        assert_eq!(lines[1][0].style, Some(style));
1408    }
1409
1410    #[test]
1411    fn test_split_lines_control_segment_unaffected() {
1412        let segments = vec![
1413            Segment::new("hello"),
1414            Segment::control(ControlType::Bell),
1415            Segment::new("world"),
1416        ];
1417        let lines = Segment::split_lines(segments);
1418        assert_eq!(lines.len(), 1);
1419        assert_eq!(lines[0].len(), 3);
1420    }
1421
1422    // ==================== split_and_crop_lines tests ====================
1423
1424    #[test]
1425    fn test_split_and_crop_lines_basic() {
1426        let segments = vec![Segment::new("hello world")];
1427        let lines = Segment::split_and_crop_lines(segments, 5, None, true, false);
1428        assert_eq!(lines.len(), 1);
1429        // Line should be cropped to 5 cells
1430        let total_len: usize = lines[0].iter().map(|s| cell_len(&s.text)).sum();
1431        assert_eq!(total_len, 5);
1432    }
1433
1434    #[test]
1435    fn test_split_and_crop_lines_with_newlines() {
1436        let segments = vec![Segment::new("hello\nworld")];
1437        let lines = Segment::split_and_crop_lines(segments, 10, None, true, false);
1438        assert_eq!(lines.len(), 2);
1439    }
1440
1441    #[test]
1442    fn test_split_and_crop_lines_include_newlines() {
1443        let segments = vec![Segment::new("hello\nworld")];
1444        let lines = Segment::split_and_crop_lines(segments, 10, None, true, true);
1445        assert_eq!(lines.len(), 2);
1446        // First line should have a newline segment at the end
1447        let last_seg = lines[0].last().unwrap();
1448        assert_eq!(&*last_seg.text, "\n");
1449    }
1450
1451    #[test]
1452    fn test_split_and_crop_lines_padding() {
1453        let segments = vec![Segment::new("hi")];
1454        let lines = Segment::split_and_crop_lines(segments, 5, None, true, false);
1455        let total_len: usize = lines[0].iter().map(|s| cell_len(&s.text)).sum();
1456        assert_eq!(total_len, 5); // Should be padded to 5
1457    }
1458
1459    #[test]
1460    fn test_split_and_crop_lines_no_padding() {
1461        let segments = vec![Segment::new("hi")];
1462        let lines = Segment::split_and_crop_lines(segments, 5, None, false, false);
1463        let total_len: usize = lines[0].iter().map(|s| cell_len(&s.text)).sum();
1464        assert_eq!(total_len, 2); // Should not be padded
1465    }
1466
1467    // ==================== adjust_line_length tests ====================
1468
1469    #[test]
1470    fn test_adjust_line_length_exact() {
1471        let line = vec![Segment::new("hello")];
1472        let result = Segment::adjust_line_length(&line, 5, None, true);
1473        assert_eq!(Segment::get_line_length(&result), 5);
1474    }
1475
1476    #[test]
1477    fn test_adjust_line_length_pad() {
1478        let line = vec![Segment::new("hi")];
1479        let result = Segment::adjust_line_length(&line, 5, None, true);
1480        assert_eq!(Segment::get_line_length(&result), 5);
1481        assert_eq!(result.len(), 2); // Original + padding
1482    }
1483
1484    #[test]
1485    fn test_adjust_line_length_pad_inherits_end_style() {
1486        let end_style = Style::new().with_bgcolor(crate::SimpleColor::Rgb { r: 1, g: 2, b: 3 });
1487        let line = vec![Segment::styled("x", end_style)];
1488        let result = Segment::adjust_line_length(&line, 3, None, true);
1489        assert_eq!(Segment::get_line_length(&result), 3);
1490        let padding = result.last().unwrap();
1491        assert_eq!(&*padding.text, "  ");
1492        assert_eq!(padding.style.unwrap().bgcolor, end_style.bgcolor);
1493    }
1494
1495    #[test]
1496    fn test_adjust_line_length_pad_combines_base_and_end_style() {
1497        let base = Style::new().with_bold(true);
1498        let end_style = Style::new().with_bgcolor(crate::SimpleColor::Rgb { r: 4, g: 5, b: 6 });
1499        let line = vec![Segment::styled("x", end_style)];
1500        let result = Segment::adjust_line_length(&line, 3, Some(base), true);
1501        let padding = result.last().unwrap().style.unwrap();
1502        assert_eq!(padding.bold, Some(true));
1503        assert_eq!(padding.bgcolor, end_style.bgcolor);
1504    }
1505
1506    #[test]
1507    fn test_adjust_line_length_no_pad() {
1508        let line = vec![Segment::new("hi")];
1509        let result = Segment::adjust_line_length(&line, 5, None, false);
1510        assert_eq!(Segment::get_line_length(&result), 2);
1511    }
1512
1513    #[test]
1514    fn test_adjust_line_length_crop() {
1515        let line = vec![Segment::new("hello world")];
1516        let result = Segment::adjust_line_length(&line, 5, None, true);
1517        assert_eq!(Segment::get_line_length(&result), 5);
1518    }
1519
1520    #[test]
1521    fn test_adjust_line_length_crop_multiple_segments() {
1522        let line = vec![Segment::new("hello"), Segment::new(" world")];
1523        let result = Segment::adjust_line_length(&line, 7, None, true);
1524        assert_eq!(Segment::get_line_length(&result), 7);
1525    }
1526
1527    #[test]
1528    fn test_adjust_line_length_preserves_control() {
1529        let line = vec![Segment::control(ControlType::Bell), Segment::new("hello")];
1530        let result = Segment::adjust_line_length(&line, 3, None, true);
1531        assert!(result[0].control.is_some());
1532    }
1533
1534    #[test]
1535    fn test_adjust_line_length_crop_cjk() {
1536        // CJK characters are 2 cells wide
1537        let line = vec![Segment::new("你好世界")]; // 8 cells
1538        let result = Segment::adjust_line_length(&line, 5, None, true);
1539        assert_eq!(Segment::get_line_length(&result), 5);
1540    }
1541
1542    // ==================== simplify tests ====================
1543
1544    #[test]
1545    fn test_simplify_empty() {
1546        let segments: Vec<Segment> = vec![];
1547        let result = Segment::simplify(segments);
1548        assert!(result.is_empty());
1549    }
1550
1551    #[test]
1552    fn test_simplify_single() {
1553        let segments = vec![Segment::new("hello")];
1554        let result = Segment::simplify(segments);
1555        assert_eq!(result.len(), 1);
1556        assert_eq!(&*result.iter().next().unwrap().text, "hello");
1557    }
1558
1559    #[test]
1560    fn test_simplify_same_style() {
1561        let segments = vec![Segment::new("hello"), Segment::new(" world")];
1562        let result = Segment::simplify(segments);
1563        assert_eq!(result.len(), 1);
1564        assert_eq!(&*result.iter().next().unwrap().text, "hello world");
1565    }
1566
1567    #[test]
1568    fn test_simplify_different_styles() {
1569        let style1 = Style::new().with_bold(true);
1570        let style2 = Style::new().with_italic(true);
1571        let segments = vec![
1572            Segment::styled("hello", style1),
1573            Segment::styled(" world", style2),
1574        ];
1575        let result = Segment::simplify(segments);
1576        assert_eq!(result.len(), 2);
1577    }
1578
1579    #[test]
1580    fn test_simplify_control_not_merged() {
1581        let segments = vec![
1582            Segment::new("hello"),
1583            Segment::control(ControlType::Bell),
1584            Segment::new(" world"),
1585        ];
1586        let result = Segment::simplify(segments);
1587        assert_eq!(result.len(), 3);
1588    }
1589
1590    #[test]
1591    fn test_simplify_mixed() {
1592        let style = Style::new().with_bold(true);
1593        let segments = vec![
1594            Segment::new("a"),
1595            Segment::new("b"),
1596            Segment::styled("c", style),
1597            Segment::styled("d", style),
1598            Segment::new("e"),
1599        ];
1600        let result = Segment::simplify(segments);
1601        assert_eq!(result.len(), 3);
1602        let texts: Vec<&str> = result.iter().map(|s| &*s.text).collect();
1603        assert_eq!(texts, vec!["ab", "cd", "e"]);
1604    }
1605
1606    // ==================== divide tests ====================
1607
1608    #[test]
1609    fn test_divide_empty_cuts() {
1610        let segments = vec![Segment::new("hello")];
1611        let result = Segment::divide(segments, &[]);
1612        assert!(result.is_empty());
1613    }
1614
1615    #[test]
1616    fn test_divide_single_cut() {
1617        let segments = vec![Segment::new("hello world")];
1618        let result = Segment::divide(segments, &[5]);
1619        // With trailing partition: [0..5) = "hello", [5..) = " world"
1620        assert_eq!(result.len(), 2);
1621        let first_text: String = result[0].iter().map(|s| s.text.to_string()).collect();
1622        let second_text: String = result[1].iter().map(|s| s.text.to_string()).collect();
1623        assert_eq!(first_text, "hello");
1624        assert_eq!(second_text, " world");
1625    }
1626
1627    #[test]
1628    fn test_divide_multiple_cuts() {
1629        let segments = vec![Segment::new("hello world!")];
1630        // Cuts at cell positions 5 and 11 divide the string into portions
1631        // [0..5) = "hello", [5..11) = " world", [11..) = "!" (trailing partition)
1632        let result = Segment::divide(segments, &[5, 11]);
1633        assert_eq!(result.len(), 3);
1634        let first: String = result[0].iter().map(|s| s.text.to_string()).collect();
1635        let second: String = result[1].iter().map(|s| s.text.to_string()).collect();
1636        let third: String = result[2].iter().map(|s| s.text.to_string()).collect();
1637        assert_eq!(first, "hello");
1638        assert_eq!(second, " world");
1639        assert_eq!(third, "!");
1640    }
1641
1642    #[test]
1643    fn test_divide_includes_remainder() {
1644        let segments = vec![Segment::new("hello world!")];
1645        // With cuts at 5 and 12, we get "hello", " world!", and empty trailing partition
1646        let result = Segment::divide(segments, &[5, 12]);
1647        assert_eq!(result.len(), 3);
1648        let first: String = result[0].iter().map(|s| s.text.to_string()).collect();
1649        let second: String = result[1].iter().map(|s| s.text.to_string()).collect();
1650        let third: String = result[2].iter().map(|s| s.text.to_string()).collect();
1651        assert_eq!(first, "hello");
1652        assert_eq!(second, " world!");
1653        assert_eq!(third, ""); // Empty trailing partition when content ends exactly at cut
1654    }
1655
1656    #[test]
1657    fn test_divide_zero_cut() {
1658        let segments = vec![Segment::new("hello")];
1659        let result = Segment::divide(segments, &[0, 3]);
1660        // [0..0) = empty, [0..3) = "hel", [3..) = "lo" (trailing)
1661        assert_eq!(result.len(), 3);
1662        assert!(result[0].is_empty()); // Zero cut yields empty
1663        let second: String = result[1].iter().map(|s| s.text.to_string()).collect();
1664        let third: String = result[2].iter().map(|s| s.text.to_string()).collect();
1665        assert_eq!(second, "hel");
1666        assert_eq!(third, "lo");
1667    }
1668
1669    #[test]
1670    fn test_divide_cjk() {
1671        // "你好世界" = 8 cells
1672        let segments = vec![Segment::new("你好世界")];
1673        let result = Segment::divide(segments, &[4]);
1674        // [0..4) = "你好", [4..) = "世界" (trailing partition)
1675        assert_eq!(result.len(), 2);
1676        let first: String = result[0].iter().map(|s| s.text.to_string()).collect();
1677        let second: String = result[1].iter().map(|s| s.text.to_string()).collect();
1678        assert_eq!(first, "你好");
1679        assert_eq!(second, "世界");
1680    }
1681
1682    #[test]
1683    fn test_divide_trailing_content_after_last_cut() {
1684        // This is the key test for the bug fix: content after last cut should be included
1685        let segments = vec![Segment::new("abc123xyz")];
1686        let result = Segment::divide(segments, &[3, 6]);
1687        // [0..3) = "abc", [3..6) = "123", [6..) = "xyz" (trailing)
1688        assert_eq!(result.len(), 3);
1689        let first: String = result[0].iter().map(|s| s.text.to_string()).collect();
1690        let second: String = result[1].iter().map(|s| s.text.to_string()).collect();
1691        let third: String = result[2].iter().map(|s| s.text.to_string()).collect();
1692        assert_eq!(first, "abc");
1693        assert_eq!(second, "123");
1694        assert_eq!(third, "xyz");
1695    }
1696
1697    #[test]
1698    fn test_divide_empty_trailing_partition_when_content_ends_at_cut() {
1699        // When content ends exactly at the last cut, we still get an empty trailing partition
1700        let segments = vec![Segment::new("hello")];
1701        let result = Segment::divide(segments, &[5]);
1702        // [0..5) = "hello", [5..) = "" (empty trailing)
1703        assert_eq!(result.len(), 2);
1704        let first: String = result[0].iter().map(|s| s.text.to_string()).collect();
1705        let second: String = result[1].iter().map(|s| s.text.to_string()).collect();
1706        assert_eq!(first, "hello");
1707        assert_eq!(second, "");
1708    }
1709
1710    #[test]
1711    fn test_divide_multiple_segments_with_trailing() {
1712        // Test with multiple input segments
1713        let segments = vec![
1714            Segment::new("hello"),
1715            Segment::new(" "),
1716            Segment::new("world"),
1717        ];
1718        let result = Segment::divide(segments, &[6]);
1719        // [0..6) = "hello ", [6..) = "world"
1720        assert_eq!(result.len(), 2);
1721        let first: String = result[0].iter().map(|s| s.text.to_string()).collect();
1722        let second: String = result[1].iter().map(|s| s.text.to_string()).collect();
1723        assert_eq!(first, "hello ");
1724        assert_eq!(second, "world");
1725    }
1726
1727    // ==================== apply_style_to_segments tests ====================
1728
1729    #[test]
1730    fn test_apply_style_base() {
1731        let base = Style::new().with_bold(true);
1732        let segments = vec![Segment::new("hello")];
1733        let result = Segment::apply_style_to_segments(segments, Some(base), None);
1734        assert_eq!(result.iter().next().unwrap().style, Some(base));
1735    }
1736
1737    #[test]
1738    fn test_apply_style_post() {
1739        let post = Style::new().with_italic(true);
1740        let segments = vec![Segment::new("hello")];
1741        let result = Segment::apply_style_to_segments(segments, None, Some(post));
1742        assert_eq!(result.iter().next().unwrap().style, Some(post));
1743    }
1744
1745    #[test]
1746    fn test_apply_style_both() {
1747        let base = Style::new().with_bold(true);
1748        let post = Style::new().with_italic(true);
1749        let segments = vec![Segment::new("hello")];
1750        let result = Segment::apply_style_to_segments(segments, Some(base), Some(post));
1751        let style = result.iter().next().unwrap().style.unwrap();
1752        assert_eq!(style.bold, Some(true));
1753        assert_eq!(style.italic, Some(true));
1754    }
1755
1756    #[test]
1757    fn test_apply_style_combines_with_existing() {
1758        let base = Style::new().with_bold(true);
1759        let existing = Style::new().with_italic(true);
1760        let segments = vec![Segment::styled("hello", existing)];
1761        let result = Segment::apply_style_to_segments(segments, Some(base), None);
1762        let style = result.iter().next().unwrap().style.unwrap();
1763        assert_eq!(style.bold, Some(true));
1764        assert_eq!(style.italic, Some(true));
1765    }
1766
1767    #[test]
1768    fn test_apply_style_control_unchanged() {
1769        let base = Style::new().with_bold(true);
1770        let segments = vec![Segment::control(ControlType::Bell)];
1771        let result = Segment::apply_style_to_segments(segments, Some(base), None);
1772        let seg = result.iter().next().unwrap();
1773        assert!(seg.control.is_some());
1774    }
1775
1776    // ==================== filter_control tests ====================
1777
1778    #[test]
1779    fn test_filter_control_keep_control() {
1780        let segments = vec![
1781            Segment::new("hello"),
1782            Segment::control(ControlType::Bell),
1783            Segment::new("world"),
1784        ];
1785        let result = Segment::filter_control(segments, true);
1786        assert_eq!(result.len(), 1);
1787        assert!(result.iter().next().unwrap().control.is_some());
1788    }
1789
1790    #[test]
1791    fn test_filter_control_keep_non_control() {
1792        let segments = vec![
1793            Segment::new("hello"),
1794            Segment::control(ControlType::Bell),
1795            Segment::new("world"),
1796        ];
1797        let result = Segment::filter_control(segments, false);
1798        assert_eq!(result.len(), 2);
1799        for seg in result.iter() {
1800            assert!(seg.control.is_none());
1801        }
1802    }
1803
1804    // ==================== strip_styles tests ====================
1805
1806    #[test]
1807    fn test_strip_styles() {
1808        let style = Style::new().with_bold(true);
1809        let segments = vec![
1810            Segment::styled("hello", style),
1811            Segment::styled("world", style),
1812        ];
1813        let result = Segment::strip_styles(segments);
1814        for seg in result.iter() {
1815            assert!(seg.style.is_none());
1816        }
1817    }
1818
1819    #[test]
1820    fn test_strip_styles_preserves_control() {
1821        let segments = vec![Segment::control(ControlType::Bell)];
1822        let result = Segment::strip_styles(segments);
1823        assert!(result.iter().next().unwrap().control.is_some());
1824    }
1825
1826    // ==================== get_line_length tests ====================
1827
1828    #[test]
1829    fn test_get_line_length_simple() {
1830        let line = vec![Segment::new("hello")];
1831        assert_eq!(Segment::get_line_length(&line), 5);
1832    }
1833
1834    #[test]
1835    fn test_get_line_length_multiple() {
1836        let line = vec![Segment::new("hello"), Segment::new(" world")];
1837        assert_eq!(Segment::get_line_length(&line), 11);
1838    }
1839
1840    #[test]
1841    fn test_get_line_length_ignores_control() {
1842        let line = vec![
1843            Segment::new("hello"),
1844            Segment::control(ControlType::Bell),
1845            Segment::new("world"),
1846        ];
1847        assert_eq!(Segment::get_line_length(&line), 10);
1848    }
1849
1850    #[test]
1851    fn test_get_line_length_cjk() {
1852        let line = vec![Segment::new("你好")];
1853        assert_eq!(Segment::get_line_length(&line), 4);
1854    }
1855
1856    // ==================== get_shape tests ====================
1857
1858    #[test]
1859    fn test_get_shape_empty() {
1860        let lines: Vec<Vec<Segment>> = vec![];
1861        assert_eq!(Segment::get_shape(&lines), (0, 0));
1862    }
1863
1864    #[test]
1865    fn test_get_shape_single_line() {
1866        let lines = vec![vec![Segment::new("hello")]];
1867        assert_eq!(Segment::get_shape(&lines), (5, 1));
1868    }
1869
1870    #[test]
1871    fn test_get_shape_multiple_lines() {
1872        let lines = vec![
1873            vec![Segment::new("hello")],
1874            vec![Segment::new("world!")],
1875            vec![Segment::new("hi")],
1876        ];
1877        assert_eq!(Segment::get_shape(&lines), (6, 3));
1878    }
1879
1880    // ==================== set_shape tests ====================
1881
1882    #[test]
1883    fn test_set_shape_pad_width() {
1884        let lines = vec![vec![Segment::new("hi")]];
1885        let result = Segment::set_shape(&lines, 5, None, None, false);
1886        assert_eq!(result.len(), 1);
1887        assert_eq!(Segment::get_line_length(&result[0]), 5);
1888    }
1889
1890    #[test]
1891    fn test_set_shape_add_height() {
1892        let lines = vec![vec![Segment::new("hello")]];
1893        let result = Segment::set_shape(&lines, 5, Some(3), None, false);
1894        assert_eq!(result.len(), 3);
1895    }
1896
1897    #[test]
1898    fn test_set_shape_crop_height() {
1899        let lines = vec![
1900            vec![Segment::new("a")],
1901            vec![Segment::new("b")],
1902            vec![Segment::new("c")],
1903        ];
1904        let result = Segment::set_shape(&lines, 5, Some(2), None, false);
1905        assert_eq!(result.len(), 2);
1906    }
1907
1908    #[test]
1909    fn test_set_shape_with_newlines() {
1910        let lines = vec![vec![Segment::new("hi")]];
1911        let result = Segment::set_shape(&lines, 5, Some(2), None, true);
1912        assert_eq!(result.len(), 2);
1913        // Blank lines should contain newline
1914        let blank_text = result[1]
1915            .iter()
1916            .map(|s| s.text.to_string())
1917            .collect::<String>();
1918        assert!(blank_text.ends_with('\n'));
1919    }
1920
1921    #[test]
1922    fn test_set_shape_with_style() {
1923        let style = Style::new().with_bold(true);
1924        let lines: Vec<Vec<Segment>> = vec![];
1925        let result = Segment::set_shape(&lines, 5, Some(1), Some(style), false);
1926        assert_eq!(result.len(), 1);
1927        assert_eq!(result[0][0].style, Some(style));
1928    }
1929
1930    // ==================== vertical alignment tests ====================
1931
1932    #[test]
1933    fn test_align_top() {
1934        let lines = vec![vec![Segment::new("hello")]];
1935        let result = Segment::align_top(&lines, 5, 3, None, false);
1936        assert_eq!(result.len(), 3);
1937        assert_eq!(&*result[0][0].text, "hello");
1938        // Blank lines
1939        assert_eq!(Segment::get_line_length(&result[1]), 5);
1940        assert_eq!(Segment::get_line_length(&result[2]), 5);
1941    }
1942
1943    #[test]
1944    fn test_align_bottom() {
1945        let lines = vec![vec![Segment::new("hello")]];
1946        let result = Segment::align_bottom(&lines, 5, 3, None, false);
1947        assert_eq!(result.len(), 3);
1948        // Content at bottom
1949        assert_eq!(&*result[2][0].text, "hello");
1950        // Blank lines at top
1951        assert_eq!(Segment::get_line_length(&result[0]), 5);
1952        assert_eq!(Segment::get_line_length(&result[1]), 5);
1953    }
1954
1955    #[test]
1956    fn test_align_middle() {
1957        let lines = vec![vec![Segment::new("hello")]];
1958        let result = Segment::align_middle(&lines, 5, 3, None, false);
1959        assert_eq!(result.len(), 3);
1960        // Content in the middle
1961        assert_eq!(&*result[1][0].text, "hello");
1962    }
1963
1964    #[test]
1965    fn test_align_no_extra_lines() {
1966        let lines = vec![
1967            vec![Segment::new("a")],
1968            vec![Segment::new("b")],
1969            vec![Segment::new("c")],
1970        ];
1971        let result = Segment::align_top(&lines, 5, 3, None, false);
1972        assert_eq!(result.len(), 3);
1973    }
1974
1975    // ==================== split_lines_terminator tests ====================
1976
1977    #[test]
1978    fn test_split_lines_terminator_basic() {
1979        let segments = vec![Segment::new("hello\nworld")];
1980        let lines = Segment::split_lines_terminator(segments);
1981        assert_eq!(lines.len(), 2);
1982        assert!(lines[0].1); // newline was found
1983        assert!(!lines[1].1); // end of content, no newline
1984        assert_eq!(&*lines[0].0[0].text, "hello");
1985        assert_eq!(&*lines[1].0[0].text, "world");
1986    }
1987
1988    #[test]
1989    fn test_split_lines_terminator_trailing_newline() {
1990        let segments = vec![Segment::new("hello\n")];
1991        let lines = Segment::split_lines_terminator(segments);
1992        assert_eq!(lines.len(), 1);
1993        assert!(lines[0].1);
1994    }
1995
1996    // ==================== strip_links tests ====================
1997
1998    #[test]
1999    fn test_strip_links() {
2000        let meta = StyleMeta::with_link("https://example.com");
2001        let segments = vec![
2002            Segment::styled_with_meta("link", Style::new().with_bold(true), meta),
2003            Segment::new("plain"),
2004        ];
2005        let result = Segment::strip_links(segments);
2006        for seg in result.iter() {
2007            assert!(seg.meta.is_none());
2008        }
2009    }
2010
2011    // ==================== remove_color tests ====================
2012
2013    #[test]
2014    fn test_remove_color() {
2015        let style = Style::new()
2016            .with_bold(true)
2017            .with_color(crate::SimpleColor::Standard(1));
2018        let segments = vec![Segment::styled("hello", style)];
2019        let result = Segment::remove_color(segments);
2020        let seg = result.iter().next().unwrap();
2021        assert_eq!(seg.style.unwrap().bold, Some(true));
2022        assert_eq!(seg.style.unwrap().color, None);
2023    }
2024
2025    // ==================== SegmentLines tests ====================
2026
2027    #[test]
2028    fn test_segment_lines_to_segments() {
2029        let lines = vec![vec![Segment::new("hello")], vec![Segment::new("world")]];
2030        let sl = SegmentLines::new(lines, true);
2031        let segs = sl.to_segments();
2032        // 2 content segments + 2 newlines
2033        assert_eq!(segs.len(), 4);
2034    }
2035
2036    #[test]
2037    fn test_segment_lines_no_newlines() {
2038        let lines = vec![vec![Segment::new("hello")], vec![Segment::new("world")]];
2039        let sl = SegmentLines::new(lines, false);
2040        let segs = sl.to_segments();
2041        assert_eq!(segs.len(), 2);
2042    }
2043
2044    // ==================== Send + Sync compile-time assertions ====================
2045
2046    /// Compile-time assertion that Segment is Send + Sync.
2047    /// This test ensures that if a future field breaks these traits, the build will fail.
2048    #[test]
2049    fn test_segment_is_send_sync() {
2050        fn assert_send<T: Send>() {}
2051        fn assert_sync<T: Sync>() {}
2052        assert_send::<Segment>();
2053        assert_sync::<Segment>();
2054    }
2055
2056    /// Compile-time assertion that Segments is Send + Sync.
2057    #[test]
2058    fn test_segments_is_send_sync() {
2059        fn assert_send<T: Send>() {}
2060        fn assert_sync<T: Sync>() {}
2061        assert_send::<Segments>();
2062        assert_sync::<Segments>();
2063    }
2064}