Skip to main content

gilt/
segment.rs

1//! Segment - the atomic unit of terminal rendering.
2//!
3//! All content flows through segments, which combine text, style, and control codes.
4
5use compact_str::CompactString;
6
7use crate::cells::{cell_len, get_character_cell_size, is_single_cell_widths, set_cell_size};
8use crate::style::Style;
9
10/// Terminal control code types.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12#[repr(u8)]
13pub enum ControlType {
14    /// Emit audible bell (BEL, `\x07`).
15    Bell = 1,
16    /// Move cursor to the beginning of the current line.
17    CarriageReturn = 2,
18    /// Move cursor to the top-left corner of the terminal.
19    Home = 3,
20    /// Clear the entire terminal screen.
21    Clear = 4,
22    /// Make the terminal cursor visible.
23    ShowCursor = 5,
24    /// Hide the terminal cursor.
25    HideCursor = 6,
26    /// Switch to the alternate screen buffer.
27    EnableAltScreen = 7,
28    /// Return to the primary screen buffer.
29    DisableAltScreen = 8,
30    /// Move cursor up by a given number of rows.
31    CursorUp = 9,
32    /// Move cursor down by a given number of rows.
33    CursorDown = 10,
34    /// Move cursor forward (right) by a given number of columns.
35    CursorForward = 11,
36    /// Move cursor backward (left) by a given number of columns.
37    CursorBackward = 12,
38    /// Move cursor to a specific column on the current line.
39    CursorMoveToColumn = 13,
40    /// Move cursor to an absolute (column, row) position.
41    CursorMoveTo = 14,
42    /// Erase content on the current line.
43    EraseInLine = 15,
44    /// Set the terminal window title via an OSC sequence.
45    SetWindowTitle = 16,
46    /// Begin synchronized output (DEC 2026).
47    BeginSync = 17,
48    /// End synchronized output (DEC 2026).
49    EndSync = 18,
50    /// Copy content to the system clipboard via OSC 52.
51    SetClipboard = 19,
52    /// Request the current clipboard contents via OSC 52.
53    RequestClipboard = 20,
54}
55
56/// Terminal control code with optional parameters.
57#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub enum ControlCode {
59    /// A control code with no parameters (e.g., Bell, Clear).
60    Simple(ControlType),
61    /// A control code with a single integer parameter (e.g., CursorUp with row count).
62    WithParam(ControlType, i32),
63    /// A control code with a single string parameter (e.g., SetWindowTitle with a title).
64    WithParamStr(ControlType, String),
65    /// A control code with two integer parameters (e.g., CursorMoveTo with column and row).
66    WithTwoParams(ControlType, i32, i32),
67}
68
69/// A segment of terminal content with text, style, and optional control codes.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Segment {
72    /// The text content of this segment.
73    pub text: CompactString,
74    /// Visual style. **Crate-private** as of v0.11.0 — accessed via
75    /// [`Segment::style`], [`Segment::set_style`], etc. Storage type stays
76    /// `Option<Style>` in alpha.2; will become `StyleId` in PR3 when the
77    /// L2 interner activates. Hiding the field now decouples the storage
78    /// swap from the API change.
79    pub(crate) style: Option<Style>,
80    /// Terminal control codes carried by this segment, or `None` for text-only segments.
81    pub control: Option<Vec<ControlCode>>,
82}
83
84impl Segment {
85    /// Creates a new segment with text, style, and control codes.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use gilt::segment::Segment;
91    /// use gilt::style::Style;
92    ///
93    /// let seg = Segment::new("hello", Some(Style::parse("bold")), None);
94    /// assert_eq!(seg.text, "hello");
95    /// assert!(!seg.is_control());
96    /// ```
97    pub fn new(text: &str, style: Option<Style>, control: Option<Vec<ControlCode>>) -> Self {
98        Segment {
99            text: CompactString::from(text),
100            style,
101            control,
102        }
103    }
104
105    /// Creates a plain text segment with no style or control codes.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use gilt::segment::Segment;
111    ///
112    /// let seg = Segment::text("hello");
113    /// assert_eq!(seg.text, "hello");
114    /// assert!(seg.style().is_none());
115    /// ```
116    pub fn text(text: &str) -> Self {
117        Segment {
118            text: CompactString::from(text),
119            style: None,
120            control: None,
121        }
122    }
123
124    /// Creates a newline segment.
125    pub fn line() -> Self {
126        Segment::text("\n")
127    }
128
129    /// Creates a segment with text and style.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use gilt::segment::Segment;
135    /// use gilt::style::Style;
136    ///
137    /// let seg = Segment::styled("warning", Style::parse("bold yellow"));
138    /// assert_eq!(seg.text, "warning");
139    /// assert!(seg.style().is_some());
140    /// ```
141    pub fn styled(text: &str, style: Style) -> Self {
142        Segment {
143            text: CompactString::from(text),
144            style: Some(style),
145            control: None,
146        }
147    }
148
149    /// Returns the cell length of this segment (0 for control segments).
150    ///
151    /// Double-width characters (CJK, emoji) count as 2 cells each.
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use gilt::segment::Segment;
157    ///
158    /// assert_eq!(Segment::text("abc").cell_length(), 3);
159    /// assert_eq!(Segment::text("\u{1F4A9}").cell_length(), 2); // emoji = 2 cells
160    /// ```
161    pub fn cell_length(&self) -> usize {
162        if self.is_control() {
163            0
164        } else {
165            cell_len(&self.text)
166        }
167    }
168
169    /// Returns true if this is a control segment.
170    pub fn is_control(&self) -> bool {
171        self.control.is_some()
172    }
173
174    // -- Style accessors (added in v0.11.0-alpha.2) --------------------------
175
176    /// Borrow the segment's style, if any. Replaces direct field access in
177    /// v0.11.0; the underlying storage moves to `StyleId` in PR3 without
178    /// changing this signature.
179    #[inline]
180    pub fn style(&self) -> Option<&Style> {
181        self.style.as_ref()
182    }
183
184    /// Mutable borrow of the optional style. Used by the few in-tree call
185    /// sites that mutate a Segment's style after construction. Once PR3
186    /// activates the interner this becomes a no-op or is removed.
187    #[inline]
188    pub fn style_mut(&mut self) -> &mut Option<Style> {
189        &mut self.style
190    }
191
192    /// Replace the segment's style. Setter form for callers that previously
193    /// did `seg.style = Some(...)`.
194    #[inline]
195    pub fn set_style(&mut self, style: Option<Style>) {
196        self.style = style;
197    }
198
199    /// Owned-style legacy convenience.
200    ///
201    /// Returns the style as an owned [`Style`], substituting [`Style::null`]
202    /// for the `None` case so callers that previously did
203    /// `seg.style.clone().unwrap_or_else(Style::null)` collapse to a single
204    /// call.
205    ///
206    /// **Why this collapses None and Some(null) deliberately:** the L2
207    /// interner (PR3) will only have `StyleId::NULL` for both. Tests that
208    /// need to distinguish them should use [`Segment::style`] which returns
209    /// the borrowed `Option<&Style>` directly.
210    pub fn style_owned(&self) -> Style {
211        self.style.clone().unwrap_or_else(Style::null)
212    }
213
214    /// Returns true if the text is empty (for bool-like checks).
215    pub fn is_empty(&self) -> bool {
216        self.text.is_empty()
217    }
218
219    /// Splits the segment at a given cell position.
220    ///
221    /// If the cut position falls in the middle of a double-width character,
222    /// it will be replaced with spaces on both sides of the split.
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// use gilt::segment::Segment;
228    ///
229    /// let seg = Segment::text("Hello");
230    /// let (left, right) = seg.split_cells(2);
231    /// assert_eq!(left.text, "He");
232    /// assert_eq!(right.text, "llo");
233    /// ```
234    pub fn split_cells(&self, cut: usize) -> (Segment, Segment) {
235        let text_len = self.text.len();
236        let cell_length = cell_len(&self.text);
237
238        // Fast path: if cut is beyond text length, return (self, empty)
239        if cut >= cell_length {
240            return (self.clone(), Segment::new("", self.style.clone(), None));
241        }
242
243        // Fast path: ASCII only
244        if is_single_cell_widths(&self.text) {
245            let byte_pos = cut.min(text_len);
246            return (
247                Segment::new(&self.text[..byte_pos], self.style.clone(), None),
248                Segment::new(&self.text[byte_pos..], self.style.clone(), None),
249            );
250        }
251
252        // General case: iterate through characters
253        let mut cell_pos = 0;
254
255        for (idx, ch) in self.text.char_indices() {
256            let char_width = get_character_cell_size(ch);
257
258            if cell_pos == cut {
259                // Exact match
260                return (
261                    Segment::new(&self.text[..idx], self.style.clone(), None),
262                    Segment::new(&self.text[idx..], self.style.clone(), None),
263                );
264            } else if cell_pos + char_width > cut {
265                // Would overflow: double-width char straddling the cut
266                // Replace with spaces
267                let before = format!("{} ", &self.text[..idx]);
268                let after = format!(" {}", &self.text[idx + ch.len_utf8()..]);
269                return (
270                    Segment::new(&before, self.style.clone(), None),
271                    Segment::new(&after, self.style.clone(), None),
272                );
273            }
274
275            cell_pos += char_width;
276        }
277
278        // Shouldn't reach here, but handle edge case
279        (self.clone(), Segment::new("", self.style.clone(), None))
280    }
281
282    /// Applies a base style and/or post style to a list of segments.
283    ///
284    /// The `style` is applied *underneath* each segment's existing style (as a base),
285    /// while `post_style` is applied *on top* of the result.
286    /// Control segments are passed through unchanged.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// use gilt::segment::Segment;
292    /// use gilt::style::Style;
293    ///
294    /// let segments = vec![Segment::text("hello")];
295    /// let styled = Segment::apply_style(
296    ///     &segments,
297    ///     Some(Style::parse("bold")),
298    ///     None,
299    /// );
300    /// assert!(styled[0].style().is_some());
301    /// ```
302    pub fn apply_style(
303        segments: &[Segment],
304        style: Option<Style>,
305        post_style: Option<Style>,
306    ) -> Vec<Segment> {
307        if style.is_none() && post_style.is_none() {
308            return segments.to_vec();
309        }
310
311        segments
312            .iter()
313            .map(|seg| {
314                if seg.is_control() {
315                    seg.clone()
316                } else {
317                    let mut new_style = seg.style.clone();
318
319                    if let Some(ref base) = style {
320                        new_style = Some(base.clone() + new_style);
321                    }
322
323                    if let Some(ref post) = post_style {
324                        new_style = Some(new_style.unwrap_or_else(Style::null) + post.clone());
325                    }
326
327                    Segment::new(&seg.text, new_style, None)
328                }
329            })
330            .collect()
331    }
332
333    /// Filters segments by control flag.
334    ///
335    /// Pass `true` to keep only control segments, or `false` to keep only text segments.
336    ///
337    /// # Examples
338    ///
339    /// ```
340    /// use gilt::segment::{Segment, ControlCode, ControlType};
341    ///
342    /// let segments = vec![
343    ///     Segment::text("hello"),
344    ///     Segment::new("", None, Some(vec![ControlCode::Simple(ControlType::Bell)])),
345    /// ];
346    /// let text_only = Segment::filter_control(&segments, false);
347    /// assert_eq!(text_only.len(), 1);
348    /// assert_eq!(text_only[0].text, "hello");
349    /// ```
350    pub fn filter_control(segments: &[Segment], is_control: bool) -> Vec<Segment> {
351        segments
352            .iter()
353            .filter(|seg| seg.is_control() == is_control)
354            .cloned()
355            .collect()
356    }
357
358    /// Splits segments at newline boundaries.
359    ///
360    /// Each `\n` in the text produces a new line. Control segments are kept
361    /// with the line they appear in.
362    ///
363    /// # Examples
364    ///
365    /// ```
366    /// use gilt::segment::Segment;
367    ///
368    /// let segments = vec![Segment::text("Hello\nWorld")];
369    /// let lines = Segment::split_lines(&segments);
370    /// assert_eq!(lines.len(), 2);
371    /// assert_eq!(lines[0][0].text, "Hello");
372    /// assert_eq!(lines[1][0].text, "World");
373    /// ```
374    pub fn split_lines(segments: &[Segment]) -> Vec<Vec<Segment>> {
375        let mut lines = Vec::new();
376        let mut current_line = Vec::new();
377
378        for segment in segments {
379            if segment.is_control() {
380                current_line.push(segment.clone());
381            } else {
382                let parts: Vec<&str> = segment.text.split('\n').collect();
383
384                for (i, part) in parts.iter().enumerate() {
385                    if i > 0 {
386                        lines.push(current_line);
387                        current_line = Vec::new();
388                    }
389
390                    if !part.is_empty() {
391                        current_line.push(Segment::new(part, segment.style.clone(), None));
392                    }
393                }
394
395                // Handle trailing newline
396                if segment.text.ends_with('\n') && !parts.is_empty() {
397                    lines.push(current_line);
398                    current_line = Vec::new();
399                }
400            }
401        }
402
403        if !current_line.is_empty() || lines.is_empty() {
404            lines.push(current_line);
405        }
406
407        lines
408    }
409
410    /// Adjusts a line to a specific cell length by cropping or padding.
411    ///
412    /// If the line is shorter than `length` and `pad` is `true`, space characters
413    /// with the given `style` are appended. If the line is longer, it is cropped.
414    ///
415    /// # Examples
416    ///
417    /// ```
418    /// use gilt::segment::Segment;
419    /// use gilt::style::Style;
420    ///
421    /// let line = vec![Segment::text("Hi")];
422    /// let padded = Segment::adjust_line_length(&line, 5, &Style::null(), true);
423    /// assert_eq!(Segment::get_line_length(&padded), 5);
424    /// ```
425    pub fn adjust_line_length(
426        line: &[Segment],
427        length: usize,
428        style: &Style,
429        pad: bool,
430    ) -> Vec<Segment> {
431        let line_length = Segment::get_line_length(line);
432
433        if line_length == length {
434            return line.to_vec();
435        }
436
437        if line_length < length {
438            if pad {
439                let mut result = line.to_vec();
440                let spaces = " ".repeat(length - line_length);
441                result.push(Segment::styled(&spaces, style.clone()));
442                result
443            } else {
444                line.to_vec()
445            }
446        } else {
447            // Need to crop
448            let mut result = Vec::new();
449            let mut current_length = 0;
450
451            for segment in line {
452                if segment.is_control() {
453                    result.push(segment.clone());
454                    continue;
455                }
456
457                let segment_length = segment.cell_length();
458
459                if current_length + segment_length <= length {
460                    result.push(segment.clone());
461                    current_length += segment_length;
462                } else {
463                    // This segment needs cropping
464                    let remaining = length - current_length;
465                    if remaining > 0 {
466                        let cropped_text = set_cell_size(&segment.text, remaining);
467                        result.push(Segment::new(&cropped_text, segment.style.clone(), None));
468                    }
469                    break;
470                }
471            }
472
473            result
474        }
475    }
476
477    /// Returns the total cell length of a line of segments.
478    ///
479    /// Control segments are excluded from the count.
480    ///
481    /// # Examples
482    ///
483    /// ```
484    /// use gilt::segment::Segment;
485    ///
486    /// let line = vec![Segment::text("foo"), Segment::text("bar")];
487    /// assert_eq!(Segment::get_line_length(&line), 6);
488    /// ```
489    pub fn get_line_length(line: &[Segment]) -> usize {
490        line.iter()
491            .filter(|seg| !seg.is_control())
492            .map(|seg| seg.cell_length())
493            .sum()
494    }
495
496    /// Returns the shape of multiple lines as `(max_width, height)`.
497    ///
498    /// # Examples
499    ///
500    /// ```
501    /// use gilt::segment::Segment;
502    ///
503    /// let lines = vec![
504    ///     vec![Segment::text("Hello")],
505    ///     vec![Segment::text("World!")],
506    /// ];
507    /// assert_eq!(Segment::get_shape(&lines), (6, 2));
508    /// ```
509    pub fn get_shape(lines: &[Vec<Segment>]) -> (usize, usize) {
510        let max_width = lines
511            .iter()
512            .map(|line| Segment::get_line_length(line))
513            .max()
514            .unwrap_or(0);
515        let height = lines.len();
516        (max_width, height)
517    }
518
519    /// Adjusts all lines to given dimensions.
520    ///
521    /// Each line is padded or cropped to `width`. If `height` is provided, extra
522    /// blank lines are appended (or excess lines are truncated) to match.
523    pub fn set_shape(
524        lines: &[Vec<Segment>],
525        width: usize,
526        height: Option<usize>,
527        style: Option<&Style>,
528        _new_lines: bool,
529    ) -> Vec<Vec<Segment>> {
530        let default_style = Style::null();
531        let style = style.unwrap_or(&default_style);
532
533        let mut shaped_lines: Vec<Vec<Segment>> = lines
534            .iter()
535            .map(|line| Segment::adjust_line_length(line, width, style, true))
536            .collect();
537
538        if let Some(target_height) = height {
539            if shaped_lines.len() < target_height {
540                let empty_line = vec![Segment::styled(&" ".repeat(width), style.clone())];
541                while shaped_lines.len() < target_height {
542                    shaped_lines.push(empty_line.clone());
543                }
544            } else if shaped_lines.len() > target_height {
545                shaped_lines.truncate(target_height);
546            }
547        }
548
549        shaped_lines
550    }
551
552    /// Merges consecutive segments with the same style.
553    ///
554    /// Adjacent non-control segments that share identical style and control values
555    /// are concatenated into a single segment, reducing allocation overhead.
556    ///
557    /// # Examples
558    ///
559    /// ```
560    /// use gilt::segment::Segment;
561    ///
562    /// let segments = vec![
563    ///     Segment::text("Hello"),
564    ///     Segment::text(" "),
565    ///     Segment::text("World!"),
566    /// ];
567    /// let simplified = Segment::simplify(&segments);
568    /// assert_eq!(simplified.len(), 1);
569    /// assert_eq!(simplified[0].text, "Hello World!");
570    /// ```
571    pub fn simplify(segments: &[Segment]) -> Vec<Segment> {
572        if segments.is_empty() {
573            return Vec::new();
574        }
575
576        let mut result = Vec::new();
577        let mut current = segments[0].clone();
578
579        for segment in &segments[1..] {
580            if !current.is_control()
581                && !segment.is_control()
582                && current.style == segment.style
583                && current.control == segment.control
584            {
585                current.text.push_str(&segment.text);
586            } else {
587                result.push(current);
588                current = segment.clone();
589            }
590        }
591
592        result.push(current);
593        result
594    }
595
596    /// Removes hyperlink metadata from segment styles, preserving all other attributes.
597    pub fn strip_links(segments: &[Segment]) -> Vec<Segment> {
598        segments
599            .iter()
600            .map(|seg| {
601                if let Some(ref style) = seg.style {
602                    if style.link().is_some() {
603                        let new_style = style.update_link(None);
604                        return Segment::new(&seg.text, Some(new_style), seg.control.clone());
605                    }
606                }
607                seg.clone()
608            })
609            .collect()
610    }
611
612    /// Removes all styles from segments, leaving plain text.
613    pub fn strip_styles(segments: &[Segment]) -> Vec<Segment> {
614        segments
615            .iter()
616            .map(|seg| Segment::new(&seg.text, None, seg.control.clone()))
617            .collect()
618    }
619
620    /// Removes foreground and background colors from segment styles while preserving
621    /// other attributes such as bold, italic, and underline.
622    pub fn remove_color(segments: &[Segment]) -> Vec<Segment> {
623        segments
624            .iter()
625            .map(|seg| {
626                if let Some(ref style) = seg.style {
627                    let new_style = style.without_color();
628                    Segment::new(&seg.text, Some(new_style), seg.control.clone())
629                } else {
630                    seg.clone()
631                }
632            })
633            .collect()
634    }
635
636    /// Divides segments into portions at given cell positions.
637    ///
638    /// Each value in `cuts` specifies a cumulative cell offset where the segment
639    /// list should be split. Returns one `Vec<Segment>` per cut.
640    ///
641    /// # Examples
642    ///
643    /// ```
644    /// use gilt::segment::Segment;
645    ///
646    /// let segments = vec![Segment::text("ABCDE")];
647    /// let parts = Segment::divide(&segments, &[2, 5]);
648    /// assert_eq!(parts[0][0].text, "AB");
649    /// assert_eq!(parts[1][0].text, "CDE");
650    /// ```
651    pub fn divide(segments: &[Segment], cuts: &[usize]) -> Vec<Vec<Segment>> {
652        if cuts.is_empty() {
653            return Vec::new();
654        }
655
656        if segments.is_empty() {
657            return vec![vec![]; cuts.len()];
658        }
659
660        let mut result = Vec::new();
661        let mut current_portion = Vec::new();
662        let mut cell_position = 0;
663        let mut cut_index = 0;
664
665        // Track remaining segments to process
666        let mut remaining_segments: Vec<Segment> = segments.to_vec();
667        let mut seg_idx = 0;
668
669        while cut_index < cuts.len() && seg_idx < remaining_segments.len() {
670            let cut = cuts[cut_index];
671
672            while seg_idx < remaining_segments.len() && cell_position < cut {
673                let segment = &remaining_segments[seg_idx];
674
675                if segment.is_control() {
676                    current_portion.push(segment.clone());
677                    seg_idx += 1;
678                    continue;
679                }
680
681                let segment_length = segment.cell_length();
682                let segment_end = cell_position + segment_length;
683
684                if segment_end <= cut {
685                    // Entire segment fits in current portion
686                    current_portion.push(segment.clone());
687                    cell_position = segment_end;
688                    seg_idx += 1;
689                } else {
690                    // Need to split this segment
691                    let offset = cut - cell_position;
692                    let (before, after) = segment.split_cells(offset);
693
694                    if !before.is_empty() {
695                        current_portion.push(before);
696                    }
697
698                    // Replace current segment with the remainder
699                    if !after.is_empty() {
700                        remaining_segments[seg_idx] = after;
701                    } else {
702                        seg_idx += 1;
703                    }
704
705                    cell_position = cut;
706                    break;
707                }
708            }
709
710            result.push(current_portion);
711            current_portion = Vec::new();
712            cut_index += 1;
713        }
714
715        result
716    }
717
718    /// Aligns lines to the top of a given height, padding with blank lines below.
719    pub fn align_top(
720        lines: &[Vec<Segment>],
721        width: usize,
722        height: usize,
723        style: &Style,
724        new_lines: bool,
725    ) -> Vec<Vec<Segment>> {
726        Segment::set_shape(lines, width, Some(height), Some(style), new_lines)
727    }
728
729    /// Aligns lines to the bottom of a given height, padding with blank lines above.
730    pub fn align_bottom(
731        lines: &[Vec<Segment>],
732        width: usize,
733        height: usize,
734        style: &Style,
735        new_lines: bool,
736    ) -> Vec<Vec<Segment>> {
737        let mut shaped = Segment::set_shape(lines, width, Some(height), Some(style), new_lines);
738
739        if lines.len() < height {
740            let padding = height - lines.len();
741            let empty_line = vec![Segment::styled(&" ".repeat(width), style.clone())];
742            let mut padding_lines = vec![empty_line; padding];
743            padding_lines.extend(
744                lines
745                    .iter()
746                    .map(|line| Segment::adjust_line_length(line, width, style, true)),
747            );
748            shaped = padding_lines;
749        }
750
751        shaped
752    }
753
754    /// Aligns lines vertically centered within a given height, padding equally above and below.
755    pub fn align_middle(
756        lines: &[Vec<Segment>],
757        width: usize,
758        height: usize,
759        style: &Style,
760        new_lines: bool,
761    ) -> Vec<Vec<Segment>> {
762        if lines.len() >= height {
763            return Segment::set_shape(lines, width, Some(height), Some(style), new_lines);
764        }
765
766        let padding = height - lines.len();
767        let top_padding = padding / 2;
768        let bottom_padding = padding - top_padding;
769
770        let empty_line = vec![Segment::styled(&" ".repeat(width), style.clone())];
771        let mut result = vec![empty_line.clone(); top_padding];
772
773        for line in lines {
774            result.push(Segment::adjust_line_length(line, width, style, true));
775        }
776
777        for _ in 0..bottom_padding {
778            result.push(empty_line.clone());
779        }
780
781        result
782    }
783
784    /// Split segments into lines on newlines, then adjust each line to the given width.
785    ///
786    /// Port of the `Segment.split_and_crop_lines`.
787    ///
788    /// # Examples
789    ///
790    /// ```
791    /// use gilt::segment::Segment;
792    ///
793    /// let segments = vec![Segment::text("Hello\nWorld")];
794    /// let lines = Segment::split_and_crop_lines(&segments, 10, None, true, false);
795    /// assert_eq!(lines.len(), 2);
796    /// assert_eq!(Segment::get_line_length(&lines[0]), 10);
797    /// ```
798    pub fn split_and_crop_lines(
799        segments: &[Segment],
800        length: usize,
801        style: Option<&Style>,
802        pad: bool,
803        include_new_lines: bool,
804    ) -> Vec<Vec<Segment>> {
805        let mut result = Vec::new();
806        let mut line: Vec<Segment> = Vec::new();
807
808        for segment in segments {
809            if segment.text.contains('\n') && segment.control.is_none() {
810                let seg_style = segment.style.clone();
811                let mut remaining = segment.text.as_str();
812                while !remaining.is_empty() {
813                    if let Some(pos) = remaining.find('\n') {
814                        let before = &remaining[..pos];
815                        if !before.is_empty() {
816                            line.push(Segment::new(before, seg_style.clone(), None));
817                        }
818                        let mut cropped = Segment::adjust_line_length(
819                            &line,
820                            length,
821                            &style.cloned().unwrap_or_else(Style::null),
822                            pad,
823                        );
824                        if include_new_lines {
825                            cropped.push(Segment::line());
826                        }
827                        result.push(cropped);
828                        line.clear();
829                        remaining = &remaining[pos + 1..];
830                    } else {
831                        if !remaining.is_empty() {
832                            line.push(Segment::new(remaining, seg_style.clone(), None));
833                        }
834                        break;
835                    }
836                }
837            } else {
838                line.push(segment.clone());
839            }
840        }
841        if !line.is_empty() {
842            let cropped = Segment::adjust_line_length(
843                &line,
844                length,
845                &style.cloned().unwrap_or_else(Style::null),
846                pad,
847            );
848            result.push(cropped);
849        }
850        result
851    }
852
853    /// Split segments into lines, returning each line with a boolean indicating
854    /// whether it was terminated by a newline character.
855    ///
856    /// Port of the `Segment.split_lines_terminator`.
857    ///
858    /// # Examples
859    ///
860    /// ```
861    /// use gilt::segment::Segment;
862    ///
863    /// let segments = vec![Segment::text("Hello\nWorld")];
864    /// let lines = Segment::split_lines_terminator(&segments);
865    /// assert_eq!(lines[0].1, true);  // "Hello" was followed by \n
866    /// assert_eq!(lines[1].1, false); // "World" was not
867    /// ```
868    pub fn split_lines_terminator(segments: &[Segment]) -> Vec<(Vec<Segment>, bool)> {
869        let mut result = Vec::new();
870        let mut line: Vec<Segment> = Vec::new();
871
872        for segment in segments {
873            if segment.text.contains('\n') && segment.control.is_none() {
874                let seg_style = segment.style.clone();
875                let mut remaining = segment.text.as_str();
876                while !remaining.is_empty() {
877                    if let Some(pos) = remaining.find('\n') {
878                        let before = &remaining[..pos];
879                        if !before.is_empty() {
880                            line.push(Segment::new(before, seg_style.clone(), None));
881                        }
882                        result.push((std::mem::take(&mut line), true));
883                        remaining = &remaining[pos + 1..];
884                    } else {
885                        if !remaining.is_empty() {
886                            line.push(Segment::new(remaining, seg_style.clone(), None));
887                        }
888                        break;
889                    }
890                }
891            } else {
892                line.push(segment.clone());
893            }
894        }
895        if !line.is_empty() {
896            result.push((line, false));
897        }
898        result
899    }
900}
901
902#[cfg(test)]
903mod tests {
904    use super::*;
905
906    #[test]
907    fn test_line() {
908        assert_eq!(Segment::line(), Segment::text("\n"));
909    }
910
911    #[test]
912    fn test_apply_style() {
913        let segments = vec![
914            Segment::text("foo"),
915            Segment::styled("bar", Style::parse("bold")),
916        ];
917        let result = Segment::apply_style(&segments, Some(Style::parse("italic")), None);
918        assert_eq!(
919            result,
920            vec![
921                Segment::styled("foo", Style::parse("italic")),
922                Segment::styled("bar", Style::parse("italic bold")),
923            ]
924        );
925    }
926
927    #[test]
928    fn test_split_lines() {
929        let lines = vec![Segment::text("Hello\nWorld")];
930        let result = Segment::split_lines(&lines);
931        assert_eq!(
932            result,
933            vec![vec![Segment::text("Hello")], vec![Segment::text("World")]]
934        );
935    }
936
937    #[test]
938    fn test_adjust_line_length_pad() {
939        let line = vec![Segment::text("Hello")];
940        let style = Style::parse("red");
941        let result = Segment::adjust_line_length(&line, 10, &style, true);
942        assert_eq!(Segment::get_line_length(&result), 10);
943    }
944
945    #[test]
946    fn test_adjust_line_length_crop() {
947        let line = vec![Segment::text("H"), Segment::text("ello, World!")];
948        let result = Segment::adjust_line_length(&line, 5, &Style::null(), true);
949        assert_eq!(Segment::get_line_length(&result), 5);
950    }
951
952    #[test]
953    fn test_get_line_length() {
954        assert_eq!(
955            Segment::get_line_length(&[Segment::text("foo"), Segment::text("bar")]),
956            6
957        );
958    }
959
960    #[test]
961    fn test_get_shape() {
962        assert_eq!(Segment::get_shape(&[vec![Segment::text("Hello")]]), (5, 1));
963        assert_eq!(
964            Segment::get_shape(&[vec![Segment::text("Hello")], vec![Segment::text("World!")]]),
965            (6, 2)
966        );
967    }
968
969    #[test]
970    fn test_simplify() {
971        let segments = vec![
972            Segment::text("Hello"),
973            Segment::text(" "),
974            Segment::text("World!"),
975        ];
976        assert_eq!(
977            Segment::simplify(&segments),
978            vec![Segment::text("Hello World!")]
979        );
980    }
981
982    #[test]
983    fn test_filter_control() {
984        let control_code = vec![ControlCode::WithParam(ControlType::Home, 0)];
985        let segments = vec![
986            Segment::text("foo"),
987            Segment::new("bar", None, Some(control_code.clone())),
988        ];
989        assert_eq!(
990            Segment::filter_control(&segments, false),
991            vec![Segment::text("foo")]
992        );
993    }
994
995    #[test]
996    fn test_strip_styles() {
997        let segments = vec![Segment::styled("foo", Style::parse("bold"))];
998        assert_eq!(Segment::strip_styles(&segments), vec![Segment::text("foo")]);
999    }
1000
1001    #[test]
1002    fn test_strip_links() {
1003        let segments = vec![Segment::styled(
1004            "foo",
1005            Style::parse("bold link https://www.example.org"),
1006        )];
1007        let result = Segment::strip_links(&segments);
1008        assert_eq!(result[0].style.as_ref().unwrap().link(), None);
1009        assert_eq!(result[0].style.as_ref().unwrap().bold(), Some(true));
1010    }
1011
1012    #[test]
1013    fn test_remove_color() {
1014        let segments = vec![
1015            Segment::styled("foo", Style::parse("bold red")),
1016            Segment::text("bar"),
1017        ];
1018        let result = Segment::remove_color(&segments);
1019        assert_eq!(result[0].style.as_ref().unwrap().color(), None);
1020        assert_eq!(result[0].style.as_ref().unwrap().bold(), Some(true));
1021    }
1022
1023    #[test]
1024    fn test_is_control() {
1025        assert!(!Segment::text("foo").is_control());
1026        assert!(Segment::new("foo", None, Some(vec![])).is_control());
1027    }
1028
1029    #[test]
1030    fn test_divide() {
1031        let bold = Style::parse("bold");
1032        let italic = Style::parse("italic");
1033        let segments = vec![
1034            Segment::styled("Hello", bold.clone()),
1035            Segment::styled(" World!", italic.clone()),
1036        ];
1037        assert_eq!(Segment::divide(&segments, &[]), Vec::<Vec<Segment>>::new());
1038        assert_eq!(Segment::divide(&[], &[1]), vec![vec![]]);
1039        assert_eq!(
1040            Segment::divide(&segments, &[1]),
1041            vec![vec![Segment::styled("H", bold.clone())]]
1042        );
1043        assert_eq!(
1044            Segment::divide(&segments, &[4, 20]),
1045            vec![
1046                vec![Segment::styled("Hell", bold.clone())],
1047                vec![
1048                    Segment::styled("o", bold.clone()),
1049                    Segment::styled(" World!", italic.clone())
1050                ],
1051            ]
1052        );
1053    }
1054
1055    #[test]
1056    fn test_split_cells_emoji() {
1057        let segment = Segment::text("💩");
1058        let (before, after) = segment.split_cells(1);
1059        assert_eq!(before.text, " ");
1060        assert_eq!(after.text, " ");
1061    }
1062
1063    #[test]
1064    fn test_split_cells_ascii() {
1065        let segment = Segment::text("XY");
1066        let (before, after) = segment.split_cells(1);
1067        assert_eq!(before.text, "X");
1068        assert_eq!(after.text, "Y");
1069    }
1070
1071    #[test]
1072    fn test_split_cells_mixed() {
1073        let segment = Segment::text("X💩Y");
1074        let (before, after) = segment.split_cells(2);
1075        assert_eq!(before.text, "X ");
1076        assert_eq!(after.text, " Y");
1077    }
1078
1079    #[test]
1080    fn test_align_top() {
1081        let lines = vec![vec![Segment::text("X")]];
1082        assert_eq!(
1083            Segment::align_top(&lines, 3, 1, &Style::null(), false),
1084            Segment::set_shape(&lines, 3, Some(1), Some(&Style::null()), false)
1085        );
1086        assert_eq!(
1087            Segment::align_top(&lines, 3, 3, &Style::null(), false).len(),
1088            3
1089        );
1090    }
1091
1092    #[test]
1093    fn test_align_middle() {
1094        let lines = vec![vec![Segment::text("X")]];
1095        let result = Segment::align_middle(&lines, 5, 3, &Style::null(), false);
1096        assert_eq!(result.len(), 3);
1097        // Middle alignment: 1 padding top, 1 content, 1 padding bottom
1098        assert_eq!(Segment::get_line_length(&result[0]), 5); // padding
1099        assert_eq!(Segment::get_line_length(&result[1]), 5); // content padded
1100        assert_eq!(Segment::get_line_length(&result[2]), 5); // padding
1101    }
1102
1103    #[test]
1104    fn test_align_bottom() {
1105        let lines = vec![vec![Segment::text("X")]];
1106        let result = Segment::align_bottom(&lines, 5, 3, &Style::null(), false);
1107        assert_eq!(result.len(), 3);
1108        // Bottom alignment: 2 padding, then content
1109        assert_eq!(Segment::get_line_length(&result[0]), 5); // padding
1110        assert_eq!(Segment::get_line_length(&result[1]), 5); // padding
1111        assert_eq!(Segment::get_line_length(&result[2]), 5); // content padded
1112    }
1113
1114    #[test]
1115    fn test_set_shape() {
1116        let result = Segment::set_shape(&[vec![Segment::text("Hello")]], 10, None, None, false);
1117        assert_eq!(Segment::get_line_length(&result[0]), 10);
1118    }
1119
1120    #[test]
1121    fn test_cell_length() {
1122        assert_eq!(Segment::text("abc").cell_length(), 3);
1123        assert_eq!(Segment::text("💩").cell_length(), 2);
1124        assert_eq!(
1125            Segment::new(
1126                "abc",
1127                None,
1128                Some(vec![ControlCode::Simple(ControlType::Bell)])
1129            )
1130            .cell_length(),
1131            0
1132        );
1133    }
1134
1135    #[test]
1136    fn test_split_lines_multiple_newlines() {
1137        let segments = vec![Segment::text("Hello\n\nWorld")];
1138        let result = Segment::split_lines(&segments);
1139        assert_eq!(result.len(), 3);
1140        assert_eq!(result[0], vec![Segment::text("Hello")]);
1141        assert_eq!(result[1], Vec::<Segment>::new());
1142        assert_eq!(result[2], vec![Segment::text("World")]);
1143    }
1144
1145    #[test]
1146    fn test_split_lines_trailing_newline() {
1147        let segments = vec![Segment::text("Hello\n")];
1148        let result = Segment::split_lines(&segments);
1149        assert_eq!(result.len(), 2);
1150        assert_eq!(result[0], vec![Segment::text("Hello")]);
1151        assert_eq!(result[1], Vec::<Segment>::new());
1152    }
1153
1154    #[test]
1155    fn test_simplify_different_styles() {
1156        let segments = vec![
1157            Segment::styled("Hello", Style::parse("bold")),
1158            Segment::styled("World", Style::parse("italic")),
1159        ];
1160        let result = Segment::simplify(&segments);
1161        assert_eq!(result.len(), 2); // Should not merge
1162    }
1163
1164    #[test]
1165    fn test_simplify_with_control() {
1166        let segments = vec![
1167            Segment::text("Hello"),
1168            Segment::new("", None, Some(vec![ControlCode::Simple(ControlType::Bell)])),
1169            Segment::text("World"),
1170        ];
1171        let result = Segment::simplify(&segments);
1172        assert_eq!(result.len(), 3); // Control segments should not be merged
1173    }
1174
1175    #[test]
1176    fn test_divide_empty_segments() {
1177        let result = Segment::divide(&[], &[1, 2, 3]);
1178        assert_eq!(result.len(), 3);
1179        assert!(result[0].is_empty());
1180        assert!(result[1].is_empty());
1181        assert!(result[2].is_empty());
1182    }
1183
1184    #[test]
1185    fn test_split_cells_beyond_length() {
1186        let segment = Segment::text("Hello");
1187        let (before, after) = segment.split_cells(10);
1188        assert_eq!(before.text, "Hello");
1189        assert_eq!(after.text, "");
1190    }
1191
1192    #[test]
1193    fn test_split_cells_cjk() {
1194        let segment = Segment::text("あいう"); // 6 cells total
1195        let (before, after) = segment.split_cells(2);
1196        assert_eq!(before.text, "あ");
1197        assert_eq!(after.text, "いう");
1198
1199        let (before, after) = segment.split_cells(3);
1200        // Split in middle of い - should get spaces
1201        assert_eq!(before.text, "あ ");
1202        assert_eq!(after.text, " う");
1203    }
1204
1205    #[test]
1206    fn test_apply_style_with_control_segments() {
1207        let control_code = vec![ControlCode::Simple(ControlType::Bell)];
1208        let segments = vec![
1209            Segment::text("foo"),
1210            Segment::new("", None, Some(control_code.clone())),
1211            Segment::text("bar"),
1212        ];
1213        let result = Segment::apply_style(&segments, Some(Style::parse("bold")), None);
1214
1215        assert_eq!(result[0].style.as_ref().unwrap().bold(), Some(true));
1216        assert!(result[1].is_control());
1217        assert_eq!(result[1].style, None);
1218        assert_eq!(result[2].style.as_ref().unwrap().bold(), Some(true));
1219    }
1220
1221    #[test]
1222    fn test_apply_style_post_style() {
1223        let segments = vec![Segment::styled("foo", Style::parse("bold"))];
1224        let result = Segment::apply_style(&segments, None, Some(Style::parse("italic")));
1225        assert_eq!(result[0].style.as_ref().unwrap().bold(), Some(true));
1226        assert_eq!(result[0].style.as_ref().unwrap().italic(), Some(true));
1227    }
1228
1229    #[test]
1230    fn test_get_shape_empty() {
1231        assert_eq!(Segment::get_shape(&[]), (0, 0));
1232        assert_eq!(Segment::get_shape(&[vec![]]), (0, 1));
1233    }
1234
1235    #[test]
1236    fn test_adjust_line_length_exact() {
1237        let line = vec![Segment::text("Hello")];
1238        let result = Segment::adjust_line_length(&line, 5, &Style::null(), true);
1239        assert_eq!(result, line);
1240    }
1241
1242    #[test]
1243    fn test_adjust_line_length_no_pad() {
1244        let line = vec![Segment::text("Hi")];
1245        let result = Segment::adjust_line_length(&line, 10, &Style::null(), false);
1246        assert_eq!(Segment::get_line_length(&result), 2); // Should not pad
1247    }
1248
1249    #[test]
1250    fn test_divide_with_control_segments() {
1251        let control_code = vec![ControlCode::Simple(ControlType::Bell)];
1252        let segments = vec![
1253            Segment::text("Hello"),
1254            Segment::new("", None, Some(control_code.clone())),
1255            Segment::text("World"),
1256        ];
1257        let result = Segment::divide(&segments, &[5, 10]);
1258        assert_eq!(result.len(), 2);
1259        // First portion should have "Hello" (5 cells)
1260        assert_eq!(result[0].len(), 1);
1261        assert_eq!(result[0][0].text, "Hello");
1262    }
1263
1264    #[test]
1265    fn test_split_cells_zero_cut() {
1266        let segment = Segment::text("Hello");
1267        let (before, after) = segment.split_cells(0);
1268        assert_eq!(before.text, "");
1269        assert_eq!(after.text, "Hello");
1270    }
1271
1272    #[test]
1273    fn test_align_methods_preserve_content() {
1274        let lines = vec![vec![Segment::text("ABC")]];
1275        let width = 5;
1276        let height = 3;
1277
1278        let top = Segment::align_top(&lines, width, height, &Style::null(), false);
1279        let middle = Segment::align_middle(&lines, width, height, &Style::null(), false);
1280        let bottom = Segment::align_bottom(&lines, width, height, &Style::null(), false);
1281
1282        // All should have same height
1283        assert_eq!(top.len(), height);
1284        assert_eq!(middle.len(), height);
1285        assert_eq!(bottom.len(), height);
1286
1287        // All should preserve the content somewhere
1288        assert!(top
1289            .iter()
1290            .any(|line| { line.iter().any(|seg| seg.text.contains("ABC")) }));
1291        assert!(middle
1292            .iter()
1293            .any(|line| { line.iter().any(|seg| seg.text.contains("ABC")) }));
1294        assert!(bottom
1295            .iter()
1296            .any(|line| { line.iter().any(|seg| seg.text.contains("ABC")) }));
1297    }
1298
1299    #[test]
1300    fn test_cell_length_with_mixed_content() {
1301        assert_eq!(Segment::text("a💩b").cell_length(), 4); // 1 + 2 + 1
1302        assert_eq!(Segment::text("あa").cell_length(), 3); // 2 + 1
1303    }
1304
1305    #[test]
1306    fn test_simplify_empty_segments() {
1307        let segments = vec![Segment::text(""), Segment::text("Hello"), Segment::text("")];
1308        let result = Segment::simplify(&segments);
1309        assert_eq!(result.len(), 1);
1310        assert_eq!(result[0].text, "Hello");
1311    }
1312
1313    #[test]
1314    fn test_apply_style_none_params() {
1315        let segments = vec![Segment::text("foo")];
1316        let result = Segment::apply_style(&segments, None, None);
1317        assert_eq!(result, segments);
1318    }
1319
1320    #[test]
1321    fn test_set_shape_with_height() {
1322        let lines = vec![vec![Segment::text("A")], vec![Segment::text("B")]];
1323        let result = Segment::set_shape(&lines, 3, Some(4), Some(&Style::null()), false);
1324        assert_eq!(result.len(), 4);
1325        assert_eq!(Segment::get_line_length(&result[0]), 3);
1326        assert_eq!(Segment::get_line_length(&result[3]), 3);
1327    }
1328
1329    #[test]
1330    fn test_set_shape_truncate() {
1331        let lines = vec![
1332            vec![Segment::text("A")],
1333            vec![Segment::text("B")],
1334            vec![Segment::text("C")],
1335        ];
1336        let result = Segment::set_shape(&lines, 3, Some(2), Some(&Style::null()), false);
1337        assert_eq!(result.len(), 2);
1338    }
1339
1340    #[test]
1341    fn test_split_lines_with_styled_segments() {
1342        let bold = Style::parse("bold");
1343        let segments = vec![Segment::styled("Hello\nWorld", bold.clone())];
1344        let result = Segment::split_lines(&segments);
1345        assert_eq!(result.len(), 2);
1346        assert_eq!(result[0][0].text, "Hello");
1347        assert_eq!(result[1][0].text, "World");
1348        // Style should be preserved
1349        assert_eq!(result[0][0].style.as_ref().unwrap().bold(), Some(true));
1350        assert_eq!(result[1][0].style.as_ref().unwrap().bold(), Some(true));
1351    }
1352
1353    #[test]
1354    fn test_divide_exact_boundaries() {
1355        let segments = vec![Segment::text("ABCDE")];
1356        let result = Segment::divide(&segments, &[2, 4]);
1357        assert_eq!(result.len(), 2);
1358        assert_eq!(result[0][0].text, "AB");
1359        assert_eq!(result[1][0].text, "CD");
1360    }
1361
1362    #[test]
1363    fn test_is_empty() {
1364        assert!(Segment::text("").is_empty());
1365        assert!(!Segment::text("a").is_empty());
1366    }
1367
1368    #[test]
1369    fn test_control_types_coverage() {
1370        // Test that we can create all control types
1371        let _ = ControlCode::Simple(ControlType::Bell);
1372        let _ = ControlCode::WithParam(ControlType::CursorUp, 5);
1373        let _ = ControlCode::WithParamStr(ControlType::SetWindowTitle, "Test".to_string());
1374        let _ = ControlCode::WithTwoParams(ControlType::CursorMoveTo, 10, 20);
1375
1376        // Verify control types are distinct
1377        assert_ne!(ControlType::Bell as u8, ControlType::Home as u8);
1378        assert_ne!(ControlType::ShowCursor as u8, ControlType::HideCursor as u8);
1379    }
1380
1381    #[test]
1382    fn test_split_and_crop_lines_basic() {
1383        let segments = vec![Segment::text("Hello\nWorld")];
1384        let lines = Segment::split_and_crop_lines(&segments, 10, None, true, false);
1385        assert_eq!(lines.len(), 2);
1386        // First line should be "Hello" padded to 10
1387        let line0_text: String = lines[0].iter().map(|s| s.text.as_str()).collect();
1388        assert_eq!(line0_text.trim_end(), "Hello");
1389        assert_eq!(lines[0].iter().map(|s| s.cell_length()).sum::<usize>(), 10);
1390    }
1391
1392    #[test]
1393    fn test_split_and_crop_lines_no_pad() {
1394        let segments = vec![Segment::text("Hi\nWorld")];
1395        let lines = Segment::split_and_crop_lines(&segments, 10, None, false, false);
1396        assert_eq!(lines.len(), 2);
1397        let line0_text: String = lines[0].iter().map(|s| s.text.as_str()).collect();
1398        assert_eq!(line0_text, "Hi");
1399    }
1400
1401    #[test]
1402    fn test_split_and_crop_lines_with_newline_segments() {
1403        let segments = vec![Segment::text("Hello\nWorld")];
1404        let lines = Segment::split_and_crop_lines(&segments, 10, None, false, true);
1405        assert_eq!(lines.len(), 2);
1406        // Each line should end with a newline segment
1407        assert_eq!(lines[0].last().unwrap().text, "\n");
1408    }
1409
1410    #[test]
1411    fn test_split_and_crop_lines_crop() {
1412        let segments = vec![Segment::text("Hello, World!")];
1413        let lines = Segment::split_and_crop_lines(&segments, 5, None, false, false);
1414        assert_eq!(lines.len(), 1);
1415        let line_text: String = lines[0].iter().map(|s| s.text.as_str()).collect();
1416        assert_eq!(line_text, "Hello");
1417    }
1418
1419    #[test]
1420    fn test_split_lines_terminator_basic() {
1421        let segments = vec![Segment::text("Hello\nWorld")];
1422        let lines = Segment::split_lines_terminator(&segments);
1423        assert_eq!(lines.len(), 2);
1424        assert!(lines[0].1); // first line has terminator
1425        assert!(!lines[1].1); // last line doesn\'t
1426        let text0: String = lines[0].0.iter().map(|s| s.text.as_str()).collect();
1427        assert_eq!(text0, "Hello");
1428    }
1429
1430    #[test]
1431    fn test_split_lines_terminator_no_newline() {
1432        let segments = vec![Segment::text("Hello")];
1433        let lines = Segment::split_lines_terminator(&segments);
1434        assert_eq!(lines.len(), 1);
1435        assert!(!lines[0].1);
1436    }
1437
1438    #[test]
1439    fn test_split_lines_terminator_trailing_newline() {
1440        let segments = vec![Segment::text("Hello\n")];
1441        let lines = Segment::split_lines_terminator(&segments);
1442        assert_eq!(lines.len(), 1);
1443        assert!(lines[0].1);
1444    }
1445}