Skip to main content

par_term_terminal/
styled_content.rs

1use par_term_emu_core_rust::grid::Grid;
2
3/// A segment of text with consistent styling
4#[derive(Debug, Clone)]
5#[allow(dead_code)]
6pub struct StyledSegment {
7    pub text: String,
8    pub fg_color: (u8, u8, u8),
9    pub bg_color: (u8, u8, u8),
10    pub bold: bool,
11    pub italic: bool,
12    pub underline: bool,
13    pub line: usize,
14    pub start_col: usize,
15}
16
17/// Extract styled segments from a terminal grid
18#[allow(dead_code)]
19pub fn extract_styled_segments(grid: &Grid) -> Vec<StyledSegment> {
20    let mut segments = Vec::new();
21    let rows = grid.rows();
22    let cols = grid.cols();
23
24    for row in 0..rows {
25        let mut current_segment: Option<StyledSegment> = None;
26
27        for col in 0..cols {
28            if let Some(cell) = grid.get(col, row) {
29                let fg = cell.fg.to_rgb();
30                let bg = cell.bg.to_rgb();
31                let bold = cell.flags.bold();
32                let italic = cell.flags.italic();
33                let underline = cell.flags.underline();
34
35                // Check if this cell can be added to the current segment
36                if let Some(ref mut segment) = current_segment {
37                    let same_style = segment.fg_color == fg
38                        && segment.bg_color == bg
39                        && segment.bold == bold
40                        && segment.italic == italic
41                        && segment.underline == underline;
42
43                    if same_style {
44                        // Add to current segment
45                        // Optimization: Avoid String allocation for cells without combining chars
46                        if cell.has_combining_chars() {
47                            segment.text.push_str(&cell.get_grapheme());
48                        } else {
49                            segment.text.push(cell.base_char());
50                        }
51                    } else {
52                        // Different style, save current segment and start new one
53                        segments.push(segment.clone());
54                        // Optimization: Avoid String allocation for cells without combining chars
55                        let text = if cell.has_combining_chars() {
56                            cell.get_grapheme()
57                        } else {
58                            cell.base_char().to_string()
59                        };
60                        current_segment = Some(StyledSegment {
61                            text,
62                            fg_color: fg,
63                            bg_color: bg,
64                            bold,
65                            italic,
66                            underline,
67                            line: row,
68                            start_col: col,
69                        });
70                    }
71                } else {
72                    // Start new segment
73                    // Optimization: Avoid String allocation for cells without combining chars
74                    let text = if cell.has_combining_chars() {
75                        cell.get_grapheme()
76                    } else {
77                        cell.base_char().to_string()
78                    };
79                    current_segment = Some(StyledSegment {
80                        text,
81                        fg_color: fg,
82                        bg_color: bg,
83                        bold,
84                        italic,
85                        underline,
86                        line: row,
87                        start_col: col,
88                    });
89                }
90            }
91        }
92
93        // Save last segment of the line
94        if let Some(segment) = current_segment {
95            segments.push(segment);
96        }
97    }
98
99    segments
100}
101
102/// Convert styled segments to plain text (for simple rendering)
103#[allow(dead_code)]
104pub fn segments_to_plain_text(segments: &[StyledSegment]) -> String {
105    let mut result = String::new();
106    let mut current_line = 0;
107
108    for segment in segments {
109        // Add newlines for line changes
110        while current_line < segment.line {
111            result.push('\n');
112            current_line += 1;
113        }
114
115        result.push_str(&segment.text);
116    }
117
118    result
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use par_term_emu_core_rust::cell::Cell;
125    use par_term_emu_core_rust::color::{Color, NamedColor};
126    use par_term_emu_core_rust::grid::Grid;
127
128    #[test]
129    fn test_extract_single_segment() {
130        let mut grid = Grid::new(10, 1, 0);
131
132        // Set some cells with same style
133        for col in 0..5 {
134            let mut cell = Cell::new('A');
135            cell.fg = Color::Named(NamedColor::White);
136            cell.bg = Color::Named(NamedColor::Black);
137            grid.set(col, 0, cell);
138        }
139
140        let segments = extract_styled_segments(&grid);
141        // Grid has 10 columns, so we get one segment for all 10
142        // (5 'A's followed by 5 default space characters)
143        assert_eq!(segments.len(), 1);
144        assert_eq!(segments[0].text.trim_end(), "AAAAA");
145    }
146
147    #[test]
148    fn test_extract_multiple_segments() {
149        let mut grid = Grid::new(10, 1, 0);
150
151        // First segment: white text
152        for col in 0..3 {
153            let mut cell = Cell::new('A');
154            cell.fg = Color::Named(NamedColor::White);
155            grid.set(col, 0, cell);
156        }
157
158        // Second segment: red text
159        for col in 3..6 {
160            let mut cell = Cell::new('B');
161            cell.fg = Color::Named(NamedColor::Red);
162            grid.set(col, 0, cell);
163        }
164
165        let segments = extract_styled_segments(&grid);
166        // We should have at least 2 segments (white and red)
167        assert!(segments.len() >= 2);
168        assert_eq!(segments[0].text, "AAA");
169        assert_eq!(segments[1].text.trim_start(), "BBB");
170    }
171}