claude_code_status_line/
render.rs

1use crate::colors::SectionColors;
2use crate::config::Config;
3use crate::types::Section;
4use crate::utils::RESET;
5
6/// Render sections using segmented style (either Powerline arrows or custom separator)
7pub fn render_segments(sections: &[&Section], config: &Config) -> String {
8    let mut result = String::new();
9
10    for (i, section) in sections.iter().enumerate() {
11        result.push_str(&render_section(&section.content, &section.colors, config));
12
13        if i < sections.len() - 1 {
14            let next = sections[i + 1];
15            if config.display.use_powerline {
16                let left_bg = section.colors.background.unwrap_or((0, 0, 0));
17                let right_bg = next.colors.background.unwrap_or((0, 0, 0));
18                result.push_str(&make_powerline_arrow(left_bg, right_bg, config));
19            } else if !config.display.segment_separator.is_empty() {
20                result.push_str(&make_separator(config));
21            }
22        }
23    }
24
25    // Add tail arrow only if using arrows
26    if config.display.use_powerline {
27        if let Some(last) = sections.last() {
28            if let Some(bg) = last.colors.background {
29                result.push_str(&format!(
30                    "\x1b[38;2;{};{};{}m\x1b[49m{}",
31                    bg.0, bg.1, bg.2, config.display.arrow
32                ));
33            }
34        }
35    }
36
37    result.push_str(RESET);
38    result
39}
40
41/// Render a single section with its colors (with padding)
42fn render_section(content: &str, colors: &SectionColors, config: &Config) -> String {
43    let bg_code = match colors.background {
44        Some(bg) => format!("\x1b[48;2;{};{};{}m", bg.0, bg.1, bg.2),
45        None => "\x1b[49m".to_string(), // Reset background
46    };
47
48    let padding = " ".repeat(config.display.section_padding);
49
50    format!(
51        "{}\x1b[38;2;{};{};{}m{}{}{}",
52        bg_code,
53        colors.foreground.0,
54        colors.foreground.1,
55        colors.foreground.2,
56        padding,
57        content,
58        padding
59    )
60}
61
62/// Generate powerline arrow between two sections
63fn make_powerline_arrow(left_bg: (u8, u8, u8), right_bg: (u8, u8, u8), config: &Config) -> String {
64    format!(
65        "\x1b[38;2;{};{};{}m\x1b[48;2;{};{};{}m{}",
66        left_bg.0, left_bg.1, left_bg.2, right_bg.0, right_bg.1, right_bg.2, config.display.arrow
67    )
68}
69
70/// Generate ANSI escape code for details text using section's details color
71fn make_details_from_section(colors: &SectionColors) -> String {
72    format!(
73        "\x1b[38;2;{};{};{}m",
74        colors.details.0, colors.details.1, colors.details.2
75    )
76}
77
78/// Generate ANSI escape code to restore foreground color for a section
79fn restore_fg_from_section(colors: &SectionColors) -> String {
80    format!(
81        "\x1b[38;2;{};{};{}m",
82        colors.foreground.0, colors.foreground.1, colors.foreground.2
83    )
84}
85
86/// Helper to get details and foreground color codes (unified logic)
87pub fn get_details_and_fg_codes(colors: &SectionColors) -> (String, String) {
88    (
89        make_details_from_section(colors),
90        restore_fg_from_section(colors),
91    )
92}
93
94/// Generate separator string with reset colors
95fn make_separator(config: &Config) -> String {
96    let color = config.theme.separator;
97    format!(
98        "{}\x1b[38;2;{};{};{}m{}\x1b[0m",
99        RESET, color.0, color.1, color.2, config.display.segment_separator
100    )
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    const TEST_COLORS: SectionColors = SectionColors {
108        background: Some((100, 100, 100)),
109        foreground: (200, 200, 200),
110        details: (150, 150, 150),
111    };
112
113    const TEST_COLORS_NO_BG: SectionColors = SectionColors {
114        background: None,
115        foreground: (255, 255, 255),
116        details: (128, 128, 128),
117    };
118
119    #[test]
120    fn test_render_segments_empty() {
121        let config = Config::default();
122        let sections: Vec<&Section> = vec![];
123        let result = render_segments(&sections, &config);
124        assert_eq!(result, RESET);
125    }
126
127    #[test]
128    fn test_render_segments_single() {
129        let config = Config::default();
130        let section = Section::new("test".to_string(), 0, TEST_COLORS);
131        let result = render_segments(&[&section], &config);
132
133        // Should contain the content
134        assert!(result.contains("test"));
135        // Should contain reset at the end
136        assert!(result.ends_with(RESET));
137    }
138
139    #[test]
140    fn test_render_segments_with_powerline() {
141        let mut config = Config::default();
142        config.display.use_powerline = true;
143        config.display.arrow = "".to_string();
144
145        let section1 = Section::new("test1".to_string(), 0, TEST_COLORS);
146        let section2 = Section::new("test2".to_string(), 1, TEST_COLORS);
147
148        let result = render_segments(&[&section1, &section2], &config);
149
150        // Should contain both sections
151        assert!(result.contains("test1"));
152        assert!(result.contains("test2"));
153        // Should contain powerline arrow
154        assert!(result.contains(""));
155    }
156
157    #[test]
158    fn test_render_segments_with_separator() {
159        let mut config = Config::default();
160        config.display.use_powerline = false;
161        config.display.segment_separator = " | ".to_string();
162
163        let section1 = Section::new("test1".to_string(), 0, TEST_COLORS);
164        let section2 = Section::new("test2".to_string(), 1, TEST_COLORS);
165
166        let result = render_segments(&[&section1, &section2], &config);
167
168        // Should contain separator
169        assert!(result.contains("|"));
170    }
171
172    #[test]
173    fn test_make_details_from_section() {
174        let result = make_details_from_section(&TEST_COLORS);
175        // Should be an ANSI color code
176        assert!(result.starts_with("\x1b[38;2;"));
177        assert!(result.contains("150"));
178    }
179
180    #[test]
181    fn test_restore_fg_from_section() {
182        let result = restore_fg_from_section(&TEST_COLORS);
183        // Should be an ANSI color code
184        assert!(result.starts_with("\x1b[38;2;"));
185        assert!(result.contains("200"));
186    }
187
188    #[test]
189    fn test_get_details_and_fg_codes() {
190        let (details, fg) = get_details_and_fg_codes(&TEST_COLORS);
191
192        assert!(details.starts_with("\x1b["));
193        assert!(fg.starts_with("\x1b["));
194        assert_ne!(details, fg);
195    }
196
197    #[test]
198    fn test_render_section_no_background() {
199        let config = Config::default();
200        let result = render_section("test", &TEST_COLORS_NO_BG, &config);
201
202        // Should contain reset background code
203        assert!(result.contains("\x1b[49m"));
204        // Should contain content
205        assert!(result.contains("test"));
206    }
207
208    #[test]
209    fn test_render_section_with_background() {
210        let config = Config::default();
211        let result = render_section("test", &TEST_COLORS, &config);
212
213        // Should contain background color code
214        assert!(result.contains("\x1b[48;2;"));
215        // Should contain RGB values
216        assert!(result.contains("100"));
217    }
218
219    #[test]
220    fn test_make_powerline_arrow() {
221        let mut config = Config::default();
222        config.display.arrow = "".to_string();
223
224        let left = (100, 100, 100);
225        let right = (200, 200, 200);
226        let result = make_powerline_arrow(left, right, &config);
227
228        // Should contain left color as foreground
229        assert!(result.contains("100"));
230        // Should contain right color as background
231        assert!(result.contains("200"));
232        // Should contain arrow
233        assert!(result.contains(""));
234    }
235
236    #[test]
237    fn test_rgb_values_boundary() {
238        // Test with min RGB values
239        let colors_min = SectionColors {
240            background: Some((0, 0, 0)),
241            foreground: (0, 0, 0),
242            details: (0, 0, 0),
243        };
244        let result = make_details_from_section(&colors_min);
245        assert!(result.contains("0"));
246
247        // Test with max RGB values
248        let colors_max = SectionColors {
249            background: Some((255, 255, 255)),
250            foreground: (255, 255, 255),
251            details: (255, 255, 255),
252        };
253        let result = make_details_from_section(&colors_max);
254        assert!(result.contains("255"));
255    }
256}